commit
07c34c3530
|
@ -8,3 +8,6 @@ repl-temp-*
|
||||||
# Elm output
|
# Elm output
|
||||||
index.html
|
index.html
|
||||||
elm.js
|
elm.js
|
||||||
|
|
||||||
|
# Elm configurations
|
||||||
|
elm-*.json
|
||||||
|
|
5
elm.json
5
elm.json
|
@ -3,7 +3,7 @@
|
||||||
"name": "noordstar/elm-matrix-sdk-beta",
|
"name": "noordstar/elm-matrix-sdk-beta",
|
||||||
"summary": "Matrix SDK for instant communication. Unstable beta version for testing only.",
|
"summary": "Matrix SDK for instant communication. Unstable beta version for testing only.",
|
||||||
"license": "EUPL-1.1",
|
"license": "EUPL-1.1",
|
||||||
"version": "3.3.1",
|
"version": "3.4.0",
|
||||||
"exposed-modules": [
|
"exposed-modules": [
|
||||||
"Matrix",
|
"Matrix",
|
||||||
"Matrix.Event",
|
"Matrix.Event",
|
||||||
|
@ -20,7 +20,8 @@
|
||||||
"elm/time": "1.0.0 <= v < 2.0.0",
|
"elm/time": "1.0.0 <= v < 2.0.0",
|
||||||
"elm/url": "1.0.0 <= v < 2.0.0",
|
"elm/url": "1.0.0 <= v < 2.0.0",
|
||||||
"micahhahn/elm-safe-recursion": "2.0.0 <= v < 3.0.0",
|
"micahhahn/elm-safe-recursion": "2.0.0 <= v < 3.0.0",
|
||||||
"miniBill/elm-fast-dict": "1.0.0 <= v < 2.0.0"
|
"miniBill/elm-fast-dict": "1.0.0 <= v < 2.0.0",
|
||||||
|
"noordstar/elm-iddict": "1.0.1 <= v < 2.0.0"
|
||||||
},
|
},
|
||||||
"test-dependencies": {
|
"test-dependencies": {
|
||||||
"elm-explorations/test": "2.1.2 <= v < 3.0.0"
|
"elm-explorations/test": "2.1.2 <= v < 3.0.0"
|
||||||
|
|
|
@ -13,7 +13,6 @@ This module looks for the right homeserver address.
|
||||||
|
|
||||||
import Internal.Api.Chain as C
|
import Internal.Api.Chain as C
|
||||||
import Internal.Api.Request as R
|
import Internal.Api.Request as R
|
||||||
import Internal.Config.Leaks as L
|
|
||||||
import Internal.Config.Log exposing (log)
|
import Internal.Config.Log exposing (log)
|
||||||
import Internal.Config.Text as Text
|
import Internal.Config.Text as Text
|
||||||
import Internal.Tools.Json as Json
|
import Internal.Tools.Json as Json
|
||||||
|
@ -87,12 +86,12 @@ coder =
|
||||||
{ fieldName = "m.homeserver"
|
{ fieldName = "m.homeserver"
|
||||||
, toField = .homeserver
|
, toField = .homeserver
|
||||||
, coder =
|
, coder =
|
||||||
Json.object2
|
Json.object1
|
||||||
{ name = "Homeserver Information"
|
{ name = "Homeserver Information"
|
||||||
, description =
|
, description =
|
||||||
[ "Used by clients to discover homeserver information."
|
[ "Used by clients to discover homeserver information."
|
||||||
]
|
]
|
||||||
, init = \a _ -> { baseUrl = a }
|
, init = HomeserverInformation
|
||||||
}
|
}
|
||||||
(Json.field.required
|
(Json.field.required
|
||||||
{ fieldName = "base_url"
|
{ fieldName = "base_url"
|
||||||
|
@ -103,17 +102,6 @@ coder =
|
||||||
, coder = Json.string
|
, 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 =
|
, description =
|
||||||
[ "Used by clients to discover homeserver information."
|
[ "Used by clients to discover homeserver information."
|
||||||
]
|
]
|
||||||
|
@ -123,12 +111,12 @@ coder =
|
||||||
{ fieldName = "m.identity_server"
|
{ fieldName = "m.identity_server"
|
||||||
, toField = .identityServer
|
, toField = .identityServer
|
||||||
, coder =
|
, coder =
|
||||||
Json.object2
|
Json.object1
|
||||||
{ name = "Homeserver Information"
|
{ name = "Homeserver Information"
|
||||||
, description =
|
, description =
|
||||||
[ "Used by clients to discover homeserver information."
|
[ "Used by clients to discover homeserver information."
|
||||||
]
|
]
|
||||||
, init = \a _ -> { baseUrl = a }
|
, init = IdentityServerInformation
|
||||||
}
|
}
|
||||||
(Json.field.required
|
(Json.field.required
|
||||||
{ fieldName = "base_url"
|
{ fieldName = "base_url"
|
||||||
|
@ -139,17 +127,6 @@ coder =
|
||||||
, coder = Json.string
|
, 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 =
|
, description =
|
||||||
[ "Used by clients to discover identity server information."
|
[ "Used by clients to discover identity server information."
|
||||||
]
|
]
|
||||||
|
|
|
@ -204,7 +204,7 @@ getEventCoderV1 =
|
||||||
[ "UnsignedData as described by the Matrix spec"
|
[ "UnsignedData as described by the Matrix spec"
|
||||||
, "https://spec.matrix.org/v1.10/client-server-api/#get_matrixclientv3roomsroomideventeventid"
|
, "https://spec.matrix.org/v1.10/client-server-api/#get_matrixclientv3roomsroomideventeventid"
|
||||||
]
|
]
|
||||||
, init = \a b c d -> Event.UnsignedData { age = a, prevContent = b, redactedBecause = c, transactionId = d }
|
, init = \a b c d -> Event.UnsignedData { age = a, membership = Nothing, prevContent = b, redactedBecause = c, transactionId = d }
|
||||||
}
|
}
|
||||||
(Json.field.optional.value
|
(Json.field.optional.value
|
||||||
{ fieldName = "age"
|
{ fieldName = "age"
|
||||||
|
|
|
@ -13,7 +13,6 @@ This module allows the user to log in using a username and password.
|
||||||
|
|
||||||
import Internal.Api.Api as A
|
import Internal.Api.Api as A
|
||||||
import Internal.Api.Request as R
|
import Internal.Api.Request as R
|
||||||
import Internal.Config.Leaks as L
|
|
||||||
import Internal.Config.Log exposing (log)
|
import Internal.Config.Log exposing (log)
|
||||||
import Internal.Config.Text as Text
|
import Internal.Config.Text as Text
|
||||||
import Internal.Tools.Json as Json
|
import Internal.Tools.Json as Json
|
||||||
|
@ -888,12 +887,12 @@ disoveryInformationCoderV1 =
|
||||||
{ fieldName = "m.homeserver"
|
{ fieldName = "m.homeserver"
|
||||||
, toField = .homeserver
|
, toField = .homeserver
|
||||||
, coder =
|
, coder =
|
||||||
Json.object2
|
Json.object1
|
||||||
{ name = "Homeserver Information"
|
{ name = "Homeserver Information"
|
||||||
, description =
|
, description =
|
||||||
[ "Used by clients to discover homeserver information."
|
[ "Used by clients to discover homeserver information."
|
||||||
]
|
]
|
||||||
, init = \a _ -> { baseUrl = a }
|
, init = HomeserverInformation
|
||||||
}
|
}
|
||||||
(Json.field.required
|
(Json.field.required
|
||||||
{ fieldName = "base_url"
|
{ fieldName = "base_url"
|
||||||
|
@ -904,17 +903,6 @@ disoveryInformationCoderV1 =
|
||||||
, coder = Json.string
|
, 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 =
|
, description =
|
||||||
[ "Used by clients to discover homeserver information."
|
[ "Used by clients to discover homeserver information."
|
||||||
]
|
]
|
||||||
|
@ -924,12 +912,12 @@ disoveryInformationCoderV1 =
|
||||||
{ fieldName = "m.identity_server"
|
{ fieldName = "m.identity_server"
|
||||||
, toField = .identityServer
|
, toField = .identityServer
|
||||||
, coder =
|
, coder =
|
||||||
Json.object2
|
Json.object1
|
||||||
{ name = "Homeserver Information"
|
{ name = "Homeserver Information"
|
||||||
, description =
|
, description =
|
||||||
[ "Used by clients to discover homeserver information."
|
[ "Used by clients to discover homeserver information."
|
||||||
]
|
]
|
||||||
, init = \a _ -> { baseUrl = a }
|
, init = HomeserverInformation
|
||||||
}
|
}
|
||||||
(Json.field.required
|
(Json.field.required
|
||||||
{ fieldName = "base_url"
|
{ fieldName = "base_url"
|
||||||
|
@ -940,17 +928,6 @@ disoveryInformationCoderV1 =
|
||||||
, coder = Json.string
|
, 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 =
|
, description =
|
||||||
[ "Used by clients to discover identity server information."
|
[ "Used by clients to discover identity server information."
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
module Internal.Api.Main exposing
|
module Internal.Api.Main exposing
|
||||||
( Msg
|
( Msg
|
||||||
, sendMessageEvent
|
, sendMessageEvent, sync
|
||||||
)
|
)
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
|
@ -18,7 +18,7 @@ This module is used as reference for getting
|
||||||
|
|
||||||
## Actions
|
## Actions
|
||||||
|
|
||||||
@docs sendMessageEvent
|
@docs sendMessageEvent, sync
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
|
@ -57,3 +57,22 @@ sendMessageEvent env data =
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Context.apiFormat env.context)
|
(Context.apiFormat env.context)
|
||||||
|
|
||||||
|
|
||||||
|
{-| Sync with the Matrix API to stay up-to-date.
|
||||||
|
-}
|
||||||
|
sync :
|
||||||
|
E.Envelope a
|
||||||
|
-> { toMsg : Msg -> msg }
|
||||||
|
-> Cmd msg
|
||||||
|
sync env data =
|
||||||
|
ITask.run
|
||||||
|
data.toMsg
|
||||||
|
(ITask.sync
|
||||||
|
{ fullState = Nothing
|
||||||
|
, presence = env.settings.presence
|
||||||
|
, since = env.context.nextBatch
|
||||||
|
, timeout = Just env.settings.syncTime
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Context.apiFormat env.context)
|
||||||
|
|
|
@ -13,7 +13,6 @@ This module helps send message events to rooms on the Matrix API.
|
||||||
|
|
||||||
import Internal.Api.Api as A
|
import Internal.Api.Api as A
|
||||||
import Internal.Api.Request as R
|
import Internal.Api.Request as R
|
||||||
import Internal.Config.Leaks as L
|
|
||||||
import Internal.Config.Log exposing (log)
|
import Internal.Config.Log exposing (log)
|
||||||
import Internal.Config.Text as Text
|
import Internal.Config.Text as Text
|
||||||
import Internal.Tools.Json as Json
|
import Internal.Tools.Json as Json
|
||||||
|
@ -143,26 +142,15 @@ sendMessageEventV3 { content, eventType, roomId, transactionId } =
|
||||||
|
|
||||||
coderV1 : Json.Coder SendMessageEventOutputV1
|
coderV1 : Json.Coder SendMessageEventOutputV1
|
||||||
coderV1 =
|
coderV1 =
|
||||||
Json.object2
|
Json.object1
|
||||||
{ name = "EventResponse"
|
{ name = "EventResponse"
|
||||||
, description =
|
, 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."
|
[ "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."
|
, "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"
|
, "https://spec.matrix.org/legacy/r0.0.0/client_server.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid"
|
||||||
]
|
]
|
||||||
, init = always SendMessageEventOutputV1
|
, init = 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
|
(Json.field.optional.value
|
||||||
{ fieldName = "event_id"
|
{ fieldName = "event_id"
|
||||||
, toField = .eventId
|
, toField = .eventId
|
||||||
|
@ -174,26 +162,15 @@ coderV1 =
|
||||||
|
|
||||||
coderV2 : Json.Coder SendMessageEventOutputV2
|
coderV2 : Json.Coder SendMessageEventOutputV2
|
||||||
coderV2 =
|
coderV2 =
|
||||||
Json.object2
|
Json.object1
|
||||||
{ name = "EventResponse"
|
{ name = "EventResponse"
|
||||||
, description =
|
, 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."
|
[ "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."
|
, "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/client_server/r0.6.1.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid"
|
, "https://spec.matrix.org/legacy/client_server/r0.6.1.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid"
|
||||||
]
|
]
|
||||||
, init = always SendMessageEventOutputV2
|
, init = 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
|
(Json.field.required
|
||||||
{ fieldName = "event_id"
|
{ fieldName = "event_id"
|
||||||
, toField = .eventId
|
, toField = .eventId
|
||||||
|
|
|
@ -0,0 +1,176 @@
|
||||||
|
module Internal.Api.Sync.Api exposing (sync, Phantom)
|
||||||
|
|
||||||
|
{-|
|
||||||
|
|
||||||
|
|
||||||
|
# Sync
|
||||||
|
|
||||||
|
The sync module might be one of the most crucial parts of the Elm SDK. It offers
|
||||||
|
users the guarantee that the `Vault` type remains up-to-date, and it helps
|
||||||
|
communicate with the Matrix server about the Vault's needs.
|
||||||
|
|
||||||
|
@docs sync, Phantom
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
import Internal.Api.Api as A
|
||||||
|
import Internal.Api.Request as R
|
||||||
|
import Internal.Api.Sync.V1 as V1
|
||||||
|
import Internal.Api.Sync.V2 as V2
|
||||||
|
import Internal.Api.Sync.V3 as V3
|
||||||
|
import Internal.Api.Sync.V4 as V4
|
||||||
|
import Internal.Filter.Timeline as Filter
|
||||||
|
|
||||||
|
|
||||||
|
{-| Sync with the Matrix API.
|
||||||
|
-}
|
||||||
|
sync : SyncInput -> A.TaskChain (Phantom a) (Phantom a)
|
||||||
|
sync =
|
||||||
|
A.startWithVersion "v1.1" syncV1
|
||||||
|
|> A.forVersion "v1.2" syncV2
|
||||||
|
|> A.sameForVersion "v1.3"
|
||||||
|
|> A.forVersion "v1.4" syncV3
|
||||||
|
|> 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.forVersion "v1.11" syncV4
|
||||||
|
|> A.versionChain
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- For simplicity, we will not use a filter for now
|
||||||
|
-- and assume that every client always wants to receive all events.
|
||||||
|
-- type FilterV1
|
||||||
|
-- = FilterV1 Filter
|
||||||
|
-- | FilterIdV1 String Filter
|
||||||
|
-- | NoFilter
|
||||||
|
|
||||||
|
|
||||||
|
type alias Phantom a =
|
||||||
|
{ a | accessToken : (), baseUrl : (), versions : () }
|
||||||
|
|
||||||
|
|
||||||
|
type alias PhantomV1 a =
|
||||||
|
{ a | accessToken : (), baseUrl : () }
|
||||||
|
|
||||||
|
|
||||||
|
type alias SyncInput =
|
||||||
|
{ -- filter : FilterV1,
|
||||||
|
fullState : Maybe Bool
|
||||||
|
, presence : Maybe String
|
||||||
|
, since : Maybe String
|
||||||
|
, timeout : Maybe Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias SyncInputV1 a =
|
||||||
|
{ a
|
||||||
|
| -- filter : FilterV1 ,
|
||||||
|
since : Maybe String
|
||||||
|
, fullState : Maybe Bool
|
||||||
|
, presence : Maybe String
|
||||||
|
, timeout : Maybe Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
presenceFromOptions : List String -> Maybe String -> Maybe String
|
||||||
|
presenceFromOptions options =
|
||||||
|
Maybe.andThen
|
||||||
|
(\v ->
|
||||||
|
if List.member v options then
|
||||||
|
Just v
|
||||||
|
|
||||||
|
else
|
||||||
|
Nothing
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
syncV1 : SyncInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
|
||||||
|
syncV1 data =
|
||||||
|
A.request
|
||||||
|
{ attributes =
|
||||||
|
[ R.accessToken
|
||||||
|
, R.queryOpString "filter" Nothing -- FILTER HERE
|
||||||
|
, R.queryOpBool "full_state" data.fullState
|
||||||
|
, data.presence
|
||||||
|
|> presenceFromOptions [ "offline", "online", "unavailable" ]
|
||||||
|
|> R.queryOpString "set_presence"
|
||||||
|
, R.queryOpString "since" data.since
|
||||||
|
, R.queryOpInt "timeout" data.timeout
|
||||||
|
]
|
||||||
|
, coder = V1.coderSyncResponse
|
||||||
|
, contextChange = always identity
|
||||||
|
, method = "GET"
|
||||||
|
, path = [ "_matrix", "client", "v3", "sync" ]
|
||||||
|
, toUpdate =
|
||||||
|
Debug.log "Handling output v1" >> V1.updateSyncResponse { filter = Filter.pass, since = data.since } >> Debug.log "Received"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
syncV2 : SyncInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
|
||||||
|
syncV2 data =
|
||||||
|
A.request
|
||||||
|
{ attributes =
|
||||||
|
[ R.accessToken
|
||||||
|
, R.queryOpString "filter" Nothing
|
||||||
|
, R.queryOpBool "full_state" data.fullState
|
||||||
|
, data.presence
|
||||||
|
|> presenceFromOptions [ "offline", "online", "unavailable" ]
|
||||||
|
|> R.queryOpString "set_presence"
|
||||||
|
, R.queryOpString "since" data.since
|
||||||
|
, R.queryOpInt "timeout" data.timeout
|
||||||
|
]
|
||||||
|
, coder = V2.coderSyncResponse
|
||||||
|
, contextChange = always identity
|
||||||
|
, method = "GET"
|
||||||
|
, path = [ "_matrix", "client", "v3", "sync" ]
|
||||||
|
, toUpdate =
|
||||||
|
Debug.log "Handling output v2" >> V2.updateSyncResponse { filter = Filter.pass, since = data.since } >> Debug.log "Received"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
syncV3 : SyncInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
|
||||||
|
syncV3 data =
|
||||||
|
A.request
|
||||||
|
{ attributes =
|
||||||
|
[ R.accessToken
|
||||||
|
, R.queryOpString "filter" Nothing
|
||||||
|
, R.queryOpBool "full_state" data.fullState
|
||||||
|
, data.presence
|
||||||
|
|> presenceFromOptions [ "offline", "online", "unavailable" ]
|
||||||
|
|> R.queryOpString "set_presence"
|
||||||
|
, R.queryOpString "since" data.since
|
||||||
|
, R.queryOpInt "timeout" data.timeout
|
||||||
|
]
|
||||||
|
, coder = V3.coderSyncResponse
|
||||||
|
, contextChange = always identity
|
||||||
|
, method = "GET"
|
||||||
|
, path = [ "_matrix", "client", "v3", "sync" ]
|
||||||
|
, toUpdate =
|
||||||
|
Debug.log "Handling output v3" >> V3.updateSyncResponse { filter = Filter.pass, since = data.since } >> Debug.log "Received"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
syncV4 : SyncInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
|
||||||
|
syncV4 data =
|
||||||
|
A.request
|
||||||
|
{ attributes =
|
||||||
|
[ R.accessToken
|
||||||
|
, R.queryOpString "filter" Nothing
|
||||||
|
, R.queryOpBool "full_state" data.fullState
|
||||||
|
, data.presence
|
||||||
|
|> presenceFromOptions [ "offline", "online", "unavailable" ]
|
||||||
|
|> R.queryOpString "set_presence"
|
||||||
|
, R.queryOpString "since" data.since
|
||||||
|
, R.queryOpInt "timeout" data.timeout
|
||||||
|
]
|
||||||
|
, coder = V4.coderSyncResponse
|
||||||
|
, contextChange = always identity
|
||||||
|
, method = "GET"
|
||||||
|
, path = [ "_matrix", "client", "v3", "sync" ]
|
||||||
|
, toUpdate =
|
||||||
|
Debug.log "Handling output v4" >> V4.updateSyncResponse { filter = Filter.pass, since = data.since } >> Debug.log "Received"
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,797 @@
|
||||||
|
module Internal.Api.Sync.V2 exposing (..)
|
||||||
|
|
||||||
|
{-|
|
||||||
|
|
||||||
|
|
||||||
|
# Sync response
|
||||||
|
|
||||||
|
This API module represents the /sync endpoint on Matrix spec version v1.2 and
|
||||||
|
v1.3.
|
||||||
|
|
||||||
|
<https://spec.matrix.org/v1.2/client-server-api/#syncing>
|
||||||
|
<https://spec.matrix.org/v1.3/client-server-api/#syncing>
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
import FastDict as Dict exposing (Dict)
|
||||||
|
import Internal.Api.Sync.V1 as PV
|
||||||
|
import Internal.Config.Log exposing (Log, log)
|
||||||
|
import Internal.Config.Text as Text
|
||||||
|
import Internal.Filter.Timeline exposing (Filter)
|
||||||
|
import Internal.Tools.Json as Json
|
||||||
|
import Internal.Tools.Timestamp as Timestamp exposing (Timestamp)
|
||||||
|
import Internal.Values.Envelope as E
|
||||||
|
import Internal.Values.Event as Event
|
||||||
|
import Internal.Values.Room as R
|
||||||
|
import Internal.Values.User as User exposing (User)
|
||||||
|
import Internal.Values.Vault as V
|
||||||
|
import Recursion
|
||||||
|
|
||||||
|
|
||||||
|
type alias SyncResponse =
|
||||||
|
{ accountData : Maybe AccountData
|
||||||
|
, deviceLists : Maybe DeviceLists
|
||||||
|
, deviceOneTimeKeysCount : Maybe (Dict String Int)
|
||||||
|
, deviceUnusedFallbackKeyTypes : List String
|
||||||
|
, nextBatch : String
|
||||||
|
, presence : Maybe Presence
|
||||||
|
, rooms : Maybe Rooms
|
||||||
|
, toDevice : Maybe ToDevice
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias AccountData =
|
||||||
|
{ events : Maybe (List Event) }
|
||||||
|
|
||||||
|
|
||||||
|
type alias Event =
|
||||||
|
{ content : Json.Value
|
||||||
|
, eventType : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Presence =
|
||||||
|
{ events : Maybe (List Event) }
|
||||||
|
|
||||||
|
|
||||||
|
type alias Rooms =
|
||||||
|
{ invite : Maybe (Dict String InvitedRoom)
|
||||||
|
, join : Maybe (Dict String JoinedRoom)
|
||||||
|
, knock : Maybe (Dict String KnockedRoom)
|
||||||
|
, leave : Maybe (Dict String LeftRoom)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias InvitedRoom =
|
||||||
|
{ inviteState : Maybe InviteState }
|
||||||
|
|
||||||
|
|
||||||
|
type alias InviteState =
|
||||||
|
{ events : Maybe (List StrippedStateEvent) }
|
||||||
|
|
||||||
|
|
||||||
|
type alias StrippedStateEvent =
|
||||||
|
{ content : Json.Value
|
||||||
|
, sender : User
|
||||||
|
, stateKey : String
|
||||||
|
, eventType : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias JoinedRoom =
|
||||||
|
{ accountData : Maybe AccountData
|
||||||
|
, ephemeral : Maybe Ephemeral
|
||||||
|
, state : Maybe State
|
||||||
|
, summary : Maybe RoomSummary
|
||||||
|
, timeline : Maybe Timeline
|
||||||
|
, unreadNotifications : Maybe UnreadNotificationCounts
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Ephemeral =
|
||||||
|
{ events : Maybe (List Event) }
|
||||||
|
|
||||||
|
|
||||||
|
type alias State =
|
||||||
|
{ events : Maybe (List ClientEventWithoutRoomID) }
|
||||||
|
|
||||||
|
|
||||||
|
type alias ClientEventWithoutRoomID =
|
||||||
|
{ content : Json.Value
|
||||||
|
, eventId : String
|
||||||
|
, originServerTs : Timestamp
|
||||||
|
, sender : User
|
||||||
|
, stateKey : Maybe String
|
||||||
|
, eventType : String
|
||||||
|
, unsigned : Maybe UnsignedData
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type UnsignedData
|
||||||
|
= UnsignedData
|
||||||
|
{ age : Maybe Int
|
||||||
|
, prevContent : Maybe Json.Value
|
||||||
|
, redactedBecause : Maybe ClientEventWithoutRoomID
|
||||||
|
, transactionId : Maybe String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias RoomSummary =
|
||||||
|
{ mHeroes : Maybe (List String)
|
||||||
|
, mInvitedMemberCount : Maybe Int
|
||||||
|
, mJoinedMemberCount : Maybe Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Timeline =
|
||||||
|
{ events : List ClientEventWithoutRoomID
|
||||||
|
, limited : Maybe Bool
|
||||||
|
, prevBatch : Maybe String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias UnreadNotificationCounts =
|
||||||
|
{ highlightCount : Maybe Int
|
||||||
|
, notificationCount : Maybe Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias KnockedRoom =
|
||||||
|
{ knockState : Maybe KnockState }
|
||||||
|
|
||||||
|
|
||||||
|
type alias KnockState =
|
||||||
|
{ events : Maybe (List StrippedStateEvent) }
|
||||||
|
|
||||||
|
|
||||||
|
type alias LeftRoom =
|
||||||
|
{ accountData : Maybe AccountData
|
||||||
|
, state : Maybe State
|
||||||
|
, timeline : Maybe Timeline
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias DeviceLists =
|
||||||
|
{ changed : Maybe (List String)
|
||||||
|
, left : Maybe (List String)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias ToDevice =
|
||||||
|
{ events : Maybe (List ToDeviceEvent) }
|
||||||
|
|
||||||
|
|
||||||
|
type alias ToDeviceEvent =
|
||||||
|
{ content : Maybe Json.Value
|
||||||
|
, sender : Maybe User
|
||||||
|
, eventType : Maybe String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
coderSyncResponse : Json.Coder SyncResponse
|
||||||
|
coderSyncResponse =
|
||||||
|
Json.object8
|
||||||
|
{ name = "SyncResponse"
|
||||||
|
, description = [ "An event that is part of a response." ]
|
||||||
|
, init = SyncResponse
|
||||||
|
}
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "account_data"
|
||||||
|
, toField = .accountData
|
||||||
|
, description = [ "The global private data created by this user." ]
|
||||||
|
, coder = coderAccountData
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "device_lists"
|
||||||
|
, toField = .deviceLists
|
||||||
|
, description = [ "Information on end-to-end device updates, as specified in End-to-end encryption." ]
|
||||||
|
, coder = coderDeviceLists
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "device_one_time_keys_count"
|
||||||
|
, toField = .deviceOneTimeKeysCount
|
||||||
|
, description = [ "Information on end-to-end encryption keys, as specified in End-to-end encryption." ]
|
||||||
|
, coder = Json.fastDict Json.int
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.required
|
||||||
|
{ fieldName = "device_unused_fallback_key_types"
|
||||||
|
, toField = .deviceUnusedFallbackKeyTypes
|
||||||
|
, description = [ "The unused fallback key algorithms." ]
|
||||||
|
, coder = Json.list Json.string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.required
|
||||||
|
{ fieldName = "next_batch"
|
||||||
|
, toField = .nextBatch
|
||||||
|
, description = [ "Required: The batch token to supply in the since param of the next /sync request." ]
|
||||||
|
, coder = Json.string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "presence"
|
||||||
|
, toField = .presence
|
||||||
|
, description = [ "The updates to the presence status of other users." ]
|
||||||
|
, coder = coderPresence
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "rooms"
|
||||||
|
, toField = .rooms
|
||||||
|
, description = [ "Updates to rooms." ]
|
||||||
|
, coder = coderRooms
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "to_device"
|
||||||
|
, toField = .toDevice
|
||||||
|
, description = [ "Information on the send-to-device messages for the client device, as defined in Send-to-Device messaging." ]
|
||||||
|
, coder = coderToDevice
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
coderAccountData : Json.Coder AccountData
|
||||||
|
coderAccountData =
|
||||||
|
PV.coderAccountData
|
||||||
|
|
||||||
|
|
||||||
|
coderEvent : Json.Coder Event
|
||||||
|
coderEvent =
|
||||||
|
PV.coderEvent
|
||||||
|
|
||||||
|
|
||||||
|
coderPresence : Json.Coder Presence
|
||||||
|
coderPresence =
|
||||||
|
PV.coderPresence
|
||||||
|
|
||||||
|
|
||||||
|
coderRooms : Json.Coder Rooms
|
||||||
|
coderRooms =
|
||||||
|
Json.object4
|
||||||
|
{ name = "Rooms"
|
||||||
|
, description = [ "Updates to rooms." ]
|
||||||
|
, init = Rooms
|
||||||
|
}
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "invite"
|
||||||
|
, toField = .invite
|
||||||
|
, description = [ "The rooms that the user has been invited to, mapped as room ID to room information." ]
|
||||||
|
, coder = Json.fastDict coderInvitedRoom
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "join"
|
||||||
|
, toField = .join
|
||||||
|
, description = [ "The rooms that the user has joined, mapped as room ID to room information." ]
|
||||||
|
, coder = Json.fastDict coderJoinedRoom
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "knock"
|
||||||
|
, toField = .knock
|
||||||
|
, description = [ "The rooms that the user has knocked upon, mapped as room ID to room information." ]
|
||||||
|
, coder = Json.fastDict coderKnockedRoom
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "leave"
|
||||||
|
, toField = .leave
|
||||||
|
, description = [ "The rooms that the user has left or been banned from, mapped as room ID to room information." ]
|
||||||
|
, coder = Json.fastDict coderLeftRoom
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
coderInvitedRoom : Json.Coder InvitedRoom
|
||||||
|
coderInvitedRoom =
|
||||||
|
PV.coderInvitedRoom
|
||||||
|
|
||||||
|
|
||||||
|
coderInviteState : Json.Coder InviteState
|
||||||
|
coderInviteState =
|
||||||
|
PV.coderInviteState
|
||||||
|
|
||||||
|
|
||||||
|
coderStrippedStateEvent : Json.Coder StrippedStateEvent
|
||||||
|
coderStrippedStateEvent =
|
||||||
|
PV.coderStrippedState
|
||||||
|
|
||||||
|
|
||||||
|
coderJoinedRoom : Json.Coder JoinedRoom
|
||||||
|
coderJoinedRoom =
|
||||||
|
Json.object6
|
||||||
|
{ name = "JoinedRoom"
|
||||||
|
, description = [ "The rooms that the user has joined." ]
|
||||||
|
, init = JoinedRoom
|
||||||
|
}
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "account_data"
|
||||||
|
, toField = .accountData
|
||||||
|
, description = [ "The private data that this user has attached to this room." ]
|
||||||
|
, coder = coderAccountData
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "ephemeral"
|
||||||
|
, toField = .ephemeral
|
||||||
|
, description = [ "The ephemeral events in the room that aren’t recorded in the timeline or state of the room. e.g. typing." ]
|
||||||
|
, coder = coderEphemeral
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "state"
|
||||||
|
, toField = .state
|
||||||
|
, description = [ "Updates to the state, between the time indicated by the since parameter, and the start of the timeline (or all state up to the start of the timeline, if since is not given, or full_state is true).", "N.B. state updates for m.room.member events will be incomplete if lazy_load_members is enabled in the /sync filter, and only return the member events required to display the senders of the timeline events in this response." ]
|
||||||
|
, coder = coderState
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "summary"
|
||||||
|
, toField = .summary
|
||||||
|
, description = [ "Information about the room which clients may need to correctly render it to users." ]
|
||||||
|
, coder = coderRoomSummary
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "timeline"
|
||||||
|
, toField = .timeline
|
||||||
|
, description = [ "The timeline of messages and state changes in the room." ]
|
||||||
|
, coder = coderTimeline
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "unread_notifications"
|
||||||
|
, toField = .unreadNotifications
|
||||||
|
, description = [ "Counts of unread notifications for this room. See the Receiving notifications section for more information on how these are calculated." ]
|
||||||
|
, coder = coderUnreadNotificationCounts
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
coderEphemeral : Json.Coder Ephemeral
|
||||||
|
coderEphemeral =
|
||||||
|
PV.coderEphemeral
|
||||||
|
|
||||||
|
|
||||||
|
coderState : Json.Coder State
|
||||||
|
coderState =
|
||||||
|
Json.object1
|
||||||
|
{ name = "State"
|
||||||
|
, description = [ "Updates to the state." ]
|
||||||
|
, init = State
|
||||||
|
}
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "events"
|
||||||
|
, toField = .events
|
||||||
|
, description = [ "List of events." ]
|
||||||
|
, coder = Json.list coderClientEventWithoutRoomID
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
coderClientEventWithoutRoomID : Json.Coder ClientEventWithoutRoomID
|
||||||
|
coderClientEventWithoutRoomID =
|
||||||
|
Json.object7
|
||||||
|
{ name = "ClientEventWithoutRoomID"
|
||||||
|
, description = [ "An event without a room ID." ]
|
||||||
|
, init = ClientEventWithoutRoomID
|
||||||
|
}
|
||||||
|
(Json.field.required
|
||||||
|
{ fieldName = "content"
|
||||||
|
, toField = .content
|
||||||
|
, description = [ "Required: The body of this event, as created by the client which sent it." ]
|
||||||
|
, coder = Json.value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.required
|
||||||
|
{ fieldName = "event_id"
|
||||||
|
, toField = .eventId
|
||||||
|
, description = [ "Required: The globally unique identifier for this event." ]
|
||||||
|
, coder = Json.string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.required
|
||||||
|
{ fieldName = "origin_server_ts"
|
||||||
|
, toField = .originServerTs
|
||||||
|
, description = [ "Required: Timestamp (in milliseconds since the unix epoch) on originating homeserver when this event was sent." ]
|
||||||
|
, coder = Timestamp.coder
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.required
|
||||||
|
{ fieldName = "sender"
|
||||||
|
, toField = .sender
|
||||||
|
, description = [ "Required: Contains the fully-qualified ID of the user who sent this event." ]
|
||||||
|
, coder = User.coder
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "state_key"
|
||||||
|
, toField = .stateKey
|
||||||
|
, description = [ "Present if, and only if, this event is a state event. The key making this piece of state unique in the room. Note that it is often an empty string." ]
|
||||||
|
, coder = Json.string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.required
|
||||||
|
{ fieldName = "type"
|
||||||
|
, toField = .eventType
|
||||||
|
, description = [ "Required: The type of the event." ]
|
||||||
|
, coder = Json.string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "unsigned"
|
||||||
|
, toField = .unsigned
|
||||||
|
, description = [ "Contains optional extra information about the event." ]
|
||||||
|
, coder = coderUnsignedData
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
coderUnsignedData : Json.Coder UnsignedData
|
||||||
|
coderUnsignedData =
|
||||||
|
Json.object4
|
||||||
|
{ name = "UnsignedData"
|
||||||
|
, description = [ "Contains optional extra information about the event." ]
|
||||||
|
, init =
|
||||||
|
\a b c d ->
|
||||||
|
UnsignedData
|
||||||
|
{ age = a
|
||||||
|
, prevContent = b
|
||||||
|
, redactedBecause = c
|
||||||
|
, transactionId = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "age"
|
||||||
|
, toField = \(UnsignedData u) -> u.age
|
||||||
|
, description = [ "The time in milliseconds that has elapsed since the event was sent. This field is generated by the local homeserver, and may be incorrect if the local time on at least one of the two servers is out of sync, which can cause the age to either be negative or greater than it actually is." ]
|
||||||
|
, coder = Json.int
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "prev_content"
|
||||||
|
, toField = \(UnsignedData u) -> u.prevContent
|
||||||
|
, description = [ "The previous content for this event. This field is generated by the local homeserver, and is only returned if the event is a state event, and the client has permission to see the previous content.", "Changed in v1.2: Previously, this field was specified at the top level of returned events rather than in unsigned (with the exception of the GET .../notifications endpoint), though in practice no known server implementations honoured this." ]
|
||||||
|
, coder = Json.value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "redacted_because"
|
||||||
|
, toField = \(UnsignedData u) -> u.redactedBecause
|
||||||
|
, description = [ "The event that redacted this event, if any." ]
|
||||||
|
, coder = Json.lazy (\_ -> coderClientEventWithoutRoomID)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "transaction_id"
|
||||||
|
, toField = \(UnsignedData u) -> u.transactionId
|
||||||
|
, description = [ "The client-supplied transaction ID, for example, provided via PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}, if the client being given the event is the same one which sent it." ]
|
||||||
|
, coder = Json.string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
coderRoomSummary : Json.Coder RoomSummary
|
||||||
|
coderRoomSummary =
|
||||||
|
PV.coderRoomSummary
|
||||||
|
|
||||||
|
|
||||||
|
coderTimeline : Json.Coder Timeline
|
||||||
|
coderTimeline =
|
||||||
|
Json.object3
|
||||||
|
{ name = "Timeline"
|
||||||
|
, description = [ "The timeline of messages and state changes in the room." ]
|
||||||
|
, init = Timeline
|
||||||
|
}
|
||||||
|
(Json.field.required
|
||||||
|
{ fieldName = "events"
|
||||||
|
, toField = .events
|
||||||
|
, description = [ "Required: List of events." ]
|
||||||
|
, coder = Json.list coderClientEventWithoutRoomID
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "limited"
|
||||||
|
, toField = .limited
|
||||||
|
, description = [ "True if the number of events returned was limited by the limit on the filter." ]
|
||||||
|
, coder = Json.bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "prev_batch"
|
||||||
|
, toField = .prevBatch
|
||||||
|
, description = [ "A token that can be supplied to the from parameter of the /rooms/<room_id>/messages endpoint in order to retrieve earlier events. If no earlier events are available, this property may be omitted from the response." ]
|
||||||
|
, coder = Json.string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
coderUnreadNotificationCounts : Json.Coder UnreadNotificationCounts
|
||||||
|
coderUnreadNotificationCounts =
|
||||||
|
PV.coderUnreadNotificationCounts
|
||||||
|
|
||||||
|
|
||||||
|
coderKnockedRoom : Json.Coder KnockedRoom
|
||||||
|
coderKnockedRoom =
|
||||||
|
PV.coderKnockedRoom
|
||||||
|
|
||||||
|
|
||||||
|
coderKnockState : Json.Coder KnockState
|
||||||
|
coderKnockState =
|
||||||
|
PV.coderKnockState
|
||||||
|
|
||||||
|
|
||||||
|
coderLeftRoom : Json.Coder LeftRoom
|
||||||
|
coderLeftRoom =
|
||||||
|
Json.object3
|
||||||
|
{ name = "LeftRoom"
|
||||||
|
, description = [ "The rooms that the user has left or been banned from." ]
|
||||||
|
, init = LeftRoom
|
||||||
|
}
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "account_data"
|
||||||
|
, toField = .accountData
|
||||||
|
, description = [ "The private data that this user has attached to this room." ]
|
||||||
|
, coder = coderAccountData
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "state"
|
||||||
|
, toField = .state
|
||||||
|
, description = [ "The state updates for the room up to the start of the timeline." ]
|
||||||
|
, coder = coderState
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "timeline"
|
||||||
|
, toField = .timeline
|
||||||
|
, description = [ "The timeline of messages and state changes in the room up to the point when the user left." ]
|
||||||
|
, coder = coderTimeline
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
coderDeviceLists : Json.Coder DeviceLists
|
||||||
|
coderDeviceLists =
|
||||||
|
PV.coderDeviceLists
|
||||||
|
|
||||||
|
|
||||||
|
coderToDevice : Json.Coder ToDevice
|
||||||
|
coderToDevice =
|
||||||
|
PV.coderToDevice
|
||||||
|
|
||||||
|
|
||||||
|
coderToDeviceEvent : Json.Coder ToDeviceEvent
|
||||||
|
coderToDeviceEvent =
|
||||||
|
PV.coderToDeviceEvent
|
||||||
|
|
||||||
|
|
||||||
|
updateSyncResponse : { filter : Filter, since : Maybe String } -> SyncResponse -> ( E.EnvelopeUpdate V.VaultUpdate, List Log )
|
||||||
|
updateSyncResponse { filter, since } response =
|
||||||
|
-- Account data
|
||||||
|
[ response.accountData
|
||||||
|
|> Maybe.andThen .events
|
||||||
|
|> Maybe.map (List.map (\e -> V.SetAccountData e.eventType e.content))
|
||||||
|
|> Maybe.map
|
||||||
|
(\x ->
|
||||||
|
( E.ContentUpdate <| V.More x
|
||||||
|
, if List.length x > 0 then
|
||||||
|
List.length x
|
||||||
|
|> Text.logs.syncAccountDataFound
|
||||||
|
|> log.debug
|
||||||
|
|> List.singleton
|
||||||
|
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
-- TODO: Add device lists
|
||||||
|
-- Next batch
|
||||||
|
, Just ( E.SetNextBatch response.nextBatch, [] )
|
||||||
|
|
||||||
|
-- TODO: Add presence
|
||||||
|
-- Rooms
|
||||||
|
, Maybe.map
|
||||||
|
(updateRooms { filter = filter, nextBatch = response.nextBatch, since = since }
|
||||||
|
>> Tuple.mapFirst E.ContentUpdate
|
||||||
|
)
|
||||||
|
response.rooms
|
||||||
|
|
||||||
|
-- TODO: Add to_device
|
||||||
|
]
|
||||||
|
|> List.filterMap identity
|
||||||
|
|> List.unzip
|
||||||
|
|> Tuple.mapFirst E.More
|
||||||
|
|> Tuple.mapSecond List.concat
|
||||||
|
|
||||||
|
|
||||||
|
updateRooms : { filter : Filter, nextBatch : String, since : Maybe String } -> Rooms -> ( V.VaultUpdate, List Log )
|
||||||
|
updateRooms { filter, nextBatch, since } rooms =
|
||||||
|
let
|
||||||
|
( roomUpdate, roomLogs ) =
|
||||||
|
rooms.join
|
||||||
|
|> Maybe.withDefault Dict.empty
|
||||||
|
|> Dict.toList
|
||||||
|
|> List.map
|
||||||
|
(\( roomId, room ) ->
|
||||||
|
let
|
||||||
|
( u, l ) =
|
||||||
|
updateJoinedRoom
|
||||||
|
{ filter = filter
|
||||||
|
, nextBatch = nextBatch
|
||||||
|
, roomId = roomId
|
||||||
|
, since = since
|
||||||
|
}
|
||||||
|
room
|
||||||
|
in
|
||||||
|
( V.MapRoom roomId u, l )
|
||||||
|
)
|
||||||
|
|> List.unzip
|
||||||
|
|> Tuple.mapBoth V.More List.concat
|
||||||
|
in
|
||||||
|
( V.More
|
||||||
|
-- Add rooms
|
||||||
|
[ rooms.join
|
||||||
|
|> Maybe.withDefault Dict.empty
|
||||||
|
|> Dict.keys
|
||||||
|
|> List.map V.CreateRoomIfNotExists
|
||||||
|
|> V.More
|
||||||
|
|
||||||
|
-- Update rooms
|
||||||
|
, roomUpdate
|
||||||
|
|
||||||
|
-- TODO: Add invited rooms
|
||||||
|
-- TODO: Add knocked rooms
|
||||||
|
-- TODO: Add left rooms
|
||||||
|
]
|
||||||
|
, roomLogs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
updateJoinedRoom : { filter : Filter, nextBatch : String, roomId : String, since : Maybe String } -> JoinedRoom -> ( R.RoomUpdate, List Log )
|
||||||
|
updateJoinedRoom data room =
|
||||||
|
( R.More
|
||||||
|
[ room.accountData
|
||||||
|
|> Maybe.andThen .events
|
||||||
|
|> Maybe.map
|
||||||
|
(\events ->
|
||||||
|
events
|
||||||
|
|> List.map (\e -> R.SetAccountData e.eventType e.content)
|
||||||
|
|> R.More
|
||||||
|
)
|
||||||
|
|> R.Optional
|
||||||
|
, room.ephemeral
|
||||||
|
|> Maybe.andThen .events
|
||||||
|
|> Maybe.map R.SetEphemeral
|
||||||
|
|> R.Optional
|
||||||
|
|
||||||
|
-- TODO: Add state
|
||||||
|
-- TODO: Add RoomSummary
|
||||||
|
, room.timeline
|
||||||
|
|> Maybe.map (updateTimeline data)
|
||||||
|
|> R.Optional
|
||||||
|
|
||||||
|
-- TODO: Add unread notifications
|
||||||
|
]
|
||||||
|
, []
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
updateTimeline : { filter : Filter, nextBatch : String, roomId : String, since : Maybe String } -> Timeline -> R.RoomUpdate
|
||||||
|
updateTimeline { filter, nextBatch, roomId, since } timeline =
|
||||||
|
let
|
||||||
|
limited : Bool
|
||||||
|
limited =
|
||||||
|
Maybe.withDefault False timeline.limited
|
||||||
|
|
||||||
|
newEvents : List Event.Event
|
||||||
|
newEvents =
|
||||||
|
List.map (toEvent roomId) timeline.events
|
||||||
|
in
|
||||||
|
case ( limited, timeline.prevBatch ) of
|
||||||
|
( False, Just p ) ->
|
||||||
|
if timeline.prevBatch == since then
|
||||||
|
R.AddSync
|
||||||
|
{ events = newEvents
|
||||||
|
, filter = filter
|
||||||
|
, start = Just p
|
||||||
|
, end = nextBatch
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
R.More
|
||||||
|
[ R.AddSync
|
||||||
|
{ events = []
|
||||||
|
, filter = filter
|
||||||
|
, start = since
|
||||||
|
, end = p
|
||||||
|
}
|
||||||
|
, R.AddSync
|
||||||
|
{ events = newEvents
|
||||||
|
, filter = filter
|
||||||
|
, start = Just p
|
||||||
|
, end = nextBatch
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
( False, Nothing ) ->
|
||||||
|
R.AddSync
|
||||||
|
{ events = newEvents
|
||||||
|
, filter = filter
|
||||||
|
, start = since
|
||||||
|
, end = nextBatch
|
||||||
|
}
|
||||||
|
|
||||||
|
( True, _ ) ->
|
||||||
|
R.AddSync
|
||||||
|
{ events = newEvents
|
||||||
|
, filter = filter
|
||||||
|
, start = timeline.prevBatch
|
||||||
|
, end = nextBatch
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
toEvent : String -> ClientEventWithoutRoomID -> Event.Event
|
||||||
|
toEvent roomId event =
|
||||||
|
Recursion.runRecursion
|
||||||
|
(\ev ->
|
||||||
|
case Maybe.andThen (\(UnsignedData u) -> u.redactedBecause) ev.unsigned of
|
||||||
|
Just e ->
|
||||||
|
Recursion.recurseThen e
|
||||||
|
(\eo ->
|
||||||
|
Recursion.base
|
||||||
|
{ content = ev.content
|
||||||
|
, eventId = ev.eventId
|
||||||
|
, originServerTs = ev.originServerTs
|
||||||
|
, roomId = roomId
|
||||||
|
, sender = ev.sender
|
||||||
|
, stateKey = ev.stateKey
|
||||||
|
, eventType = ev.eventType
|
||||||
|
, unsigned = toUnsigned (Just eo) ev.unsigned
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
Recursion.base
|
||||||
|
{ content = ev.content
|
||||||
|
, eventId = ev.eventId
|
||||||
|
, originServerTs = ev.originServerTs
|
||||||
|
, roomId = roomId
|
||||||
|
, sender = ev.sender
|
||||||
|
, stateKey = ev.stateKey
|
||||||
|
, eventType = ev.eventType
|
||||||
|
, unsigned = toUnsigned Nothing ev.unsigned
|
||||||
|
}
|
||||||
|
)
|
||||||
|
event
|
||||||
|
|
||||||
|
|
||||||
|
toUnsigned : Maybe Event.Event -> Maybe UnsignedData -> Maybe Event.UnsignedData
|
||||||
|
toUnsigned ev unsigned =
|
||||||
|
case ( ev, unsigned ) of
|
||||||
|
( Nothing, Nothing ) ->
|
||||||
|
Nothing
|
||||||
|
|
||||||
|
( Just e, Nothing ) ->
|
||||||
|
{ age = Nothing
|
||||||
|
, membership = Nothing
|
||||||
|
, prevContent = Nothing
|
||||||
|
, redactedBecause = Just e
|
||||||
|
, transactionId = Nothing
|
||||||
|
}
|
||||||
|
|> Event.UnsignedData
|
||||||
|
|> Just
|
||||||
|
|
||||||
|
( _, Just (UnsignedData u) ) ->
|
||||||
|
{ age = u.age
|
||||||
|
, membership = Nothing
|
||||||
|
, prevContent = u.prevContent
|
||||||
|
, redactedBecause = ev
|
||||||
|
, transactionId = u.transactionId
|
||||||
|
}
|
||||||
|
|> Event.UnsignedData
|
||||||
|
|> Just
|
|
@ -0,0 +1,580 @@
|
||||||
|
module Internal.Api.Sync.V3 exposing (..)
|
||||||
|
|
||||||
|
{-|
|
||||||
|
|
||||||
|
|
||||||
|
# Sync response
|
||||||
|
|
||||||
|
This API module represents the /sync endpoint on the following Matrix spec
|
||||||
|
versions:
|
||||||
|
|
||||||
|
<https://spec.matrix.org/v1.4/client-server-api/#syncing>
|
||||||
|
<https://spec.matrix.org/v1.5/client-server-api/#syncing>
|
||||||
|
<https://spec.matrix.org/v1.6/client-server-api/#syncing>
|
||||||
|
<https://spec.matrix.org/v1.7/client-server-api/#syncing>
|
||||||
|
<https://spec.matrix.org/v1.8/client-server-api/#syncing>
|
||||||
|
<https://spec.matrix.org/v1.9/client-server-api/#syncing>
|
||||||
|
<https://spec.matrix.org/v1.10/client-server-api/#syncing>
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
import FastDict as Dict exposing (Dict)
|
||||||
|
import Internal.Api.Sync.V2 as PV
|
||||||
|
import Internal.Config.Log exposing (Log, log)
|
||||||
|
import Internal.Config.Text as Text
|
||||||
|
import Internal.Filter.Timeline exposing (Filter)
|
||||||
|
import Internal.Tools.Json as Json
|
||||||
|
import Internal.Tools.Timestamp exposing (Timestamp)
|
||||||
|
import Internal.Values.Envelope as E
|
||||||
|
import Internal.Values.Event as Event
|
||||||
|
import Internal.Values.Room as R
|
||||||
|
import Internal.Values.User exposing (User)
|
||||||
|
import Internal.Values.Vault as V
|
||||||
|
|
||||||
|
|
||||||
|
type alias SyncResponse =
|
||||||
|
{ accountData : Maybe AccountData
|
||||||
|
, deviceLists : Maybe DeviceLists
|
||||||
|
, deviceOneTimeKeysCount : Maybe (Dict String Int)
|
||||||
|
, deviceUnusedFallbackKeyTypes : List String
|
||||||
|
, nextBatch : String
|
||||||
|
, presence : Maybe Presence
|
||||||
|
, rooms : Maybe Rooms
|
||||||
|
, toDevice : Maybe ToDevice
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias AccountData =
|
||||||
|
{ events : Maybe (List Event) }
|
||||||
|
|
||||||
|
|
||||||
|
type alias Event =
|
||||||
|
{ content : Json.Value
|
||||||
|
, eventType : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Presence =
|
||||||
|
{ events : Maybe (List Event) }
|
||||||
|
|
||||||
|
|
||||||
|
type alias Rooms =
|
||||||
|
{ invite : Maybe (Dict String InvitedRoom)
|
||||||
|
, join : Maybe (Dict String JoinedRoom)
|
||||||
|
, knock : Maybe (Dict String KnockedRoom)
|
||||||
|
, leave : Maybe (Dict String LeftRoom)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias InvitedRoom =
|
||||||
|
{ inviteState : Maybe InviteState }
|
||||||
|
|
||||||
|
|
||||||
|
type alias InviteState =
|
||||||
|
{ events : Maybe (List StrippedStateEvent) }
|
||||||
|
|
||||||
|
|
||||||
|
type alias StrippedStateEvent =
|
||||||
|
{ content : Json.Value
|
||||||
|
, sender : User
|
||||||
|
, stateKey : String
|
||||||
|
, eventType : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias JoinedRoom =
|
||||||
|
{ accountData : Maybe AccountData
|
||||||
|
, ephemeral : Maybe Ephemeral
|
||||||
|
, state : Maybe State
|
||||||
|
, summary : Maybe RoomSummary
|
||||||
|
, timeline : Maybe Timeline
|
||||||
|
, unreadNotifications : Maybe UnreadNotificationCounts
|
||||||
|
, unreadThreadNotifications : Maybe (Dict String ThreadNotificationCounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Ephemeral =
|
||||||
|
{ events : Maybe (List Event) }
|
||||||
|
|
||||||
|
|
||||||
|
type alias State =
|
||||||
|
{ events : Maybe (List ClientEventWithoutRoomID) }
|
||||||
|
|
||||||
|
|
||||||
|
type alias ClientEventWithoutRoomID =
|
||||||
|
{ content : Json.Value
|
||||||
|
, eventId : String
|
||||||
|
, originServerTs : Timestamp
|
||||||
|
, sender : User
|
||||||
|
, stateKey : Maybe String
|
||||||
|
, eventType : String
|
||||||
|
, unsigned : Maybe UnsignedData
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias UnsignedData =
|
||||||
|
PV.UnsignedData
|
||||||
|
|
||||||
|
|
||||||
|
type alias RoomSummary =
|
||||||
|
{ mHeroes : Maybe (List String)
|
||||||
|
, mInvitedMemberCount : Maybe Int
|
||||||
|
, mJoinedMemberCount : Maybe Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Timeline =
|
||||||
|
{ events : List ClientEventWithoutRoomID
|
||||||
|
, limited : Maybe Bool
|
||||||
|
, prevBatch : Maybe String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias UnreadNotificationCounts =
|
||||||
|
{ highlightCount : Maybe Int
|
||||||
|
, notificationCount : Maybe Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias ThreadNotificationCounts =
|
||||||
|
{ highlightCount : Maybe Int
|
||||||
|
, notificationCount : Maybe Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias KnockedRoom =
|
||||||
|
{ knockState : Maybe KnockState }
|
||||||
|
|
||||||
|
|
||||||
|
type alias KnockState =
|
||||||
|
{ events : Maybe (List StrippedStateEvent) }
|
||||||
|
|
||||||
|
|
||||||
|
type alias LeftRoom =
|
||||||
|
{ accountData : Maybe AccountData
|
||||||
|
, state : Maybe State
|
||||||
|
, timeline : Maybe Timeline
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias DeviceLists =
|
||||||
|
{ changed : Maybe (List String)
|
||||||
|
, left : Maybe (List String)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias ToDevice =
|
||||||
|
{ events : Maybe (List ToDeviceEvent) }
|
||||||
|
|
||||||
|
|
||||||
|
type alias ToDeviceEvent =
|
||||||
|
{ content : Maybe Json.Value
|
||||||
|
, sender : Maybe User
|
||||||
|
, eventType : Maybe String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
coderSyncResponse : Json.Coder SyncResponse
|
||||||
|
coderSyncResponse =
|
||||||
|
Json.object8
|
||||||
|
{ name = "SyncResponse"
|
||||||
|
, description = [ "The response for a sync request." ]
|
||||||
|
, init = SyncResponse
|
||||||
|
}
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "account_data"
|
||||||
|
, toField = .accountData
|
||||||
|
, description = [ "The global private data created by this user." ]
|
||||||
|
, coder = coderAccountData
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "device_lists"
|
||||||
|
, toField = .deviceLists
|
||||||
|
, description = [ "Information on end-to-end device updates, as specified in End-to-end encryption." ]
|
||||||
|
, coder = coderDeviceLists
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "device_one_time_keys_count"
|
||||||
|
, toField = .deviceOneTimeKeysCount
|
||||||
|
, description = [ "Information on end-to-end encryption keys, as specified in End-to-end encryption." ]
|
||||||
|
, coder = Json.fastDict Json.int
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.required
|
||||||
|
{ fieldName = "device_unused_fallback_key_types"
|
||||||
|
, toField = .deviceUnusedFallbackKeyTypes
|
||||||
|
, description = [ "The unused fallback key algorithms." ]
|
||||||
|
, coder = Json.list Json.string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.required
|
||||||
|
{ fieldName = "next_batch"
|
||||||
|
, toField = .nextBatch
|
||||||
|
, description = [ "The batch token to supply in the since param of the next /sync request." ]
|
||||||
|
, coder = Json.string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "presence"
|
||||||
|
, toField = .presence
|
||||||
|
, description = [ "The updates to the presence status of other users." ]
|
||||||
|
, coder = coderPresence
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "rooms"
|
||||||
|
, toField = .rooms
|
||||||
|
, description = [ "Updates to rooms." ]
|
||||||
|
, coder = coderRooms
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "to_device"
|
||||||
|
, toField = .toDevice
|
||||||
|
, description = [ "Information on the send-to-device messages for the client device, as defined in Send-to-Device messaging." ]
|
||||||
|
, coder = coderToDevice
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
coderAccountData : Json.Coder AccountData
|
||||||
|
coderAccountData =
|
||||||
|
PV.coderAccountData
|
||||||
|
|
||||||
|
|
||||||
|
coderEvent : Json.Coder Event
|
||||||
|
coderEvent =
|
||||||
|
PV.coderEvent
|
||||||
|
|
||||||
|
|
||||||
|
coderPresence : Json.Coder Presence
|
||||||
|
coderPresence =
|
||||||
|
PV.coderPresence
|
||||||
|
|
||||||
|
|
||||||
|
coderRooms : Json.Coder Rooms
|
||||||
|
coderRooms =
|
||||||
|
Json.object4
|
||||||
|
{ name = "Rooms"
|
||||||
|
, description = [ "Updates to rooms." ]
|
||||||
|
, init = Rooms
|
||||||
|
}
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "invite"
|
||||||
|
, toField = .invite
|
||||||
|
, description = [ "The rooms that the user has been invited to, mapped as room ID to room information." ]
|
||||||
|
, coder = Json.fastDict coderInvitedRoom
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "join"
|
||||||
|
, toField = .join
|
||||||
|
, description = [ "The rooms that the user has joined, mapped as room ID to room information." ]
|
||||||
|
, coder = Json.fastDict coderJoinedRoom
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "knock"
|
||||||
|
, toField = .knock
|
||||||
|
, description = [ "The rooms that the user has knocked upon, mapped as room ID to room information." ]
|
||||||
|
, coder = Json.fastDict coderKnockedRoom
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "leave"
|
||||||
|
, toField = .leave
|
||||||
|
, description = [ "The rooms that the user has left or been banned from, mapped as room ID to room information." ]
|
||||||
|
, coder = Json.fastDict coderLeftRoom
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
coderInvitedRoom : Json.Coder InvitedRoom
|
||||||
|
coderInvitedRoom =
|
||||||
|
PV.coderInvitedRoom
|
||||||
|
|
||||||
|
|
||||||
|
coderInviteState : Json.Coder InviteState
|
||||||
|
coderInviteState =
|
||||||
|
PV.coderInviteState
|
||||||
|
|
||||||
|
|
||||||
|
coderStrippedStateEvent : Json.Coder StrippedStateEvent
|
||||||
|
coderStrippedStateEvent =
|
||||||
|
PV.coderStrippedStateEvent
|
||||||
|
|
||||||
|
|
||||||
|
coderJoinedRoom : Json.Coder JoinedRoom
|
||||||
|
coderJoinedRoom =
|
||||||
|
Json.object7
|
||||||
|
{ name = "JoinedRoom"
|
||||||
|
, description = [ "Information about a room the user has joined." ]
|
||||||
|
, init = JoinedRoom
|
||||||
|
}
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "account_data"
|
||||||
|
, toField = .accountData
|
||||||
|
, description = [ "The private data that this user has attached to this room." ]
|
||||||
|
, coder = coderAccountData
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "ephemeral"
|
||||||
|
, toField = .ephemeral
|
||||||
|
, description = [ "The ephemeral events in the room that aren’t recorded in the timeline or state of the room. e.g. typing." ]
|
||||||
|
, coder = coderEphemeral
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "state"
|
||||||
|
, toField = .state
|
||||||
|
, description = [ "Updates to the state, between the time indicated by the since parameter, and the start of the timeline (or all state up to the start of the timeline, if since is not given, or full_state is true).", "N.B. state updates for m.room.member events will be incomplete if lazy_load_members is enabled in the /sync filter, and only return the member events required to display the senders of the timeline events in this response." ]
|
||||||
|
, coder = coderState
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "summary"
|
||||||
|
, toField = .summary
|
||||||
|
, description = [ "Information about the room which clients may need to correctly render it to users." ]
|
||||||
|
, coder = coderRoomSummary
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "timeline"
|
||||||
|
, toField = .timeline
|
||||||
|
, description = [ "The timeline of messages and state changes in the room." ]
|
||||||
|
, coder = coderTimeline
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "unread_notifications"
|
||||||
|
, toField = .unreadNotifications
|
||||||
|
, description = [ "Counts of unread notifications for this room. See the Receiving notifications section for more information on how these are calculated.", "If unread_thread_notifications was specified as true on the RoomEventFilter, these counts will only be for the main timeline rather than all events in the room. See the threading module for more information.", "Changed in v1.4: Updated to reflect behaviour of having unread_thread_notifications as true in the RoomEventFilter for /sync." ]
|
||||||
|
, coder = coderUnreadNotificationCounts
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "unread_thread_notifications"
|
||||||
|
, toField = .unreadThreadNotifications
|
||||||
|
, description = [ "If unread_thread_notifications was specified as true on the RoomEventFilter, the notification counts for each thread in this room. The object is keyed by thread root ID, with values matching unread_notifications.", "If a thread does not have any notifications it can be omitted from this object. If no threads have notification counts, this whole object can be omitted.", "Added in v1.4" ]
|
||||||
|
, coder = Json.fastDict coderThreadNotificationCounts
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
coderEphemeral : Json.Coder Ephemeral
|
||||||
|
coderEphemeral =
|
||||||
|
PV.coderEphemeral
|
||||||
|
|
||||||
|
|
||||||
|
coderState : Json.Coder State
|
||||||
|
coderState =
|
||||||
|
PV.coderState
|
||||||
|
|
||||||
|
|
||||||
|
coderClientEventWithoutRoomID : Json.Coder ClientEventWithoutRoomID
|
||||||
|
coderClientEventWithoutRoomID =
|
||||||
|
PV.coderClientEventWithoutRoomID
|
||||||
|
|
||||||
|
|
||||||
|
coderUnsignedData : Json.Coder UnsignedData
|
||||||
|
coderUnsignedData =
|
||||||
|
PV.coderUnsignedData
|
||||||
|
|
||||||
|
|
||||||
|
coderRoomSummary : Json.Coder RoomSummary
|
||||||
|
coderRoomSummary =
|
||||||
|
PV.coderRoomSummary
|
||||||
|
|
||||||
|
|
||||||
|
coderTimeline : Json.Coder Timeline
|
||||||
|
coderTimeline =
|
||||||
|
PV.coderTimeline
|
||||||
|
|
||||||
|
|
||||||
|
coderUnreadNotificationCounts : Json.Coder UnreadNotificationCounts
|
||||||
|
coderUnreadNotificationCounts =
|
||||||
|
PV.coderUnreadNotificationCounts
|
||||||
|
|
||||||
|
|
||||||
|
coderThreadNotificationCounts : Json.Coder ThreadNotificationCounts
|
||||||
|
coderThreadNotificationCounts =
|
||||||
|
Json.object2
|
||||||
|
{ name = "ThreadNotificationCounts"
|
||||||
|
, description = [ "The notification counts for each thread in this room." ]
|
||||||
|
, init = ThreadNotificationCounts
|
||||||
|
}
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "highlight_count"
|
||||||
|
, toField = .highlightCount
|
||||||
|
, description = [ "The number of unread notifications for this thread with the highlight flag set." ]
|
||||||
|
, coder = Json.int
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "notification_count"
|
||||||
|
, toField = .notificationCount
|
||||||
|
, description = [ "The total number of unread notifications for this thread." ]
|
||||||
|
, coder = Json.int
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
coderKnockedRoom : Json.Coder KnockedRoom
|
||||||
|
coderKnockedRoom =
|
||||||
|
PV.coderKnockedRoom
|
||||||
|
|
||||||
|
|
||||||
|
coderKnockState : Json.Coder KnockState
|
||||||
|
coderKnockState =
|
||||||
|
PV.coderKnockState
|
||||||
|
|
||||||
|
|
||||||
|
coderLeftRoom : Json.Coder LeftRoom
|
||||||
|
coderLeftRoom =
|
||||||
|
PV.coderLeftRoom
|
||||||
|
|
||||||
|
|
||||||
|
coderDeviceLists : Json.Coder DeviceLists
|
||||||
|
coderDeviceLists =
|
||||||
|
PV.coderDeviceLists
|
||||||
|
|
||||||
|
|
||||||
|
coderToDevice : Json.Coder ToDevice
|
||||||
|
coderToDevice =
|
||||||
|
PV.coderToDevice
|
||||||
|
|
||||||
|
|
||||||
|
coderToDeviceEvent : Json.Coder ToDeviceEvent
|
||||||
|
coderToDeviceEvent =
|
||||||
|
PV.coderToDeviceEvent
|
||||||
|
|
||||||
|
|
||||||
|
updateSyncResponse : { filter : Filter, since : Maybe String } -> SyncResponse -> ( E.EnvelopeUpdate V.VaultUpdate, List Log )
|
||||||
|
updateSyncResponse { filter, since } response =
|
||||||
|
-- Account data
|
||||||
|
[ response.accountData
|
||||||
|
|> Maybe.andThen .events
|
||||||
|
|> Maybe.map (List.map (\e -> V.SetAccountData e.eventType e.content))
|
||||||
|
|> Maybe.map
|
||||||
|
(\x ->
|
||||||
|
( E.ContentUpdate <| V.More x
|
||||||
|
, if List.length x > 0 then
|
||||||
|
List.length x
|
||||||
|
|> Text.logs.syncAccountDataFound
|
||||||
|
|> log.debug
|
||||||
|
|> List.singleton
|
||||||
|
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
-- TODO: Add device lists
|
||||||
|
-- Next batch
|
||||||
|
, Just ( E.SetNextBatch response.nextBatch, [] )
|
||||||
|
|
||||||
|
-- TODO: Add presence
|
||||||
|
-- Rooms
|
||||||
|
, Maybe.map
|
||||||
|
(updateRooms { filter = filter, nextBatch = response.nextBatch, since = since }
|
||||||
|
>> Tuple.mapFirst E.ContentUpdate
|
||||||
|
)
|
||||||
|
response.rooms
|
||||||
|
|
||||||
|
-- TODO: Add to_device
|
||||||
|
]
|
||||||
|
|> List.filterMap identity
|
||||||
|
|> List.unzip
|
||||||
|
|> Tuple.mapFirst E.More
|
||||||
|
|> Tuple.mapSecond List.concat
|
||||||
|
|
||||||
|
|
||||||
|
updateRooms : { filter : Filter, nextBatch : String, since : Maybe String } -> Rooms -> ( V.VaultUpdate, List Log )
|
||||||
|
updateRooms { filter, nextBatch, since } rooms =
|
||||||
|
let
|
||||||
|
( roomUpdate, roomLogs ) =
|
||||||
|
rooms.join
|
||||||
|
|> Maybe.withDefault Dict.empty
|
||||||
|
|> Dict.toList
|
||||||
|
|> List.map
|
||||||
|
(\( roomId, room ) ->
|
||||||
|
let
|
||||||
|
( u, l ) =
|
||||||
|
updateJoinedRoom
|
||||||
|
{ filter = filter
|
||||||
|
, nextBatch = nextBatch
|
||||||
|
, roomId = roomId
|
||||||
|
, since = since
|
||||||
|
}
|
||||||
|
room
|
||||||
|
in
|
||||||
|
( V.MapRoom roomId u, l )
|
||||||
|
)
|
||||||
|
|> List.unzip
|
||||||
|
|> Tuple.mapBoth V.More List.concat
|
||||||
|
in
|
||||||
|
( V.More
|
||||||
|
-- Add rooms
|
||||||
|
[ rooms.join
|
||||||
|
|> Maybe.withDefault Dict.empty
|
||||||
|
|> Dict.keys
|
||||||
|
|> List.map V.CreateRoomIfNotExists
|
||||||
|
|> V.More
|
||||||
|
|
||||||
|
-- Update rooms
|
||||||
|
, roomUpdate
|
||||||
|
|
||||||
|
-- TODO: Add invited rooms
|
||||||
|
-- TODO: Add knocked rooms
|
||||||
|
-- TODO: Add left rooms
|
||||||
|
]
|
||||||
|
, roomLogs
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
updateJoinedRoom : { filter : Filter, nextBatch : String, roomId : String, since : Maybe String } -> JoinedRoom -> ( R.RoomUpdate, List Log )
|
||||||
|
updateJoinedRoom data room =
|
||||||
|
( R.More
|
||||||
|
[ room.accountData
|
||||||
|
|> Maybe.andThen .events
|
||||||
|
|> Maybe.map
|
||||||
|
(\events ->
|
||||||
|
events
|
||||||
|
|> List.map (\e -> R.SetAccountData e.eventType e.content)
|
||||||
|
|> R.More
|
||||||
|
)
|
||||||
|
|> R.Optional
|
||||||
|
, room.ephemeral
|
||||||
|
|> Maybe.andThen .events
|
||||||
|
|> Maybe.map R.SetEphemeral
|
||||||
|
|> R.Optional
|
||||||
|
|
||||||
|
-- TODO: Add state
|
||||||
|
-- TODO: Add RoomSummary
|
||||||
|
, room.timeline
|
||||||
|
|> Maybe.map (updateTimeline data)
|
||||||
|
|> R.Optional
|
||||||
|
|
||||||
|
-- TODO: Add unread notifications
|
||||||
|
-- TODO: Add unread thread notifications
|
||||||
|
]
|
||||||
|
, []
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
updateTimeline : { filter : Filter, nextBatch : String, roomId : String, since : Maybe String } -> Timeline -> R.RoomUpdate
|
||||||
|
updateTimeline =
|
||||||
|
PV.updateTimeline
|
||||||
|
|
||||||
|
|
||||||
|
toEvent : String -> ClientEventWithoutRoomID -> Event.Event
|
||||||
|
toEvent =
|
||||||
|
PV.toEvent
|
||||||
|
|
||||||
|
|
||||||
|
toUnsigned : Maybe Event.Event -> Maybe UnsignedData -> Maybe Event.UnsignedData
|
||||||
|
toUnsigned =
|
||||||
|
PV.toUnsigned
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
module Internal.Api.Task exposing
|
module Internal.Api.Task exposing
|
||||||
( Task, run, Backpack
|
( Task, run, Backpack
|
||||||
, sendMessageEvent
|
, sendMessageEvent, sync
|
||||||
)
|
)
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
|
@ -23,7 +23,7 @@ up-to-date.
|
||||||
|
|
||||||
## Tasks
|
## Tasks
|
||||||
|
|
||||||
@docs sendMessageEvent
|
@docs sendMessageEvent, sync
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ import Internal.Api.LoginWithUsernameAndPassword.Api
|
||||||
import Internal.Api.Now.Api
|
import Internal.Api.Now.Api
|
||||||
import Internal.Api.Request as Request
|
import Internal.Api.Request as Request
|
||||||
import Internal.Api.SendMessageEvent.Api
|
import Internal.Api.SendMessageEvent.Api
|
||||||
|
import Internal.Api.Sync.Api
|
||||||
import Internal.Api.Versions.Api
|
import Internal.Api.Versions.Api
|
||||||
import Internal.Config.Log exposing (Log, log)
|
import Internal.Config.Log exposing (Log, log)
|
||||||
import Internal.Config.Text as Text
|
import Internal.Config.Text as Text
|
||||||
|
@ -231,6 +232,15 @@ sendMessageEvent input =
|
||||||
|> finishTask
|
|> finishTask
|
||||||
|
|
||||||
|
|
||||||
|
{-| Sync with the Matrix API to stay up-to-date.
|
||||||
|
-}
|
||||||
|
sync : { fullState : Maybe Bool, presence : Maybe String, since : Maybe String, timeout : Maybe Int } -> Task
|
||||||
|
sync input =
|
||||||
|
makeVBA
|
||||||
|
|> C.andThen (Internal.Api.Sync.Api.sync input)
|
||||||
|
|> finishTask
|
||||||
|
|
||||||
|
|
||||||
{-| Transform a completed task into a Cmd.
|
{-| Transform a completed task into a Cmd.
|
||||||
-}
|
-}
|
||||||
run : (Backpack -> msg) -> Task -> APIContext {} -> Cmd msg
|
run : (Backpack -> msg) -> Task -> APIContext {} -> Cmd msg
|
||||||
|
|
|
@ -86,6 +86,5 @@ versionsCoder =
|
||||||
Set.empty
|
Set.empty
|
||||||
}
|
}
|
||||||
, default = ( Set.empty, [] )
|
, default = ( Set.empty, [] )
|
||||||
, defaultToString = always "{}"
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -29,7 +29,7 @@ will assume until overriden by the user.
|
||||||
-}
|
-}
|
||||||
currentVersion : String
|
currentVersion : String
|
||||||
currentVersion =
|
currentVersion =
|
||||||
"beta 3.3.1"
|
"beta 3.4.0"
|
||||||
|
|
||||||
|
|
||||||
{-| The default device name that is being communicated with the Matrix API.
|
{-| The default device name that is being communicated with the Matrix API.
|
||||||
|
|
|
@ -118,12 +118,12 @@ docs :
|
||||||
, event : TypeDocs
|
, event : TypeDocs
|
||||||
, hashdict : TypeDocs
|
, hashdict : TypeDocs
|
||||||
, ibatch : TypeDocs
|
, ibatch : TypeDocs
|
||||||
, iddict : TypeDocs
|
|
||||||
, itoken : TypeDocs
|
, itoken : TypeDocs
|
||||||
, mashdict : TypeDocs
|
, mashdict : TypeDocs
|
||||||
, room : TypeDocs
|
, room : TypeDocs
|
||||||
, settings : TypeDocs
|
, settings : TypeDocs
|
||||||
, stateManager : TypeDocs
|
, stateManager : TypeDocs
|
||||||
|
, strippedEvent : TypeDocs
|
||||||
, timeline : TypeDocs
|
, timeline : TypeDocs
|
||||||
, timelineFilter : TypeDocs
|
, timelineFilter : TypeDocs
|
||||||
, unsigned : TypeDocs
|
, unsigned : TypeDocs
|
||||||
|
@ -169,12 +169,6 @@ docs =
|
||||||
[ "The internal batch tracks a patch of events on the Matrix timeline."
|
[ "The internal batch tracks a patch of events on the Matrix timeline."
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
, iddict =
|
|
||||||
{ name = "Iddict"
|
|
||||||
, description =
|
|
||||||
[ "An iddict automatically handles creating appropriate keys by incrementally assiging a new key to new values."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
, itoken =
|
, itoken =
|
||||||
{ name = "IToken"
|
{ name = "IToken"
|
||||||
, description =
|
, description =
|
||||||
|
@ -206,6 +200,12 @@ docs =
|
||||||
, "Instead of making the user loop through the room's timeline of events, the StateManager offers the user a dictionary-like experience to navigate through the Matrix room state."
|
, "Instead of making the user loop through the room's timeline of events, the StateManager offers the user a dictionary-like experience to navigate through the Matrix room state."
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
, strippedEvent =
|
||||||
|
{ name = "StrippedEvent"
|
||||||
|
, description =
|
||||||
|
[ "The StrippedEvent is a simplified Matrix event that contains no metadata."
|
||||||
|
]
|
||||||
|
}
|
||||||
, timeline =
|
, timeline =
|
||||||
{ name = "Timeline"
|
{ name = "Timeline"
|
||||||
, description =
|
, description =
|
||||||
|
@ -270,6 +270,7 @@ fields :
|
||||||
, baseUrl : Desc
|
, baseUrl : Desc
|
||||||
, deviceId : Desc
|
, deviceId : Desc
|
||||||
, experimental : Desc
|
, experimental : Desc
|
||||||
|
, nextBatch : Desc
|
||||||
, now : Desc
|
, now : Desc
|
||||||
, password : Desc
|
, password : Desc
|
||||||
, refreshToken : Desc
|
, refreshToken : Desc
|
||||||
|
@ -313,6 +314,7 @@ fields :
|
||||||
}
|
}
|
||||||
, room :
|
, room :
|
||||||
{ accountData : Desc
|
{ accountData : Desc
|
||||||
|
, ephemeral : Desc
|
||||||
, events : Desc
|
, events : Desc
|
||||||
, roomId : Desc
|
, roomId : Desc
|
||||||
, state : Desc
|
, state : Desc
|
||||||
|
@ -321,6 +323,7 @@ fields :
|
||||||
, settings :
|
, settings :
|
||||||
{ currentVersion : Desc
|
{ currentVersion : Desc
|
||||||
, deviceName : Desc
|
, deviceName : Desc
|
||||||
|
, presence : Desc
|
||||||
, removePasswordOnLogin : Desc
|
, removePasswordOnLogin : Desc
|
||||||
, syncTime : Desc
|
, syncTime : Desc
|
||||||
}
|
}
|
||||||
|
@ -339,12 +342,14 @@ fields :
|
||||||
}
|
}
|
||||||
, unsigned :
|
, unsigned :
|
||||||
{ age : Desc
|
{ age : Desc
|
||||||
|
, membership : Desc
|
||||||
, prevContent : Desc
|
, prevContent : Desc
|
||||||
, redactedBecause : Desc
|
, redactedBecause : Desc
|
||||||
, transactionId : Desc
|
, transactionId : Desc
|
||||||
}
|
}
|
||||||
, vault :
|
, vault :
|
||||||
{ accountData : Desc
|
{ accountData : Desc
|
||||||
|
, nextBatch : Desc
|
||||||
, rooms : Desc
|
, rooms : Desc
|
||||||
, user : Desc
|
, user : Desc
|
||||||
}
|
}
|
||||||
|
@ -379,6 +384,9 @@ fields =
|
||||||
, experimental =
|
, experimental =
|
||||||
[ "Experimental features supported by the homeserver."
|
[ "Experimental features supported by the homeserver."
|
||||||
]
|
]
|
||||||
|
, nextBatch =
|
||||||
|
[ "The batch token to supply in the since param of the next /sync request."
|
||||||
|
]
|
||||||
, now =
|
, now =
|
||||||
[ "The most recently found timestamp."
|
[ "The most recently found timestamp."
|
||||||
]
|
]
|
||||||
|
@ -486,6 +494,9 @@ fields =
|
||||||
, room =
|
, room =
|
||||||
{ accountData =
|
{ accountData =
|
||||||
[ "Room account data tracking the user's private storage about this room." ]
|
[ "Room account data tracking the user's private storage about this room." ]
|
||||||
|
, ephemeral =
|
||||||
|
[ "Ephemeral events that were sent recently in this room."
|
||||||
|
]
|
||||||
, events =
|
, events =
|
||||||
[ "Database containing events that were sent in this room." ]
|
[ "Database containing events that were sent in this room." ]
|
||||||
, roomId =
|
, roomId =
|
||||||
|
@ -502,6 +513,9 @@ fields =
|
||||||
, deviceName =
|
, deviceName =
|
||||||
[ "Indicates the device name that is communicated to the Matrix API."
|
[ "Indicates the device name that is communicated to the Matrix API."
|
||||||
]
|
]
|
||||||
|
, presence =
|
||||||
|
[ "Controls whether the client is automatically marked as online. The value is passed on to the Matrix API."
|
||||||
|
]
|
||||||
, removePasswordOnLogin =
|
, removePasswordOnLogin =
|
||||||
[ "Remove the password as soon as a valid access token has been received."
|
[ "Remove the password as soon as a valid access token has been received."
|
||||||
]
|
]
|
||||||
|
@ -547,6 +561,9 @@ fields =
|
||||||
{ age =
|
{ age =
|
||||||
[ "The time in milliseconds that has elapsed since the event was sent. This field is generated by the local homeserver, and may be incorrect if the local time on at least one of the two servers is out of sync, which can cause the age to either be negative or greater than it actually is."
|
[ "The time in milliseconds that has elapsed since the event was sent. This field is generated by the local homeserver, and may be incorrect if the local time on at least one of the two servers is out of sync, which can cause the age to either be negative or greater than it actually is."
|
||||||
]
|
]
|
||||||
|
, membership =
|
||||||
|
[ "The room membership of the user making the request, at the time of the event."
|
||||||
|
]
|
||||||
, prevContent =
|
, prevContent =
|
||||||
[ "The previous content for this event. This field is generated by the local homeserver, and is only returned if the event is a state event, and the client has permission to see the previous content."
|
[ "The previous content for this event. This field is generated by the local homeserver, and is only returned if the event is a state event, and the client has permission to see the previous content."
|
||||||
]
|
]
|
||||||
|
@ -561,6 +578,9 @@ fields =
|
||||||
{ accountData =
|
{ accountData =
|
||||||
[ "The account's global private data."
|
[ "The account's global private data."
|
||||||
]
|
]
|
||||||
|
, nextBatch =
|
||||||
|
[ "The next batch that can be used to sync with the Matrix API."
|
||||||
|
]
|
||||||
, rooms =
|
, rooms =
|
||||||
[ "Directory of joined rooms that the user is a member of."
|
[ "Directory of joined rooms that the user is a member of."
|
||||||
]
|
]
|
||||||
|
@ -620,6 +640,7 @@ logs :
|
||||||
, sendEvent : Maybe String -> String
|
, sendEvent : Maybe String -> String
|
||||||
, serverReturnedInvalidJSON : String -> String
|
, serverReturnedInvalidJSON : String -> String
|
||||||
, serverReturnedUnknownJSON : String -> String
|
, serverReturnedUnknownJSON : String -> String
|
||||||
|
, syncAccountDataFound : Int -> String
|
||||||
}
|
}
|
||||||
logs =
|
logs =
|
||||||
{ baseUrlFailed =
|
{ baseUrlFailed =
|
||||||
|
@ -659,6 +680,8 @@ logs =
|
||||||
"Sent event, event id not known - make sure to check transaction id"
|
"Sent event, event id not known - make sure to check transaction id"
|
||||||
, serverReturnedInvalidJSON = (++) "The server returned invalid JSON: "
|
, serverReturnedInvalidJSON = (++) "The server returned invalid JSON: "
|
||||||
, serverReturnedUnknownJSON = (++) "The server returned JSON that doesn't seem to live up to spec rules: "
|
, serverReturnedUnknownJSON = (++) "The server returned JSON that doesn't seem to live up to spec rules: "
|
||||||
|
, syncAccountDataFound =
|
||||||
|
\n -> String.concat [ "Found ", String.fromInt n, " account data updates" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -183,7 +183,6 @@ coder =
|
||||||
, description = Text.fields.timelineFilter.senders
|
, description = Text.fields.timelineFilter.senders
|
||||||
, coder = Json.set Json.string
|
, coder = Json.set Json.string
|
||||||
, default = ( Set.empty, [] )
|
, default = ( Set.empty, [] )
|
||||||
, defaultToString = always "[]"
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Json.field.required
|
(Json.field.required
|
||||||
|
@ -199,7 +198,6 @@ coder =
|
||||||
, description = Text.fields.timelineFilter.types
|
, description = Text.fields.timelineFilter.types
|
||||||
, coder = Json.set Json.string
|
, coder = Json.set Json.string
|
||||||
, default = ( Set.empty, [] )
|
, default = ( Set.empty, [] )
|
||||||
, defaultToString = always "[]"
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Json.field.required
|
(Json.field.required
|
||||||
|
|
|
@ -189,21 +189,20 @@ ipv6RightParser n =
|
||||||
|. P.symbol ":"
|
|. P.symbol ":"
|
||||||
|
|
||||||
|
|
||||||
{-| Convert an IPv6 address to a readable string format
|
|
||||||
-}
|
|
||||||
ipv6ToString : IPv6Address -> String
|
|
||||||
ipv6ToString { front, back } =
|
|
||||||
(if List.length front == 8 then
|
|
||||||
front
|
|
||||||
|
|
||||||
else if List.length back == 8 then
|
-- {-| Convert an IPv6 address to a readable string format
|
||||||
back
|
-- -}
|
||||||
|
-- ipv6ToString : IPv6Address -> String
|
||||||
else
|
-- ipv6ToString { front, back } =
|
||||||
List.concat [ front, [ "" ], back ]
|
-- (if List.length front == 8 then
|
||||||
)
|
-- front
|
||||||
|> List.intersperse ":"
|
-- else if List.length back == 8 then
|
||||||
|> String.concat
|
-- back
|
||||||
|
-- else
|
||||||
|
-- List.concat [ front, [ "" ], back ]
|
||||||
|
-- )
|
||||||
|
-- |> List.intersperse ":"
|
||||||
|
-- |> String.concat
|
||||||
|
|
||||||
|
|
||||||
portParser : Parser Int
|
portParser : Parser Int
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
module Internal.Tools.DecodeExtra exposing
|
module Internal.Tools.DecodeExtra exposing
|
||||||
( opField, opFieldWithDefault
|
( opField, opFieldWithDefault
|
||||||
, map9, map10, map11
|
, map9, map10, map11, map12
|
||||||
)
|
)
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
|
@ -18,7 +18,7 @@ This module contains helper functions that help decode JSON.
|
||||||
|
|
||||||
## Extended map functions
|
## Extended map functions
|
||||||
|
|
||||||
@docs map9, map10, map11
|
@docs map9, map10, map11, map12
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
|
@ -153,3 +153,35 @@ map11 func da db dc dd de df dg dh di dj dk =
|
||||||
(D.map2 Tuple.pair df dg)
|
(D.map2 Tuple.pair df dg)
|
||||||
(D.map2 Tuple.pair dh di)
|
(D.map2 Tuple.pair dh di)
|
||||||
(D.map2 Tuple.pair dj dk)
|
(D.map2 Tuple.pair dj dk)
|
||||||
|
|
||||||
|
|
||||||
|
{-| Try 12 decoders and combine the result.
|
||||||
|
-}
|
||||||
|
map12 :
|
||||||
|
(a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k -> l -> value)
|
||||||
|
-> D.Decoder a
|
||||||
|
-> D.Decoder b
|
||||||
|
-> D.Decoder c
|
||||||
|
-> D.Decoder d
|
||||||
|
-> D.Decoder e
|
||||||
|
-> D.Decoder f
|
||||||
|
-> D.Decoder g
|
||||||
|
-> D.Decoder h
|
||||||
|
-> D.Decoder i
|
||||||
|
-> D.Decoder j
|
||||||
|
-> D.Decoder k
|
||||||
|
-> D.Decoder l
|
||||||
|
-> D.Decoder value
|
||||||
|
map12 func da db dc dd de df dg dh di dj dk dl =
|
||||||
|
D.map8
|
||||||
|
(\a b c d ( e, f ) ( g, h ) ( i, j ) ( k, l ) ->
|
||||||
|
func a b c d e f g h i j k l
|
||||||
|
)
|
||||||
|
da
|
||||||
|
db
|
||||||
|
dc
|
||||||
|
dd
|
||||||
|
(D.map2 Tuple.pair de df)
|
||||||
|
(D.map2 Tuple.pair dg dh)
|
||||||
|
(D.map2 Tuple.pair di dj)
|
||||||
|
(D.map2 Tuple.pair dk dl)
|
||||||
|
|
|
@ -1,198 +0,0 @@
|
||||||
module Internal.Tools.Iddict exposing
|
|
||||||
( Iddict
|
|
||||||
, empty, singleton, insert, map, remove
|
|
||||||
, isEmpty, member, get, size
|
|
||||||
, keys, values
|
|
||||||
, coder, encode, decoder
|
|
||||||
)
|
|
||||||
|
|
||||||
{-| The id-dict is a data type that lets us store values in a dictionary using
|
|
||||||
unique identifiers. This can be used as a dictionary where the keys do not
|
|
||||||
matter.
|
|
||||||
|
|
||||||
The benefit of the iddict is that it generates the keys FOR you. This way, you
|
|
||||||
do not need to generate identifiers yourself.
|
|
||||||
|
|
||||||
|
|
||||||
## Id-dict
|
|
||||||
|
|
||||||
@docs Iddict
|
|
||||||
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
@docs empty, singleton, insert, map, remove
|
|
||||||
|
|
||||||
|
|
||||||
## Query
|
|
||||||
|
|
||||||
@docs isEmpty, member, get, size
|
|
||||||
|
|
||||||
|
|
||||||
## Lists
|
|
||||||
|
|
||||||
@docs keys, values
|
|
||||||
|
|
||||||
|
|
||||||
## JSON coders
|
|
||||||
|
|
||||||
@docs coder, encode, decoder
|
|
||||||
|
|
||||||
-}
|
|
||||||
|
|
||||||
import FastDict as Dict exposing (Dict)
|
|
||||||
import Internal.Config.Text as Text
|
|
||||||
import Internal.Tools.Json as Json
|
|
||||||
|
|
||||||
|
|
||||||
{-| The Iddict data type.
|
|
||||||
-}
|
|
||||||
type Iddict a
|
|
||||||
= Iddict
|
|
||||||
{ cursor : Int
|
|
||||||
, dict : Dict Int a
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
{-| Define how an Iddict can be encoded and decoded to and from a JSON value.
|
|
||||||
-}
|
|
||||||
coder : Json.Coder a -> Json.Coder (Iddict a)
|
|
||||||
coder x =
|
|
||||||
Json.object2
|
|
||||||
{ name = Text.docs.iddict.name
|
|
||||||
, description = Text.docs.iddict.description
|
|
||||||
, init =
|
|
||||||
\c d ->
|
|
||||||
Iddict
|
|
||||||
{ cursor =
|
|
||||||
Dict.keys d
|
|
||||||
|> List.maximum
|
|
||||||
|> Maybe.map ((+) 1)
|
|
||||||
|> Maybe.withDefault 0
|
|
||||||
|> max (Dict.size d)
|
|
||||||
|> max c
|
|
||||||
, dict = d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Json.field.optional.withDefault
|
|
||||||
{ fieldName = "cursor"
|
|
||||||
, toField = \(Iddict i) -> i.cursor
|
|
||||||
, description = Text.fields.iddict.cursor
|
|
||||||
, coder = Json.int
|
|
||||||
, default = ( 0, [] )
|
|
||||||
, defaultToString = String.fromInt
|
|
||||||
}
|
|
||||||
)
|
|
||||||
(Json.field.required
|
|
||||||
{ fieldName = "dict"
|
|
||||||
, toField = \(Iddict i) -> i.dict
|
|
||||||
, description = Text.fields.iddict.dict
|
|
||||||
, coder = Json.fastIntDict x
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Decode an id-dict from a JSON value.
|
|
||||||
-}
|
|
||||||
decoder : Json.Coder a -> Json.Decoder (Iddict a)
|
|
||||||
decoder x =
|
|
||||||
Json.decode (coder x)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Create an empty id-dict.
|
|
||||||
-}
|
|
||||||
empty : Iddict a
|
|
||||||
empty =
|
|
||||||
Iddict
|
|
||||||
{ cursor = 0
|
|
||||||
, dict = Dict.empty
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
{-| Encode an id-dict to a JSON value.
|
|
||||||
-}
|
|
||||||
encode : Json.Coder a -> Json.Encoder (Iddict a)
|
|
||||||
encode x =
|
|
||||||
Json.encode (coder x)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Get a value from the id-dict using its key.
|
|
||||||
-}
|
|
||||||
get : Int -> Iddict a -> Maybe a
|
|
||||||
get k (Iddict { dict }) =
|
|
||||||
Dict.get k dict
|
|
||||||
|
|
||||||
|
|
||||||
{-| Insert a new value into the id-dict. Given that the id-dict generates its
|
|
||||||
key, the function returns both the updated id-dict as the newly generated key.
|
|
||||||
|
|
||||||
x = empty |> insert "hello" -- ( 0, <Iddict with value "hello"> )
|
|
||||||
|
|
||||||
case x of
|
|
||||||
( _, iddict ) ->
|
|
||||||
get 0 iddict -- Just "hello"
|
|
||||||
|
|
||||||
-}
|
|
||||||
insert : a -> Iddict a -> ( Int, Iddict a )
|
|
||||||
insert v (Iddict d) =
|
|
||||||
( d.cursor
|
|
||||||
, Iddict { cursor = d.cursor + 1, dict = Dict.insert d.cursor v d.dict }
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Determine if an id-dict is empty.
|
|
||||||
-}
|
|
||||||
isEmpty : Iddict a -> Bool
|
|
||||||
isEmpty (Iddict d) =
|
|
||||||
Dict.isEmpty d.dict
|
|
||||||
|
|
||||||
|
|
||||||
{-| Get all of the keys from the id-dict, sorted from lowest to highest.
|
|
||||||
-}
|
|
||||||
keys : Iddict a -> List Int
|
|
||||||
keys (Iddict { dict }) =
|
|
||||||
Dict.keys dict
|
|
||||||
|
|
||||||
|
|
||||||
{-| Map an existing value at a given key, if it exists. If it does not exist,
|
|
||||||
the operation does nothing.
|
|
||||||
-}
|
|
||||||
map : Int -> (a -> a) -> Iddict a -> Iddict a
|
|
||||||
map k f (Iddict d) =
|
|
||||||
Iddict { d | dict = Dict.update k (Maybe.map f) d.dict }
|
|
||||||
|
|
||||||
|
|
||||||
{-| Determine if a key is in an id-dict.
|
|
||||||
-}
|
|
||||||
member : Int -> Iddict a -> Bool
|
|
||||||
member k (Iddict d) =
|
|
||||||
k < d.cursor && Dict.member k d.dict
|
|
||||||
|
|
||||||
|
|
||||||
{-| Remove a key-value pair from the id-dict. If the key is not found, no
|
|
||||||
changes are made.
|
|
||||||
-}
|
|
||||||
remove : Int -> Iddict a -> Iddict a
|
|
||||||
remove k (Iddict d) =
|
|
||||||
Iddict { d | dict = Dict.remove k d.dict }
|
|
||||||
|
|
||||||
|
|
||||||
{-| Create an id-dict with a single value.
|
|
||||||
-}
|
|
||||||
singleton : a -> ( Int, Iddict a )
|
|
||||||
singleton v =
|
|
||||||
insert v empty
|
|
||||||
|
|
||||||
|
|
||||||
{-| Determine the number of key-value pairs in the id-dict.
|
|
||||||
-}
|
|
||||||
size : Iddict a -> Int
|
|
||||||
size (Iddict d) =
|
|
||||||
Dict.size d.dict
|
|
||||||
|
|
||||||
|
|
||||||
{-| Get all of the values from an id-dict, in the order of their keys.
|
|
||||||
-}
|
|
||||||
values : Iddict a -> List a
|
|
||||||
values (Iddict { dict }) =
|
|
||||||
Dict.values dict
|
|
|
@ -3,9 +3,9 @@ module Internal.Tools.Json exposing
|
||||||
, Encoder, encode, Decoder, decode, Value
|
, Encoder, encode, Decoder, decode, Value
|
||||||
, succeed, fail, andThen, lazy, map
|
, succeed, fail, andThen, lazy, map
|
||||||
, Docs(..), RequiredField(..), toDocs
|
, Docs(..), RequiredField(..), toDocs
|
||||||
, list, listWithOne, slowDict, fastDict, fastIntDict, set, maybe
|
, list, listWithOne, slowDict, fastDict, fastIntDict, set, iddict, maybe
|
||||||
, Field, field, parser
|
, Field, field, parser
|
||||||
, object2, object3, object4, object5, object6, object7, object8, object9, object10, object11
|
, object1, object2, object3, object4, object5, object6, object7, object8, object9, object10, object11, object12
|
||||||
)
|
)
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
|
@ -49,7 +49,7 @@ module to build its encoders and decoders.
|
||||||
|
|
||||||
## Data types
|
## Data types
|
||||||
|
|
||||||
@docs list, listWithOne, slowDict, fastDict, fastIntDict, set, maybe
|
@docs list, listWithOne, slowDict, fastDict, fastIntDict, set, iddict, maybe
|
||||||
|
|
||||||
|
|
||||||
## Objects
|
## Objects
|
||||||
|
@ -62,12 +62,13 @@ first.
|
||||||
|
|
||||||
Once all fields are constructed, the user can create JSON objects.
|
Once all fields are constructed, the user can create JSON objects.
|
||||||
|
|
||||||
@docs object2, object3, object4, object5, object6, object7, object8, object9, object10, object11
|
@docs object1, object2, object3, object4, object5, object6, object7, object8, object9, object10, object11, object12
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
import Dict as SlowDict
|
import Dict as SlowDict
|
||||||
import FastDict
|
import FastDict
|
||||||
|
import Iddict exposing (Iddict)
|
||||||
import Internal.Config.Log as Log exposing (Log)
|
import Internal.Config.Log as Log exposing (Log)
|
||||||
import Internal.Config.Text as Text
|
import Internal.Config.Text as Text
|
||||||
import Internal.Tools.DecodeExtra as D
|
import Internal.Tools.DecodeExtra as D
|
||||||
|
@ -141,6 +142,7 @@ type Docs
|
||||||
= DocsBool
|
= DocsBool
|
||||||
| DocsDict Docs
|
| DocsDict Docs
|
||||||
| DocsFloat
|
| DocsFloat
|
||||||
|
| DocsIddict Docs
|
||||||
| DocsInt
|
| DocsInt
|
||||||
| DocsIntDict Docs
|
| DocsIntDict Docs
|
||||||
| DocsLazy (() -> Docs)
|
| DocsLazy (() -> Docs)
|
||||||
|
@ -362,7 +364,7 @@ then the following field type would be used:
|
||||||
, coder = string
|
, coder = string
|
||||||
}
|
}
|
||||||
|
|
||||||
Suppose the JSO isn't obligated to provide a list of hobbies, and the list would
|
Suppose the JSON isn't obligated to provide a list of hobbies, and the list would
|
||||||
by default be overriden with an empty list, then we would use the following
|
by default be overriden with an empty list, then we would use the following
|
||||||
field type:
|
field type:
|
||||||
|
|
||||||
|
@ -373,8 +375,7 @@ field type:
|
||||||
[ "The hobbies of the person. Can be omitted."
|
[ "The hobbies of the person. Can be omitted."
|
||||||
]
|
]
|
||||||
, coder = list string
|
, coder = list string
|
||||||
, default = ( [], [] ) -- The `List Log` can be inserted in case you wish to insert a message when relying on a default
|
, default = ( [ "football" ], [] ) -- The `List Log` can be inserted in case you wish to insert a message when relying on a default
|
||||||
, defaultToString = always "[]" -- Default converted to a string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
@ -382,7 +383,7 @@ field :
|
||||||
{ required : { fieldName : String, toField : object -> a, description : List String, coder : Coder a } -> Field a object
|
{ required : { fieldName : String, toField : object -> a, description : List String, coder : Coder a } -> Field a object
|
||||||
, optional :
|
, optional :
|
||||||
{ value : { fieldName : String, toField : object -> Maybe a, description : List String, coder : Coder a } -> Field (Maybe a) object
|
{ value : { fieldName : String, toField : object -> Maybe a, description : List String, coder : Coder a } -> Field (Maybe a) object
|
||||||
, withDefault : { fieldName : String, toField : object -> a, description : List String, coder : Coder a, default : ( a, List Log ), defaultToString : a -> String } -> Field a object
|
, withDefault : { fieldName : String, toField : object -> a, description : List String, coder : Coder a, default : ( a, List Log ) } -> Field a object
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
field =
|
field =
|
||||||
|
@ -425,7 +426,7 @@ field =
|
||||||
, requiredness = OptionalField
|
, requiredness = OptionalField
|
||||||
}
|
}
|
||||||
, withDefault =
|
, withDefault =
|
||||||
\{ fieldName, toField, description, coder, default, defaultToString } ->
|
\{ fieldName, toField, description, coder, default } ->
|
||||||
case coder of
|
case coder of
|
||||||
Coder { encoder, decoder, docs } ->
|
Coder { encoder, decoder, docs } ->
|
||||||
Field
|
Field
|
||||||
|
@ -449,7 +450,8 @@ field =
|
||||||
, requiredness =
|
, requiredness =
|
||||||
default
|
default
|
||||||
|> Tuple.first
|
|> Tuple.first
|
||||||
|> defaultToString
|
|> encoder
|
||||||
|
|> E.encode 0
|
||||||
|> OptionalFieldWithDefault
|
|> OptionalFieldWithDefault
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -467,6 +469,26 @@ float =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{-| Define an Iddict as defined in
|
||||||
|
[noordstar/elm-iddict](https://package.elm-lang.org/packages/noordstar/elm-iddict/latest/).
|
||||||
|
-}
|
||||||
|
iddict : Coder a -> Coder (Iddict a)
|
||||||
|
iddict (Coder old) =
|
||||||
|
Coder
|
||||||
|
{ encoder = Iddict.encode old.encoder
|
||||||
|
, decoder =
|
||||||
|
Iddict.decoder old.decoder
|
||||||
|
|> D.map
|
||||||
|
(\out ->
|
||||||
|
( Iddict.map (always Tuple.first) out
|
||||||
|
, Iddict.values out
|
||||||
|
|> List.concatMap Tuple.second
|
||||||
|
)
|
||||||
|
)
|
||||||
|
, docs = DocsIddict old.docs
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
{-| Define an int value.
|
{-| Define an int value.
|
||||||
-}
|
-}
|
||||||
int : Coder Int
|
int : Coder Int
|
||||||
|
@ -596,6 +618,23 @@ objectEncoder items object =
|
||||||
|> E.maybeObject
|
|> E.maybeObject
|
||||||
|
|
||||||
|
|
||||||
|
object1 :
|
||||||
|
Descriptive { init : a -> object }
|
||||||
|
-> Field a object
|
||||||
|
-> Coder object
|
||||||
|
object1 { name, description, init } fa =
|
||||||
|
Coder
|
||||||
|
{ encoder = objectEncoder [ toEncodeField fa ]
|
||||||
|
, decoder = D.map (Tuple.mapFirst init) (toDecoderField fa)
|
||||||
|
, docs =
|
||||||
|
DocsObject
|
||||||
|
{ name = name
|
||||||
|
, description = description
|
||||||
|
, keys = [ toDocsField fa ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
{-| Define an object with 2 keys
|
{-| Define an object with 2 keys
|
||||||
|
|
||||||
type alias Human =
|
type alias Human =
|
||||||
|
@ -1158,6 +1197,81 @@ object11 { name, description, init } fa fb fc fd fe ff fg fh fi fj fk =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{-| Define an object with 12 keys
|
||||||
|
-}
|
||||||
|
object12 :
|
||||||
|
Descriptive { init : a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> k -> l -> object }
|
||||||
|
-> Field a object
|
||||||
|
-> Field b object
|
||||||
|
-> Field c object
|
||||||
|
-> Field d object
|
||||||
|
-> Field e object
|
||||||
|
-> Field f object
|
||||||
|
-> Field g object
|
||||||
|
-> Field h object
|
||||||
|
-> Field i object
|
||||||
|
-> Field j object
|
||||||
|
-> Field k object
|
||||||
|
-> Field l object
|
||||||
|
-> Coder object
|
||||||
|
object12 { name, description, init } fa fb fc fd fe ff fg fh fi fj fk fl =
|
||||||
|
Coder
|
||||||
|
{ encoder =
|
||||||
|
objectEncoder
|
||||||
|
[ toEncodeField fa
|
||||||
|
, toEncodeField fb
|
||||||
|
, toEncodeField fc
|
||||||
|
, toEncodeField fd
|
||||||
|
, toEncodeField fe
|
||||||
|
, toEncodeField ff
|
||||||
|
, toEncodeField fg
|
||||||
|
, toEncodeField fh
|
||||||
|
, toEncodeField fi
|
||||||
|
, toEncodeField fj
|
||||||
|
, toEncodeField fk
|
||||||
|
, toEncodeField fl
|
||||||
|
]
|
||||||
|
, decoder =
|
||||||
|
D.map12
|
||||||
|
(\( a, la ) ( b, lb ) ( c, lc ) ( d, ld ) ( e, le ) ( f, lf ) ( g, lg ) ( h, lh ) ( i, li ) ( j, lj ) ( k, lk ) ( l, ll ) ->
|
||||||
|
( init a b c d e f g h i j k l
|
||||||
|
, List.concat [ la, lb, lc, ld, le, lf, lg, lh, li, lj, lk, ll ]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(toDecoderField fa)
|
||||||
|
(toDecoderField fb)
|
||||||
|
(toDecoderField fc)
|
||||||
|
(toDecoderField fd)
|
||||||
|
(toDecoderField fe)
|
||||||
|
(toDecoderField ff)
|
||||||
|
(toDecoderField fg)
|
||||||
|
(toDecoderField fh)
|
||||||
|
(toDecoderField fi)
|
||||||
|
(toDecoderField fj)
|
||||||
|
(toDecoderField fk)
|
||||||
|
(toDecoderField fl)
|
||||||
|
, docs =
|
||||||
|
DocsObject
|
||||||
|
{ name = name
|
||||||
|
, description = description
|
||||||
|
, keys =
|
||||||
|
[ toDocsField fa
|
||||||
|
, toDocsField fb
|
||||||
|
, toDocsField fc
|
||||||
|
, toDocsField fd
|
||||||
|
, toDocsField fe
|
||||||
|
, toDocsField ff
|
||||||
|
, toDocsField fg
|
||||||
|
, toDocsField fh
|
||||||
|
, toDocsField fi
|
||||||
|
, toDocsField fj
|
||||||
|
, toDocsField fk
|
||||||
|
, toDocsField fl
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
{-| Define a parser that converts a string into a custom Elm type.
|
{-| Define a parser that converts a string into a custom Elm type.
|
||||||
-}
|
-}
|
||||||
parser : { name : String, p : P.Parser ( a, List Log ), toString : a -> String } -> Coder a
|
parser : { name : String, p : P.Parser ( a, List Log ), toString : a -> String } -> Coder a
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
module Internal.Tools.StrippedEvent exposing (StrippedEvent, coder, strip)
|
||||||
|
|
||||||
|
{-|
|
||||||
|
|
||||||
|
|
||||||
|
# Stripped event
|
||||||
|
|
||||||
|
The stripped event is a simple Matrix event that does not contain any metadata.
|
||||||
|
|
||||||
|
@docs StrippedEvent, coder, strip
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
import Internal.Config.Text as Text
|
||||||
|
import Internal.Tools.Json as Json
|
||||||
|
|
||||||
|
|
||||||
|
type alias StrippedEvent =
|
||||||
|
{ content : Json.Value, eventType : String }
|
||||||
|
|
||||||
|
|
||||||
|
coder : Json.Coder StrippedEvent
|
||||||
|
coder =
|
||||||
|
Json.object2
|
||||||
|
{ name = Text.docs.strippedEvent.name
|
||||||
|
, description = Text.docs.strippedEvent.description
|
||||||
|
, init = StrippedEvent
|
||||||
|
}
|
||||||
|
(Json.field.required
|
||||||
|
{ fieldName = "content"
|
||||||
|
, toField = .content
|
||||||
|
, description =
|
||||||
|
[ "Event content"
|
||||||
|
]
|
||||||
|
, coder = Json.value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Json.field.required
|
||||||
|
{ fieldName = "type"
|
||||||
|
, toField = .eventType
|
||||||
|
, description =
|
||||||
|
[ "Event type, generally namespaced using the Java package naming convention."
|
||||||
|
]
|
||||||
|
, coder = Json.string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
strip : { a | content : Json.Value, eventType : String } -> StrippedEvent
|
||||||
|
strip { content, eventType } =
|
||||||
|
{ content = content, eventType = eventType }
|
|
@ -71,7 +71,6 @@ import Internal.Config.Text as Text
|
||||||
import Internal.Tools.Hashdict as Hashdict exposing (Hashdict)
|
import Internal.Tools.Hashdict as Hashdict exposing (Hashdict)
|
||||||
import Internal.Tools.Json as Json
|
import Internal.Tools.Json as Json
|
||||||
import Internal.Tools.Timestamp as Timestamp exposing (Timestamp)
|
import Internal.Tools.Timestamp as Timestamp exposing (Timestamp)
|
||||||
import Json.Encode as E
|
|
||||||
import Set exposing (Set)
|
import Set exposing (Set)
|
||||||
import Time
|
import Time
|
||||||
|
|
||||||
|
@ -95,6 +94,7 @@ type alias Context =
|
||||||
{ accessTokens : Hashdict AccessToken
|
{ accessTokens : Hashdict AccessToken
|
||||||
, baseUrl : Maybe String
|
, baseUrl : Maybe String
|
||||||
, deviceId : Maybe String
|
, deviceId : Maybe String
|
||||||
|
, nextBatch : Maybe String
|
||||||
, now : Maybe Timestamp
|
, now : Maybe Timestamp
|
||||||
, password : Maybe String
|
, password : Maybe String
|
||||||
, refreshToken : Maybe String
|
, refreshToken : Maybe String
|
||||||
|
@ -152,7 +152,7 @@ fromApiFormat (APIContext c) =
|
||||||
-}
|
-}
|
||||||
coder : Json.Coder Context
|
coder : Json.Coder Context
|
||||||
coder =
|
coder =
|
||||||
Json.object11
|
Json.object12
|
||||||
{ name = Text.docs.context.name
|
{ name = Text.docs.context.name
|
||||||
, description = Text.docs.context.description
|
, description = Text.docs.context.description
|
||||||
, init = Context
|
, init = Context
|
||||||
|
@ -178,6 +178,13 @@ coder =
|
||||||
, coder = Json.string
|
, coder = Json.string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "nextBatch"
|
||||||
|
, toField = .nextBatch
|
||||||
|
, description = Text.fields.context.nextBatch
|
||||||
|
, coder = Json.string
|
||||||
|
}
|
||||||
|
)
|
||||||
(Json.field.optional.value
|
(Json.field.optional.value
|
||||||
{ fieldName = "now"
|
{ fieldName = "now"
|
||||||
, toField = .now
|
, toField = .now
|
||||||
|
@ -303,6 +310,7 @@ init sn =
|
||||||
{ accessTokens = Hashdict.empty .value
|
{ accessTokens = Hashdict.empty .value
|
||||||
, baseUrl = Nothing
|
, baseUrl = Nothing
|
||||||
, deviceId = Nothing
|
, deviceId = Nothing
|
||||||
|
, nextBatch = Nothing
|
||||||
, now = Nothing
|
, now = Nothing
|
||||||
, refreshToken = Nothing
|
, refreshToken = Nothing
|
||||||
, password = Nothing
|
, password = Nothing
|
||||||
|
@ -439,6 +447,5 @@ versionsCoder =
|
||||||
, description = Text.fields.versions.unstableFeatures
|
, description = Text.fields.versions.unstableFeatures
|
||||||
, coder = Json.set Json.string
|
, coder = Json.set Json.string
|
||||||
, default = ( Set.empty, [] )
|
, default = ( Set.empty, [] )
|
||||||
, defaultToString = Json.encode (Json.set Json.string) >> E.encode 0
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -56,6 +56,8 @@ import Internal.Tools.Json as Json
|
||||||
import Internal.Tools.Timestamp exposing (Timestamp)
|
import Internal.Tools.Timestamp exposing (Timestamp)
|
||||||
import Internal.Values.Context as Context exposing (AccessToken, Context, Versions)
|
import Internal.Values.Context as Context exposing (AccessToken, Context, Versions)
|
||||||
import Internal.Values.Settings as Settings
|
import Internal.Values.Settings as Settings
|
||||||
|
import Recursion
|
||||||
|
import Recursion.Fold
|
||||||
|
|
||||||
|
|
||||||
{-| There are lots of different data types in the Elm SDK, and many of them
|
{-| There are lots of different data types in the Elm SDK, and many of them
|
||||||
|
@ -82,6 +84,7 @@ type EnvelopeUpdate a
|
||||||
| SetAccessToken AccessToken
|
| SetAccessToken AccessToken
|
||||||
| SetBaseUrl String
|
| SetBaseUrl String
|
||||||
| SetDeviceId String
|
| SetDeviceId String
|
||||||
|
| SetNextBatch String
|
||||||
| SetNow Timestamp
|
| SetNow Timestamp
|
||||||
| SetRefreshToken String
|
| SetRefreshToken String
|
||||||
| SetVersions Versions
|
| SetVersions Versions
|
||||||
|
@ -124,7 +127,6 @@ coder c1 =
|
||||||
, description = Text.fields.envelope.settings
|
, description = Text.fields.envelope.settings
|
||||||
, coder = Settings.coder
|
, coder = Settings.coder
|
||||||
, default = Tuple.pair Settings.init []
|
, default = Tuple.pair Settings.init []
|
||||||
, defaultToString = always "<Default settings>"
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -292,47 +294,91 @@ toMaybe data =
|
||||||
{-| Updates the Envelope with a given EnvelopeUpdate value.
|
{-| Updates the Envelope with a given EnvelopeUpdate value.
|
||||||
-}
|
-}
|
||||||
update : (au -> a -> a) -> EnvelopeUpdate au -> Envelope a -> Envelope a
|
update : (au -> a -> a) -> EnvelopeUpdate au -> Envelope a -> Envelope a
|
||||||
update updateContent eu ({ context } as data) =
|
update updateContent eu startData =
|
||||||
case eu of
|
Recursion.runRecursion
|
||||||
ContentUpdate v ->
|
(\updt ->
|
||||||
{ data | content = updateContent v data.content }
|
case updt of
|
||||||
|
ContentUpdate v ->
|
||||||
|
Recursion.base
|
||||||
|
(\data ->
|
||||||
|
{ data | content = updateContent v data.content }
|
||||||
|
)
|
||||||
|
|
||||||
HttpRequest _ ->
|
HttpRequest _ ->
|
||||||
data
|
Recursion.base identity
|
||||||
|
|
||||||
More items ->
|
More items ->
|
||||||
List.foldl (update updateContent) data items
|
Recursion.Fold.foldList (<<) identity items
|
||||||
|
|
||||||
Optional (Just u) ->
|
Optional (Just u) ->
|
||||||
update updateContent u data
|
Recursion.recurse u
|
||||||
|
|
||||||
Optional Nothing ->
|
Optional Nothing ->
|
||||||
data
|
Recursion.base identity
|
||||||
|
|
||||||
RemoveAccessToken token ->
|
RemoveAccessToken token ->
|
||||||
{ data | context = { context | accessTokens = Hashdict.removeKey token context.accessTokens } }
|
Recursion.base
|
||||||
|
(\({ context } as data) ->
|
||||||
|
{ data
|
||||||
|
| context =
|
||||||
|
{ context
|
||||||
|
| accessTokens =
|
||||||
|
Hashdict.removeKey token context.accessTokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
RemovePasswordIfNecessary ->
|
RemovePasswordIfNecessary ->
|
||||||
if data.settings.removePasswordOnLogin then
|
Recursion.base
|
||||||
{ data | context = { context | password = Nothing } }
|
(\({ context } as data) ->
|
||||||
|
if data.settings.removePasswordOnLogin then
|
||||||
|
{ data | context = { context | password = Nothing } }
|
||||||
|
|
||||||
else
|
else
|
||||||
data
|
data
|
||||||
|
)
|
||||||
|
|
||||||
SetAccessToken a ->
|
SetAccessToken a ->
|
||||||
{ data | context = { context | accessTokens = Hashdict.insert a context.accessTokens } }
|
Recursion.base
|
||||||
|
(\({ context } as data) ->
|
||||||
|
{ data | context = { context | accessTokens = Hashdict.insert a context.accessTokens } }
|
||||||
|
)
|
||||||
|
|
||||||
SetBaseUrl b ->
|
SetBaseUrl b ->
|
||||||
{ data | context = { context | baseUrl = Just b } }
|
Recursion.base
|
||||||
|
(\({ context } as data) ->
|
||||||
|
{ data | context = { context | baseUrl = Just b } }
|
||||||
|
)
|
||||||
|
|
||||||
SetDeviceId d ->
|
SetDeviceId d ->
|
||||||
{ data | context = { context | deviceId = Just d } }
|
Recursion.base
|
||||||
|
(\({ context } as data) ->
|
||||||
|
{ data | context = { context | deviceId = Just d } }
|
||||||
|
)
|
||||||
|
|
||||||
SetNow n ->
|
SetNextBatch nextBatch ->
|
||||||
{ data | context = { context | now = Just n } }
|
Recursion.base
|
||||||
|
(\({ context } as data) ->
|
||||||
|
{ data | context = { context | nextBatch = Just nextBatch } }
|
||||||
|
)
|
||||||
|
|
||||||
SetRefreshToken r ->
|
SetNow n ->
|
||||||
{ data | context = { context | refreshToken = Just r } }
|
Recursion.base
|
||||||
|
(\({ context } as data) ->
|
||||||
|
{ data | context = { context | now = Just n } }
|
||||||
|
)
|
||||||
|
|
||||||
SetVersions vs ->
|
SetRefreshToken r ->
|
||||||
{ data | context = { context | versions = Just vs } }
|
Recursion.base
|
||||||
|
(\({ context } as data) ->
|
||||||
|
{ data | context = { context | refreshToken = Just r } }
|
||||||
|
)
|
||||||
|
|
||||||
|
SetVersions vs ->
|
||||||
|
Recursion.base
|
||||||
|
(\({ context } as data) ->
|
||||||
|
{ data | context = { context | versions = Just vs } }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
eu
|
||||||
|
startData
|
||||||
|
|
|
@ -59,6 +59,7 @@ helper functions.
|
||||||
type UnsignedData
|
type UnsignedData
|
||||||
= UnsignedData
|
= UnsignedData
|
||||||
{ age : Maybe Int
|
{ age : Maybe Int
|
||||||
|
, membership : Maybe String
|
||||||
, prevContent : Maybe Json.Value
|
, prevContent : Maybe Json.Value
|
||||||
, redactedBecause : Maybe Event
|
, redactedBecause : Maybe Event
|
||||||
, transactionId : Maybe String
|
, transactionId : Maybe String
|
||||||
|
@ -242,10 +243,10 @@ transactionId event =
|
||||||
|
|
||||||
unsignedCoder : Json.Coder UnsignedData
|
unsignedCoder : Json.Coder UnsignedData
|
||||||
unsignedCoder =
|
unsignedCoder =
|
||||||
Json.object4
|
Json.object5
|
||||||
{ name = Text.docs.unsigned.name
|
{ name = Text.docs.unsigned.name
|
||||||
, description = Text.docs.unsigned.description
|
, description = Text.docs.unsigned.description
|
||||||
, init = \a b c d -> UnsignedData { age = a, prevContent = b, redactedBecause = c, transactionId = d }
|
, init = \a b c d e -> UnsignedData { age = a, membership = b, prevContent = c, redactedBecause = d, transactionId = e }
|
||||||
}
|
}
|
||||||
(Json.field.optional.value
|
(Json.field.optional.value
|
||||||
{ fieldName = "age"
|
{ fieldName = "age"
|
||||||
|
@ -254,6 +255,13 @@ unsignedCoder =
|
||||||
, coder = Json.int
|
, coder = Json.int
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "membership"
|
||||||
|
, toField = \(UnsignedData data) -> data.membership
|
||||||
|
, description = Text.fields.unsigned.membership
|
||||||
|
, coder = Json.string
|
||||||
|
}
|
||||||
|
)
|
||||||
(Json.field.optional.value
|
(Json.field.optional.value
|
||||||
{ fieldName = "prevContent"
|
{ fieldName = "prevContent"
|
||||||
, toField = \(UnsignedData data) -> data.prevContent
|
, toField = \(UnsignedData data) -> data.prevContent
|
||||||
|
|
|
@ -53,11 +53,13 @@ import Internal.Config.Text as Text
|
||||||
import Internal.Filter.Timeline as Filter exposing (Filter)
|
import Internal.Filter.Timeline as Filter exposing (Filter)
|
||||||
import Internal.Tools.Hashdict as Hashdict exposing (Hashdict)
|
import Internal.Tools.Hashdict as Hashdict exposing (Hashdict)
|
||||||
import Internal.Tools.Json as Json
|
import Internal.Tools.Json as Json
|
||||||
|
import Internal.Tools.StrippedEvent as StrippedEvent exposing (StrippedEvent)
|
||||||
import Internal.Values.Event as Event exposing (Event)
|
import Internal.Values.Event as Event exposing (Event)
|
||||||
import Internal.Values.StateManager as StateManager exposing (StateManager)
|
import Internal.Values.StateManager as StateManager exposing (StateManager)
|
||||||
import Internal.Values.Timeline as Timeline exposing (Timeline)
|
import Internal.Values.Timeline as Timeline exposing (Timeline)
|
||||||
import Internal.Values.User exposing (User)
|
import Internal.Values.User exposing (User)
|
||||||
import Json.Encode as E
|
import Recursion
|
||||||
|
import Recursion.Fold
|
||||||
|
|
||||||
|
|
||||||
{-| The Batch is a group of new events from somewhere in the timeline.
|
{-| The Batch is a group of new events from somewhere in the timeline.
|
||||||
|
@ -71,6 +73,7 @@ homeserver.
|
||||||
-}
|
-}
|
||||||
type alias Room =
|
type alias Room =
|
||||||
{ accountData : Dict String Json.Value
|
{ accountData : Dict String Json.Value
|
||||||
|
, ephemeral : List StrippedEvent
|
||||||
, events : Hashdict Event
|
, events : Hashdict Event
|
||||||
, roomId : String
|
, roomId : String
|
||||||
, state : StateManager
|
, state : StateManager
|
||||||
|
@ -86,7 +89,9 @@ type RoomUpdate
|
||||||
| AddSync Batch
|
| AddSync Batch
|
||||||
| Invite User
|
| Invite User
|
||||||
| More (List RoomUpdate)
|
| More (List RoomUpdate)
|
||||||
|
| Optional (Maybe RoomUpdate)
|
||||||
| SetAccountData String Json.Value
|
| SetAccountData String Json.Value
|
||||||
|
| SetEphemeral (List { eventType : String, content : Json.Value })
|
||||||
|
|
||||||
|
|
||||||
{-| Add new events to the Room's event directory + Room's timeline.
|
{-| Add new events to the Room's event directory + Room's timeline.
|
||||||
|
@ -140,7 +145,7 @@ addSync =
|
||||||
-}
|
-}
|
||||||
coder : Json.Coder Room
|
coder : Json.Coder Room
|
||||||
coder =
|
coder =
|
||||||
Json.object5
|
Json.object6
|
||||||
{ name = Text.docs.room.name
|
{ name = Text.docs.room.name
|
||||||
, description = Text.docs.room.description
|
, description = Text.docs.room.description
|
||||||
, init = Room
|
, init = Room
|
||||||
|
@ -151,7 +156,14 @@ coder =
|
||||||
, description = Text.fields.room.accountData
|
, description = Text.fields.room.accountData
|
||||||
, coder = Json.fastDict Json.value
|
, coder = Json.fastDict Json.value
|
||||||
, default = ( Dict.empty, [] )
|
, default = ( Dict.empty, [] )
|
||||||
, defaultToString = Json.encode (Json.fastDict Json.value) >> E.encode 0
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.withDefault
|
||||||
|
{ fieldName = "ephemeral"
|
||||||
|
, toField = .ephemeral
|
||||||
|
, description = Text.fields.room.ephemeral
|
||||||
|
, coder = Json.list StrippedEvent.coder
|
||||||
|
, default = ( [], [] )
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Json.field.optional.withDefault
|
(Json.field.optional.withDefault
|
||||||
|
@ -160,7 +172,6 @@ coder =
|
||||||
, description = Text.fields.room.events
|
, description = Text.fields.room.events
|
||||||
, coder = Hashdict.coder .eventId Event.coder
|
, coder = Hashdict.coder .eventId Event.coder
|
||||||
, default = ( Hashdict.empty .eventId, [ log.warn "Found a room with no known events! Is it empty?" ] )
|
, default = ( Hashdict.empty .eventId, [ log.warn "Found a room with no known events! Is it empty?" ] )
|
||||||
, defaultToString = Json.encode (Hashdict.coder .eventId Event.coder) >> E.encode 0
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Json.field.required
|
(Json.field.required
|
||||||
|
@ -176,7 +187,6 @@ coder =
|
||||||
, description = Text.fields.room.state
|
, description = Text.fields.room.state
|
||||||
, coder = StateManager.coder
|
, coder = StateManager.coder
|
||||||
, default = ( StateManager.empty, [] )
|
, default = ( StateManager.empty, [] )
|
||||||
, defaultToString = Json.encode StateManager.coder >> E.encode 0
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Json.field.optional.withDefault
|
(Json.field.optional.withDefault
|
||||||
|
@ -185,7 +195,6 @@ coder =
|
||||||
, description = Text.fields.room.timeline
|
, description = Text.fields.room.timeline
|
||||||
, coder = Timeline.coder
|
, coder = Timeline.coder
|
||||||
, default = ( Timeline.empty, [] )
|
, default = ( Timeline.empty, [] )
|
||||||
, defaultToString = Json.encode Timeline.coder >> E.encode 0
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -216,6 +225,7 @@ getAccountData key room =
|
||||||
init : String -> Room
|
init : String -> Room
|
||||||
init roomId =
|
init roomId =
|
||||||
{ accountData = Dict.empty
|
{ accountData = Dict.empty
|
||||||
|
, ephemeral = []
|
||||||
, events = Hashdict.empty .eventId
|
, events = Hashdict.empty .eventId
|
||||||
, roomId = roomId
|
, roomId = roomId
|
||||||
, state = StateManager.empty
|
, state = StateManager.empty
|
||||||
|
@ -246,21 +256,35 @@ setAccountData key value room =
|
||||||
{-| Update the Room based on given instructions.
|
{-| Update the Room based on given instructions.
|
||||||
-}
|
-}
|
||||||
update : RoomUpdate -> Room -> Room
|
update : RoomUpdate -> Room -> Room
|
||||||
update ru room =
|
update roomUpdate startRoom =
|
||||||
case ru of
|
Recursion.runRecursion
|
||||||
AddEvent _ ->
|
(\ru ->
|
||||||
-- TODO: Add event
|
case ru of
|
||||||
room
|
AddEvent _ ->
|
||||||
|
-- TODO: Add event
|
||||||
|
Recursion.base identity
|
||||||
|
|
||||||
AddSync batch ->
|
AddSync batch ->
|
||||||
addSync batch room
|
Recursion.base (addSync batch)
|
||||||
|
|
||||||
Invite _ ->
|
Invite _ ->
|
||||||
-- TODO: Invite user
|
-- TODO: Invite user
|
||||||
room
|
Recursion.base identity
|
||||||
|
|
||||||
More items ->
|
More items ->
|
||||||
List.foldl update room items
|
Recursion.Fold.foldList (<<) identity items
|
||||||
|
|
||||||
SetAccountData key value ->
|
Optional (Just u) ->
|
||||||
setAccountData key value room
|
Recursion.recurse u
|
||||||
|
|
||||||
|
Optional Nothing ->
|
||||||
|
Recursion.base identity
|
||||||
|
|
||||||
|
SetAccountData key value ->
|
||||||
|
Recursion.base (setAccountData key value)
|
||||||
|
|
||||||
|
SetEphemeral eph ->
|
||||||
|
Recursion.base (\room -> { room | ephemeral = eph })
|
||||||
|
)
|
||||||
|
roomUpdate
|
||||||
|
startRoom
|
||||||
|
|
|
@ -35,6 +35,7 @@ behave under the user's preferred settings.
|
||||||
type alias Settings =
|
type alias Settings =
|
||||||
{ currentVersion : String
|
{ currentVersion : String
|
||||||
, deviceName : String
|
, deviceName : String
|
||||||
|
, presence : Maybe String
|
||||||
, removePasswordOnLogin : Bool
|
, removePasswordOnLogin : Bool
|
||||||
, syncTime : Int
|
, syncTime : Int
|
||||||
}
|
}
|
||||||
|
@ -44,7 +45,7 @@ type alias Settings =
|
||||||
-}
|
-}
|
||||||
coder : Json.Coder Settings
|
coder : Json.Coder Settings
|
||||||
coder =
|
coder =
|
||||||
Json.object4
|
Json.object5
|
||||||
{ name = Text.docs.settings.name
|
{ name = Text.docs.settings.name
|
||||||
, description = Text.docs.settings.description
|
, description = Text.docs.settings.description
|
||||||
, init = Settings
|
, init = Settings
|
||||||
|
@ -55,7 +56,6 @@ coder =
|
||||||
, description = Text.fields.settings.currentVersion
|
, description = Text.fields.settings.currentVersion
|
||||||
, coder = Json.string
|
, coder = Json.string
|
||||||
, default = Tuple.pair Default.currentVersion []
|
, default = Tuple.pair Default.currentVersion []
|
||||||
, defaultToString = identity
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Json.field.optional.withDefault
|
(Json.field.optional.withDefault
|
||||||
|
@ -64,7 +64,13 @@ coder =
|
||||||
, description = Text.fields.settings.deviceName
|
, description = Text.fields.settings.deviceName
|
||||||
, coder = Json.string
|
, coder = Json.string
|
||||||
, default = Tuple.pair Default.deviceName []
|
, default = Tuple.pair Default.deviceName []
|
||||||
, defaultToString = identity
|
}
|
||||||
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "presence"
|
||||||
|
, toField = .presence
|
||||||
|
, description = Text.fields.settings.presence
|
||||||
|
, coder = Json.string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Json.field.optional.withDefault
|
(Json.field.optional.withDefault
|
||||||
|
@ -73,13 +79,6 @@ coder =
|
||||||
, description = Text.fields.settings.removePasswordOnLogin
|
, description = Text.fields.settings.removePasswordOnLogin
|
||||||
, coder = Json.bool
|
, coder = Json.bool
|
||||||
, default = Tuple.pair Default.removePasswordOnLogin []
|
, default = Tuple.pair Default.removePasswordOnLogin []
|
||||||
, defaultToString =
|
|
||||||
\b ->
|
|
||||||
if b then
|
|
||||||
"true"
|
|
||||||
|
|
||||||
else
|
|
||||||
"false"
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Json.field.optional.withDefault
|
(Json.field.optional.withDefault
|
||||||
|
@ -88,7 +87,6 @@ coder =
|
||||||
, description = Text.fields.settings.syncTime
|
, description = Text.fields.settings.syncTime
|
||||||
, coder = Json.int
|
, coder = Json.int
|
||||||
, default = Tuple.pair Default.syncTime []
|
, default = Tuple.pair Default.syncTime []
|
||||||
, defaultToString = String.fromInt
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -113,6 +111,7 @@ init : Settings
|
||||||
init =
|
init =
|
||||||
{ currentVersion = Default.currentVersion
|
{ currentVersion = Default.currentVersion
|
||||||
, deviceName = Default.deviceName
|
, deviceName = Default.deviceName
|
||||||
|
, presence = Nothing
|
||||||
, removePasswordOnLogin = Default.removePasswordOnLogin
|
, removePasswordOnLogin = Default.removePasswordOnLogin
|
||||||
, syncTime = Default.syncTime
|
, syncTime = Default.syncTime
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,10 +67,10 @@ events!
|
||||||
-}
|
-}
|
||||||
|
|
||||||
import FastDict as Dict exposing (Dict)
|
import FastDict as Dict exposing (Dict)
|
||||||
|
import Iddict exposing (Iddict)
|
||||||
import Internal.Config.Text as Text
|
import Internal.Config.Text as Text
|
||||||
import Internal.Filter.Timeline as Filter exposing (Filter)
|
import Internal.Filter.Timeline as Filter exposing (Filter)
|
||||||
import Internal.Tools.Hashdict as Hashdict exposing (Hashdict)
|
import Internal.Tools.Hashdict as Hashdict exposing (Hashdict)
|
||||||
import Internal.Tools.Iddict as Iddict exposing (Iddict)
|
|
||||||
import Internal.Tools.Json as Json
|
import Internal.Tools.Json as Json
|
||||||
import Recursion
|
import Recursion
|
||||||
import Recursion.Traverse
|
import Recursion.Traverse
|
||||||
|
@ -210,7 +210,7 @@ coder =
|
||||||
{ fieldName = "batches"
|
{ fieldName = "batches"
|
||||||
, toField = \(Timeline t) -> t.batches
|
, toField = \(Timeline t) -> t.batches
|
||||||
, description = Text.fields.timeline.batches
|
, description = Text.fields.timeline.batches
|
||||||
, coder = Iddict.coder coderIBatch
|
, coder = Json.iddict coderIBatch
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Json.field.required
|
(Json.field.required
|
||||||
|
@ -226,7 +226,6 @@ coder =
|
||||||
, description = Text.fields.timeline.filledBatches
|
, description = Text.fields.timeline.filledBatches
|
||||||
, coder = Json.int
|
, coder = Json.int
|
||||||
, default = ( 0, [] )
|
, default = ( 0, [] )
|
||||||
, defaultToString = String.fromInt
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Json.field.required
|
(Json.field.required
|
||||||
|
@ -326,7 +325,6 @@ coderIToken =
|
||||||
, description = Text.fields.itoken.starts
|
, description = Text.fields.itoken.starts
|
||||||
, coder = Json.set coderIBatchPTRValue
|
, coder = Json.set coderIBatchPTRValue
|
||||||
, default = ( Set.empty, [] )
|
, default = ( Set.empty, [] )
|
||||||
, defaultToString = always "[]"
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Json.field.optional.withDefault
|
(Json.field.optional.withDefault
|
||||||
|
@ -335,7 +333,6 @@ coderIToken =
|
||||||
, description = Text.fields.itoken.ends
|
, description = Text.fields.itoken.ends
|
||||||
, coder = Json.set coderIBatchPTRValue
|
, coder = Json.set coderIBatchPTRValue
|
||||||
, default = ( Set.empty, [] )
|
, default = ( Set.empty, [] )
|
||||||
, defaultToString = always "[]"
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Json.field.optional.withDefault
|
(Json.field.optional.withDefault
|
||||||
|
@ -344,7 +341,6 @@ coderIToken =
|
||||||
, description = Text.fields.itoken.inFrontOf
|
, description = Text.fields.itoken.inFrontOf
|
||||||
, coder = Json.set coderITokenPTRValue
|
, coder = Json.set coderITokenPTRValue
|
||||||
, default = ( Set.empty, [] )
|
, default = ( Set.empty, [] )
|
||||||
, defaultToString = always "[]"
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Json.field.optional.withDefault
|
(Json.field.optional.withDefault
|
||||||
|
@ -353,7 +349,6 @@ coderIToken =
|
||||||
, description = Text.fields.itoken.behind
|
, description = Text.fields.itoken.behind
|
||||||
, coder = Json.set coderITokenPTRValue
|
, coder = Json.set coderITokenPTRValue
|
||||||
, default = ( Set.empty, [] )
|
, default = ( Set.empty, [] )
|
||||||
, defaultToString = always "[]"
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -411,8 +406,8 @@ connectIBatchToIToken (IBatchPTR bptr) pointer (Timeline tl) =
|
||||||
Timeline
|
Timeline
|
||||||
{ tl
|
{ tl
|
||||||
| batches =
|
| batches =
|
||||||
Iddict.map bptr
|
Iddict.update bptr
|
||||||
(\batch -> { batch | end = pointer })
|
(Maybe.map (\batch -> { batch | end = pointer }))
|
||||||
tl.batches
|
tl.batches
|
||||||
, tokens =
|
, tokens =
|
||||||
Hashdict.map tptr
|
Hashdict.map tptr
|
||||||
|
@ -437,8 +432,8 @@ connectITokenToIBatch pointer (IBatchPTR bptr) (Timeline tl) =
|
||||||
(\token -> { token | starts = Set.insert bptr token.starts })
|
(\token -> { token | starts = Set.insert bptr token.starts })
|
||||||
tl.tokens
|
tl.tokens
|
||||||
, batches =
|
, batches =
|
||||||
Iddict.map bptr
|
Iddict.update bptr
|
||||||
(\batch -> { batch | start = pointer })
|
(Maybe.map (\batch -> { batch | start = pointer }))
|
||||||
tl.batches
|
tl.batches
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -683,20 +678,21 @@ mostRecentFrom filter timeline ptr =
|
||||||
{ ptr = ptr, visited = Set.empty }
|
{ ptr = ptr, visited = Set.empty }
|
||||||
|
|
||||||
|
|
||||||
{-| Recount the Timeline's amount of filled batches. Since the Timeline
|
|
||||||
automatically tracks the count on itself, this is generally exclusively used in
|
-- {-| Recount the Timeline's amount of filled batches. Since the Timeline
|
||||||
specific scenarios like decoding JSON values.
|
-- automatically tracks the count on itself, this is generally exclusively used in
|
||||||
-}
|
-- specific scenarios like decoding JSON values.
|
||||||
recountFilledBatches : Timeline -> Timeline
|
-- -}
|
||||||
recountFilledBatches (Timeline tl) =
|
-- recountFilledBatches : Timeline -> Timeline
|
||||||
Timeline
|
-- recountFilledBatches (Timeline tl) =
|
||||||
{ tl
|
-- Timeline
|
||||||
| filledBatches =
|
-- { tl
|
||||||
tl.batches
|
-- | filledBatches =
|
||||||
|> Iddict.values
|
-- tl.batches
|
||||||
|> List.filter (\v -> v.events /= [])
|
-- |> Iddict.values
|
||||||
|> List.length
|
-- |> List.filter (\v -> v.events /= [])
|
||||||
}
|
-- |> List.length
|
||||||
|
-- }
|
||||||
|
|
||||||
|
|
||||||
{-| Create a timeline with a single batch inserted. This batch is considered the
|
{-| Create a timeline with a single batch inserted. This batch is considered the
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
module Internal.Values.Vault exposing
|
module Internal.Values.Vault exposing
|
||||||
( Vault, init
|
( Vault, init
|
||||||
, VaultUpdate(..), update
|
, VaultUpdate(..), update
|
||||||
, fromRoomId, mapRoom, updateRoom
|
, rooms, fromRoomId, mapRoom, updateRoom
|
||||||
, getAccountData, setAccountData
|
, getAccountData, setAccountData
|
||||||
|
, coder
|
||||||
)
|
)
|
||||||
|
|
||||||
{-| This module hosts the Vault module. The Vault is the data type storing all
|
{-| This module hosts the Vault module. The Vault is the data type storing all
|
||||||
|
@ -23,13 +24,18 @@ To update the Vault, one uses VaultUpdate types.
|
||||||
|
|
||||||
Rooms are environments where people can have a conversation with each other.
|
Rooms are environments where people can have a conversation with each other.
|
||||||
|
|
||||||
@docs fromRoomId, mapRoom, updateRoom
|
@docs rooms, fromRoomId, mapRoom, updateRoom
|
||||||
|
|
||||||
|
|
||||||
## Account data
|
## Account data
|
||||||
|
|
||||||
@docs getAccountData, setAccountData
|
@docs getAccountData, setAccountData
|
||||||
|
|
||||||
|
|
||||||
|
## JSON
|
||||||
|
|
||||||
|
@docs coder
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
import FastDict as Dict exposing (Dict)
|
import FastDict as Dict exposing (Dict)
|
||||||
|
@ -38,12 +44,15 @@ import Internal.Tools.Hashdict as Hashdict exposing (Hashdict)
|
||||||
import Internal.Tools.Json as Json
|
import Internal.Tools.Json as Json
|
||||||
import Internal.Values.Room as Room exposing (Room)
|
import Internal.Values.Room as Room exposing (Room)
|
||||||
import Internal.Values.User as User exposing (User)
|
import Internal.Values.User as User exposing (User)
|
||||||
|
import Recursion
|
||||||
|
import Recursion.Fold
|
||||||
|
|
||||||
|
|
||||||
{-| This is the Vault type.
|
{-| This is the Vault type.
|
||||||
-}
|
-}
|
||||||
type alias Vault =
|
type alias Vault =
|
||||||
{ accountData : Dict String Json.Value
|
{ accountData : Dict String Json.Value
|
||||||
|
, nextBatch : Maybe String
|
||||||
, rooms : Hashdict Room
|
, rooms : Hashdict Room
|
||||||
, user : Maybe User
|
, user : Maybe User
|
||||||
}
|
}
|
||||||
|
@ -56,13 +65,17 @@ type VaultUpdate
|
||||||
= CreateRoomIfNotExists String
|
= CreateRoomIfNotExists String
|
||||||
| MapRoom String Room.RoomUpdate
|
| MapRoom String Room.RoomUpdate
|
||||||
| More (List VaultUpdate)
|
| More (List VaultUpdate)
|
||||||
|
| Optional (Maybe VaultUpdate)
|
||||||
| SetAccountData String Json.Value
|
| SetAccountData String Json.Value
|
||||||
|
| SetNextBatch String
|
||||||
| SetUser User
|
| SetUser User
|
||||||
|
|
||||||
|
|
||||||
|
{-| Convert a Vault to and from a JSON object.
|
||||||
|
-}
|
||||||
coder : Json.Coder Vault
|
coder : Json.Coder Vault
|
||||||
coder =
|
coder =
|
||||||
Json.object3
|
Json.object4
|
||||||
{ name = Text.docs.vault.name
|
{ name = Text.docs.vault.name
|
||||||
, description = Text.docs.vault.description
|
, description = Text.docs.vault.description
|
||||||
, init = Vault
|
, init = Vault
|
||||||
|
@ -74,6 +87,13 @@ coder =
|
||||||
, coder = Json.fastDict Json.value
|
, coder = Json.fastDict Json.value
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
(Json.field.optional.value
|
||||||
|
{ fieldName = "nextBatch"
|
||||||
|
, toField = .nextBatch
|
||||||
|
, description = Text.fields.vault.nextBatch
|
||||||
|
, coder = Json.string
|
||||||
|
}
|
||||||
|
)
|
||||||
(Json.field.required
|
(Json.field.required
|
||||||
{ fieldName = "rooms"
|
{ fieldName = "rooms"
|
||||||
, toField = .rooms
|
, toField = .rooms
|
||||||
|
@ -109,6 +129,7 @@ getAccountData key vault =
|
||||||
init : Maybe User -> Vault
|
init : Maybe User -> Vault
|
||||||
init mUser =
|
init mUser =
|
||||||
{ accountData = Dict.empty
|
{ accountData = Dict.empty
|
||||||
|
, nextBatch = Nothing
|
||||||
, rooms = Hashdict.empty .roomId
|
, rooms = Hashdict.empty .roomId
|
||||||
, user = mUser
|
, user = mUser
|
||||||
}
|
}
|
||||||
|
@ -122,6 +143,13 @@ mapRoom roomId f vault =
|
||||||
{ vault | rooms = Hashdict.map roomId f vault.rooms }
|
{ vault | rooms = Hashdict.map roomId f vault.rooms }
|
||||||
|
|
||||||
|
|
||||||
|
{-| Get a list of all joined rooms present in the vault.
|
||||||
|
-}
|
||||||
|
rooms : Vault -> List Room
|
||||||
|
rooms vault =
|
||||||
|
Hashdict.values vault.rooms
|
||||||
|
|
||||||
|
|
||||||
{-| Set a piece of account data as information in the global vault data.
|
{-| Set a piece of account data as information in the global vault data.
|
||||||
-}
|
-}
|
||||||
setAccountData : String -> Json.Value -> Vault -> Vault
|
setAccountData : String -> Json.Value -> Vault -> Vault
|
||||||
|
@ -139,21 +167,41 @@ updateRoom roomId f vault =
|
||||||
{-| Update the Vault using a VaultUpdate type.
|
{-| Update the Vault using a VaultUpdate type.
|
||||||
-}
|
-}
|
||||||
update : VaultUpdate -> Vault -> Vault
|
update : VaultUpdate -> Vault -> Vault
|
||||||
update vu vault =
|
update vaultUpdate startVault =
|
||||||
case vu of
|
Recursion.runRecursion
|
||||||
CreateRoomIfNotExists roomId ->
|
(\vu ->
|
||||||
updateRoom roomId
|
case vu of
|
||||||
(Maybe.withDefault (Room.init roomId) >> Maybe.Just)
|
CreateRoomIfNotExists roomId ->
|
||||||
vault
|
(Maybe.withDefault (Room.init roomId) >> Maybe.Just)
|
||||||
|
|> updateRoom roomId
|
||||||
|
|> Recursion.base
|
||||||
|
|
||||||
MapRoom roomId ru ->
|
MapRoom roomId ru ->
|
||||||
mapRoom roomId (Room.update ru) vault
|
Recursion.base (mapRoom roomId (Room.update ru))
|
||||||
|
|
||||||
More items ->
|
More items ->
|
||||||
List.foldl update vault items
|
Recursion.Fold.foldList (<<) identity items
|
||||||
|
|
||||||
SetAccountData key value ->
|
Optional (Just u) ->
|
||||||
setAccountData key value vault
|
Recursion.recurse u
|
||||||
|
|
||||||
SetUser user ->
|
Optional Nothing ->
|
||||||
{ vault | user = Just user }
|
Recursion.base identity
|
||||||
|
|
||||||
|
SetAccountData key value ->
|
||||||
|
Recursion.base (setAccountData key value)
|
||||||
|
|
||||||
|
SetNextBatch nb ->
|
||||||
|
Recursion.base
|
||||||
|
(\vault ->
|
||||||
|
{ vault | nextBatch = Just nb }
|
||||||
|
)
|
||||||
|
|
||||||
|
SetUser user ->
|
||||||
|
Recursion.base
|
||||||
|
(\vault ->
|
||||||
|
{ vault | user = Just user }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
vaultUpdate
|
||||||
|
startVault
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
module Matrix exposing
|
module Matrix exposing
|
||||||
( Vault, fromUserId, fromUsername
|
( Vault, fromUserId, fromUsername
|
||||||
, VaultUpdate, update
|
, VaultUpdate, update, sync, logs
|
||||||
|
, rooms, fromRoomId
|
||||||
, addAccessToken, sendMessageEvent
|
, addAccessToken, sendMessageEvent
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,7 +25,12 @@ support a monolithic public registry. (:
|
||||||
|
|
||||||
## Keeping the Vault up-to-date
|
## Keeping the Vault up-to-date
|
||||||
|
|
||||||
@docs VaultUpdate, update
|
@docs VaultUpdate, update, sync, logs
|
||||||
|
|
||||||
|
|
||||||
|
## Exploring the Vault
|
||||||
|
|
||||||
|
@docs rooms, fromRoomId
|
||||||
|
|
||||||
|
|
||||||
## Debugging
|
## Debugging
|
||||||
|
@ -66,6 +72,14 @@ addAccessToken token (Vault vault) =
|
||||||
|> Vault
|
|> Vault
|
||||||
|
|
||||||
|
|
||||||
|
{-| Get a room based on its room ID, if the user is a member of that room.
|
||||||
|
-}
|
||||||
|
fromRoomId : String -> Vault -> Maybe Types.Room
|
||||||
|
fromRoomId roomId (Vault vault) =
|
||||||
|
Envelope.mapMaybe (Internal.fromRoomId roomId) vault
|
||||||
|
|> Maybe.map Types.Room
|
||||||
|
|
||||||
|
|
||||||
{-| Use a fully-fledged Matrix ID to connect.
|
{-| Use a fully-fledged Matrix ID to connect.
|
||||||
|
|
||||||
case Matrix.fromUserId "@alice:example.org" of
|
case Matrix.fromUserId "@alice:example.org" of
|
||||||
|
@ -112,6 +126,36 @@ fromUsername { username, host, port_ } =
|
||||||
|> Vault
|
|> Vault
|
||||||
|
|
||||||
|
|
||||||
|
{-| Get a list of all the rooms that the user has joined.
|
||||||
|
-}
|
||||||
|
rooms : Vault -> List Types.Room
|
||||||
|
rooms (Vault vault) =
|
||||||
|
Envelope.mapList Internal.rooms vault
|
||||||
|
|> List.map Types.Room
|
||||||
|
|
||||||
|
|
||||||
|
{-| The VaultUpdate is a complex type that helps update the Vault. However,
|
||||||
|
it also contains a human output!
|
||||||
|
|
||||||
|
Using this function, you can get a human output that describes everything that
|
||||||
|
the VaultUpdate has to tell the Vault.
|
||||||
|
|
||||||
|
The `channel` field describes the context of the log, allowing you to filter
|
||||||
|
further. For example:
|
||||||
|
|
||||||
|
- `debug` is a comprehensive channel describing everything the Elm runtime has
|
||||||
|
executed.
|
||||||
|
- `warn` contains warnings that aren't breaking, but relevant.
|
||||||
|
- `securityWarn` warns about potential security issues or potential attacks.
|
||||||
|
- `error` has errors that were encountered.
|
||||||
|
- `caughtError` has errors that were dealt with successfully.
|
||||||
|
|
||||||
|
-}
|
||||||
|
logs : VaultUpdate -> List { channel : String, content : String }
|
||||||
|
logs (VaultUpdate vu) =
|
||||||
|
vu.logs
|
||||||
|
|
||||||
|
|
||||||
{-| Send a message event to a room.
|
{-| Send a message event to a room.
|
||||||
|
|
||||||
This function can be used in a scenario where the user does not want to sync
|
This function can be used in a scenario where the user does not want to sync
|
||||||
|
@ -119,6 +163,18 @@ the client, or is unable to. This function doesn't check whether the given room
|
||||||
exists and the user is able to send a message to, and instead just sends the
|
exists and the user is able to send a message to, and instead just sends the
|
||||||
request to the Matrix API.
|
request to the Matrix API.
|
||||||
|
|
||||||
|
The fields stand for the following:
|
||||||
|
|
||||||
|
- `content` is the JSON object that is sent to the Matrix room.
|
||||||
|
- `eventType` is the event type that is sent to the Matrix room.
|
||||||
|
- `roomId` is the Matrix room ID.
|
||||||
|
- `toMsg` is the `msg` type that is returned after the message has been sent.
|
||||||
|
- `transactionId` is a unique identifier that helps the Matrix server
|
||||||
|
distringuish messages. If you send the same message with the same transactionId,
|
||||||
|
the server promises to register it only once.
|
||||||
|
- `vault` is the Matrix Vault that contains all the latest and most relevant
|
||||||
|
information.
|
||||||
|
|
||||||
-}
|
-}
|
||||||
sendMessageEvent :
|
sendMessageEvent :
|
||||||
{ content : E.Value
|
{ content : E.Value
|
||||||
|
@ -141,6 +197,17 @@ sendMessageEvent data =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{-| Synchronize the Vault with the Matrix API.
|
||||||
|
|
||||||
|
Effectively, this task asks the Matrix API to provide the latest information,
|
||||||
|
which will be returned as your VaultUpdate.
|
||||||
|
|
||||||
|
-}
|
||||||
|
sync : (VaultUpdate -> msg) -> Vault -> Cmd msg
|
||||||
|
sync toMsg (Vault vault) =
|
||||||
|
Api.sync vault { toMsg = Types.VaultUpdate >> toMsg }
|
||||||
|
|
||||||
|
|
||||||
{-| Using new VaultUpdate information, update the Vault accordingly.
|
{-| Using new VaultUpdate information, update the Vault accordingly.
|
||||||
|
|
||||||
This allows us to change our perception of the Matrix environment: has anyone
|
This allows us to change our perception of the Matrix environment: has anyone
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
module Matrix.Room exposing
|
module Matrix.Room exposing
|
||||||
( Room, mostRecentEvents
|
( Room, mostRecentEvents, roomId
|
||||||
, getAccountData
|
, getAccountData
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ What is usually called a chat, a channel, a conversation or a group chat on
|
||||||
other platforms, the term used in Matrix is a "room". A room is a conversation
|
other platforms, the term used in Matrix is a "room". A room is a conversation
|
||||||
where a group of users talk to each other.
|
where a group of users talk to each other.
|
||||||
|
|
||||||
@docs Room, mostRecentEvents
|
@docs Room, mostRecentEvents, roomId
|
||||||
|
|
||||||
This module exposes various functions that help you inspect various aspects of
|
This module exposes various functions that help you inspect various aspects of
|
||||||
a room.
|
a room.
|
||||||
|
@ -56,6 +56,14 @@ getAccountData key (Room room) =
|
||||||
Envelope.extract (Internal.getAccountData key) room
|
Envelope.extract (Internal.getAccountData key) room
|
||||||
|
|
||||||
|
|
||||||
|
{-| Get a room's room id. This is an opaque string that distinguishes rooms from
|
||||||
|
each other.
|
||||||
|
-}
|
||||||
|
roomId : Room -> String
|
||||||
|
roomId (Room room) =
|
||||||
|
Envelope.extract .roomId room
|
||||||
|
|
||||||
|
|
||||||
{-| Get a list of the most recent events sent in the room.
|
{-| Get a list of the most recent events sent in the room.
|
||||||
-}
|
-}
|
||||||
mostRecentEvents : Room -> List Types.Event
|
mostRecentEvents : Room -> List Types.Event
|
||||||
|
|
|
@ -1,280 +0,0 @@
|
||||||
module Test.Tools.Iddict exposing (..)
|
|
||||||
|
|
||||||
import Expect
|
|
||||||
import Fuzz exposing (Fuzzer)
|
|
||||||
import Internal.Tools.Iddict as Iddict exposing (Iddict)
|
|
||||||
import Internal.Tools.Json as Json
|
|
||||||
import Json.Decode as D
|
|
||||||
import Json.Encode as E
|
|
||||||
import Test exposing (..)
|
|
||||||
|
|
||||||
|
|
||||||
fuzzer : Fuzzer a -> Fuzzer (Iddict a)
|
|
||||||
fuzzer fuz =
|
|
||||||
fuz
|
|
||||||
|> Fuzz.pair Fuzz.bool
|
|
||||||
|> Fuzz.list
|
|
||||||
|> Fuzz.map
|
|
||||||
(\items ->
|
|
||||||
List.foldl
|
|
||||||
(\( rm, item ) dict ->
|
|
||||||
case Iddict.insert item dict of
|
|
||||||
( key, d ) ->
|
|
||||||
if rm then
|
|
||||||
Iddict.remove key d
|
|
||||||
|
|
||||||
else
|
|
||||||
d
|
|
||||||
)
|
|
||||||
Iddict.empty
|
|
||||||
items
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
empty : Test
|
|
||||||
empty =
|
|
||||||
describe "empty"
|
|
||||||
[ test "isEmpty"
|
|
||||||
(Iddict.empty
|
|
||||||
|> Iddict.isEmpty
|
|
||||||
|> Expect.equal True
|
|
||||||
|> always
|
|
||||||
)
|
|
||||||
, fuzz Fuzz.int
|
|
||||||
"No members"
|
|
||||||
(\i ->
|
|
||||||
Iddict.empty
|
|
||||||
|> Iddict.member i
|
|
||||||
|> Expect.equal False
|
|
||||||
)
|
|
||||||
, fuzz Fuzz.int
|
|
||||||
"Get gets Nothing"
|
|
||||||
(\i ->
|
|
||||||
Iddict.empty
|
|
||||||
|> Iddict.get i
|
|
||||||
|> Expect.equal Nothing
|
|
||||||
)
|
|
||||||
, test "Size = 0"
|
|
||||||
(Iddict.empty
|
|
||||||
|> Iddict.size
|
|
||||||
|> Expect.equal 0
|
|
||||||
|> always
|
|
||||||
)
|
|
||||||
, test "No keys"
|
|
||||||
(Iddict.empty
|
|
||||||
|> Iddict.keys
|
|
||||||
|> Expect.equal []
|
|
||||||
|> always
|
|
||||||
)
|
|
||||||
, test "No values"
|
|
||||||
(Iddict.empty
|
|
||||||
|> Iddict.values
|
|
||||||
|> Expect.equal []
|
|
||||||
|> always
|
|
||||||
)
|
|
||||||
, test "JSON encode -> decode -> empty"
|
|
||||||
(Iddict.empty
|
|
||||||
|> Iddict.encode Json.value
|
|
||||||
|> D.decodeValue (Iddict.decoder Json.value)
|
|
||||||
|> Result.map Tuple.first
|
|
||||||
|> Expect.equal (Ok Iddict.empty)
|
|
||||||
|> always
|
|
||||||
)
|
|
||||||
, test "JSON encode"
|
|
||||||
(Iddict.empty
|
|
||||||
|> Iddict.encode Json.value
|
|
||||||
|> E.encode 0
|
|
||||||
|> Expect.equal "{\"dict\":{}}"
|
|
||||||
|> always
|
|
||||||
)
|
|
||||||
, test "JSON decode"
|
|
||||||
("{\"dict\":{}}"
|
|
||||||
|> D.decodeString (Iddict.decoder Json.value)
|
|
||||||
|> Result.map Tuple.first
|
|
||||||
|> Expect.equal (Ok Iddict.empty)
|
|
||||||
|> always
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
singleton : Test
|
|
||||||
singleton =
|
|
||||||
let
|
|
||||||
singleFuzzer : Fuzzer (Iddict Int)
|
|
||||||
singleFuzzer =
|
|
||||||
Fuzz.map
|
|
||||||
(\i ->
|
|
||||||
Iddict.singleton i
|
|
||||||
|> Tuple.second
|
|
||||||
)
|
|
||||||
Fuzz.int
|
|
||||||
in
|
|
||||||
describe "singleton"
|
|
||||||
[ fuzz singleFuzzer
|
|
||||||
"not isEmpty"
|
|
||||||
(\single ->
|
|
||||||
single
|
|
||||||
|> Iddict.isEmpty
|
|
||||||
|> Expect.equal False
|
|
||||||
)
|
|
||||||
, fuzz Fuzz.int
|
|
||||||
"singleton == insert empty"
|
|
||||||
(\i ->
|
|
||||||
Iddict.empty
|
|
||||||
|> Iddict.insert i
|
|
||||||
|> Expect.equal (Iddict.singleton i)
|
|
||||||
)
|
|
||||||
, fuzz Fuzz.int
|
|
||||||
"First item is key 0"
|
|
||||||
(\i ->
|
|
||||||
Iddict.singleton i
|
|
||||||
|> Tuple.first
|
|
||||||
|> Expect.equal 0
|
|
||||||
)
|
|
||||||
, fuzz singleFuzzer
|
|
||||||
"Key 0 is member"
|
|
||||||
(\single ->
|
|
||||||
single
|
|
||||||
|> Iddict.member 0
|
|
||||||
|> Expect.equal True
|
|
||||||
)
|
|
||||||
, fuzz Fuzz.int
|
|
||||||
"Key 0 get returns Just value"
|
|
||||||
(\i ->
|
|
||||||
Iddict.singleton i
|
|
||||||
|> Tuple.second
|
|
||||||
|> Iddict.get 0
|
|
||||||
|> Expect.equal (Just i)
|
|
||||||
)
|
|
||||||
, fuzz singleFuzzer
|
|
||||||
"Size == 1"
|
|
||||||
(\single ->
|
|
||||||
single
|
|
||||||
|> Iddict.size
|
|
||||||
|> Expect.equal 1
|
|
||||||
)
|
|
||||||
, fuzz Fuzz.int
|
|
||||||
"Only key 0"
|
|
||||||
(\i ->
|
|
||||||
Iddict.singleton i
|
|
||||||
|> Tuple.second
|
|
||||||
|> Iddict.keys
|
|
||||||
|> Expect.equal [ 0 ]
|
|
||||||
)
|
|
||||||
, fuzz Fuzz.int
|
|
||||||
"Only value value"
|
|
||||||
(\i ->
|
|
||||||
Iddict.singleton i
|
|
||||||
|> Tuple.second
|
|
||||||
|> Iddict.values
|
|
||||||
|> Expect.equal [ i ]
|
|
||||||
)
|
|
||||||
, fuzz singleFuzzer
|
|
||||||
"JSON encode -> decode -> singleton"
|
|
||||||
(\single ->
|
|
||||||
single
|
|
||||||
|> Iddict.encode Json.int
|
|
||||||
|> D.decodeValue (Iddict.decoder Json.int)
|
|
||||||
|> Result.map Tuple.first
|
|
||||||
|> Expect.equal (Ok single)
|
|
||||||
)
|
|
||||||
, fuzz Fuzz.int
|
|
||||||
"JSON encode"
|
|
||||||
(\i ->
|
|
||||||
Iddict.singleton i
|
|
||||||
|> Tuple.second
|
|
||||||
|> Iddict.encode Json.int
|
|
||||||
|> E.encode 0
|
|
||||||
|> Expect.equal ("{\"cursor\":1,\"dict\":{\"0\":" ++ String.fromInt i ++ "}}")
|
|
||||||
)
|
|
||||||
, fuzz Fuzz.int
|
|
||||||
"JSON decode"
|
|
||||||
(\i ->
|
|
||||||
("{\"cursor\":1,\"dict\":{\"0\":" ++ String.fromInt i ++ "}}")
|
|
||||||
|> D.decodeString (Iddict.decoder Json.int)
|
|
||||||
|> Result.map Tuple.first
|
|
||||||
|> Tuple.pair 0
|
|
||||||
|> Expect.equal (Iddict.singleton i |> Tuple.mapSecond Ok)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
insert : Test
|
|
||||||
insert =
|
|
||||||
describe "insert"
|
|
||||||
[ fuzz2 (fuzzer Fuzz.int)
|
|
||||||
Fuzz.int
|
|
||||||
"Add something"
|
|
||||||
(\d i ->
|
|
||||||
case Iddict.insert i d of
|
|
||||||
( key, dict ) ->
|
|
||||||
dict
|
|
||||||
|> Iddict.get key
|
|
||||||
|> Expect.equal (Just i)
|
|
||||||
)
|
|
||||||
, fuzz2 (fuzzer Fuzz.int)
|
|
||||||
Fuzz.int
|
|
||||||
"Never isEmpty"
|
|
||||||
(\d i ->
|
|
||||||
Iddict.insert i d
|
|
||||||
|> Tuple.second
|
|
||||||
|> Iddict.isEmpty
|
|
||||||
|> Expect.equal False
|
|
||||||
)
|
|
||||||
, fuzz2 (fuzzer Fuzz.int)
|
|
||||||
Fuzz.int
|
|
||||||
"New key"
|
|
||||||
(\d i ->
|
|
||||||
case Iddict.insert i d of
|
|
||||||
( key, dict ) ->
|
|
||||||
dict
|
|
||||||
|> Iddict.remove key
|
|
||||||
|> Iddict.insert i
|
|
||||||
|> (\( newKey, _ ) ->
|
|
||||||
Expect.notEqual key newKey
|
|
||||||
)
|
|
||||||
)
|
|
||||||
, fuzz2 (fuzzer Fuzz.int)
|
|
||||||
Fuzz.int
|
|
||||||
"New dict"
|
|
||||||
(\d i ->
|
|
||||||
case Iddict.insert i d of
|
|
||||||
( key, dict ) ->
|
|
||||||
dict
|
|
||||||
|> Iddict.remove key
|
|
||||||
|> Iddict.insert i
|
|
||||||
|> (\( _, newDict ) ->
|
|
||||||
Expect.notEqual dict newDict
|
|
||||||
)
|
|
||||||
)
|
|
||||||
, fuzz2 (fuzzer Fuzz.int)
|
|
||||||
Fuzz.int
|
|
||||||
"Inserted value is member"
|
|
||||||
(\d i ->
|
|
||||||
case Iddict.insert i d of
|
|
||||||
( key, dict ) ->
|
|
||||||
dict
|
|
||||||
|> Iddict.member key
|
|
||||||
|> Expect.equal True
|
|
||||||
)
|
|
||||||
, fuzz2 (fuzzer Fuzz.int)
|
|
||||||
Fuzz.int
|
|
||||||
"Get inserted value"
|
|
||||||
(\d i ->
|
|
||||||
case Iddict.insert i d of
|
|
||||||
( key, dict ) ->
|
|
||||||
dict
|
|
||||||
|> Iddict.get key
|
|
||||||
|> Expect.equal (Just i)
|
|
||||||
)
|
|
||||||
, fuzz2 (fuzzer Fuzz.int)
|
|
||||||
Fuzz.int
|
|
||||||
"size = size + 1"
|
|
||||||
(\d i ->
|
|
||||||
case Iddict.insert i d of
|
|
||||||
( _, dict ) ->
|
|
||||||
Expect.equal
|
|
||||||
(Iddict.size dict)
|
|
||||||
(Iddict.size d + 1)
|
|
||||||
)
|
|
||||||
]
|
|
|
@ -100,7 +100,6 @@ gridField =
|
||||||
, description = []
|
, description = []
|
||||||
, coder = Json.list (Json.list Json.int)
|
, coder = Json.list (Json.list Json.int)
|
||||||
, default = ( [], [] )
|
, default = ( [], [] )
|
||||||
, defaultToString = always "[]"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -132,7 +131,6 @@ hobbiesField =
|
||||||
, description = []
|
, description = []
|
||||||
, coder = Json.list Json.string
|
, coder = Json.list Json.string
|
||||||
, default = ( [], [] )
|
, default = ( [], [] )
|
||||||
, defaultToString = always "[]"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -149,13 +147,6 @@ invitedToPartyField =
|
||||||
, description = []
|
, description = []
|
||||||
, coder = Json.bool
|
, coder = Json.bool
|
||||||
, default = ( False, [] )
|
, default = ( False, [] )
|
||||||
, defaultToString =
|
|
||||||
\b ->
|
|
||||||
if b then
|
|
||||||
"True"
|
|
||||||
|
|
||||||
else
|
|
||||||
"False"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,6 @@ import Fuzz exposing (Fuzzer)
|
||||||
import Internal.Config.Leaks as Leaks
|
import Internal.Config.Leaks as Leaks
|
||||||
import Internal.Tools.Hashdict as Hashdict
|
import Internal.Tools.Hashdict as Hashdict
|
||||||
import Internal.Values.Context as Context exposing (Context, Versions)
|
import Internal.Values.Context as Context exposing (Context, Versions)
|
||||||
import Json.Decode as D
|
|
||||||
import Json.Encode as E
|
|
||||||
import Set
|
import Set
|
||||||
import Test exposing (..)
|
import Test exposing (..)
|
||||||
import Test.Tools.Timestamp as TestTimestamp
|
import Test.Tools.Timestamp as TestTimestamp
|
||||||
|
@ -19,12 +17,15 @@ fuzzer =
|
||||||
maybeString =
|
maybeString =
|
||||||
Fuzz.maybe Fuzz.string
|
Fuzz.maybe Fuzz.string
|
||||||
in
|
in
|
||||||
Fuzz.map8 (\a b c d e ( f, g ) ( h, i ) ( j, k ) -> Context a b c d e f g h i j k)
|
Fuzz.map8 (\a b c d ( e, f ) ( g, h ) ( i, j ) ( k, l ) -> Context a b c d e f g h i j k l)
|
||||||
(Fuzz.constant <| Hashdict.empty .value)
|
(Fuzz.constant <| Hashdict.empty .value)
|
||||||
maybeString
|
maybeString
|
||||||
maybeString
|
maybeString
|
||||||
(Fuzz.maybe TestTimestamp.fuzzer)
|
|
||||||
maybeString
|
maybeString
|
||||||
|
(Fuzz.pair
|
||||||
|
(Fuzz.maybe TestTimestamp.fuzzer)
|
||||||
|
maybeString
|
||||||
|
)
|
||||||
(Fuzz.pair
|
(Fuzz.pair
|
||||||
maybeString
|
maybeString
|
||||||
Fuzz.string
|
Fuzz.string
|
||||||
|
|
|
@ -3,10 +3,7 @@ module Test.Values.Envelope exposing (..)
|
||||||
import Expect
|
import Expect
|
||||||
import Fuzz exposing (Fuzzer)
|
import Fuzz exposing (Fuzzer)
|
||||||
import Internal.Config.Default as Default
|
import Internal.Config.Default as Default
|
||||||
import Internal.Tools.Json as Json
|
|
||||||
import Internal.Values.Envelope as Envelope exposing (Envelope)
|
import Internal.Values.Envelope as Envelope exposing (Envelope)
|
||||||
import Json.Decode as D
|
|
||||||
import Json.Encode as E
|
|
||||||
import Test exposing (..)
|
import Test exposing (..)
|
||||||
import Test.Values.Context as TestContext
|
import Test.Values.Context as TestContext
|
||||||
import Test.Values.Settings as TestSettings
|
import Test.Values.Settings as TestSettings
|
||||||
|
|
|
@ -41,16 +41,18 @@ fuzzerState =
|
||||||
|
|
||||||
unsignedDataFuzzer : Fuzzer Event.UnsignedData
|
unsignedDataFuzzer : Fuzzer Event.UnsignedData
|
||||||
unsignedDataFuzzer =
|
unsignedDataFuzzer =
|
||||||
Fuzz.map4
|
Fuzz.map5
|
||||||
(\age prev redact trans ->
|
(\age memb prev redact trans ->
|
||||||
Event.UnsignedData
|
Event.UnsignedData
|
||||||
{ age = age
|
{ age = age
|
||||||
|
, membership = memb
|
||||||
, prevContent = prev
|
, prevContent = prev
|
||||||
, redactedBecause = redact
|
, redactedBecause = redact
|
||||||
, transactionId = trans
|
, transactionId = trans
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Fuzz.maybe Fuzz.int)
|
(Fuzz.maybe Fuzz.int)
|
||||||
|
(Fuzz.maybe Fuzz.string)
|
||||||
(Fuzz.maybe valueFuzzer)
|
(Fuzz.maybe valueFuzzer)
|
||||||
(Fuzz.maybe <| Fuzz.lazy (\_ -> fuzzer))
|
(Fuzz.maybe <| Fuzz.lazy (\_ -> fuzzer))
|
||||||
(Fuzz.maybe Fuzz.string)
|
(Fuzz.maybe Fuzz.string)
|
||||||
|
|
|
@ -4,8 +4,6 @@ import Fuzz exposing (Fuzzer)
|
||||||
import Internal.Values.Room as Room exposing (Room)
|
import Internal.Values.Room as Room exposing (Room)
|
||||||
import Json.Encode as E
|
import Json.Encode as E
|
||||||
import Test exposing (..)
|
import Test exposing (..)
|
||||||
import Test.Filter.Timeline as TestFilter
|
|
||||||
import Test.Values.Event as TestEvent
|
|
||||||
|
|
||||||
|
|
||||||
placeholderValue : E.Value
|
placeholderValue : E.Value
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Test exposing (..)
|
||||||
|
|
||||||
fuzzer : Fuzzer Settings
|
fuzzer : Fuzzer Settings
|
||||||
fuzzer =
|
fuzzer =
|
||||||
Fuzz.map4 Settings
|
Fuzz.map5 Settings
|
||||||
(Fuzz.oneOf
|
(Fuzz.oneOf
|
||||||
[ Fuzz.constant Default.currentVersion
|
[ Fuzz.constant Default.currentVersion
|
||||||
, Fuzz.string
|
, Fuzz.string
|
||||||
|
@ -22,6 +22,7 @@ fuzzer =
|
||||||
, Fuzz.string
|
, Fuzz.string
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
(Fuzz.maybe Fuzz.string)
|
||||||
(Fuzz.oneOf
|
(Fuzz.oneOf
|
||||||
[ Fuzz.constant Default.removePasswordOnLogin
|
[ Fuzz.constant Default.removePasswordOnLogin
|
||||||
, Fuzz.bool
|
, Fuzz.bool
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Internal.Filter.Timeline as Filter
|
||||||
import Internal.Tools.Json as Json
|
import Internal.Tools.Json as Json
|
||||||
import Internal.Values.Timeline as Timeline exposing (Batch, Timeline)
|
import Internal.Values.Timeline as Timeline exposing (Batch, Timeline)
|
||||||
import Json.Decode as D
|
import Json.Decode as D
|
||||||
|
import Json.Encode as E
|
||||||
import Test exposing (..)
|
import Test exposing (..)
|
||||||
import Test.Filter.Timeline as TestFilter
|
import Test.Filter.Timeline as TestFilter
|
||||||
|
|
||||||
|
@ -250,7 +251,8 @@ suite =
|
||||||
(\timeline ->
|
(\timeline ->
|
||||||
timeline
|
timeline
|
||||||
|> Json.encode Timeline.coder
|
|> Json.encode Timeline.coder
|
||||||
|> D.decodeValue (Json.decode Timeline.coder)
|
|> E.encode 0
|
||||||
|
|> D.decodeString (Json.decode Timeline.coder)
|
||||||
|> Result.map Tuple.first
|
|> Result.map Tuple.first
|
||||||
|> Result.map (Timeline.mostRecentEvents Filter.pass)
|
|> Result.map (Timeline.mostRecentEvents Filter.pass)
|
||||||
|> Expect.equal (Ok <| Timeline.mostRecentEvents Filter.pass timeline)
|
|> Expect.equal (Ok <| Timeline.mostRecentEvents Filter.pass timeline)
|
||||||
|
|
|
@ -12,11 +12,12 @@ import Test.Values.User as TestUser
|
||||||
|
|
||||||
vault : Fuzzer Vault
|
vault : Fuzzer Vault
|
||||||
vault =
|
vault =
|
||||||
Fuzz.map3 Vault
|
Fuzz.map4 Vault
|
||||||
(Fuzz.string
|
(Fuzz.string
|
||||||
|> Fuzz.map (\k -> ( k, Json.encode Json.int 0 ))
|
|> Fuzz.map (\k -> ( k, Json.encode Json.int 0 ))
|
||||||
|> Fuzz.list
|
|> Fuzz.list
|
||||||
|> Fuzz.map Dict.fromList
|
|> Fuzz.map Dict.fromList
|
||||||
)
|
)
|
||||||
|
(Fuzz.maybe Fuzz.string)
|
||||||
(TestHashdict.fuzzer .roomId TestRoom.fuzzer)
|
(TestHashdict.fuzzer .roomId TestRoom.fuzzer)
|
||||||
(Fuzz.maybe TestUser.fuzzer)
|
(Fuzz.maybe TestUser.fuzzer)
|
||||||
|
|
Loading…
Reference in New Issue