Skip to content

Commit 6c307d4

Browse files
maheichykMikhail Aheichyk
and
Mikhail Aheichyk
authored
Sync knock rooms (#3703)
Signed-off-by: Mikhail Aheichyk <[email protected]> Co-authored-by: Mikhail Aheichyk <[email protected]>
1 parent 88ec0e3 commit 6c307d4

7 files changed

+320
-4
lines changed

spec/integ/matrix-client-syncing.spec.ts

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,121 @@ describe("MatrixClient syncing", () => {
223223
expect(fires).toBe(3);
224224
});
225225

226+
it("should emit RoomEvent.MyMembership for knock->leave->knock cycles", async () => {
227+
await client!.initCrypto();
228+
229+
const roomId = "!cycles:example.org";
230+
231+
// First sync: an knock
232+
const knockSyncRoomSection = {
233+
knock: {
234+
[roomId]: {
235+
knock_state: {
236+
events: [
237+
{
238+
type: "m.room.member",
239+
state_key: selfUserId,
240+
content: {
241+
membership: "knock",
242+
},
243+
},
244+
],
245+
},
246+
},
247+
},
248+
};
249+
httpBackend!.when("GET", "/sync").respond(200, {
250+
...syncData,
251+
rooms: knockSyncRoomSection,
252+
});
253+
254+
// Second sync: a leave (reject of some kind)
255+
httpBackend!.when("POST", "/leave").respond(200, {});
256+
httpBackend!.when("GET", "/sync").respond(200, {
257+
...syncData,
258+
rooms: {
259+
leave: {
260+
[roomId]: {
261+
account_data: { events: [] },
262+
ephemeral: { events: [] },
263+
state: {
264+
events: [
265+
{
266+
type: "m.room.member",
267+
state_key: selfUserId,
268+
content: {
269+
membership: "leave",
270+
},
271+
prev_content: {
272+
membership: "knock",
273+
},
274+
// XXX: And other fields required on an event
275+
},
276+
],
277+
},
278+
timeline: {
279+
limited: false,
280+
events: [
281+
{
282+
type: "m.room.member",
283+
state_key: selfUserId,
284+
content: {
285+
membership: "leave",
286+
},
287+
prev_content: {
288+
membership: "knock",
289+
},
290+
// XXX: And other fields required on an event
291+
},
292+
],
293+
},
294+
},
295+
},
296+
},
297+
});
298+
299+
// Third sync: another knock
300+
httpBackend!.when("GET", "/sync").respond(200, {
301+
...syncData,
302+
rooms: knockSyncRoomSection,
303+
});
304+
305+
// First fire: an initial knock
306+
let fires = 0;
307+
client!.once(RoomEvent.MyMembership, (room, membership, oldMembership) => {
308+
// Room, string, string
309+
fires++;
310+
expect(room.roomId).toBe(roomId);
311+
expect(membership).toBe("knock");
312+
expect(oldMembership).toBeFalsy();
313+
314+
// Second fire: a leave
315+
client!.once(RoomEvent.MyMembership, (room, membership, oldMembership) => {
316+
fires++;
317+
expect(room.roomId).toBe(roomId);
318+
expect(membership).toBe("leave");
319+
expect(oldMembership).toBe("knock");
320+
321+
// Third/final fire: a second knock
322+
client!.once(RoomEvent.MyMembership, (room, membership, oldMembership) => {
323+
fires++;
324+
expect(room.roomId).toBe(roomId);
325+
expect(membership).toBe("knock");
326+
expect(oldMembership).toBe("leave");
327+
});
328+
});
329+
330+
// For maximum safety, "leave" the room after we register the handler
331+
client!.leave(roomId);
332+
});
333+
334+
// noinspection ES6MissingAwait
335+
client!.startClient();
336+
await httpBackend!.flushAllExpected();
337+
338+
expect(fires).toBe(3);
339+
});
340+
226341
it("should honour lazyLoadMembers if user is not a guest", () => {
227342
httpBackend!
228343
.when("GET", "/sync")
@@ -293,6 +408,46 @@ describe("MatrixClient syncing", () => {
293408
expect(fires).toBe(1);
294409
});
295410

411+
it("should emit ClientEvent.Room when knocked while crypto is disabled", async () => {
412+
const roomId = "!knock:example.org";
413+
414+
// First sync: a knock
415+
const knockSyncRoomSection = {
416+
knock: {
417+
[roomId]: {
418+
knock_state: {
419+
events: [
420+
{
421+
type: "m.room.member",
422+
state_key: selfUserId,
423+
content: {
424+
membership: "knock",
425+
},
426+
},
427+
],
428+
},
429+
},
430+
},
431+
};
432+
httpBackend!.when("GET", "/sync").respond(200, {
433+
...syncData,
434+
rooms: knockSyncRoomSection,
435+
});
436+
437+
// First fire: an initial knock
438+
let fires = 0;
439+
client!.once(ClientEvent.Room, (room) => {
440+
fires++;
441+
expect(room.roomId).toBe(roomId);
442+
});
443+
444+
// noinspection ES6MissingAwait
445+
client!.startClient();
446+
await httpBackend!.flushAllExpected();
447+
448+
expect(fires).toBe(1);
449+
});
450+
296451
it("should work when all network calls fail", async () => {
297452
httpBackend!.expectedRequests = [];
298453
httpBackend!.when("GET", "").fail(0, new Error("CORS or something"));
@@ -358,6 +513,7 @@ describe("MatrixClient syncing", () => {
358513
join: {},
359514
invite: {},
360515
leave: {},
516+
knock: {},
361517
},
362518
};
363519

spec/integ/matrix-client-unread-notifications.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ describe("MatrixClient syncing", () => {
376376
},
377377
[Category.Leave]: {},
378378
[Category.Invite]: {},
379+
[Category.Knock]: {},
379380
},
380381
};
381382
}

spec/test-utils/test-utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export function getSyncResponse(roomMembers: string[], roomId = TEST_ROOM_ID): I
9999
join: { [roomId]: roomResponse },
100100
invite: {},
101101
leave: {},
102+
knock: {},
102103
},
103104
account_data: { events: [] },
104105
};

spec/unit/sync-accumulator.spec.ts

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ limitations under the License.
1616
*/
1717

1818
import { ReceiptType } from "../../src/@types/read_receipts";
19-
import { IJoinedRoom, ISyncResponse, SyncAccumulator } from "../../src/sync-accumulator";
19+
import { IJoinedRoom, IKnockedRoom, IStrippedState, ISyncResponse, SyncAccumulator } from "../../src/sync-accumulator";
2020
import { IRoomSummary } from "../../src";
21+
import * as utils from "../test-utils/test-utils";
2122

2223
// The event body & unsigned object get frozen to assert that they don't get altered
2324
// by the impl
@@ -95,6 +96,13 @@ describe("SyncAccumulator", function () {
9596
},
9697
},
9798
},
99+
knock: {
100+
"!knock": {
101+
knock_state: {
102+
events: [member("alice", "knock")],
103+
},
104+
},
105+
},
98106
},
99107
} as unknown as ISyncResponse;
100108
sa.accumulate(res);
@@ -287,6 +295,71 @@ describe("SyncAccumulator", function () {
287295
expect(sa.getJSON().accountData[0]).toEqual(acc2);
288296
});
289297

298+
it("should accumulate knock state", () => {
299+
const initKnockState = {
300+
events: [member("alice", "knock")],
301+
};
302+
sa.accumulate(
303+
syncSkeleton(
304+
{},
305+
{
306+
knock_state: initKnockState,
307+
},
308+
),
309+
);
310+
expect(sa.getJSON().roomsData.knock["!knock:bar"].knock_state).toBe(initKnockState);
311+
312+
sa.accumulate(
313+
syncSkeleton(
314+
{},
315+
{
316+
knock_state: {
317+
events: [
318+
utils.mkEvent({
319+
user: "alice",
320+
room: "!knock:bar",
321+
type: "m.room.name",
322+
content: {
323+
name: "Room 1",
324+
},
325+
}) as IStrippedState,
326+
],
327+
},
328+
},
329+
),
330+
);
331+
332+
expect(
333+
sa.getJSON().roomsData.knock["!knock:bar"].knock_state.events.find((e) => e.type === "m.room.name")?.content
334+
.name,
335+
).toEqual("Room 1");
336+
337+
sa.accumulate(
338+
syncSkeleton(
339+
{},
340+
{
341+
knock_state: {
342+
events: [
343+
utils.mkEvent({
344+
user: "alice",
345+
room: "!knock:bar",
346+
type: "m.room.name",
347+
content: {
348+
name: "Room 2",
349+
},
350+
}) as IStrippedState,
351+
],
352+
},
353+
},
354+
),
355+
);
356+
357+
expect(
358+
sa.getJSON().roomsData.knock["!knock:bar"].knock_state.events.find((e) => e.type === "m.room.name")?.content
359+
.name,
360+
).toEqual("Room 2");
361+
});
362+
290363
it("should accumulate read receipts", () => {
291364
const receipt1 = {
292365
type: "m.receipt",
@@ -601,14 +674,19 @@ describe("SyncAccumulator", function () {
601674
});
602675
});
603676

604-
function syncSkeleton(joinObj: Partial<IJoinedRoom>): ISyncResponse {
677+
function syncSkeleton(joinObj: Partial<IJoinedRoom>, knockObj?: Partial<IKnockedRoom>): ISyncResponse {
605678
joinObj = joinObj || {};
606679
return {
607680
next_batch: "abc",
608681
rooms: {
609682
join: {
610683
"!foo:bar": joinObj,
611684
},
685+
knock: knockObj
686+
? {
687+
"!knock:bar": knockObj,
688+
}
689+
: undefined,
612690
},
613691
} as unknown as ISyncResponse;
614692
}

src/models/room.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
849849
}
850850

851851
/**
852-
* @returns the membership type (join | leave | invite) for the logged in user
852+
* @returns the membership type (join | leave | invite | knock) for the logged in user
853853
*/
854854
public getMyMembership(): string {
855855
return this.selfMembership ?? "leave";

0 commit comments

Comments
 (0)