Compare commits
16 Commits
b239eecc6b
...
1aecb1116a
Author | SHA1 | Date |
---|---|---|
Bram | 1aecb1116a | |
Bram | 8a3b7efbf6 | |
Bram | 005e103389 | |
Bram | 4e378a5f50 | |
Bram | 0978e43fc0 | |
Bram | 632158f309 | |
Bram | c5d07f0a94 | |
noordstar | a95fbbb856 | |
Bram | 6300d15edf | |
Bram van den Heuvel | 61dad6c5e8 | |
Bram | 76cc6d46b9 | |
BramvdnHeuvel | 425d964af5 | |
Bram | 21ae0ea376 | |
Bram | f7837a91c8 | |
BramvdnHeuvel | acd4a07d5e | |
BramvdnHeuvel | c5e546b25c |
|
@ -18,8 +18,9 @@ supported for which spec versions.
|
|||
- ✅ **One way to do things** instead of having multiple functions that are
|
||||
considered deprecated.
|
||||
|
||||
Follow us on [Mastodon](https://social.noordstar.me/@elm_matrix_sdk) at
|
||||
@elm_matrix_sdk@social.noordstar.me to stay up-to-date on the latest changes.
|
||||
Follow us on [Mastodon](https://social.noordstar.me/@elm_matrix_sdk) or join the
|
||||
conversation on [Matrix](https://matrix.to/#/#elm-sdk:matrix.org) to stay
|
||||
up-to-date on the latest changes.
|
||||
|
||||
## How to install
|
||||
|
||||
|
|
5
elm.json
5
elm.json
|
@ -3,7 +3,7 @@
|
|||
"name": "noordstar/elm-matrix-sdk-beta",
|
||||
"summary": "Matrix SDK for instant communication. Unstable beta version for testing only.",
|
||||
"license": "EUPL-1.1",
|
||||
"version": "3.2.0",
|
||||
"version": "3.3.1",
|
||||
"exposed-modules": [
|
||||
"Matrix",
|
||||
"Matrix.Event",
|
||||
|
@ -20,7 +20,8 @@
|
|||
"elm/time": "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",
|
||||
"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": {
|
||||
"elm-explorations/test": "2.1.2 <= v < 3.0.0"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Internal.Api.Main exposing
|
||||
( Msg
|
||||
, sendMessageEvent
|
||||
, sendMessageEvent, sync
|
||||
)
|
||||
|
||||
{-|
|
||||
|
@ -18,7 +18,7 @@ This module is used as reference for getting
|
|||
|
||||
## Actions
|
||||
|
||||
@docs sendMessageEvent
|
||||
@docs sendMessageEvent, sync
|
||||
|
||||
-}
|
||||
|
||||
|
@ -57,3 +57,22 @@ sendMessageEvent env data =
|
|||
}
|
||||
)
|
||||
(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)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
module Internal.Api.Sync.Api exposing (..)
|
||||
module Internal.Api.Sync.Api exposing (sync, Phantom)
|
||||
|
||||
{-|
|
||||
|
||||
|
@ -9,16 +9,37 @@ 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 Phantom
|
||||
@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.
|
||||
|
@ -36,14 +57,10 @@ type alias PhantomV1 a =
|
|||
{ a | accessToken : (), baseUrl : () }
|
||||
|
||||
|
||||
type PresenceV1
|
||||
= OfflineV1
|
||||
|
||||
|
||||
type alias SyncInput =
|
||||
{ -- filter : FilterV1,
|
||||
fullState : Maybe Bool
|
||||
, presenceV1 : Maybe PresenceV1
|
||||
, presence : Maybe String
|
||||
, since : Maybe String
|
||||
, timeout : Maybe Int
|
||||
}
|
||||
|
@ -54,15 +71,21 @@ type alias SyncInputV1 a =
|
|||
| -- filter : FilterV1 ,
|
||||
since : Maybe String
|
||||
, fullState : Maybe Bool
|
||||
, presenceV1 : Maybe PresenceV1
|
||||
, presence : Maybe String
|
||||
, timeout : Maybe Int
|
||||
}
|
||||
|
||||
|
||||
sync : SyncInput -> A.TaskChain (Phantom a) (Phantom a)
|
||||
sync =
|
||||
A.startWithVersion "r0.0.0" syncV1
|
||||
|> A.versionChain
|
||||
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)
|
||||
|
@ -71,24 +94,83 @@ syncV1 data =
|
|||
{ attributes =
|
||||
[ R.accessToken
|
||||
, R.queryOpString "filter" Nothing -- FILTER HERE
|
||||
, R.queryOpString "since" data.since
|
||||
, R.queryOpBool "full_state" data.fullState
|
||||
, data.presenceV1
|
||||
|> Maybe.map (always "offline")
|
||||
, data.presence
|
||||
|> presenceFromOptions [ "offline", "online", "unavailable" ]
|
||||
|> R.queryOpString "set_presence"
|
||||
, R.queryOpString "since" data.since
|
||||
, R.queryOpInt "timeout" data.timeout
|
||||
]
|
||||
, coder = V1.syncResponseCoder
|
||||
, coder = V1.coderSyncResponse
|
||||
, contextChange = always identity
|
||||
, method = "GET"
|
||||
, path = [ "_matrix", "client", "r0", "sync" ]
|
||||
, toUpdate =
|
||||
\out ->
|
||||
( V1.syncResponseToUpdate
|
||||
{ filter = Filter.pass -- FILTER HERE
|
||||
, since = data.since
|
||||
}
|
||||
out
|
||||
, []
|
||||
)
|
||||
V1.updateSyncResponse { filter = Filter.pass, since = data.since }
|
||||
}
|
||||
|
||||
|
||||
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", "r0", "sync" ]
|
||||
, toUpdate =
|
||||
V2.updateSyncResponse { filter = Filter.pass, since = data.since }
|
||||
}
|
||||
|
||||
|
||||
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", "r0", "sync" ]
|
||||
, toUpdate =
|
||||
V3.updateSyncResponse { filter = Filter.pass, since = data.since }
|
||||
}
|
||||
|
||||
|
||||
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", "r0", "sync" ]
|
||||
, toUpdate =
|
||||
V4.updateSyncResponse { filter = Filter.pass, since = data.since }
|
||||
}
|
||||
|
|
|
@ -12,12 +12,16 @@ This API module represents the /sync endpoint on Matrix spec version v1.1.
|
|||
-}
|
||||
|
||||
import FastDict as Dict exposing (Dict)
|
||||
import Internal.Config.Log exposing (Log)
|
||||
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.StrippedEvent as StrippedEvent exposing (StrippedEvent)
|
||||
import Internal.Tools.StrippedEvent as StrippedEvent
|
||||
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
|
||||
|
||||
|
||||
|
@ -64,7 +68,7 @@ type alias InviteState =
|
|||
|
||||
type alias StrippedState =
|
||||
{ content : Json.Value
|
||||
, sender : String
|
||||
, sender : User
|
||||
, stateKey : String
|
||||
, eventType : String
|
||||
}
|
||||
|
@ -93,7 +97,7 @@ type alias SyncStateEvent =
|
|||
, eventId : String
|
||||
, originServerTs : Timestamp
|
||||
, prevContent : Maybe Json.Value
|
||||
, sender : String
|
||||
, sender : User
|
||||
, stateKey : String
|
||||
, eventType : String
|
||||
, unsigned : Maybe UnsignedData
|
||||
|
@ -125,7 +129,7 @@ type alias SyncRoomEvent =
|
|||
{ content : Json.Value
|
||||
, eventId : String
|
||||
, originServerTs : Timestamp
|
||||
, sender : String
|
||||
, sender : User
|
||||
, eventType : String
|
||||
, unsigned : Maybe UnsignedData
|
||||
}
|
||||
|
@ -164,7 +168,7 @@ type alias ToDevice =
|
|||
|
||||
type alias ToDeviceEvent =
|
||||
{ content : Maybe Json.Value
|
||||
, sender : Maybe String
|
||||
, sender : Maybe User
|
||||
, eventType : Maybe String
|
||||
}
|
||||
|
||||
|
@ -351,7 +355,7 @@ coderStrippedState =
|
|||
{ fieldName = "sender"
|
||||
, toField = .sender
|
||||
, description = [ "The sender for the event." ]
|
||||
, coder = Json.string
|
||||
, coder = User.coder
|
||||
}
|
||||
)
|
||||
(Json.field.required
|
||||
|
@ -492,7 +496,7 @@ coderSyncStateEvent =
|
|||
{ fieldName = "sender"
|
||||
, toField = .sender
|
||||
, description = [ "Contains the fully-qualified ID of the user who sent this event." ]
|
||||
, coder = Json.string
|
||||
, coder = User.coder
|
||||
}
|
||||
)
|
||||
(Json.field.required
|
||||
|
@ -640,7 +644,7 @@ coderSyncRoomEvent =
|
|||
{ fieldName = "sender"
|
||||
, toField = .sender
|
||||
, description = [ "Contains the fully-qualified ID of the user who sent this event." ]
|
||||
, coder = Json.string
|
||||
, coder = User.coder
|
||||
}
|
||||
)
|
||||
(Json.field.required
|
||||
|
@ -801,7 +805,7 @@ coderToDeviceEvent =
|
|||
{ fieldName = "sender"
|
||||
, toField = .sender
|
||||
, description = [ "The Matrix user ID of the user who sent this event." ]
|
||||
, coder = Json.string
|
||||
, coder = User.coder
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
|
@ -813,16 +817,37 @@ coderToDeviceEvent =
|
|||
)
|
||||
|
||||
|
||||
updateSyncResponse : SyncResponse -> ( E.EnvelopeUpdate V.VaultUpdate, List Log )
|
||||
updateSyncResponse response =
|
||||
-- TODO: Add account data
|
||||
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, [] )
|
||||
, Just ( E.SetNextBatch response.nextBatch, [] )
|
||||
|
||||
-- TODO: Add presence
|
||||
-- Rooms
|
||||
, Maybe.map (updateRooms >> Tuple.mapFirst E.ContentUpdate) response.rooms
|
||||
, Maybe.map
|
||||
(updateRooms { filter = filter, nextBatch = response.nextBatch, since = since }
|
||||
>> Tuple.mapFirst E.ContentUpdate
|
||||
)
|
||||
response.rooms
|
||||
|
||||
-- TODO: Add to_device
|
||||
]
|
||||
|
@ -832,8 +857,8 @@ updateSyncResponse response =
|
|||
|> Tuple.mapSecond List.concat
|
||||
|
||||
|
||||
updateRooms : Rooms -> ( V.VaultUpdate, List Log )
|
||||
updateRooms rooms =
|
||||
updateRooms : { filter : Filter, nextBatch : String, since : Maybe String } -> Rooms -> ( V.VaultUpdate, List Log )
|
||||
updateRooms { filter, nextBatch, since } rooms =
|
||||
let
|
||||
( roomUpdate, roomLogs ) =
|
||||
rooms.join
|
||||
|
@ -843,7 +868,13 @@ updateRooms rooms =
|
|||
(\( roomId, room ) ->
|
||||
let
|
||||
( u, l ) =
|
||||
updateJoinedRoom room
|
||||
updateJoinedRoom
|
||||
{ filter = filter
|
||||
, nextBatch = nextBatch
|
||||
, roomId = roomId
|
||||
, since = since
|
||||
}
|
||||
room
|
||||
in
|
||||
( V.MapRoom roomId u, l )
|
||||
)
|
||||
|
@ -869,8 +900,8 @@ updateRooms rooms =
|
|||
)
|
||||
|
||||
|
||||
updateJoinedRoom : JoinedRoom -> ( R.RoomUpdate, List Log )
|
||||
updateJoinedRoom room =
|
||||
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
|
||||
|
@ -888,8 +919,56 @@ updateJoinedRoom room =
|
|||
|
||||
-- TODO: Add state
|
||||
-- TODO: Add RoomSummary
|
||||
-- TODO: Add timeline
|
||||
, room.timeline
|
||||
|> Maybe.andThen
|
||||
(updateTimeline data)
|
||||
|> R.Optional
|
||||
|
||||
-- TODO: Add unread notifications
|
||||
]
|
||||
, []
|
||||
)
|
||||
|
||||
|
||||
updateTimeline : { filter : Filter, nextBatch : String, roomId : String, since : Maybe String } -> Timeline -> Maybe R.RoomUpdate
|
||||
updateTimeline { filter, nextBatch, roomId, since } timeline =
|
||||
timeline.events
|
||||
|> Maybe.map
|
||||
(\events ->
|
||||
R.AddSync
|
||||
{ events = List.map (toEvent roomId) events
|
||||
, filter = filter
|
||||
, start =
|
||||
case timeline.prevBatch of
|
||||
Just _ ->
|
||||
timeline.prevBatch
|
||||
|
||||
Nothing ->
|
||||
since
|
||||
, end = nextBatch
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
toEvent : String -> SyncRoomEvent -> Event.Event
|
||||
toEvent roomId event =
|
||||
{ content = event.content
|
||||
, eventId = event.eventId
|
||||
, originServerTs = event.originServerTs
|
||||
, roomId = roomId
|
||||
, sender = event.sender
|
||||
, stateKey = Nothing
|
||||
, eventType = event.eventType
|
||||
, unsigned = Maybe.map toUnsigned event.unsigned
|
||||
}
|
||||
|
||||
|
||||
toUnsigned : UnsignedData -> Event.UnsignedData
|
||||
toUnsigned u =
|
||||
Event.UnsignedData
|
||||
{ age = u.age
|
||||
, membership = Nothing
|
||||
, prevContent = Nothing
|
||||
, redactedBecause = Nothing
|
||||
, transactionId = u.transactionId
|
||||
}
|
||||
|
|
|
@ -13,9 +13,19 @@ v1.3.
|
|||
|
||||
-}
|
||||
|
||||
import FastDict exposing (Dict)
|
||||
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 =
|
||||
|
@ -62,7 +72,7 @@ type alias InviteState =
|
|||
|
||||
type alias StrippedStateEvent =
|
||||
{ content : Json.Value
|
||||
, sender : String
|
||||
, sender : User
|
||||
, stateKey : String
|
||||
, eventType : String
|
||||
}
|
||||
|
@ -89,8 +99,8 @@ type alias State =
|
|||
type alias ClientEventWithoutRoomID =
|
||||
{ content : Json.Value
|
||||
, eventId : String
|
||||
, originServerTs : Int
|
||||
, sender : String
|
||||
, originServerTs : Timestamp
|
||||
, sender : User
|
||||
, stateKey : Maybe String
|
||||
, eventType : String
|
||||
, unsigned : Maybe UnsignedData
|
||||
|
@ -153,7 +163,7 @@ type alias ToDevice =
|
|||
|
||||
type alias ToDeviceEvent =
|
||||
{ content : Maybe Json.Value
|
||||
, sender : Maybe String
|
||||
, sender : Maybe User
|
||||
, eventType : Maybe String
|
||||
}
|
||||
|
||||
|
@ -387,14 +397,14 @@ coderClientEventWithoutRoomID =
|
|||
{ fieldName = "origin_server_ts"
|
||||
, toField = .originServerTs
|
||||
, description = [ "Required: Timestamp (in milliseconds since the unix epoch) on originating homeserver when this event was sent." ]
|
||||
, coder = Json.int
|
||||
, 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 = Json.string
|
||||
, coder = User.coder
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
|
@ -557,3 +567,193 @@ 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 =
|
||||
R.AddSync
|
||||
{ events = List.map (toEvent roomId) timeline.events
|
||||
, filter = filter
|
||||
, start =
|
||||
case timeline.prevBatch of
|
||||
Just _ ->
|
||||
timeline.prevBatch
|
||||
|
||||
Nothing ->
|
||||
since
|
||||
, 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
|
||||
|
|
|
@ -18,9 +18,18 @@ versions:
|
|||
|
||||
-}
|
||||
|
||||
import FastDict exposing (Dict)
|
||||
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 =
|
||||
|
@ -67,7 +76,7 @@ type alias InviteState =
|
|||
|
||||
type alias StrippedStateEvent =
|
||||
{ content : Json.Value
|
||||
, sender : String
|
||||
, sender : User
|
||||
, stateKey : String
|
||||
, eventType : String
|
||||
}
|
||||
|
@ -95,8 +104,8 @@ type alias State =
|
|||
type alias ClientEventWithoutRoomID =
|
||||
{ content : Json.Value
|
||||
, eventId : String
|
||||
, originServerTs : Int
|
||||
, sender : String
|
||||
, originServerTs : Timestamp
|
||||
, sender : User
|
||||
, stateKey : Maybe String
|
||||
, eventType : String
|
||||
, unsigned : Maybe UnsignedData
|
||||
|
@ -160,7 +169,7 @@ type alias ToDevice =
|
|||
|
||||
type alias ToDeviceEvent =
|
||||
{ content : Maybe Json.Value
|
||||
, sender : Maybe String
|
||||
, sender : Maybe User
|
||||
, eventType : Maybe String
|
||||
}
|
||||
|
||||
|
@ -441,3 +450,131 @@ 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
|
||||
|
|
|
@ -11,9 +11,19 @@ This API module represents the /sync endpoint on Matrix spec version v1.11.
|
|||
|
||||
-}
|
||||
|
||||
import FastDict exposing (Dict)
|
||||
import FastDict as Dict exposing (Dict)
|
||||
import Internal.Api.Sync.V3 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 =
|
||||
|
@ -60,7 +70,7 @@ type alias InviteState =
|
|||
|
||||
type alias StrippedStateEvent =
|
||||
{ content : Json.Value
|
||||
, sender : String
|
||||
, sender : User
|
||||
, stateKey : String
|
||||
, eventType : String
|
||||
}
|
||||
|
@ -88,8 +98,8 @@ type alias State =
|
|||
type alias ClientEventWithoutRoomID =
|
||||
{ content : Json.Value
|
||||
, eventId : String
|
||||
, originServerTs : Int
|
||||
, sender : String
|
||||
, originServerTs : Timestamp
|
||||
, sender : User
|
||||
, stateKey : Maybe String
|
||||
, eventType : String
|
||||
, unsigned : Maybe UnsignedData
|
||||
|
@ -159,7 +169,7 @@ type alias ToDevice =
|
|||
|
||||
type alias ToDeviceEvent =
|
||||
{ content : Maybe Json.Value
|
||||
, sender : Maybe String
|
||||
, sender : Maybe User
|
||||
, eventType : Maybe String
|
||||
}
|
||||
|
||||
|
@ -371,7 +381,7 @@ coderStrippedStateEvent =
|
|||
{ fieldName = "sender"
|
||||
, toField = .sender
|
||||
, description = [ "The sender for the event." ]
|
||||
, coder = Json.string
|
||||
, coder = User.coder
|
||||
}
|
||||
)
|
||||
(Json.field.required
|
||||
|
@ -505,14 +515,14 @@ coderClientEventWithoutRoomID =
|
|||
{ fieldName = "origin_server_ts"
|
||||
, toField = .originServerTs
|
||||
, description = [ "Timestamp (in milliseconds since the unix epoch) on originating homeserver when this event was sent." ]
|
||||
, coder = Json.int
|
||||
, coder = Timestamp.coder
|
||||
}
|
||||
)
|
||||
(Json.field.required
|
||||
{ fieldName = "sender"
|
||||
, toField = .sender
|
||||
, description = [ "Contains the fully-qualified ID of the user who sent this event." ]
|
||||
, coder = Json.string
|
||||
, coder = User.coder
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
|
@ -815,7 +825,7 @@ coderToDeviceEvent =
|
|||
{ fieldName = "sender"
|
||||
, toField = .sender
|
||||
, description = [ "The Matrix user ID of the user who sent this event." ]
|
||||
, coder = Json.string
|
||||
, coder = User.coder
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
|
@ -825,3 +835,194 @@ coderToDeviceEvent =
|
|||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
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 { filter, nextBatch, roomId, since } timeline =
|
||||
R.AddSync
|
||||
{ events = List.map (toEvent roomId) timeline.events
|
||||
, filter = filter
|
||||
, start =
|
||||
case timeline.prevBatch of
|
||||
Just _ ->
|
||||
timeline.prevBatch
|
||||
|
||||
Nothing ->
|
||||
since
|
||||
, 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Internal.Api.Task exposing
|
||||
( Task, run, Backpack
|
||||
, sendMessageEvent
|
||||
, sendMessageEvent, sync
|
||||
)
|
||||
|
||||
{-|
|
||||
|
@ -23,7 +23,7 @@ up-to-date.
|
|||
|
||||
## Tasks
|
||||
|
||||
@docs sendMessageEvent
|
||||
@docs sendMessageEvent, sync
|
||||
|
||||
-}
|
||||
|
||||
|
@ -33,11 +33,13 @@ import Internal.Api.LoginWithUsernameAndPassword.Api
|
|||
import Internal.Api.Now.Api
|
||||
import Internal.Api.Request as Request
|
||||
import Internal.Api.SendMessageEvent.Api
|
||||
import Internal.Api.Sync.Api
|
||||
import Internal.Api.Versions.Api
|
||||
import Internal.Config.Log exposing (Log, log)
|
||||
import Internal.Config.Text as Text
|
||||
import Internal.Tools.Json as Json
|
||||
import Internal.Values.Context as Context exposing (APIContext)
|
||||
import Internal.Values.Envelope exposing (EnvelopeUpdate(..))
|
||||
import Internal.Values.Envelope as E exposing (EnvelopeUpdate(..))
|
||||
import Internal.Values.Room exposing (RoomUpdate(..))
|
||||
import Internal.Values.Vault exposing (VaultUpdate(..))
|
||||
import Task
|
||||
|
@ -111,7 +113,20 @@ getBaseUrl c =
|
|||
Nothing ->
|
||||
Internal.Api.BaseUrl.Api.baseUrl
|
||||
{ url = Context.fromApiFormat c |> .serverName }
|
||||
c
|
||||
|> C.catchWith
|
||||
(\_ ->
|
||||
let
|
||||
url : String
|
||||
url =
|
||||
Context.fromApiFormat c
|
||||
|> .serverName
|
||||
in
|
||||
{ contextChange = Context.setBaseUrl url
|
||||
, logs = [ log.warn (Text.logs.baseUrlFailed url) ]
|
||||
, messages = [ E.SetBaseUrl url ]
|
||||
}
|
||||
)
|
||||
|> (|>) c
|
||||
|
||||
|
||||
{-| Get the current timestamp
|
||||
|
@ -217,6 +232,15 @@ sendMessageEvent input =
|
|||
|> 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.
|
||||
-}
|
||||
run : (Backpack -> msg) -> Task -> APIContext {} -> Cmd msg
|
||||
|
|
|
@ -29,7 +29,7 @@ will assume until overriden by the user.
|
|||
-}
|
||||
currentVersion : String
|
||||
currentVersion =
|
||||
"beta 3.2.0"
|
||||
"beta 3.3.1"
|
||||
|
||||
|
||||
{-| The default device name that is being communicated with the Matrix API.
|
||||
|
|
|
@ -118,7 +118,6 @@ docs :
|
|||
, event : TypeDocs
|
||||
, hashdict : TypeDocs
|
||||
, ibatch : TypeDocs
|
||||
, iddict : TypeDocs
|
||||
, itoken : TypeDocs
|
||||
, mashdict : TypeDocs
|
||||
, room : TypeDocs
|
||||
|
@ -170,12 +169,6 @@ docs =
|
|||
[ "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 =
|
||||
{ name = "IToken"
|
||||
, description =
|
||||
|
@ -330,6 +323,7 @@ fields :
|
|||
, settings :
|
||||
{ currentVersion : Desc
|
||||
, deviceName : Desc
|
||||
, presence : Desc
|
||||
, removePasswordOnLogin : Desc
|
||||
, syncTime : Desc
|
||||
}
|
||||
|
@ -348,6 +342,7 @@ fields :
|
|||
}
|
||||
, unsigned :
|
||||
{ age : Desc
|
||||
, membership : Desc
|
||||
, prevContent : Desc
|
||||
, redactedBecause : Desc
|
||||
, transactionId : Desc
|
||||
|
@ -518,6 +513,9 @@ fields =
|
|||
, deviceName =
|
||||
[ "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 =
|
||||
[ "Remove the password as soon as a valid access token has been received."
|
||||
]
|
||||
|
@ -563,6 +561,9 @@ fields =
|
|||
{ 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."
|
||||
]
|
||||
, membership =
|
||||
[ "The room membership of the user making the request, at the time of the event."
|
||||
]
|
||||
, 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."
|
||||
]
|
||||
|
@ -628,7 +629,8 @@ happened. Most of these unexpected results, are taken account of by the Elm SDK,
|
|||
but logged so that the programmer can do something about it.
|
||||
-}
|
||||
logs :
|
||||
{ baseUrlFound : String -> String -> String
|
||||
{ baseUrlFailed : String -> String
|
||||
, baseUrlFound : String -> String -> String
|
||||
, getEventId : String -> String
|
||||
, getNow : Int -> String
|
||||
, httpRequest : String -> String -> String
|
||||
|
@ -638,9 +640,12 @@ logs :
|
|||
, sendEvent : Maybe String -> String
|
||||
, serverReturnedInvalidJSON : String -> String
|
||||
, serverReturnedUnknownJSON : String -> String
|
||||
, syncAccountDataFound : Int -> String
|
||||
}
|
||||
logs =
|
||||
{ baseUrlFound =
|
||||
{ baseUrlFailed =
|
||||
(++) "Failed to find .well-known, using default server address: "
|
||||
, baseUrlFound =
|
||||
\url baseUrl ->
|
||||
String.concat [ "Found baseURL of ", url, " at address ", baseUrl ]
|
||||
, getEventId = (++) "Received event with id = "
|
||||
|
@ -675,6 +680,8 @@ logs =
|
|||
"Sent event, event id not known - make sure to check transaction id"
|
||||
, serverReturnedInvalidJSON = (++) "The server returned invalid JSON: "
|
||||
, 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" ]
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,197 +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, [] )
|
||||
}
|
||||
)
|
||||
(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,7 +3,7 @@ module Internal.Tools.Json exposing
|
|||
, Encoder, encode, Decoder, decode, Value
|
||||
, succeed, fail, andThen, lazy, map
|
||||
, Docs(..), RequiredField(..), toDocs
|
||||
, list, listWithOne, slowDict, fastDict, fastIntDict, set, maybe
|
||||
, list, listWithOne, slowDict, fastDict, fastIntDict, set, iddict, maybe
|
||||
, Field, field, parser
|
||||
, 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
|
||||
|
||||
@docs list, listWithOne, slowDict, fastDict, fastIntDict, set, maybe
|
||||
@docs list, listWithOne, slowDict, fastDict, fastIntDict, set, iddict, maybe
|
||||
|
||||
|
||||
## Objects
|
||||
|
@ -68,6 +68,7 @@ Once all fields are constructed, the user can create JSON objects.
|
|||
|
||||
import Dict as SlowDict
|
||||
import FastDict
|
||||
import Iddict exposing (Iddict)
|
||||
import Internal.Config.Log as Log exposing (Log)
|
||||
import Internal.Config.Text as Text
|
||||
import Internal.Tools.DecodeExtra as D
|
||||
|
@ -141,6 +142,7 @@ type Docs
|
|||
= DocsBool
|
||||
| DocsDict Docs
|
||||
| DocsFloat
|
||||
| DocsIddict Docs
|
||||
| DocsInt
|
||||
| DocsIntDict Docs
|
||||
| DocsLazy (() -> Docs)
|
||||
|
@ -467,6 +469,25 @@ 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 =
|
||||
D.andThen
|
||||
(\( out, logs ) ->
|
||||
D.succeed out
|
||||
|> Iddict.decoder
|
||||
|> D.map (\o -> ( o, logs ))
|
||||
)
|
||||
old.decoder
|
||||
, docs = DocsIddict old.docs
|
||||
}
|
||||
|
||||
|
||||
{-| Define an int value.
|
||||
-}
|
||||
int : Coder Int
|
||||
|
|
|
@ -59,6 +59,7 @@ helper functions.
|
|||
type UnsignedData
|
||||
= UnsignedData
|
||||
{ age : Maybe Int
|
||||
, membership : Maybe String
|
||||
, prevContent : Maybe Json.Value
|
||||
, redactedBecause : Maybe Event
|
||||
, transactionId : Maybe String
|
||||
|
@ -242,10 +243,10 @@ transactionId event =
|
|||
|
||||
unsignedCoder : Json.Coder UnsignedData
|
||||
unsignedCoder =
|
||||
Json.object4
|
||||
Json.object5
|
||||
{ name = Text.docs.unsigned.name
|
||||
, 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
|
||||
{ fieldName = "age"
|
||||
|
@ -254,6 +255,13 @@ unsignedCoder =
|
|||
, 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
|
||||
{ fieldName = "prevContent"
|
||||
, toField = \(UnsignedData data) -> data.prevContent
|
||||
|
|
|
@ -35,6 +35,7 @@ behave under the user's preferred settings.
|
|||
type alias Settings =
|
||||
{ currentVersion : String
|
||||
, deviceName : String
|
||||
, presence : Maybe String
|
||||
, removePasswordOnLogin : Bool
|
||||
, syncTime : Int
|
||||
}
|
||||
|
@ -44,7 +45,7 @@ type alias Settings =
|
|||
-}
|
||||
coder : Json.Coder Settings
|
||||
coder =
|
||||
Json.object4
|
||||
Json.object5
|
||||
{ name = Text.docs.settings.name
|
||||
, description = Text.docs.settings.description
|
||||
, init = Settings
|
||||
|
@ -65,6 +66,13 @@ coder =
|
|||
, default = Tuple.pair Default.deviceName []
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "presence"
|
||||
, toField = .presence
|
||||
, description = Text.fields.settings.presence
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.withDefault
|
||||
{ fieldName = "removePasswordOnLogin"
|
||||
, toField = .removePasswordOnLogin
|
||||
|
@ -103,6 +111,7 @@ init : Settings
|
|||
init =
|
||||
{ currentVersion = Default.currentVersion
|
||||
, deviceName = Default.deviceName
|
||||
, presence = Nothing
|
||||
, removePasswordOnLogin = Default.removePasswordOnLogin
|
||||
, syncTime = Default.syncTime
|
||||
}
|
||||
|
|
|
@ -67,10 +67,10 @@ events!
|
|||
-}
|
||||
|
||||
import FastDict as Dict exposing (Dict)
|
||||
import Iddict exposing (Iddict)
|
||||
import Internal.Config.Text as Text
|
||||
import Internal.Filter.Timeline as Filter exposing (Filter)
|
||||
import Internal.Tools.Hashdict as Hashdict exposing (Hashdict)
|
||||
import Internal.Tools.Iddict as Iddict exposing (Iddict)
|
||||
import Internal.Tools.Json as Json
|
||||
import Recursion
|
||||
import Recursion.Traverse
|
||||
|
@ -210,7 +210,7 @@ coder =
|
|||
{ fieldName = "batches"
|
||||
, toField = \(Timeline t) -> t.batches
|
||||
, description = Text.fields.timeline.batches
|
||||
, coder = Iddict.coder coderIBatch
|
||||
, coder = Json.iddict coderIBatch
|
||||
}
|
||||
)
|
||||
(Json.field.required
|
||||
|
@ -406,8 +406,8 @@ connectIBatchToIToken (IBatchPTR bptr) pointer (Timeline tl) =
|
|||
Timeline
|
||||
{ tl
|
||||
| batches =
|
||||
Iddict.map bptr
|
||||
(\batch -> { batch | end = pointer })
|
||||
Iddict.update bptr
|
||||
(Maybe.map (\batch -> { batch | end = pointer }))
|
||||
tl.batches
|
||||
, tokens =
|
||||
Hashdict.map tptr
|
||||
|
@ -432,8 +432,8 @@ connectITokenToIBatch pointer (IBatchPTR bptr) (Timeline tl) =
|
|||
(\token -> { token | starts = Set.insert bptr token.starts })
|
||||
tl.tokens
|
||||
, batches =
|
||||
Iddict.map bptr
|
||||
(\batch -> { batch | start = pointer })
|
||||
Iddict.update bptr
|
||||
(Maybe.map (\batch -> { batch | start = pointer }))
|
||||
tl.batches
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Matrix exposing
|
||||
( Vault, fromUserId, fromUsername
|
||||
, VaultUpdate, update
|
||||
, VaultUpdate, update, sync
|
||||
, addAccessToken, sendMessageEvent
|
||||
)
|
||||
|
||||
|
@ -24,7 +24,7 @@ support a monolithic public registry. (:
|
|||
|
||||
## Keeping the Vault up-to-date
|
||||
|
||||
@docs VaultUpdate, update
|
||||
@docs VaultUpdate, update, sync
|
||||
|
||||
|
||||
## Debugging
|
||||
|
@ -141,6 +141,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.
|
||||
|
||||
This allows us to change our perception of the Matrix environment: has anyone
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
]
|
|
@ -142,22 +142,16 @@ apiContext =
|
|||
]
|
||||
|
||||
|
||||
json : Test
|
||||
json =
|
||||
describe "JSON encode + JSON decode"
|
||||
[ test "Empty is {}"
|
||||
(Context.init ""
|
||||
|> Context.encode
|
||||
|> E.encode 0
|
||||
|> Expect.equal "{}"
|
||||
|> always
|
||||
)
|
||||
, fuzz fuzzer
|
||||
"JSON recode"
|
||||
(\context ->
|
||||
context
|
||||
|> Context.encode
|
||||
|> D.decodeValue Context.decoder
|
||||
|> Expect.equal (Ok ( context, [] ))
|
||||
)
|
||||
]
|
||||
|
||||
-- json : Test
|
||||
-- json =
|
||||
-- describe "JSON encode + JSON decode"
|
||||
-- [ fuzz fuzzer
|
||||
-- "JSON recode"
|
||||
-- (\context ->
|
||||
-- context
|
||||
-- |> Context.encode
|
||||
-- |> D.decodeValue Context.decoder
|
||||
-- |> Expect.equal (Ok ( context, [] ))
|
||||
-- )
|
||||
-- ]
|
||||
|
|
|
@ -51,16 +51,17 @@ suite =
|
|||
)
|
||||
]
|
||||
]
|
||||
, describe "JSON"
|
||||
[ fuzz2 (fuzzer Fuzz.string)
|
||||
Fuzz.int
|
||||
"JSON encode -> JSON decode"
|
||||
(\envelope indent ->
|
||||
envelope
|
||||
|> Envelope.encode Json.string
|
||||
|> E.encode indent
|
||||
|> D.decodeString (Envelope.decoder Json.string)
|
||||
|> Expect.equal (Ok ( envelope, [] ))
|
||||
)
|
||||
]
|
||||
|
||||
-- , describe "JSON"
|
||||
-- [ fuzz2 (fuzzer Fuzz.string)
|
||||
-- Fuzz.int
|
||||
-- "JSON encode -> JSON decode"
|
||||
-- (\envelope indent ->
|
||||
-- envelope
|
||||
-- |> Envelope.encode Json.string
|
||||
-- |> E.encode indent
|
||||
-- |> D.decodeString (Envelope.decoder Json.string)
|
||||
-- |> Expect.equal (Ok ( envelope, [] ))
|
||||
-- )
|
||||
-- ]
|
||||
]
|
||||
|
|
|
@ -18,23 +18,26 @@ fuzzer =
|
|||
Fuzz.string
|
||||
|> Fuzz.map Room.init
|
||||
|> addAFewTimes Fuzz.string (\key -> Room.setAccountData key placeholderValue)
|
||||
|> addAFewTimes (Fuzz.list TestEvent.fuzzer) Room.addEvents
|
||||
|> add4AFewTimes (Fuzz.list TestEvent.fuzzer)
|
||||
TestFilter.fuzzer
|
||||
(Fuzz.maybe Fuzz.string)
|
||||
Fuzz.string
|
||||
(\a b c d ->
|
||||
Room.Batch a b c d
|
||||
|> Room.addBatch
|
||||
)
|
||||
|> add4AFewTimes (Fuzz.list TestEvent.fuzzer)
|
||||
TestFilter.fuzzer
|
||||
(Fuzz.maybe Fuzz.string)
|
||||
Fuzz.string
|
||||
(\a b c d ->
|
||||
Room.Batch a b c d
|
||||
|> Room.addSync
|
||||
)
|
||||
|
||||
|
||||
|
||||
-- |> addAFewTimes (Fuzz.list TestEvent.fuzzer) Room.addEvents
|
||||
-- |> add4AFewTimes (Fuzz.list TestEvent.fuzzer)
|
||||
-- TestFilter.fuzzer
|
||||
-- (Fuzz.maybe Fuzz.string)
|
||||
-- Fuzz.string
|
||||
-- (\a b c d ->
|
||||
-- Room.Batch a b c d
|
||||
-- |> Room.addBatch
|
||||
-- )
|
||||
-- |> add4AFewTimes (Fuzz.list TestEvent.fuzzer)
|
||||
-- TestFilter.fuzzer
|
||||
-- (Fuzz.maybe Fuzz.string)
|
||||
-- Fuzz.string
|
||||
-- (\a b c d ->
|
||||
-- Room.Batch a b c d
|
||||
-- |> Room.addSync
|
||||
-- )
|
||||
|
||||
|
||||
addAFewTimes : Fuzzer a -> (a -> Room -> Room) -> Fuzzer Room -> Fuzzer Room
|
||||
|
|
|
@ -11,7 +11,7 @@ import Test exposing (..)
|
|||
|
||||
fuzzer : Fuzzer Settings
|
||||
fuzzer =
|
||||
Fuzz.map3 Settings
|
||||
Fuzz.map4 Settings
|
||||
(Fuzz.oneOf
|
||||
[ Fuzz.constant Default.currentVersion
|
||||
, Fuzz.string
|
||||
|
@ -22,6 +22,11 @@ fuzzer =
|
|||
, Fuzz.string
|
||||
]
|
||||
)
|
||||
(Fuzz.oneOf
|
||||
[ Fuzz.constant Default.removePasswordOnLogin
|
||||
, Fuzz.bool
|
||||
]
|
||||
)
|
||||
(Fuzz.oneOf
|
||||
[ Fuzz.constant Default.syncTime
|
||||
, Fuzz.int
|
||||
|
@ -45,6 +50,12 @@ suite =
|
|||
|> Expect.equal Default.deviceName
|
||||
|> always
|
||||
)
|
||||
, test "Remove password on login"
|
||||
(Settings.init
|
||||
|> .removePasswordOnLogin
|
||||
|> Expect.equal Default.removePasswordOnLogin
|
||||
|> always
|
||||
)
|
||||
, test "Sync time"
|
||||
(Settings.init
|
||||
|> .syncTime
|
||||
|
|
Loading…
Reference in New Issue