Skip to content

Commit

Permalink
Sync knock rooms (#3703)
Browse files Browse the repository at this point in the history
Signed-off-by: Mikhail Aheichyk <mikhail.aheichyk@nordeck.net>
Co-authored-by: Mikhail Aheichyk <mikhail.aheichyk@nordeck.net>
  • Loading branch information
maheichyk and Mikhail Aheichyk authored Sep 6, 2023
1 parent 88ec0e3 commit 6c307d4
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 4 deletions.
156 changes: 156 additions & 0 deletions spec/integ/matrix-client-syncing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,121 @@ describe("MatrixClient syncing", () => {
expect(fires).toBe(3);
});

it("should emit RoomEvent.MyMembership for knock->leave->knock cycles", async () => {
await client!.initCrypto();

const roomId = "!cycles:example.org";

// First sync: an knock
const knockSyncRoomSection = {
knock: {
[roomId]: {
knock_state: {
events: [
{
type: "m.room.member",
state_key: selfUserId,
content: {
membership: "knock",
},
},
],
},
},
},
};
httpBackend!.when("GET", "/sync").respond(200, {
...syncData,
rooms: knockSyncRoomSection,
});

// Second sync: a leave (reject of some kind)
httpBackend!.when("POST", "/leave").respond(200, {});
httpBackend!.when("GET", "/sync").respond(200, {
...syncData,
rooms: {
leave: {
[roomId]: {
account_data: { events: [] },
ephemeral: { events: [] },
state: {
events: [
{
type: "m.room.member",
state_key: selfUserId,
content: {
membership: "leave",
},
prev_content: {
membership: "knock",
},
// XXX: And other fields required on an event
},
],
},
timeline: {
limited: false,
events: [
{
type: "m.room.member",
state_key: selfUserId,
content: {
membership: "leave",
},
prev_content: {
membership: "knock",
},
// XXX: And other fields required on an event
},
],
},
},
},
},
});

// Third sync: another knock
httpBackend!.when("GET", "/sync").respond(200, {
...syncData,
rooms: knockSyncRoomSection,
});

// First fire: an initial knock
let fires = 0;
client!.once(RoomEvent.MyMembership, (room, membership, oldMembership) => {
// Room, string, string
fires++;
expect(room.roomId).toBe(roomId);
expect(membership).toBe("knock");
expect(oldMembership).toBeFalsy();

// Second fire: a leave
client!.once(RoomEvent.MyMembership, (room, membership, oldMembership) => {
fires++;
expect(room.roomId).toBe(roomId);
expect(membership).toBe("leave");
expect(oldMembership).toBe("knock");

// Third/final fire: a second knock
client!.once(RoomEvent.MyMembership, (room, membership, oldMembership) => {
fires++;
expect(room.roomId).toBe(roomId);
expect(membership).toBe("knock");
expect(oldMembership).toBe("leave");
});
});

// For maximum safety, "leave" the room after we register the handler
client!.leave(roomId);
});

// noinspection ES6MissingAwait
client!.startClient();
await httpBackend!.flushAllExpected();

expect(fires).toBe(3);
});

it("should honour lazyLoadMembers if user is not a guest", () => {
httpBackend!
.when("GET", "/sync")
Expand Down Expand Up @@ -293,6 +408,46 @@ describe("MatrixClient syncing", () => {
expect(fires).toBe(1);
});

it("should emit ClientEvent.Room when knocked while crypto is disabled", async () => {
const roomId = "!knock:example.org";

// First sync: a knock
const knockSyncRoomSection = {
knock: {
[roomId]: {
knock_state: {
events: [
{
type: "m.room.member",
state_key: selfUserId,
content: {
membership: "knock",
},
},
],
},
},
},
};
httpBackend!.when("GET", "/sync").respond(200, {
...syncData,
rooms: knockSyncRoomSection,
});

// First fire: an initial knock
let fires = 0;
client!.once(ClientEvent.Room, (room) => {
fires++;
expect(room.roomId).toBe(roomId);
});

// noinspection ES6MissingAwait
client!.startClient();
await httpBackend!.flushAllExpected();

expect(fires).toBe(1);
});

it("should work when all network calls fail", async () => {
httpBackend!.expectedRequests = [];
httpBackend!.when("GET", "").fail(0, new Error("CORS or something"));
Expand Down Expand Up @@ -358,6 +513,7 @@ describe("MatrixClient syncing", () => {
join: {},
invite: {},
leave: {},
knock: {},
},
};

Expand Down
1 change: 1 addition & 0 deletions spec/integ/matrix-client-unread-notifications.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ describe("MatrixClient syncing", () => {
},
[Category.Leave]: {},
[Category.Invite]: {},
[Category.Knock]: {},
},
};
}
Expand Down
1 change: 1 addition & 0 deletions spec/test-utils/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export function getSyncResponse(roomMembers: string[], roomId = TEST_ROOM_ID): I
join: { [roomId]: roomResponse },
invite: {},
leave: {},
knock: {},
},
account_data: { events: [] },
};
Expand Down
82 changes: 80 additions & 2 deletions spec/unit/sync-accumulator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ limitations under the License.
*/

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

// The event body & unsigned object get frozen to assert that they don't get altered
// by the impl
Expand Down Expand Up @@ -95,6 +96,13 @@ describe("SyncAccumulator", function () {
},
},
},
knock: {
"!knock": {
knock_state: {
events: [member("alice", "knock")],
},
},
},
},
} as unknown as ISyncResponse;
sa.accumulate(res);
Expand Down Expand Up @@ -287,6 +295,71 @@ describe("SyncAccumulator", function () {
expect(sa.getJSON().accountData[0]).toEqual(acc2);
});

it("should accumulate knock state", () => {
const initKnockState = {
events: [member("alice", "knock")],
};
sa.accumulate(
syncSkeleton(
{},
{
knock_state: initKnockState,
},
),
);
expect(sa.getJSON().roomsData.knock["!knock:bar"].knock_state).toBe(initKnockState);

sa.accumulate(
syncSkeleton(
{},
{
knock_state: {
events: [
utils.mkEvent({
user: "alice",
room: "!knock:bar",
type: "m.room.name",
content: {
name: "Room 1",
},
}) as IStrippedState,
],
},
},
),
);

expect(
sa.getJSON().roomsData.knock["!knock:bar"].knock_state.events.find((e) => e.type === "m.room.name")?.content
.name,
).toEqual("Room 1");

sa.accumulate(
syncSkeleton(
{},
{
knock_state: {
events: [
utils.mkEvent({
user: "alice",
room: "!knock:bar",
type: "m.room.name",
content: {
name: "Room 2",
},
}) as IStrippedState,
],
},
},
),
);

expect(
sa.getJSON().roomsData.knock["!knock:bar"].knock_state.events.find((e) => e.type === "m.room.name")?.content
.name,
).toEqual("Room 2");
});

it("should accumulate read receipts", () => {
const receipt1 = {
type: "m.receipt",
Expand Down Expand Up @@ -601,14 +674,19 @@ describe("SyncAccumulator", function () {
});
});

function syncSkeleton(joinObj: Partial<IJoinedRoom>): ISyncResponse {
function syncSkeleton(joinObj: Partial<IJoinedRoom>, knockObj?: Partial<IKnockedRoom>): ISyncResponse {
joinObj = joinObj || {};
return {
next_batch: "abc",
rooms: {
join: {
"!foo:bar": joinObj,
},
knock: knockObj
? {
"!knock:bar": knockObj,
}
: undefined,
},
} as unknown as ISyncResponse;
}
Expand Down
2 changes: 1 addition & 1 deletion src/models/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
}

/**
* @returns the membership type (join | leave | invite) for the logged in user
* @returns the membership type (join | leave | invite | knock) for the logged in user
*/
public getMyMembership(): string {
return this.selfMembership ?? "leave";
Expand Down
Loading

0 comments on commit 6c307d4

Please sign in to comment.