From 52c8a2e1de39bd19d809cf5f3697a4090357b652 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 9 Mar 2025 14:29:54 +0200 Subject: [PATCH] sync: add support for MSC4222 --- client.go | 4 ++++ event/events.go | 3 +++ responses.go | 1 + sync.go | 32 +++++++++++++++++++------------- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/client.go b/client.go index 5a54110c..dfbd190b 100644 --- a/client.go +++ b/client.go @@ -696,6 +696,7 @@ type ReqSync struct { FullState bool SetPresence event.Presence StreamResponse bool + UseStateAfter bool BeeperStreaming bool Client *http.Client } @@ -716,6 +717,9 @@ func (req *ReqSync) BuildQuery() map[string]string { if req.FullState { query["full_state"] = "true" } + if req.UseStateAfter { + query["org.matrix.msc4222.use_state_after"] = "true" + } if req.BeeperStreaming { query["com.beeper.streaming"] = "true" } diff --git a/event/events.go b/event/events.go index 1c173351..92cc39ae 100644 --- a/event/events.go +++ b/event/events.go @@ -118,6 +118,9 @@ type MautrixInfo struct { DecryptionDuration time.Duration CheckpointSent bool + // When using MSC4222 and the state_after field, this field is set + // for timeline events to indicate they shouldn't update room state. + IgnoreState bool } func (evt *Event) GetStateKey() string { diff --git a/responses.go b/responses.go index 158d2444..3123a530 100644 --- a/responses.go +++ b/responses.go @@ -393,6 +393,7 @@ type BeeperInboxPreviewEvent struct { type SyncJoinedRoom struct { Summary LazyLoadSummary `json:"summary"` State SyncEventsList `json:"state"` + StateAfter *SyncEventsList `json:"org.matrix.msc4222.state_after,omitempty"` Timeline SyncTimeline `json:"timeline"` Ephemeral SyncEventsList `json:"ephemeral"` AccountData SyncEventsList `json:"account_data"` diff --git a/sync.go b/sync.go index 48906bbc..9a2b9edf 100644 --- a/sync.go +++ b/sync.go @@ -97,33 +97,38 @@ func (s *DefaultSyncer) ProcessResponse(ctx context.Context, res *RespSync, sinc } } - s.processSyncEvents(ctx, "", res.ToDevice.Events, event.SourceToDevice) - s.processSyncEvents(ctx, "", res.Presence.Events, event.SourcePresence) - s.processSyncEvents(ctx, "", res.AccountData.Events, event.SourceAccountData) + s.processSyncEvents(ctx, "", res.ToDevice.Events, event.SourceToDevice, false) + s.processSyncEvents(ctx, "", res.Presence.Events, event.SourcePresence, false) + s.processSyncEvents(ctx, "", res.AccountData.Events, event.SourceAccountData, false) for roomID, roomData := range res.Rooms.Join { - s.processSyncEvents(ctx, roomID, roomData.State.Events, event.SourceJoin|event.SourceState) - s.processSyncEvents(ctx, roomID, roomData.Timeline.Events, event.SourceJoin|event.SourceTimeline) - s.processSyncEvents(ctx, roomID, roomData.Ephemeral.Events, event.SourceJoin|event.SourceEphemeral) - s.processSyncEvents(ctx, roomID, roomData.AccountData.Events, event.SourceJoin|event.SourceAccountData) + if roomData.StateAfter == nil { + s.processSyncEvents(ctx, roomID, roomData.State.Events, event.SourceJoin|event.SourceState, false) + s.processSyncEvents(ctx, roomID, roomData.Timeline.Events, event.SourceJoin|event.SourceTimeline, false) + } else { + s.processSyncEvents(ctx, roomID, roomData.Timeline.Events, event.SourceJoin|event.SourceTimeline, true) + s.processSyncEvents(ctx, roomID, roomData.StateAfter.Events, event.SourceJoin|event.SourceState, false) + } + s.processSyncEvents(ctx, roomID, roomData.Ephemeral.Events, event.SourceJoin|event.SourceEphemeral, false) + s.processSyncEvents(ctx, roomID, roomData.AccountData.Events, event.SourceJoin|event.SourceAccountData, false) } for roomID, roomData := range res.Rooms.Invite { - s.processSyncEvents(ctx, roomID, roomData.State.Events, event.SourceInvite|event.SourceState) + s.processSyncEvents(ctx, roomID, roomData.State.Events, event.SourceInvite|event.SourceState, false) } for roomID, roomData := range res.Rooms.Leave { - s.processSyncEvents(ctx, roomID, roomData.State.Events, event.SourceLeave|event.SourceState) - s.processSyncEvents(ctx, roomID, roomData.Timeline.Events, event.SourceLeave|event.SourceTimeline) + s.processSyncEvents(ctx, roomID, roomData.State.Events, event.SourceLeave|event.SourceState, false) + s.processSyncEvents(ctx, roomID, roomData.Timeline.Events, event.SourceLeave|event.SourceTimeline, false) } return } -func (s *DefaultSyncer) processSyncEvents(ctx context.Context, roomID id.RoomID, events []*event.Event, source event.Source) { +func (s *DefaultSyncer) processSyncEvents(ctx context.Context, roomID id.RoomID, events []*event.Event, source event.Source, ignoreState bool) { for _, evt := range events { - s.processSyncEvent(ctx, roomID, evt, source) + s.processSyncEvent(ctx, roomID, evt, source, ignoreState) } } -func (s *DefaultSyncer) processSyncEvent(ctx context.Context, roomID id.RoomID, evt *event.Event, source event.Source) { +func (s *DefaultSyncer) processSyncEvent(ctx context.Context, roomID id.RoomID, evt *event.Event, source event.Source, ignoreState bool) { evt.RoomID = roomID // Ensure the type class is correct. It's safe to mutate the class since the event type is not a pointer. @@ -149,6 +154,7 @@ func (s *DefaultSyncer) processSyncEvent(ctx context.Context, roomID id.RoomID, } evt.Mautrix.EventSource = source + evt.Mautrix.IgnoreState = ignoreState s.Dispatch(ctx, evt) }