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 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.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.Tools.Timestamp as Timestamp exposing (Timestamp)
import Internal.Values.Envelope as E import Internal.Values.Envelope as E
import Internal.Values.Event as Event
import Internal.Values.Room as R import Internal.Values.Room as R
import Internal.Values.User as User exposing (User)
import Internal.Values.Vault as V import Internal.Values.Vault as V
@ -64,7 +68,7 @@ type alias InviteState =
type alias StrippedState = type alias StrippedState =
{ content : Json.Value { content : Json.Value
, sender : String , sender : User
, stateKey : String , stateKey : String
, eventType : String , eventType : String
} }
@ -93,7 +97,7 @@ type alias SyncStateEvent =
, eventId : String , eventId : String
, originServerTs : Timestamp , originServerTs : Timestamp
, prevContent : Maybe Json.Value , prevContent : Maybe Json.Value
, sender : String , sender : User
, stateKey : String , stateKey : String
, eventType : String , eventType : String
, unsigned : Maybe UnsignedData , unsigned : Maybe UnsignedData
@ -125,7 +129,7 @@ type alias SyncRoomEvent =
{ content : Json.Value { content : Json.Value
, eventId : String , eventId : String
, originServerTs : Timestamp , originServerTs : Timestamp
, sender : String , sender : User
, eventType : String , eventType : String
, unsigned : Maybe UnsignedData , unsigned : Maybe UnsignedData
} }
@ -164,7 +168,7 @@ type alias ToDevice =
type alias ToDeviceEvent = type alias ToDeviceEvent =
{ content : Maybe Json.Value { content : Maybe Json.Value
, sender : Maybe String , sender : Maybe User
, eventType : Maybe String , eventType : Maybe String
} }
@ -351,7 +355,7 @@ coderStrippedState =
{ fieldName = "sender" { fieldName = "sender"
, toField = .sender , toField = .sender
, description = [ "The sender for the event." ] , description = [ "The sender for the event." ]
, coder = Json.string , coder = User.coder
} }
) )
(Json.field.required (Json.field.required
@ -492,7 +496,7 @@ coderSyncStateEvent =
{ fieldName = "sender" { fieldName = "sender"
, toField = .sender , toField = .sender
, description = [ "Contains the fully-qualified ID of the user who sent this event." ] , description = [ "Contains the fully-qualified ID of the user who sent this event." ]
, coder = Json.string , coder = User.coder
} }
) )
(Json.field.required (Json.field.required
@ -640,7 +644,7 @@ coderSyncRoomEvent =
{ fieldName = "sender" { fieldName = "sender"
, toField = .sender , toField = .sender
, description = [ "Contains the fully-qualified ID of the user who sent this event." ] , description = [ "Contains the fully-qualified ID of the user who sent this event." ]
, coder = Json.string , coder = User.coder
} }
) )
(Json.field.required (Json.field.required
@ -801,7 +805,7 @@ coderToDeviceEvent =
{ fieldName = "sender" { fieldName = "sender"
, toField = .sender , toField = .sender
, description = [ "The Matrix user ID of the user who sent this event." ] , description = [ "The Matrix user ID of the user who sent this event." ]
, coder = Json.string , coder = User.coder
} }
) )
(Json.field.optional.value (Json.field.optional.value
@ -813,16 +817,37 @@ coderToDeviceEvent =
) )
updateSyncResponse : SyncResponse -> ( E.EnvelopeUpdate V.VaultUpdate, List Log ) updateSyncResponse : { filter : Filter, since : Maybe String } -> SyncResponse -> ( E.EnvelopeUpdate V.VaultUpdate, List Log )
updateSyncResponse response = updateSyncResponse { filter, since } response =
-- TODO: Add account data -- 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 -- TODO: Add device lists
-- Next batch -- Next batch
[ Just ( E.SetNextBatch response.nextBatch, [] ) , Just ( E.SetNextBatch response.nextBatch, [] )
-- TODO: Add presence -- TODO: Add presence
-- Rooms -- 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 -- TODO: Add to_device
] ]
@ -832,8 +857,8 @@ updateSyncResponse response =
|> Tuple.mapSecond List.concat |> Tuple.mapSecond List.concat
updateRooms : Rooms -> ( V.VaultUpdate, List Log ) updateRooms : { filter : Filter, nextBatch : String, since : Maybe String } -> Rooms -> ( V.VaultUpdate, List Log )
updateRooms rooms = updateRooms { filter, nextBatch, since } rooms =
let let
( roomUpdate, roomLogs ) = ( roomUpdate, roomLogs ) =
rooms.join rooms.join
@ -843,7 +868,13 @@ updateRooms rooms =
(\( roomId, room ) -> (\( roomId, room ) ->
let let
( u, l ) = ( u, l ) =
updateJoinedRoom room updateJoinedRoom
{ filter = filter
, nextBatch = nextBatch
, roomId = roomId
, since = since
}
room
in in
( V.MapRoom roomId u, l ) ( V.MapRoom roomId u, l )
) )
@ -869,8 +900,8 @@ updateRooms rooms =
) )
updateJoinedRoom : JoinedRoom -> ( R.RoomUpdate, List Log ) updateJoinedRoom : { filter : Filter, nextBatch : String, roomId : String, since : Maybe String } -> JoinedRoom -> ( R.RoomUpdate, List Log )
updateJoinedRoom room = updateJoinedRoom data room =
( R.More ( R.More
[ room.accountData [ room.accountData
|> Maybe.andThen .events |> Maybe.andThen .events
@ -888,8 +919,55 @@ updateJoinedRoom room =
-- TODO: Add state -- TODO: Add state
-- TODO: Add RoomSummary -- TODO: Add RoomSummary
-- TODO: Add timeline , room.timeline
|> Maybe.andThen
(updateTimeline data)
|> R.Optional
-- TODO: Add unread notifications -- 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.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.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 = type alias SyncResponse =
@ -62,7 +72,7 @@ type alias InviteState =
type alias StrippedStateEvent = type alias StrippedStateEvent =
{ content : Json.Value { content : Json.Value
, sender : String , sender : User
, stateKey : String , stateKey : String
, eventType : String , eventType : String
} }
@ -89,8 +99,8 @@ type alias State =
type alias ClientEventWithoutRoomID = type alias ClientEventWithoutRoomID =
{ content : Json.Value { content : Json.Value
, eventId : String , eventId : String
, originServerTs : Int , originServerTs : Timestamp
, sender : String , sender : User
, stateKey : Maybe String , stateKey : Maybe String
, eventType : String , eventType : String
, unsigned : Maybe UnsignedData , unsigned : Maybe UnsignedData
@ -153,7 +163,7 @@ type alias ToDevice =
type alias ToDeviceEvent = type alias ToDeviceEvent =
{ content : Maybe Json.Value { content : Maybe Json.Value
, sender : Maybe String , sender : Maybe User
, eventType : Maybe String , eventType : Maybe String
} }
@ -387,14 +397,14 @@ coderClientEventWithoutRoomID =
{ fieldName = "origin_server_ts" { fieldName = "origin_server_ts"
, toField = .originServerTs , toField = .originServerTs
, description = [ "Required: Timestamp (in milliseconds since the unix epoch) on originating homeserver when this event was sent." ] , 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 (Json.field.required
{ fieldName = "sender" { fieldName = "sender"
, toField = .sender , toField = .sender
, description = [ "Required: Contains the fully-qualified ID of the user who sent this event." ] , description = [ "Required: Contains the fully-qualified ID of the user who sent this event." ]
, coder = Json.string , coder = User.coder
} }
) )
(Json.field.optional.value (Json.field.optional.value
@ -557,3 +567,191 @@ coderToDevice =
coderToDeviceEvent : Json.Coder ToDeviceEvent coderToDeviceEvent : Json.Coder ToDeviceEvent
coderToDeviceEvent = coderToDeviceEvent =
PV.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.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.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 = type alias SyncResponse =
@ -67,7 +76,7 @@ type alias InviteState =
type alias StrippedStateEvent = type alias StrippedStateEvent =
{ content : Json.Value { content : Json.Value
, sender : String , sender : User
, stateKey : String , stateKey : String
, eventType : String , eventType : String
} }
@ -95,8 +104,8 @@ type alias State =
type alias ClientEventWithoutRoomID = type alias ClientEventWithoutRoomID =
{ content : Json.Value { content : Json.Value
, eventId : String , eventId : String
, originServerTs : Int , originServerTs : Timestamp
, sender : String , sender : User
, stateKey : Maybe String , stateKey : Maybe String
, eventType : String , eventType : String
, unsigned : Maybe UnsignedData , unsigned : Maybe UnsignedData
@ -160,7 +169,7 @@ type alias ToDevice =
type alias ToDeviceEvent = type alias ToDeviceEvent =
{ content : Maybe Json.Value { content : Maybe Json.Value
, sender : Maybe String , sender : Maybe User
, eventType : Maybe String , eventType : Maybe String
} }
@ -441,3 +450,131 @@ coderToDevice =
coderToDeviceEvent : Json.Coder ToDeviceEvent coderToDeviceEvent : Json.Coder ToDeviceEvent
coderToDeviceEvent = coderToDeviceEvent =
PV.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 , sendEvent : Maybe String -> String
, serverReturnedInvalidJSON : String -> String , serverReturnedInvalidJSON : String -> String
, serverReturnedUnknownJSON : String -> String , serverReturnedUnknownJSON : String -> String
, syncAccountDataFound : Int -> String
} }
logs = logs =
{ baseUrlFound = { baseUrlFound =
@ -675,6 +676,8 @@ logs =
"Sent event, event id not known - make sure to check transaction id" "Sent event, event id not known - make sure to check transaction id"
, serverReturnedInvalidJSON = (++) "The server returned invalid JSON: " , serverReturnedInvalidJSON = (++) "The server returned invalid JSON: "
, serverReturnedUnknownJSON = (++) "The server returned JSON that doesn't seem to live up to spec rules: " , serverReturnedUnknownJSON = (++) "The server returned JSON that doesn't seem to live up to spec rules: "
, syncAccountDataFound =
\n -> String.concat [ "Found ", String.fromInt n, " account data updates" ]
} }