Skip to content

Commit 558cbb3

Browse files
authored
Remove the need to call /initialSync in getMessagesByUserIn. (#297)
* Remove the need to call `/initialSync` in `getMessagesByUserIn`. At the moment we call `/initialSync` to give a `from` token to `/messages`. In this PR we instead do not provide a `from` token when calling `/messages`, which has recently been permitted in the spec Technically this is still unstable in the spec https://spec.matrix.org/unstable/client-server-api/#get_matrixclientv3roomsroomidmessages matrix-org/matrix-spec#1002 Synapse has supported this for over 2 years and Element web depends on it for threads. matrix-org/matrix-js-sdk#2065 Given that redactions are super heavy in Mjolnir already and have been reported as barely functional on matrix.org I believe we should also adopt this approach as if for some reason the spec did change before the next release (1.3) (extremely unlikely) we can revert this commit.
1 parent bf7f131 commit 558cbb3

File tree

1 file changed

+39
-40
lines changed

1 file changed

+39
-40
lines changed

src/utils.ts

Lines changed: 39 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -122,28 +122,35 @@ export async function getMessagesByUserIn(client: MatrixClient, sender: string,
122122
}
123123

124124
/**
125-
* Note: `rooms/initialSync` is deprecated. However, there is no replacement for this API for the time being.
126-
* While previous versions of this function used `/sync`, experience shows that it can grow extremely
127-
* slow (4-5 minutes long) when we need to sync many large rooms, which leads to timeouts and
128-
* breakage in Mjolnir, see https://github.com/matrix-org/synapse/issues/10842.
125+
* The response returned from `backfill`
126+
* See https://spec.matrix.org/latest/client-server-api/#get_matrixclientv3roomsroomidmessages
127+
* for what the fields mean in detail. You have to read the spec even with the summary.
128+
* The `chunk` contains the events in reverse-chronological order.
129+
* The `end` is a token for the end of the `chunk` (where the older events are).
130+
* The `start` is a token for the beginning of the `chunk` (where the most recent events are).
129131
*/
130-
function roomInitialSync() {
131-
return client.doRequest("GET", `/_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/initialSync`);
132+
interface BackfillResponse {
133+
chunk?: any[],
134+
end?: string,
135+
start: string
132136
}
133137

134-
function backfill(from: string) {
138+
/**
139+
* Call `/messages` "backwards".
140+
* @param from a token that was returned previously from this API to start paginating from or
141+
* if `null`, start from the most recent point in the timeline.
142+
* @returns The response part of the `/messages` API, see `BackfillResponse`.
143+
*/
144+
async function backfill(from: string|null): Promise<BackfillResponse> {
135145
const qs = {
136146
filter: JSON.stringify(roomEventFilter),
137-
from: from,
138147
dir: "b",
148+
... from ? { from } : {}
139149
};
140150
LogService.info("utils", "Backfilling with token: " + from);
141-
return client.doRequest("GET", `/_matrix/client/r0/rooms/${encodeURIComponent(roomId)}/messages`, qs);
151+
return client.doRequest("GET", `/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/messages`, qs);
142152
}
143153

144-
// Do an initial sync first to get the batch token
145-
const response = await roomInitialSync();
146-
147154
let processed = 0;
148155
/**
149156
* Filter events from the timeline to events that are from a matching sender and under the limit that can be processed by the callback.
@@ -160,35 +167,27 @@ export async function getMessagesByUserIn(client: MatrixClient, sender: string,
160167
}
161168
return messages;
162169
}
163-
164-
// The recommended APIs for fetching events from a room is to use both rooms/initialSync then /messages.
165-
// Unfortunately, this results in code that is rather hard to read, as these two APIs employ very different data structures.
166-
// We prefer discarding the results from rooms/initialSync and reading only from /messages,
167-
// even if it's a little slower, for the sake of code maintenance.
168-
const timeline = response['messages']
169-
if (timeline) {
170-
// The end of the PaginationChunk has the most recent events from rooms/initialSync.
171-
// This token is required be present in the PagintionChunk from rooms/initialSync.
172-
let token = timeline['end']!;
173-
// We check that we have the token because rooms/messages is not required to provide one
174-
// and will not provide one when there is no more history to paginate.
175-
while (token && processed < limit) {
176-
const bfMessages = await backfill(token);
177-
let lastToken = token;
178-
token = bfMessages['end'];
179-
if (lastToken === token) {
180-
LogService.debug("utils", "Backfill returned same end token - returning early.");
181-
return;
182-
}
183-
const events = filterEvents(bfMessages['chunk'] || []);
184-
// If we are using a glob, there may be no relevant events in this chunk.
185-
if (events.length > 0) {
186-
await cb(events);
187-
}
170+
// We check that we have the token because rooms/messages is not required to provide one
171+
// and will not provide one when there is no more history to paginate.
172+
let token: string|null = null;
173+
do {
174+
const bfMessages: BackfillResponse = await backfill(token);
175+
const previousToken: string|null = token;
176+
token = bfMessages['end'] ?? null;
177+
const events = filterEvents(bfMessages['chunk'] || []);
178+
// If we are using a glob, there may be no relevant events in this chunk.
179+
if (events.length > 0) {
180+
await cb(events);
188181
}
189-
} else {
190-
throw new Error(`Internal Error: rooms/initialSync did not return a pagination chunk for ${roomId}, this is not normal and if it is we need to stop using it. See roomInitialSync() for why we are using it.`);
191-
}
182+
// This check exists only because of a Synapse compliance bug https://github.com/matrix-org/synapse/issues/12102.
183+
// We also check after processing events as the `previousToken` can be 'null' if we are at the start of the steam
184+
// and `token` can also be 'null' as we have paginated the entire timeline, but there would be unprocessed events in the
185+
// chunk that was returned in this request.
186+
if (previousToken === token) {
187+
LogService.debug("utils", "Backfill returned same end token - returning early.");
188+
return;
189+
}
190+
} while (token && processed < limit)
192191
}
193192

194193
/*

0 commit comments

Comments
 (0)