Matrix mouth: re-read the brake before every join; pin the encoded presence path

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Eureka 2026-11-08 16:24:17 +00:00
commit 94606d04a6
2 changed files with 19 additions and 3 deletions

View file

@ -1035,6 +1035,7 @@ test("presence follows the brake: online while released, offline and one PUT on
await mouth.syncOnce(); await mouth.syncOnce(); await mouth.syncOnce();
assert.deepEqual(syncs().slice(2), ["offline", "offline", "offline"]);
assert.deepEqual(hs.presencePuts().map(r => [r.path, r.body]), [[`/_matrix/client/v3/presence/${SELF}/status`, { presence: "offline" }]], "exactly one PUT");
assert.equal(hs.presencePuts()[0].rawPath, `/_matrix/client/v3/presence/${encodeURIComponent(SELF)}/status`, "the user id is percent-encoded on the wire");
open = true;
await mouth.syncOnce();
assert.equal(syncs().at(-1), "online");
@ -1151,3 +1152,18 @@ test("a full backlog still sets presence from the brake", async () => {
assert.equal(hs.seen.filter(r => r.path.endsWith("/sync")).length, syncsBefore);
assert.deepEqual(hs.presencePuts().map(r => r.body), [{ presence: "offline" }]);
});
test("a brake placed during the first of two joins stops the second join and its m.direct write", async () => {
const DM2 = "!newdm2:gamecult.test";
const invites2 = { ...invite(`@meta:${HS_NAME}`), [DM2]: invite(`@meta:${HS_NAME}`)[DMROOM] };
const hs = await fakeHomeserver({ sync: since => since === null ? { next_batch: "b0" } : since === "b0" ? { next_batch: "b1", rooms: { invite: invites2 } } : { next_batch: since } });
let open = true;
const fetch = async (url, init) => { const res = await hs.fetch(url, init); if (init?.method === "POST" && String(url).includes("/join/")) open = false; return res; }; // the brake lands during the first join
const mouth = mouthFor(hs, { fetch, brake: async () => open });
await mouth.syncOnce(); await mouth.syncOnce();
assert.deepEqual(joinsOf(hs), [DMROOM], "only the join already started");
assert.equal(hs.seen.filter(r => r.method === "PUT" && r.path.endsWith("/account_data/m.direct")).length, 1, "one m.direct, for the first join");
open = true;
await mouth.syncOnce();
assert.deepEqual(joinsOf(hs), [DMROOM, DM2], "the second waits for the release");
});

View file

@ -164,7 +164,7 @@ export function createMouth({
// A DM invitation from an account on our own homeserver is accepted, and only while the brake is
// released; one received while braked waits for the release. An invitation from any other server is
// declined. A same-server invitation to a room that is not a DM is not ours to accept and is dropped.
async function answerInvites(free) {
async function answerInvites() {
for (const [roomId, invite] of [...invites]) {
try {
const room = encodeURIComponent(roomId);
@ -174,7 +174,7 @@ export function createMouth({
} else if (!invite.direct) {
invites.delete(roomId);
} else {
if (!free) continue;
if (!(await released())) continue; // read fresh before every join: a brake placed mid-pass stops the next one
await matrix("POST", `/join/${room}`, {});
invites.delete(roomId);
directRooms.add(roomId);
@ -299,7 +299,7 @@ export function createMouth({
const baseline = cursor === null; // first ever sync: history is context, not mail
for (const event of reply.account_data?.events ?? []) if (event.type === "m.direct") noteDirect(event.content);
noteInvites(reply.rooms?.invite);
await answerInvites(await released()); // a brake placed during the long-poll stops the join, as send re-reads it
await answerInvites(); // each join re-reads the brake, as send does
const made = [];
const round = new Map();
let truncated = false; // the pending cap was reached inside this round