diff --git a/elm.json b/elm.json index 7c3b668..0eed841 100644 --- a/elm.json +++ b/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.3.1", + "version": "3.4.0", "exposed-modules": [ "Matrix", "Matrix.Event", diff --git a/src/Internal/Api/BaseUrl/Api.elm b/src/Internal/Api/BaseUrl/Api.elm index 5e98ed3..21333dc 100644 --- a/src/Internal/Api/BaseUrl/Api.elm +++ b/src/Internal/Api/BaseUrl/Api.elm @@ -13,7 +13,6 @@ This module looks for the right homeserver address. import Internal.Api.Chain as C import Internal.Api.Request as R -import Internal.Config.Leaks as L import Internal.Config.Log exposing (log) import Internal.Config.Text as Text import Internal.Tools.Json as Json diff --git a/src/Internal/Api/GetEvent/Api.elm b/src/Internal/Api/GetEvent/Api.elm index dd10f0d..e81c599 100644 --- a/src/Internal/Api/GetEvent/Api.elm +++ b/src/Internal/Api/GetEvent/Api.elm @@ -204,7 +204,7 @@ getEventCoderV1 = [ "UnsignedData as described by the Matrix spec" , "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 { fieldName = "age" diff --git a/src/Internal/Api/LoginWithUsernameAndPassword/Api.elm b/src/Internal/Api/LoginWithUsernameAndPassword/Api.elm index 9242ba2..b5cddcd 100644 --- a/src/Internal/Api/LoginWithUsernameAndPassword/Api.elm +++ b/src/Internal/Api/LoginWithUsernameAndPassword/Api.elm @@ -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.Request as R -import Internal.Config.Leaks as L import Internal.Config.Log exposing (log) import Internal.Config.Text as Text import Internal.Tools.Json as Json diff --git a/src/Internal/Api/SendMessageEvent/Api.elm b/src/Internal/Api/SendMessageEvent/Api.elm index abccf41..9e298f0 100644 --- a/src/Internal/Api/SendMessageEvent/Api.elm +++ b/src/Internal/Api/SendMessageEvent/Api.elm @@ -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.Request as R -import Internal.Config.Leaks as L import Internal.Config.Log exposing (log) import Internal.Config.Text as Text import Internal.Tools.Json as Json diff --git a/src/Internal/Api/Sync/V4.elm b/src/Internal/Api/Sync/V4.elm index aa03724..b574997 100644 --- a/src/Internal/Api/Sync/V4.elm +++ b/src/Internal/Api/Sync/V4.elm @@ -12,7 +12,6 @@ This API module represents the /sync endpoint on Matrix spec version v1.11. -} 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) diff --git a/src/Internal/Config/Default.elm b/src/Internal/Config/Default.elm index 4b56824..606468e 100644 --- a/src/Internal/Config/Default.elm +++ b/src/Internal/Config/Default.elm @@ -29,7 +29,7 @@ will assume until overriden by the user. -} currentVersion : String currentVersion = - "beta 3.3.1" + "beta 3.4.0" {-| The default device name that is being communicated with the Matrix API. diff --git a/src/Internal/Grammar/ServerName.elm b/src/Internal/Grammar/ServerName.elm index 4b9f1b6..4899881 100644 --- a/src/Internal/Grammar/ServerName.elm +++ b/src/Internal/Grammar/ServerName.elm @@ -189,21 +189,20 @@ ipv6RightParser n = |. 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 - back - - else - List.concat [ front, [ "" ], back ] - ) - |> List.intersperse ":" - |> String.concat +-- {-| 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 +-- back +-- else +-- List.concat [ front, [ "" ], back ] +-- ) +-- |> List.intersperse ":" +-- |> String.concat portParser : Parser Int diff --git a/src/Internal/Tools/Json.elm b/src/Internal/Tools/Json.elm index ab609fa..799298a 100644 --- a/src/Internal/Tools/Json.elm +++ b/src/Internal/Tools/Json.elm @@ -477,13 +477,14 @@ 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 + Iddict.decoder old.decoder + |> D.map + (\out -> + ( Iddict.map (always Tuple.first) out + , Iddict.values out + |> List.concatMap Tuple.second + ) + ) , docs = DocsIddict old.docs } diff --git a/src/Internal/Values/Context.elm b/src/Internal/Values/Context.elm index 5fc358d..d53783d 100644 --- a/src/Internal/Values/Context.elm +++ b/src/Internal/Values/Context.elm @@ -71,7 +71,6 @@ import Internal.Config.Text as Text import Internal.Tools.Hashdict as Hashdict exposing (Hashdict) import Internal.Tools.Json as Json import Internal.Tools.Timestamp as Timestamp exposing (Timestamp) -import Json.Encode as E import Set exposing (Set) import Time diff --git a/src/Internal/Values/Envelope.elm b/src/Internal/Values/Envelope.elm index c9cc126..82c8d81 100644 --- a/src/Internal/Values/Envelope.elm +++ b/src/Internal/Values/Envelope.elm @@ -56,6 +56,8 @@ import Internal.Tools.Json as Json import Internal.Tools.Timestamp exposing (Timestamp) import Internal.Values.Context as Context exposing (AccessToken, Context, Versions) 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 @@ -292,50 +294,91 @@ toMaybe data = {-| Updates the Envelope with a given EnvelopeUpdate value. -} update : (au -> a -> a) -> EnvelopeUpdate au -> Envelope a -> Envelope a -update updateContent eu ({ context } as data) = - case eu of - ContentUpdate v -> - { data | content = updateContent v data.content } +update updateContent eu startData = + Recursion.runRecursion + (\updt -> + case updt of + ContentUpdate v -> + Recursion.base + (\data -> + { data | content = updateContent v data.content } + ) - HttpRequest _ -> - data + HttpRequest _ -> + Recursion.base identity - More items -> - List.foldl (update updateContent) data items + More items -> + Recursion.Fold.foldList (<<) identity items - Optional (Just u) -> - update updateContent u data + Optional (Just u) -> + Recursion.recurse u - Optional Nothing -> - data + Optional Nothing -> + Recursion.base identity - RemoveAccessToken token -> - { data | context = { context | accessTokens = Hashdict.removeKey token context.accessTokens } } + RemoveAccessToken token -> + Recursion.base + (\({ context } as data) -> + { data + | context = + { context + | accessTokens = + Hashdict.removeKey token context.accessTokens + } + } + ) - RemovePasswordIfNecessary -> - if data.settings.removePasswordOnLogin then - { data | context = { context | password = Nothing } } + RemovePasswordIfNecessary -> + Recursion.base + (\({ context } as data) -> + if data.settings.removePasswordOnLogin then + { data | context = { context | password = Nothing } } - else - data + else + data + ) - SetAccessToken a -> - { data | context = { context | accessTokens = Hashdict.insert a context.accessTokens } } + SetAccessToken a -> + Recursion.base + (\({ context } as data) -> + { data | context = { context | accessTokens = Hashdict.insert a context.accessTokens } } + ) - SetBaseUrl b -> - { data | context = { context | baseUrl = Just b } } + SetBaseUrl b -> + Recursion.base + (\({ context } as data) -> + { data | context = { context | baseUrl = Just b } } + ) - SetDeviceId d -> - { data | context = { context | deviceId = Just d } } + SetDeviceId d -> + Recursion.base + (\({ context } as data) -> + { data | context = { context | deviceId = Just d } } + ) - SetNextBatch nextBatch -> - { data | context = { context | nextBatch = Just nextBatch } } + SetNextBatch nextBatch -> + Recursion.base + (\({ context } as data) -> + { data | context = { context | nextBatch = Just nextBatch } } + ) - SetNow n -> - { data | context = { context | now = Just n } } + SetNow n -> + Recursion.base + (\({ context } as data) -> + { data | context = { context | now = Just n } } + ) - SetRefreshToken r -> - { data | context = { context | refreshToken = Just r } } + SetRefreshToken r -> + Recursion.base + (\({ context } as data) -> + { data | context = { context | refreshToken = Just r } } + ) - SetVersions vs -> - { data | context = { context | versions = Just vs } } + SetVersions vs -> + Recursion.base + (\({ context } as data) -> + { data | context = { context | versions = Just vs } } + ) + ) + eu + startData diff --git a/src/Internal/Values/Room.elm b/src/Internal/Values/Room.elm index 2dd93b9..8602c11 100644 --- a/src/Internal/Values/Room.elm +++ b/src/Internal/Values/Room.elm @@ -58,7 +58,8 @@ import Internal.Values.Event as Event exposing (Event) import Internal.Values.StateManager as StateManager exposing (StateManager) import Internal.Values.Timeline as Timeline exposing (Timeline) 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. @@ -255,30 +256,35 @@ setAccountData key value room = {-| Update the Room based on given instructions. -} update : RoomUpdate -> Room -> Room -update ru room = - case ru of - AddEvent _ -> - -- TODO: Add event - room +update roomUpdate startRoom = + Recursion.runRecursion + (\ru -> + case ru of + AddEvent _ -> + -- TODO: Add event + Recursion.base identity - AddSync batch -> - addSync batch room + AddSync batch -> + Recursion.base (addSync batch) - Invite _ -> - -- TODO: Invite user - room + Invite _ -> + -- TODO: Invite user + Recursion.base identity - More items -> - List.foldl update room items + More items -> + Recursion.Fold.foldList (<<) identity items - Optional (Just u) -> - update u room + Optional (Just u) -> + Recursion.recurse u - Optional Nothing -> - room + Optional Nothing -> + Recursion.base identity - SetAccountData key value -> - setAccountData key value room + SetAccountData key value -> + Recursion.base (setAccountData key value) - SetEphemeral eph -> - { room | ephemeral = eph } + SetEphemeral eph -> + Recursion.base (\room -> { room | ephemeral = eph }) + ) + roomUpdate + startRoom diff --git a/src/Internal/Values/Timeline.elm b/src/Internal/Values/Timeline.elm index 1e0459a..513baa0 100644 --- a/src/Internal/Values/Timeline.elm +++ b/src/Internal/Values/Timeline.elm @@ -678,20 +678,21 @@ mostRecentFrom filter timeline ptr = { 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 -specific scenarios like decoding JSON values. --} -recountFilledBatches : Timeline -> Timeline -recountFilledBatches (Timeline tl) = - Timeline - { tl - | filledBatches = - tl.batches - |> Iddict.values - |> List.filter (\v -> v.events /= []) - |> List.length - } + +-- {-| Recount the Timeline's amount of filled batches. Since the Timeline +-- automatically tracks the count on itself, this is generally exclusively used in +-- specific scenarios like decoding JSON values. +-- -} +-- recountFilledBatches : Timeline -> Timeline +-- recountFilledBatches (Timeline tl) = +-- Timeline +-- { tl +-- | filledBatches = +-- tl.batches +-- |> Iddict.values +-- |> List.filter (\v -> v.events /= []) +-- |> List.length +-- } {-| Create a timeline with a single batch inserted. This batch is considered the diff --git a/src/Internal/Values/Vault.elm b/src/Internal/Values/Vault.elm index 1c4112f..3e048d3 100644 --- a/src/Internal/Values/Vault.elm +++ b/src/Internal/Values/Vault.elm @@ -1,8 +1,9 @@ module Internal.Values.Vault exposing ( Vault, init , VaultUpdate(..), update - , fromRoomId, mapRoom, updateRoom + , rooms, fromRoomId, mapRoom, updateRoom , getAccountData, setAccountData + , coder ) {-| 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. -@docs fromRoomId, mapRoom, updateRoom +@docs rooms, fromRoomId, mapRoom, updateRoom ## Account data @docs getAccountData, setAccountData + +## JSON + +@docs coder + -} import FastDict as Dict exposing (Dict) @@ -38,6 +44,8 @@ import Internal.Tools.Hashdict as Hashdict exposing (Hashdict) import Internal.Tools.Json as Json import Internal.Values.Room as Room exposing (Room) import Internal.Values.User as User exposing (User) +import Recursion +import Recursion.Fold {-| This is the Vault type. @@ -63,6 +71,8 @@ type VaultUpdate | SetUser User +{-| Convert a Vault to and from a JSON object. +-} coder : Json.Coder Vault coder = Json.object4 @@ -133,6 +143,13 @@ mapRoom roomId f vault = { 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. -} setAccountData : String -> Json.Value -> Vault -> Vault @@ -150,30 +167,41 @@ updateRoom roomId f vault = {-| Update the Vault using a VaultUpdate type. -} update : VaultUpdate -> Vault -> Vault -update vu vault = - case vu of - CreateRoomIfNotExists roomId -> - updateRoom roomId - (Maybe.withDefault (Room.init roomId) >> Maybe.Just) - vault +update vaultUpdate startVault = + Recursion.runRecursion + (\vu -> + case vu of + CreateRoomIfNotExists roomId -> + (Maybe.withDefault (Room.init roomId) >> Maybe.Just) + |> updateRoom roomId + |> Recursion.base - MapRoom roomId ru -> - mapRoom roomId (Room.update ru) vault + MapRoom roomId ru -> + Recursion.base (mapRoom roomId (Room.update ru)) - More items -> - List.foldl update vault items + More items -> + Recursion.Fold.foldList (<<) identity items - Optional (Just u) -> - update u vault + Optional (Just u) -> + Recursion.recurse u - Optional Nothing -> - vault + Optional Nothing -> + Recursion.base identity - SetAccountData key value -> - setAccountData key value vault + SetAccountData key value -> + Recursion.base (setAccountData key value) - SetNextBatch nb -> - { vault | nextBatch = Just nb } + SetNextBatch nb -> + Recursion.base + (\vault -> + { vault | nextBatch = Just nb } + ) - SetUser user -> - { vault | user = Just user } + SetUser user -> + Recursion.base + (\vault -> + { vault | user = Just user } + ) + ) + vaultUpdate + startVault diff --git a/src/Matrix.elm b/src/Matrix.elm index 09c32f4..6c66953 100644 --- a/src/Matrix.elm +++ b/src/Matrix.elm @@ -1,6 +1,7 @@ module Matrix exposing ( Vault, fromUserId, fromUsername , VaultUpdate, update, sync, logs + , rooms, fromRoomId , addAccessToken, sendMessageEvent ) @@ -27,6 +28,11 @@ support a monolithic public registry. (: @docs VaultUpdate, update, sync, logs +## Exploring the Vault + +@docs rooms, fromRoomId + + ## Debugging @docs addAccessToken, sendMessageEvent @@ -66,6 +72,14 @@ addAccessToken token (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. case Matrix.fromUserId "@alice:example.org" of @@ -112,6 +126,14 @@ fromUsername { username, host, port_ } = |> 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! diff --git a/src/Matrix/Room.elm b/src/Matrix/Room.elm index 9ebc08e..a3e24ae 100644 --- a/src/Matrix/Room.elm +++ b/src/Matrix/Room.elm @@ -1,5 +1,5 @@ module Matrix.Room exposing - ( Room, mostRecentEvents + ( Room, mostRecentEvents, roomId , 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 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 a room. @@ -56,6 +56,14 @@ getAccountData key (Room 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. -} mostRecentEvents : Room -> List Types.Event diff --git a/tests/Test/Values/Context.elm b/tests/Test/Values/Context.elm index 2994de8..888287d 100644 --- a/tests/Test/Values/Context.elm +++ b/tests/Test/Values/Context.elm @@ -5,8 +5,6 @@ import Fuzz exposing (Fuzzer) import Internal.Config.Leaks as Leaks import Internal.Tools.Hashdict as Hashdict import Internal.Values.Context as Context exposing (Context, Versions) -import Json.Decode as D -import Json.Encode as E import Set import Test exposing (..) import Test.Tools.Timestamp as TestTimestamp @@ -19,12 +17,15 @@ fuzzer = maybeString = Fuzz.maybe Fuzz.string 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) maybeString maybeString - (Fuzz.maybe TestTimestamp.fuzzer) maybeString + (Fuzz.pair + (Fuzz.maybe TestTimestamp.fuzzer) + maybeString + ) (Fuzz.pair maybeString Fuzz.string diff --git a/tests/Test/Values/Envelope.elm b/tests/Test/Values/Envelope.elm index f86bf7f..81bb569 100644 --- a/tests/Test/Values/Envelope.elm +++ b/tests/Test/Values/Envelope.elm @@ -3,10 +3,7 @@ module Test.Values.Envelope exposing (..) import Expect import Fuzz exposing (Fuzzer) import Internal.Config.Default as Default -import Internal.Tools.Json as Json import Internal.Values.Envelope as Envelope exposing (Envelope) -import Json.Decode as D -import Json.Encode as E import Test exposing (..) import Test.Values.Context as TestContext import Test.Values.Settings as TestSettings diff --git a/tests/Test/Values/Event.elm b/tests/Test/Values/Event.elm index 35ba18e..3731cb1 100644 --- a/tests/Test/Values/Event.elm +++ b/tests/Test/Values/Event.elm @@ -41,16 +41,18 @@ fuzzerState = unsignedDataFuzzer : Fuzzer Event.UnsignedData unsignedDataFuzzer = - Fuzz.map4 - (\age prev redact trans -> + Fuzz.map5 + (\age memb prev redact trans -> Event.UnsignedData { age = age + , membership = memb , prevContent = prev , redactedBecause = redact , transactionId = trans } ) (Fuzz.maybe Fuzz.int) + (Fuzz.maybe Fuzz.string) (Fuzz.maybe valueFuzzer) (Fuzz.maybe <| Fuzz.lazy (\_ -> fuzzer)) (Fuzz.maybe Fuzz.string) diff --git a/tests/Test/Values/Room.elm b/tests/Test/Values/Room.elm index d2aed8d..d3fec18 100644 --- a/tests/Test/Values/Room.elm +++ b/tests/Test/Values/Room.elm @@ -4,8 +4,6 @@ import Fuzz exposing (Fuzzer) import Internal.Values.Room as Room exposing (Room) import Json.Encode as E import Test exposing (..) -import Test.Filter.Timeline as TestFilter -import Test.Values.Event as TestEvent placeholderValue : E.Value diff --git a/tests/Test/Values/Settings.elm b/tests/Test/Values/Settings.elm index 55aff8b..d011d92 100644 --- a/tests/Test/Values/Settings.elm +++ b/tests/Test/Values/Settings.elm @@ -11,7 +11,7 @@ import Test exposing (..) fuzzer : Fuzzer Settings fuzzer = - Fuzz.map4 Settings + Fuzz.map5 Settings (Fuzz.oneOf [ Fuzz.constant Default.currentVersion , Fuzz.string @@ -22,6 +22,7 @@ fuzzer = , Fuzz.string ] ) + (Fuzz.maybe Fuzz.string) (Fuzz.oneOf [ Fuzz.constant Default.removePasswordOnLogin , Fuzz.bool diff --git a/tests/Test/Values/Timeline.elm b/tests/Test/Values/Timeline.elm index 83a15dd..2097ea0 100644 --- a/tests/Test/Values/Timeline.elm +++ b/tests/Test/Values/Timeline.elm @@ -6,6 +6,7 @@ import Internal.Filter.Timeline as Filter import Internal.Tools.Json as Json import Internal.Values.Timeline as Timeline exposing (Batch, Timeline) import Json.Decode as D +import Json.Encode as E import Test exposing (..) import Test.Filter.Timeline as TestFilter @@ -250,7 +251,8 @@ suite = (\timeline -> timeline |> 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 (Timeline.mostRecentEvents Filter.pass) |> Expect.equal (Ok <| Timeline.mostRecentEvents Filter.pass timeline)