You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-06 12:02:40 +03:00
Support MSC4222 state_after
(#4487)
* WIP support for state_after
* Fix sliding sync sdk / embedded tests
* Allow both state & state_after to be undefined
Since it must have allowed state to be undefined previously: the test
had it as such.
* Fix limited sync handling
* Need to use state_after being undefined
if state can be undefined anyway
* Make sliding sync sdk tests pass
* Remove deprecated interfaces & backwards-compat code
* Remove useless assignment
* Use updates unstable prefix
* Clarify docs
* Remove additional semi-backwards compatible overload
* Update unstable prefixes
* Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Fix test
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Add test for MSC4222 behaviour
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Improve coverage
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Fix tests
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Tidy
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Add comments to explain why things work as they are.
* Fix sync accumulator for state_after sync handling
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Add tests
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Revert "Fix room state being updated with old (now overwritten) state and emitting for those updates. (#4242)"
This reverts commit 957329b218
.
* Fix Sync Accumulator toJSON putting start timeline state in state_after field
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Update tests
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Add test case
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
* Iterate
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---------
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: Hugh Nimmo-Smith <hughns@matrix.org>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: Timo <toger5@hotmail.de>
This commit is contained in:
@@ -6136,7 +6136,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
room.partitionThreadedEvents(matrixEvents);
|
||||
|
||||
this.processAggregatedTimelineEvents(room, timelineEvents);
|
||||
room.addEventsToTimeline(timelineEvents, true, room.getLiveTimeline());
|
||||
room.addEventsToTimeline(timelineEvents, true, true, room.getLiveTimeline());
|
||||
this.processThreadEvents(room, threadedEvents, true);
|
||||
unknownRelations.forEach((event) => room.relations.aggregateChildEvent(event));
|
||||
|
||||
@@ -6248,7 +6248,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
|
||||
const [timelineEvents, threadedEvents, unknownRelations] = timelineSet.room.partitionThreadedEvents(events);
|
||||
timelineSet.addEventsToTimeline(timelineEvents, true, timeline, res.start);
|
||||
timelineSet.addEventsToTimeline(timelineEvents, true, false, timeline, res.start);
|
||||
// The target event is not in a thread but process the contextual events, so we can show any threads around it.
|
||||
this.processThreadEvents(timelineSet.room, threadedEvents, true);
|
||||
this.processAggregatedTimelineEvents(timelineSet.room, timelineEvents);
|
||||
@@ -6342,10 +6342,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
timeline.initialiseState(res.state.map(mapper));
|
||||
}
|
||||
|
||||
timelineSet.addEventsToTimeline(events, true, timeline, resNewer.next_batch);
|
||||
timelineSet.addEventsToTimeline(events, true, false, timeline, resNewer.next_batch);
|
||||
if (!resOlder.next_batch) {
|
||||
const originalEvent = await this.fetchRoomEvent(timelineSet.room.roomId, thread.id);
|
||||
timelineSet.addEventsToTimeline([mapper(originalEvent)], true, timeline, null);
|
||||
timelineSet.addEventsToTimeline([mapper(originalEvent)], true, false, timeline, null);
|
||||
}
|
||||
timeline.setPaginationToken(resOlder.next_batch ?? null, Direction.Backward);
|
||||
timeline.setPaginationToken(resNewer.next_batch ?? null, Direction.Forward);
|
||||
@@ -6399,10 +6399,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
const timeline = timelineSet.getLiveTimeline();
|
||||
timeline.getState(EventTimeline.BACKWARDS)!.setUnknownStateEvents(res.state.map(mapper));
|
||||
|
||||
timelineSet.addEventsToTimeline(events, true, timeline, null);
|
||||
timelineSet.addEventsToTimeline(events, true, false, timeline, null);
|
||||
if (!resOlder.next_batch) {
|
||||
const originalEvent = await this.fetchRoomEvent(timelineSet.room.roomId, thread.id);
|
||||
timelineSet.addEventsToTimeline([mapper(originalEvent)], true, timeline, null);
|
||||
timelineSet.addEventsToTimeline([mapper(originalEvent)], true, false, timeline, null);
|
||||
}
|
||||
timeline.setPaginationToken(resOlder.next_batch ?? null, Direction.Backward);
|
||||
timeline.setPaginationToken(null, Direction.Forward);
|
||||
@@ -6665,7 +6665,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
// No need to partition events for threads here, everything lives
|
||||
// in the notification timeline set
|
||||
const timelineSet = eventTimeline.getTimelineSet();
|
||||
timelineSet.addEventsToTimeline(matrixEvents, backwards, eventTimeline, token);
|
||||
timelineSet.addEventsToTimeline(matrixEvents, backwards, false, eventTimeline, token);
|
||||
this.processAggregatedTimelineEvents(timelineSet.room, matrixEvents);
|
||||
|
||||
// if we've hit the end of the timeline, we need to stop trying to
|
||||
@@ -6708,7 +6708,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
const matrixEvents = res.chunk.filter(noUnsafeEventProps).map(this.getEventMapper());
|
||||
|
||||
const timelineSet = eventTimeline.getTimelineSet();
|
||||
timelineSet.addEventsToTimeline(matrixEvents, backwards, eventTimeline, token);
|
||||
timelineSet.addEventsToTimeline(matrixEvents, backwards, false, eventTimeline, token);
|
||||
this.processAggregatedTimelineEvents(room, matrixEvents);
|
||||
this.processThreadRoots(room, matrixEvents, backwards);
|
||||
|
||||
@@ -6756,12 +6756,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
const newToken = res.next_batch;
|
||||
|
||||
const timelineSet = eventTimeline.getTimelineSet();
|
||||
timelineSet.addEventsToTimeline(matrixEvents, backwards, eventTimeline, newToken ?? null);
|
||||
timelineSet.addEventsToTimeline(matrixEvents, backwards, false, eventTimeline, newToken ?? null);
|
||||
if (!newToken && backwards) {
|
||||
const originalEvent =
|
||||
thread.rootEvent ??
|
||||
mapper(await this.fetchRoomEvent(eventTimeline.getRoomId() ?? "", thread.id));
|
||||
timelineSet.addEventsToTimeline([originalEvent], true, eventTimeline, null);
|
||||
timelineSet.addEventsToTimeline([originalEvent], true, false, eventTimeline, null);
|
||||
}
|
||||
this.processAggregatedTimelineEvents(timelineSet.room, matrixEvents);
|
||||
|
||||
@@ -6800,7 +6800,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
|
||||
const timelineSet = eventTimeline.getTimelineSet();
|
||||
const [timelineEvents, , unknownRelations] = room.partitionThreadedEvents(matrixEvents);
|
||||
timelineSet.addEventsToTimeline(timelineEvents, backwards, eventTimeline, token);
|
||||
timelineSet.addEventsToTimeline(timelineEvents, backwards, false, eventTimeline, token);
|
||||
this.processAggregatedTimelineEvents(room, timelineEvents);
|
||||
this.processThreadRoots(
|
||||
room,
|
||||
|
@@ -284,7 +284,13 @@ export class RoomWidgetClient extends MatrixClient {
|
||||
const rawEvents = await this.widgetApi.readStateEvents(eventType, undefined, stateKey, [this.roomId]);
|
||||
const events = rawEvents.map((rawEvent) => new MatrixEvent(rawEvent as Partial<IEvent>));
|
||||
|
||||
await this.syncApi!.injectRoomEvents(this.room!, [], events);
|
||||
if (this.syncApi instanceof SyncApi) {
|
||||
// Passing undefined for `stateAfterEventList` allows will make `injectRoomEvents` run in legacy mode
|
||||
// -> state events in `timelineEventList` will update the state.
|
||||
await this.syncApi.injectRoomEvents(this.room!, undefined, events);
|
||||
} else {
|
||||
await this.syncApi!.injectRoomEvents(this.room!, events); // Sliding Sync
|
||||
}
|
||||
events.forEach((event) => {
|
||||
this.emit(ClientEvent.Event, event);
|
||||
logger.info(`Backfilled event ${event.getId()} ${event.getType()} ${event.getStateKey()}`);
|
||||
@@ -567,7 +573,34 @@ export class RoomWidgetClient extends MatrixClient {
|
||||
|
||||
// Only inject once we have update the txId
|
||||
await this.updateTxId(event);
|
||||
await this.syncApi!.injectRoomEvents(this.room!, [], [event]);
|
||||
|
||||
// The widget API does not tell us whether a state event came from `state_after` or not so we assume legacy behaviour for now.
|
||||
if (this.syncApi instanceof SyncApi) {
|
||||
// The code will want to be something like:
|
||||
// ```
|
||||
// if (!params.addToTimeline && !params.addToState) {
|
||||
// // Passing undefined for `stateAfterEventList` makes `injectRoomEvents` run in "legacy mode"
|
||||
// // -> state events part of the `timelineEventList` parameter will update the state.
|
||||
// this.injectRoomEvents(this.room!, [], undefined, [event]);
|
||||
// } else {
|
||||
// this.injectRoomEvents(this.room!, undefined, params.addToState ? [event] : [], params.addToTimeline ? [event] : []);
|
||||
// }
|
||||
// ```
|
||||
|
||||
// Passing undefined for `stateAfterEventList` allows will make `injectRoomEvents` run in legacy mode
|
||||
// -> state events in `timelineEventList` will update the state.
|
||||
await this.syncApi.injectRoomEvents(this.room!, [], undefined, [event]);
|
||||
} else {
|
||||
// The code will want to be something like:
|
||||
// ```
|
||||
// if (!params.addToTimeline && !params.addToState) {
|
||||
// this.injectRoomEvents(this.room!, [], [event]);
|
||||
// } else {
|
||||
// this.injectRoomEvents(this.room!, params.addToState ? [event] : [], params.addToTimeline ? [event] : []);
|
||||
// }
|
||||
// ```
|
||||
await this.syncApi!.injectRoomEvents(this.room!, [], [event]); // Sliding Sync
|
||||
}
|
||||
this.emit(ClientEvent.Event, event);
|
||||
this.setSyncState(SyncState.Syncing);
|
||||
logger.info(`Received event ${event.getId()} ${event.getType()} ${event.getStateKey()}`);
|
||||
|
@@ -58,13 +58,13 @@ export interface IRoomTimelineData {
|
||||
}
|
||||
|
||||
export interface IAddEventToTimelineOptions
|
||||
extends Pick<IAddEventOptions, "toStartOfTimeline" | "roomState" | "timelineWasEmpty"> {
|
||||
extends Pick<IAddEventOptions, "toStartOfTimeline" | "roomState" | "timelineWasEmpty" | "addToState"> {
|
||||
/** Whether the sync response came from cache */
|
||||
fromCache?: boolean;
|
||||
}
|
||||
|
||||
export interface IAddLiveEventOptions
|
||||
extends Pick<IAddEventToTimelineOptions, "fromCache" | "roomState" | "timelineWasEmpty"> {
|
||||
extends Pick<IAddEventToTimelineOptions, "fromCache" | "roomState" | "timelineWasEmpty" | "addToState"> {
|
||||
/** Applies to events in the timeline only. If this is 'replace' then if a
|
||||
* duplicate is encountered, the event passed to this function will replace
|
||||
* the existing event in the timeline. If this is not specified, or is
|
||||
@@ -391,6 +391,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
||||
public addEventsToTimeline(
|
||||
events: MatrixEvent[],
|
||||
toStartOfTimeline: boolean,
|
||||
addToState: boolean,
|
||||
timeline: EventTimeline,
|
||||
paginationToken?: string | null,
|
||||
): void {
|
||||
@@ -495,6 +496,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
||||
// we don't know about this event yet. Just add it to the timeline.
|
||||
this.addEventToTimeline(event, timeline, {
|
||||
toStartOfTimeline,
|
||||
addToState,
|
||||
});
|
||||
lastEventWasNew = true;
|
||||
didUpdate = true;
|
||||
@@ -592,7 +594,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
||||
*/
|
||||
public addLiveEvent(
|
||||
event: MatrixEvent,
|
||||
{ duplicateStrategy, fromCache, roomState, timelineWasEmpty }: IAddLiveEventOptions = {},
|
||||
{ duplicateStrategy, fromCache, roomState, timelineWasEmpty, addToState }: IAddLiveEventOptions,
|
||||
): void {
|
||||
if (this.filter) {
|
||||
const events = this.filter.filterRoomTimeline([event]);
|
||||
@@ -630,6 +632,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
||||
fromCache,
|
||||
roomState,
|
||||
timelineWasEmpty,
|
||||
addToState,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -649,40 +652,8 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
||||
public addEventToTimeline(
|
||||
event: MatrixEvent,
|
||||
timeline: EventTimeline,
|
||||
{ toStartOfTimeline, fromCache, roomState, timelineWasEmpty }: IAddEventToTimelineOptions,
|
||||
): void;
|
||||
/**
|
||||
* @deprecated In favor of the overload with `IAddEventToTimelineOptions`
|
||||
*/
|
||||
public addEventToTimeline(
|
||||
event: MatrixEvent,
|
||||
timeline: EventTimeline,
|
||||
toStartOfTimeline: boolean,
|
||||
fromCache?: boolean,
|
||||
roomState?: RoomState,
|
||||
): void;
|
||||
public addEventToTimeline(
|
||||
event: MatrixEvent,
|
||||
timeline: EventTimeline,
|
||||
toStartOfTimelineOrOpts: boolean | IAddEventToTimelineOptions,
|
||||
fromCache = false,
|
||||
roomState?: RoomState,
|
||||
{ toStartOfTimeline, fromCache = false, roomState, timelineWasEmpty, addToState }: IAddEventToTimelineOptions,
|
||||
): void {
|
||||
let toStartOfTimeline = !!toStartOfTimelineOrOpts;
|
||||
let timelineWasEmpty: boolean | undefined;
|
||||
if (typeof toStartOfTimelineOrOpts === "object") {
|
||||
({ toStartOfTimeline, fromCache = false, roomState, timelineWasEmpty } = toStartOfTimelineOrOpts);
|
||||
} else if (toStartOfTimelineOrOpts !== undefined) {
|
||||
// Deprecation warning
|
||||
// FIXME: Remove after 2023-06-01 (technical debt)
|
||||
logger.warn(
|
||||
"Overload deprecated: " +
|
||||
"`EventTimelineSet.addEventToTimeline(event, timeline, toStartOfTimeline, fromCache?, roomState?)` " +
|
||||
"is deprecated in favor of the overload with " +
|
||||
"`EventTimelineSet.addEventToTimeline(event, timeline, IAddEventToTimelineOptions)`",
|
||||
);
|
||||
}
|
||||
|
||||
if (timeline.getTimelineSet() !== this) {
|
||||
throw new Error(`EventTimelineSet.addEventToTimeline: Timeline=${timeline.toString()} does not belong " +
|
||||
"in timelineSet(threadId=${this.thread?.id})`);
|
||||
@@ -713,6 +684,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
||||
toStartOfTimeline,
|
||||
roomState,
|
||||
timelineWasEmpty,
|
||||
addToState,
|
||||
});
|
||||
this._eventIdToTimeline.set(eventId, timeline);
|
||||
|
||||
@@ -741,7 +713,12 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
||||
* @remarks
|
||||
* Fires {@link RoomEvent.Timeline}
|
||||
*/
|
||||
public insertEventIntoTimeline(event: MatrixEvent, timeline: EventTimeline, roomState: RoomState): void {
|
||||
public insertEventIntoTimeline(
|
||||
event: MatrixEvent,
|
||||
timeline: EventTimeline,
|
||||
roomState: RoomState,
|
||||
addToState: boolean,
|
||||
): void {
|
||||
if (timeline.getTimelineSet() !== this) {
|
||||
throw new Error(`EventTimelineSet.insertEventIntoTimeline: Timeline=${timeline.toString()} does not belong " +
|
||||
"in timelineSet(threadId=${this.thread?.id})`);
|
||||
@@ -777,6 +754,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
||||
fromCache: false,
|
||||
timelineWasEmpty: false,
|
||||
roomState,
|
||||
addToState,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -799,7 +777,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
||||
// If we got to the end of the loop, insertIndex points at the end of
|
||||
// the list.
|
||||
|
||||
timeline.insertEvent(event, insertIndex, roomState);
|
||||
timeline.insertEvent(event, insertIndex, roomState, addToState);
|
||||
this._eventIdToTimeline.set(eventId, timeline);
|
||||
|
||||
const data: IRoomTimelineData = {
|
||||
@@ -832,6 +810,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
||||
} else if (!this.filter || this.filter.filterRoomTimeline([localEvent]).length) {
|
||||
this.addEventToTimeline(localEvent, this.liveTimeline, {
|
||||
toStartOfTimeline: false,
|
||||
addToState: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -35,6 +35,11 @@ export interface IAddEventOptions extends Pick<IMarkerFoundOptions, "timelineWas
|
||||
toStartOfTimeline: boolean;
|
||||
/** The state events to reconcile metadata from */
|
||||
roomState?: RoomState;
|
||||
/** Whether to add timeline events to the state as was done in legacy sync v2.
|
||||
* If true then timeline events will be added to the state.
|
||||
* In sync v2 with org.matrix.msc4222.use_state_after and simplified sliding sync,
|
||||
* all state arrives explicitly and timeline events should not be added. */
|
||||
addToState: boolean;
|
||||
}
|
||||
|
||||
export enum Direction {
|
||||
@@ -362,7 +367,7 @@ export class EventTimeline {
|
||||
*/
|
||||
public addEvent(
|
||||
event: MatrixEvent,
|
||||
{ toStartOfTimeline, roomState, timelineWasEmpty }: IAddEventOptions = { toStartOfTimeline: false },
|
||||
{ toStartOfTimeline, roomState, timelineWasEmpty, addToState }: IAddEventOptions,
|
||||
): void {
|
||||
if (!roomState) {
|
||||
roomState = toStartOfTimeline ? this.startState : this.endState;
|
||||
@@ -374,7 +379,7 @@ export class EventTimeline {
|
||||
EventTimeline.setEventMetadata(event, roomState!, toStartOfTimeline);
|
||||
|
||||
// modify state but only on unfiltered timelineSets
|
||||
if (event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet) {
|
||||
if (addToState && event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet) {
|
||||
roomState?.setStateEvents([event], { timelineWasEmpty });
|
||||
// it is possible that the act of setting the state event means we
|
||||
// can set more metadata (specifically sender/target props), so try
|
||||
@@ -417,14 +422,14 @@ export class EventTimeline {
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public insertEvent(event: MatrixEvent, insertIndex: number, roomState: RoomState): void {
|
||||
public insertEvent(event: MatrixEvent, insertIndex: number, roomState: RoomState, addToState: boolean): void {
|
||||
const timelineSet = this.getTimelineSet();
|
||||
|
||||
if (timelineSet.room) {
|
||||
EventTimeline.setEventMetadata(event, roomState, false);
|
||||
|
||||
// modify state but only on unfiltered timelineSets
|
||||
if (event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet) {
|
||||
if (addToState && event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet) {
|
||||
roomState.setStateEvents([event], {});
|
||||
// it is possible that the act of setting the state event means we
|
||||
// can set more metadata (specifically sender/target props), so try
|
||||
|
@@ -1250,7 +1250,9 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
||||
const timeline = room.getLiveTimeline();
|
||||
// We use insertEventIntoTimeline to insert it in timestamp order,
|
||||
// because we don't know where it should go (until we have MSC4033).
|
||||
timeline.getTimelineSet().insertEventIntoTimeline(this, timeline, timeline.getState(EventTimeline.FORWARDS)!);
|
||||
timeline
|
||||
.getTimelineSet()
|
||||
.insertEventIntoTimeline(this, timeline, timeline.getState(EventTimeline.FORWARDS)!, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1739,10 +1739,11 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
public addEventsToTimeline(
|
||||
events: MatrixEvent[],
|
||||
toStartOfTimeline: boolean,
|
||||
addToState: boolean,
|
||||
timeline: EventTimeline,
|
||||
paginationToken?: string,
|
||||
): void {
|
||||
timeline.getTimelineSet().addEventsToTimeline(events, toStartOfTimeline, timeline, paginationToken);
|
||||
timeline.getTimelineSet().addEventsToTimeline(events, toStartOfTimeline, addToState, timeline, paginationToken);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1907,7 +1908,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
// see https://github.com/vector-im/vector-web/issues/2109
|
||||
|
||||
unfilteredLiveTimeline.getEvents().forEach(function (event) {
|
||||
timelineSet.addLiveEvent(event);
|
||||
timelineSet.addLiveEvent(event, { addToState: false }); // Filtered timeline sets should not track state
|
||||
});
|
||||
|
||||
// find the earliest unfiltered timeline
|
||||
@@ -1994,6 +1995,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
if (filterType !== ThreadFilterType.My || currentUserParticipated) {
|
||||
timelineSet.getLiveTimeline().addEvent(thread.rootEvent!, {
|
||||
toStartOfTimeline: false,
|
||||
addToState: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -2068,6 +2070,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
const opts = {
|
||||
duplicateStrategy: DuplicateStrategy.Ignore,
|
||||
fromCache: false,
|
||||
addToState: false,
|
||||
roomState,
|
||||
};
|
||||
this.threadsTimelineSets[0]?.addLiveEvent(rootEvent, opts);
|
||||
@@ -2190,6 +2193,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
duplicateStrategy: DuplicateStrategy.Replace,
|
||||
fromCache: false,
|
||||
roomState,
|
||||
addToState: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2381,9 +2385,13 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
duplicateStrategy: DuplicateStrategy.Replace,
|
||||
fromCache: false,
|
||||
roomState: this.currentState,
|
||||
addToState: false,
|
||||
});
|
||||
} else {
|
||||
timelineSet.addEventToTimeline(thread.rootEvent, timelineSet.getLiveTimeline(), { toStartOfTimeline });
|
||||
timelineSet.addEventToTimeline(thread.rootEvent, timelineSet.getLiveTimeline(), {
|
||||
toStartOfTimeline,
|
||||
addToState: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -2540,7 +2548,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
* Fires {@link RoomEvent.Timeline}
|
||||
*/
|
||||
private addLiveEvent(event: MatrixEvent, addLiveEventOptions: IAddLiveEventOptions): void {
|
||||
const { duplicateStrategy, timelineWasEmpty, fromCache } = addLiveEventOptions;
|
||||
const { duplicateStrategy, timelineWasEmpty, fromCache, addToState } = addLiveEventOptions;
|
||||
|
||||
// add to our timeline sets
|
||||
for (const timelineSet of this.timelineSets) {
|
||||
@@ -2548,6 +2556,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
duplicateStrategy,
|
||||
fromCache,
|
||||
timelineWasEmpty,
|
||||
addToState,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2631,11 +2640,13 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
if (timelineSet.getFilter()!.filterRoomTimeline([event]).length) {
|
||||
timelineSet.addEventToTimeline(event, timelineSet.getLiveTimeline(), {
|
||||
toStartOfTimeline: false,
|
||||
addToState: false, // We don't support localEcho of state events yet
|
||||
});
|
||||
}
|
||||
} else {
|
||||
timelineSet.addEventToTimeline(event, timelineSet.getLiveTimeline(), {
|
||||
toStartOfTimeline: false,
|
||||
addToState: false, // We don't support localEcho of state events yet
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2886,8 +2897,8 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
* @param addLiveEventOptions - addLiveEvent options
|
||||
* @throws If `duplicateStrategy` is not falsey, 'replace' or 'ignore'.
|
||||
*/
|
||||
public async addLiveEvents(events: MatrixEvent[], addLiveEventOptions?: IAddLiveEventOptions): Promise<void> {
|
||||
const { duplicateStrategy, fromCache, timelineWasEmpty = false } = addLiveEventOptions ?? {};
|
||||
public async addLiveEvents(events: MatrixEvent[], addLiveEventOptions: IAddLiveEventOptions): Promise<void> {
|
||||
const { duplicateStrategy, fromCache, timelineWasEmpty = false, addToState } = addLiveEventOptions;
|
||||
if (duplicateStrategy && ["replace", "ignore"].indexOf(duplicateStrategy) === -1) {
|
||||
throw new Error("duplicateStrategy MUST be either 'replace' or 'ignore'");
|
||||
}
|
||||
@@ -2902,6 +2913,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
duplicateStrategy,
|
||||
fromCache,
|
||||
timelineWasEmpty,
|
||||
addToState,
|
||||
};
|
||||
|
||||
// List of extra events to check for being parents of any relations encountered
|
||||
|
@@ -208,6 +208,7 @@ export class Thread extends ReadReceipt<ThreadEmittedEvents, ThreadEventHandlerM
|
||||
|
||||
public static setServerSideSupport(status: FeatureSupport): void {
|
||||
Thread.hasServerSideSupport = status;
|
||||
// XXX: This global latching behaviour is really unexpected and means that you can't undo when moving to a server without support
|
||||
if (status !== FeatureSupport.Stable) {
|
||||
FILTER_RELATED_BY_SENDERS.setPreferUnstable(true);
|
||||
FILTER_RELATED_BY_REL_TYPES.setPreferUnstable(true);
|
||||
@@ -317,6 +318,7 @@ export class Thread extends ReadReceipt<ThreadEmittedEvents, ThreadEventHandlerM
|
||||
toStartOfTimeline,
|
||||
fromCache: false,
|
||||
roomState: this.roomState,
|
||||
addToState: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -343,7 +345,7 @@ export class Thread extends ReadReceipt<ThreadEmittedEvents, ThreadEventHandlerM
|
||||
if (this.findEventById(eventId)) {
|
||||
return;
|
||||
}
|
||||
this.timelineSet.insertEventIntoTimeline(event, this.liveTimeline, this.roomState);
|
||||
this.timelineSet.insertEventIntoTimeline(event, this.liveTimeline, this.roomState, false);
|
||||
}
|
||||
|
||||
public addEvents(events: MatrixEvent[], toStartOfTimeline: boolean): void {
|
||||
@@ -618,7 +620,7 @@ export class Thread extends ReadReceipt<ThreadEmittedEvents, ThreadEventHandlerM
|
||||
// if the thread has regular events, this will just load the last reply.
|
||||
// if the thread is newly created, this will load the root event.
|
||||
if (this.replyCount === 0 && this.rootEvent) {
|
||||
this.timelineSet.addEventsToTimeline([this.rootEvent], true, this.liveTimeline, null);
|
||||
this.timelineSet.addEventsToTimeline([this.rootEvent], true, false, this.liveTimeline, null);
|
||||
this.liveTimeline.setPaginationToken(null, Direction.Backward);
|
||||
} else {
|
||||
this.initalEventFetchProm = this.client.paginateEventTimeline(this.liveTimeline, {
|
||||
|
@@ -612,7 +612,7 @@ export class SlidingSyncSdk {
|
||||
timelineEvents = newEvents;
|
||||
if (oldEvents.length > 0) {
|
||||
// old events are scrollback, insert them now
|
||||
room.addEventsToTimeline(oldEvents, true, room.getLiveTimeline(), roomData.prev_batch);
|
||||
room.addEventsToTimeline(oldEvents, true, false, room.getLiveTimeline(), roomData.prev_batch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -754,7 +754,7 @@ export class SlidingSyncSdk {
|
||||
/**
|
||||
* Injects events into a room's model.
|
||||
* @param stateEventList - A list of state events. This is the state
|
||||
* at the *START* of the timeline list if it is supplied.
|
||||
* at the *END* of the timeline list if it is supplied.
|
||||
* @param timelineEventList - A list of timeline events. Lower index
|
||||
* is earlier in time. Higher index is later.
|
||||
* @param numLive - the number of events in timelineEventList which just happened,
|
||||
@@ -763,13 +763,9 @@ export class SlidingSyncSdk {
|
||||
public async injectRoomEvents(
|
||||
room: Room,
|
||||
stateEventList: MatrixEvent[],
|
||||
timelineEventList?: MatrixEvent[],
|
||||
numLive?: number,
|
||||
timelineEventList: MatrixEvent[] = [],
|
||||
numLive: number = 0,
|
||||
): Promise<void> {
|
||||
timelineEventList = timelineEventList || [];
|
||||
stateEventList = stateEventList || [];
|
||||
numLive = numLive || 0;
|
||||
|
||||
// If there are no events in the timeline yet, initialise it with
|
||||
// the given state events
|
||||
const liveTimeline = room.getLiveTimeline();
|
||||
@@ -820,16 +816,17 @@ export class SlidingSyncSdk {
|
||||
timelineEventList = timelineEventList.slice(0, -1 * liveTimelineEvents.length);
|
||||
}
|
||||
|
||||
// execute the timeline events. This will continue to diverge the current state
|
||||
// if the timeline has any state events in it.
|
||||
// Execute the timeline events.
|
||||
// This also needs to be done before running push rules on the events as they need
|
||||
// to be decorated with sender etc.
|
||||
await room.addLiveEvents(timelineEventList, {
|
||||
fromCache: true,
|
||||
addToState: false,
|
||||
});
|
||||
if (liveTimelineEvents.length > 0) {
|
||||
await room.addLiveEvents(liveTimelineEvents, {
|
||||
fromCache: false,
|
||||
addToState: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -966,7 +963,7 @@ export class SlidingSyncSdk {
|
||||
return a.getTs() - b.getTs();
|
||||
});
|
||||
this.notifEvents.forEach((event) => {
|
||||
this.client.getNotifTimelineSet()?.addLiveEvent(event);
|
||||
this.client.getNotifTimelineSet()?.addLiveEvent(event, { addToState: false });
|
||||
});
|
||||
this.notifEvents = [];
|
||||
}
|
||||
|
@@ -77,7 +77,9 @@ export interface ITimeline {
|
||||
|
||||
export interface IJoinedRoom {
|
||||
"summary": IRoomSummary;
|
||||
"state": IState;
|
||||
// One of `state` or `state_after` is required.
|
||||
"state"?: IState;
|
||||
"org.matrix.msc4222.state_after"?: IState; // https://github.com/matrix-org/matrix-spec-proposals/pull/4222
|
||||
"timeline": ITimeline;
|
||||
"ephemeral": IEphemeral;
|
||||
"account_data": IAccountData;
|
||||
@@ -106,9 +108,11 @@ export interface IInvitedRoom {
|
||||
}
|
||||
|
||||
export interface ILeftRoom {
|
||||
state: IState;
|
||||
timeline: ITimeline;
|
||||
account_data: IAccountData;
|
||||
// One of `state` or `state_after` is required.
|
||||
"state"?: IState;
|
||||
"org.matrix.msc4222.state_after"?: IState;
|
||||
"timeline": ITimeline;
|
||||
"account_data": IAccountData;
|
||||
}
|
||||
|
||||
export interface IKnockedRoom {
|
||||
@@ -481,13 +485,18 @@ export class SyncAccumulator {
|
||||
// Work out the current state. The deltas need to be applied in the order:
|
||||
// - existing state which didn't come down /sync.
|
||||
// - State events under the 'state' key.
|
||||
// - State events in the 'timeline'.
|
||||
// - State events under the 'state_after' key OR state events in the 'timeline' if 'state_after' is not present.
|
||||
data.state?.events?.forEach((e) => {
|
||||
setState(currentData._currentState, e);
|
||||
});
|
||||
data.timeline?.events?.forEach((e, index) => {
|
||||
// this nops if 'e' isn't a state event
|
||||
data["org.matrix.msc4222.state_after"]?.events?.forEach((e) => {
|
||||
setState(currentData._currentState, e);
|
||||
});
|
||||
data.timeline?.events?.forEach((e, index) => {
|
||||
if (!data["org.matrix.msc4222.state_after"]) {
|
||||
// this nops if 'e' isn't a state event
|
||||
setState(currentData._currentState, e);
|
||||
}
|
||||
// append the event to the timeline. The back-pagination token
|
||||
// corresponds to the first event in the timeline
|
||||
let transformedEvent: TaggedEvent;
|
||||
@@ -563,17 +572,22 @@ export class SyncAccumulator {
|
||||
});
|
||||
Object.keys(this.joinRooms).forEach((roomId) => {
|
||||
const roomData = this.joinRooms[roomId];
|
||||
const roomJson: IJoinedRoom = {
|
||||
ephemeral: { events: [] },
|
||||
account_data: { events: [] },
|
||||
state: { events: [] },
|
||||
timeline: {
|
||||
const roomJson: IJoinedRoom & {
|
||||
// We track both `state` and `state_after` for downgrade compatibility
|
||||
"state": IState;
|
||||
"org.matrix.msc4222.state_after": IState;
|
||||
} = {
|
||||
"ephemeral": { events: [] },
|
||||
"account_data": { events: [] },
|
||||
"state": { events: [] },
|
||||
"org.matrix.msc4222.state_after": { events: [] },
|
||||
"timeline": {
|
||||
events: [],
|
||||
prev_batch: null,
|
||||
},
|
||||
unread_notifications: roomData._unreadNotifications,
|
||||
unread_thread_notifications: roomData._unreadThreadNotifications,
|
||||
summary: roomData._summary as IRoomSummary,
|
||||
"unread_notifications": roomData._unreadNotifications,
|
||||
"unread_thread_notifications": roomData._unreadThreadNotifications,
|
||||
"summary": roomData._summary as IRoomSummary,
|
||||
};
|
||||
// Add account data
|
||||
Object.keys(roomData._accountData).forEach((evType) => {
|
||||
@@ -650,8 +664,11 @@ export class SyncAccumulator {
|
||||
Object.keys(roomData._currentState).forEach((evType) => {
|
||||
Object.keys(roomData._currentState[evType]).forEach((stateKey) => {
|
||||
let ev = roomData._currentState[evType][stateKey];
|
||||
// Push to both fields to provide downgrade compatibility in the sync accumulator db
|
||||
// the code will prefer `state_after` if it is present
|
||||
roomJson["org.matrix.msc4222.state_after"].events.push(ev);
|
||||
// Roll the state back to the value at the start of the timeline if it was changed
|
||||
if (rollBackState[evType] && rollBackState[evType][stateKey]) {
|
||||
// use the reverse clobbered event instead.
|
||||
ev = rollBackState[evType][stateKey];
|
||||
}
|
||||
roomJson.state.events.push(ev);
|
||||
|
158
src/sync.ts
158
src/sync.ts
@@ -175,14 +175,15 @@ export enum SetPresence {
|
||||
}
|
||||
|
||||
interface ISyncParams {
|
||||
filter?: string;
|
||||
timeout: number;
|
||||
since?: string;
|
||||
"filter"?: string;
|
||||
"timeout": number;
|
||||
"since"?: string;
|
||||
// eslint-disable-next-line camelcase
|
||||
full_state?: boolean;
|
||||
"full_state"?: boolean;
|
||||
// eslint-disable-next-line camelcase
|
||||
set_presence?: SetPresence;
|
||||
_cacheBuster?: string | number; // not part of the API itself
|
||||
"set_presence"?: SetPresence;
|
||||
"_cacheBuster"?: string | number; // not part of the API itself
|
||||
"org.matrix.msc4222.use_state_after"?: boolean; // https://github.com/matrix-org/matrix-spec-proposals/pull/4222
|
||||
}
|
||||
|
||||
type WrappedRoom<T> = T & {
|
||||
@@ -344,8 +345,9 @@ export class SyncApi {
|
||||
);
|
||||
|
||||
const qps: ISyncParams = {
|
||||
timeout: 0, // don't want to block since this is a single isolated req
|
||||
filter: filterId,
|
||||
"timeout": 0, // don't want to block since this is a single isolated req
|
||||
"filter": filterId,
|
||||
"org.matrix.msc4222.use_state_after": true,
|
||||
};
|
||||
|
||||
const data = await client.http.authedRequest<ISyncResponse>(Method.Get, "/sync", qps as any, undefined, {
|
||||
@@ -375,21 +377,18 @@ export class SyncApi {
|
||||
prev_batch: null,
|
||||
events: [],
|
||||
};
|
||||
const events = this.mapSyncEventsFormat(leaveObj.timeline, room);
|
||||
|
||||
const stateEvents = this.mapSyncEventsFormat(leaveObj.state, room);
|
||||
|
||||
// set the back-pagination token. Do this *before* adding any
|
||||
// events so that clients can start back-paginating.
|
||||
room.getLiveTimeline().setPaginationToken(leaveObj.timeline.prev_batch, EventTimeline.BACKWARDS);
|
||||
|
||||
await this.injectRoomEvents(room, stateEvents, events);
|
||||
const { timelineEvents } = await this.mapAndInjectRoomEvents(leaveObj);
|
||||
|
||||
room.recalculate();
|
||||
client.store.storeRoom(room);
|
||||
client.emit(ClientEvent.Room, room);
|
||||
|
||||
this.processEventsForNotifs(room, events);
|
||||
this.processEventsForNotifs(room, timelineEvents);
|
||||
return room;
|
||||
}),
|
||||
);
|
||||
@@ -464,6 +463,7 @@ export class SyncApi {
|
||||
this._peekRoom.addEventsToTimeline(
|
||||
messages.reverse(),
|
||||
true,
|
||||
true,
|
||||
this._peekRoom.getLiveTimeline(),
|
||||
response.messages.start,
|
||||
);
|
||||
@@ -551,7 +551,7 @@ export class SyncApi {
|
||||
})
|
||||
.map(this.client.getEventMapper());
|
||||
|
||||
await peekRoom.addLiveEvents(events);
|
||||
await peekRoom.addLiveEvents(events, { addToState: true });
|
||||
this.peekPoll(peekRoom, res.end);
|
||||
},
|
||||
(err) => {
|
||||
@@ -976,7 +976,11 @@ export class SyncApi {
|
||||
filter = this.getGuestFilter();
|
||||
}
|
||||
|
||||
const qps: ISyncParams = { filter, timeout };
|
||||
const qps: ISyncParams = {
|
||||
filter,
|
||||
timeout,
|
||||
"org.matrix.msc4222.use_state_after": true,
|
||||
};
|
||||
|
||||
if (this.opts.disablePresence) {
|
||||
qps.set_presence = SetPresence.Offline;
|
||||
@@ -1242,7 +1246,7 @@ export class SyncApi {
|
||||
const room = inviteObj.room;
|
||||
const stateEvents = this.mapSyncEventsFormat(inviteObj.invite_state, room);
|
||||
|
||||
await this.injectRoomEvents(room, stateEvents);
|
||||
await this.injectRoomEvents(room, stateEvents, undefined);
|
||||
|
||||
const inviter = room.currentState.getStateEvents(EventType.RoomMember, client.getUserId()!)?.getSender();
|
||||
|
||||
@@ -1282,15 +1286,24 @@ export class SyncApi {
|
||||
await promiseMapSeries(joinRooms, async (joinObj) => {
|
||||
const room = joinObj.room;
|
||||
const stateEvents = this.mapSyncEventsFormat(joinObj.state, room);
|
||||
const stateAfterEvents = this.mapSyncEventsFormat(joinObj["org.matrix.msc4222.state_after"], room);
|
||||
// Prevent events from being decrypted ahead of time
|
||||
// this helps large account to speed up faster
|
||||
// room::decryptCriticalEvent is in charge of decrypting all the events
|
||||
// required for a client to function properly
|
||||
const events = this.mapSyncEventsFormat(joinObj.timeline, room, false);
|
||||
const timelineEvents = this.mapSyncEventsFormat(joinObj.timeline, room, false);
|
||||
const ephemeralEvents = this.mapSyncEventsFormat(joinObj.ephemeral);
|
||||
const accountDataEvents = this.mapSyncEventsFormat(joinObj.account_data);
|
||||
|
||||
const encrypted = this.isRoomEncrypted(room, stateEvents, events);
|
||||
// If state_after is present, this is the events that form the state at the end of the timeline block and
|
||||
// regular timeline events do *not* count towards state. If it's not present, then the state is formed by
|
||||
// the state events plus the timeline events. Note mapSyncEventsFormat returns an empty array if the field
|
||||
// is absent so we explicitly check the field on the original object.
|
||||
const eventsFormingFinalState = joinObj["org.matrix.msc4222.state_after"]
|
||||
? stateAfterEvents
|
||||
: stateEvents.concat(timelineEvents);
|
||||
|
||||
const encrypted = this.isRoomEncrypted(room, eventsFormingFinalState);
|
||||
// We store the server-provided value first so it's correct when any of the events fire.
|
||||
if (joinObj.unread_notifications) {
|
||||
/**
|
||||
@@ -1378,8 +1391,8 @@ export class SyncApi {
|
||||
// which we'll try to paginate but not get any new events (which
|
||||
// will stop us linking the empty timeline into the chain).
|
||||
//
|
||||
for (let i = events.length - 1; i >= 0; i--) {
|
||||
const eventId = events[i].getId()!;
|
||||
for (let i = timelineEvents.length - 1; i >= 0; i--) {
|
||||
const eventId = timelineEvents[i].getId()!;
|
||||
if (room.getTimelineForEvent(eventId)) {
|
||||
debuglog(`Already have event ${eventId} in limited sync - not resetting`);
|
||||
limited = false;
|
||||
@@ -1387,7 +1400,7 @@ export class SyncApi {
|
||||
// we might still be missing some of the events before i;
|
||||
// we don't want to be adding them to the end of the
|
||||
// timeline because that would put them out of order.
|
||||
events.splice(0, i);
|
||||
timelineEvents.splice(0, i);
|
||||
|
||||
// XXX: there's a problem here if the skipped part of the
|
||||
// timeline modifies the state set in stateEvents, because
|
||||
@@ -1419,8 +1432,9 @@ export class SyncApi {
|
||||
// avoids a race condition if the application tries to send a message after the
|
||||
// state event is processed, but before crypto is enabled, which then causes the
|
||||
// crypto layer to complain.
|
||||
|
||||
if (this.syncOpts.cryptoCallbacks) {
|
||||
for (const e of stateEvents.concat(events)) {
|
||||
for (const e of eventsFormingFinalState) {
|
||||
if (e.isState() && e.getType() === EventType.RoomEncryption && e.getStateKey() === "") {
|
||||
await this.syncOpts.cryptoCallbacks.onCryptoEvent(room, e);
|
||||
}
|
||||
@@ -1428,7 +1442,17 @@ export class SyncApi {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.injectRoomEvents(room, stateEvents, events, syncEventData.fromCache);
|
||||
if ("org.matrix.msc4222.state_after" in joinObj) {
|
||||
await this.injectRoomEvents(
|
||||
room,
|
||||
undefined,
|
||||
stateAfterEvents,
|
||||
timelineEvents,
|
||||
syncEventData.fromCache,
|
||||
);
|
||||
} else {
|
||||
await this.injectRoomEvents(room, stateEvents, undefined, timelineEvents, syncEventData.fromCache);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(`Failed to process events on room ${room.roomId}:`, e);
|
||||
}
|
||||
@@ -1452,11 +1476,11 @@ export class SyncApi {
|
||||
client.emit(ClientEvent.Room, room);
|
||||
}
|
||||
|
||||
this.processEventsForNotifs(room, events);
|
||||
this.processEventsForNotifs(room, timelineEvents);
|
||||
|
||||
const emitEvent = (e: MatrixEvent): boolean => client.emit(ClientEvent.Event, e);
|
||||
stateEvents.forEach(emitEvent);
|
||||
events.forEach(emitEvent);
|
||||
timelineEvents.forEach(emitEvent);
|
||||
ephemeralEvents.forEach(emitEvent);
|
||||
accountDataEvents.forEach(emitEvent);
|
||||
|
||||
@@ -1469,11 +1493,9 @@ export class SyncApi {
|
||||
// Handle leaves (e.g. kicked rooms)
|
||||
await promiseMapSeries(leaveRooms, async (leaveObj) => {
|
||||
const room = leaveObj.room;
|
||||
const stateEvents = this.mapSyncEventsFormat(leaveObj.state, room);
|
||||
const events = this.mapSyncEventsFormat(leaveObj.timeline, room);
|
||||
const { timelineEvents, stateEvents, stateAfterEvents } = await this.mapAndInjectRoomEvents(leaveObj);
|
||||
const accountDataEvents = this.mapSyncEventsFormat(leaveObj.account_data);
|
||||
|
||||
await this.injectRoomEvents(room, stateEvents, events);
|
||||
room.addAccountData(accountDataEvents);
|
||||
|
||||
room.recalculate();
|
||||
@@ -1482,12 +1504,15 @@ export class SyncApi {
|
||||
client.emit(ClientEvent.Room, room);
|
||||
}
|
||||
|
||||
this.processEventsForNotifs(room, events);
|
||||
this.processEventsForNotifs(room, timelineEvents);
|
||||
|
||||
stateEvents.forEach(function (e) {
|
||||
stateEvents?.forEach(function (e) {
|
||||
client.emit(ClientEvent.Event, e);
|
||||
});
|
||||
events.forEach(function (e) {
|
||||
stateAfterEvents?.forEach(function (e) {
|
||||
client.emit(ClientEvent.Event, e);
|
||||
});
|
||||
timelineEvents.forEach(function (e) {
|
||||
client.emit(ClientEvent.Event, e);
|
||||
});
|
||||
accountDataEvents.forEach(function (e) {
|
||||
@@ -1500,7 +1525,7 @@ export class SyncApi {
|
||||
const room = knockObj.room;
|
||||
const stateEvents = this.mapSyncEventsFormat(knockObj.knock_state, room);
|
||||
|
||||
await this.injectRoomEvents(room, stateEvents);
|
||||
await this.injectRoomEvents(room, stateEvents, undefined);
|
||||
|
||||
if (knockObj.isBrandNewRoom) {
|
||||
room.recalculate();
|
||||
@@ -1525,7 +1550,7 @@ export class SyncApi {
|
||||
return a.getTs() - b.getTs();
|
||||
});
|
||||
this.notifEvents.forEach(function (event) {
|
||||
client.getNotifTimelineSet()?.addLiveEvent(event);
|
||||
client.getNotifTimelineSet()?.addLiveEvent(event, { addToState: true });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1669,7 +1694,7 @@ export class SyncApi {
|
||||
}
|
||||
|
||||
private mapSyncEventsFormat(
|
||||
obj: IInviteState | ITimeline | IEphemeral,
|
||||
obj: IInviteState | ITimeline | IEphemeral | undefined,
|
||||
room?: Room,
|
||||
decrypt = true,
|
||||
): MatrixEvent[] {
|
||||
@@ -1737,28 +1762,69 @@ export class SyncApi {
|
||||
|
||||
// When processing the sync response we cannot rely on Room.hasEncryptionStateEvent we actually
|
||||
// inject the events into the room object, so we have to inspect the events themselves.
|
||||
private isRoomEncrypted(room: Room, stateEventList: MatrixEvent[], timelineEventList?: MatrixEvent[]): boolean {
|
||||
return (
|
||||
room.hasEncryptionStateEvent() ||
|
||||
!!this.findEncryptionEvent(stateEventList) ||
|
||||
!!this.findEncryptionEvent(timelineEventList)
|
||||
private isRoomEncrypted(room: Room, eventsFormingFinalState: MatrixEvent[]): boolean {
|
||||
return room.hasEncryptionStateEvent() || !!this.findEncryptionEvent(eventsFormingFinalState);
|
||||
}
|
||||
|
||||
private async mapAndInjectRoomEvents(wrappedRoom: WrappedRoom<ILeftRoom>): Promise<{
|
||||
timelineEvents: MatrixEvent[];
|
||||
stateEvents?: MatrixEvent[];
|
||||
stateAfterEvents?: MatrixEvent[];
|
||||
}> {
|
||||
const stateEvents = this.mapSyncEventsFormat(wrappedRoom.state, wrappedRoom.room);
|
||||
const stateAfterEvents = this.mapSyncEventsFormat(
|
||||
wrappedRoom["org.matrix.msc4222.state_after"],
|
||||
wrappedRoom.room,
|
||||
);
|
||||
const timelineEvents = this.mapSyncEventsFormat(wrappedRoom.timeline, wrappedRoom.room);
|
||||
|
||||
if ("org.matrix.msc4222.state_after" in wrappedRoom) {
|
||||
await this.injectRoomEvents(wrappedRoom.room, undefined, stateAfterEvents, timelineEvents);
|
||||
} else {
|
||||
await this.injectRoomEvents(wrappedRoom.room, stateEvents, undefined, timelineEvents);
|
||||
}
|
||||
|
||||
return { timelineEvents, stateEvents, stateAfterEvents };
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects events into a room's model.
|
||||
* @param stateEventList - A list of state events. This is the state
|
||||
* at the *START* of the timeline list if it is supplied.
|
||||
* @param stateAfterEventList - A list of state events. This is the state
|
||||
* at the *END* of the timeline list if it is supplied.
|
||||
* @param timelineEventList - A list of timeline events, including threaded. Lower index
|
||||
* is earlier in time. Higher index is later.
|
||||
* @param fromCache - whether the sync response came from cache
|
||||
*
|
||||
* No more than one of stateEventList and stateAfterEventList must be supplied. If
|
||||
* stateEventList is supplied, the events in timelineEventList are added to the state
|
||||
* after stateEventList. If stateAfterEventList is supplied, the events in timelineEventList
|
||||
* are not added to the state.
|
||||
*/
|
||||
public async injectRoomEvents(
|
||||
room: Room,
|
||||
stateEventList: MatrixEvent[],
|
||||
stateAfterEventList: undefined,
|
||||
timelineEventList?: MatrixEvent[],
|
||||
fromCache?: boolean,
|
||||
): Promise<void>;
|
||||
public async injectRoomEvents(
|
||||
room: Room,
|
||||
stateEventList: undefined,
|
||||
stateAfterEventList: MatrixEvent[],
|
||||
timelineEventList?: MatrixEvent[],
|
||||
fromCache?: boolean,
|
||||
): Promise<void>;
|
||||
public async injectRoomEvents(
|
||||
room: Room,
|
||||
stateEventList: MatrixEvent[] | undefined,
|
||||
stateAfterEventList: MatrixEvent[] | undefined,
|
||||
timelineEventList?: MatrixEvent[],
|
||||
fromCache = false,
|
||||
): Promise<void> {
|
||||
const eitherStateEventList = stateAfterEventList ?? stateEventList!;
|
||||
|
||||
// If there are no events in the timeline yet, initialise it with
|
||||
// the given state events
|
||||
const liveTimeline = room.getLiveTimeline();
|
||||
@@ -1772,10 +1838,11 @@ export class SyncApi {
|
||||
// push actions cache elsewhere so we can freeze MatrixEvents, or otherwise
|
||||
// find some solution where MatrixEvents are immutable but allow for a cache
|
||||
// field.
|
||||
for (const ev of stateEventList) {
|
||||
|
||||
for (const ev of eitherStateEventList) {
|
||||
this.client.getPushActionsForEvent(ev);
|
||||
}
|
||||
liveTimeline.initialiseState(stateEventList, {
|
||||
liveTimeline.initialiseState(eitherStateEventList, {
|
||||
timelineWasEmpty,
|
||||
});
|
||||
}
|
||||
@@ -1807,17 +1874,18 @@ export class SyncApi {
|
||||
// XXX: As above, don't do this...
|
||||
//room.addLiveEvents(stateEventList || []);
|
||||
// Do this instead...
|
||||
room.oldState.setStateEvents(stateEventList || []);
|
||||
room.currentState.setStateEvents(stateEventList || []);
|
||||
room.oldState.setStateEvents(eitherStateEventList);
|
||||
room.currentState.setStateEvents(eitherStateEventList);
|
||||
}
|
||||
|
||||
// Execute the timeline events. This will continue to diverge the current state
|
||||
// if the timeline has any state events in it.
|
||||
// Execute the timeline events. If addToState is true the timeline has any state
|
||||
// events in it, this will continue to diverge the current state.
|
||||
// This also needs to be done before running push rules on the events as they need
|
||||
// to be decorated with sender etc.
|
||||
await room.addLiveEvents(timelineEventList || [], {
|
||||
fromCache,
|
||||
timelineWasEmpty,
|
||||
addToState: stateAfterEventList === undefined,
|
||||
});
|
||||
this.client.processBeaconEvents(room, timelineEventList);
|
||||
}
|
||||
|
Reference in New Issue
Block a user