Add toUpdate function for sync V1 - V3

pull/31/head
Bram 2024-07-09 13:29:45 +02:00
parent b239eecc6b
commit c5d07f0a94
4 changed files with 449 additions and 33 deletions

View File

@ -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,55 @@ 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
, prevContent = Nothing
, redactedBecause = Nothing
, transactionId = u.transactionId
}

View File

@ -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,191 @@ 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
, prevContent = Nothing
, redactedBecause = Just e
, transactionId = Nothing
}
|> Event.UnsignedData
|> Just
( _, Just (UnsignedData u) ) ->
{ age = u.age
, prevContent = u.prevContent
, redactedBecause = ev
, transactionId = u.transactionId
}
|> Event.UnsignedData
|> Just

View File

@ -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

View File

@ -638,6 +638,7 @@ logs :
, sendEvent : Maybe String -> String
, serverReturnedInvalidJSON : String -> String
, serverReturnedUnknownJSON : String -> String
, syncAccountDataFound : Int -> String
}
logs =
{ baseUrlFound =
@ -675,6 +676,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" ]
}