Skip to content

Commit

Permalink
Merge pull request #2912 from matrix-org/kegan/ss-receipts
Browse files Browse the repository at this point in the history
sliding sync: add receipts extension
  • Loading branch information
kegsay authored Nov 28, 2022
2 parents fc91153 + 847766c commit 7fd55a6
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 12 deletions.
86 changes: 86 additions & 0 deletions spec/integ/sliding-sync-sdk.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -928,4 +928,90 @@ describe("SlidingSyncSdk", () => {
expect(room.getMember(selfUserId)?.typing).toEqual(false);
});
});

describe("ExtensionReceipts", () => {
let ext: Extension;

const generateReceiptResponse = (
userId: string, roomId: string, eventId: string, recType: string, ts: number,
) => {
return {
rooms: {
[roomId]: {
type: EventType.Receipt,
content: {
[eventId]: {
[recType]: {
[userId]: {
ts: ts,
},
},
},
},
},
},
};
};

beforeAll(async () => {
await setupClient();
const hasSynced = sdk!.sync();
await httpBackend!.flushAllExpected();
await hasSynced;
ext = findExtension("receipts");
});

it("gets enabled on the initial request only", () => {
expect(ext.onRequest(true)).toEqual({
enabled: true,
});
expect(ext.onRequest(false)).toEqual(undefined);
});

it("processes receipts", async () => {
const roomId = "!room:id";
const alice = "@alice:alice";
const lastEvent = mkOwnEvent(EventType.RoomMessage, { body: "hello" });
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomId, {
name: "Room with receipts",
required_state: [],
timeline: [
mkOwnStateEvent(EventType.RoomCreate, { creator: selfUserId }, ""),
mkOwnStateEvent(EventType.RoomMember, { membership: "join" }, selfUserId),
mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""),
{
type: EventType.RoomMember,
state_key: alice,
content: { membership: "join" },
sender: alice,
origin_server_ts: Date.now(),
event_id: "$alice",
},
lastEvent,
],
initial: true,
});
const room = client!.getRoom(roomId)!;
expect(room).toBeDefined();
expect(room.getReadReceiptForUserId(alice, true)).toBeNull();
ext.onResponse(
generateReceiptResponse(alice, roomId, lastEvent.event_id, "m.read", 1234567),
);
const receipt = room.getReadReceiptForUserId(alice);
expect(receipt).toBeDefined();
expect(receipt?.eventId).toEqual(lastEvent.event_id);
expect(receipt?.data.ts).toEqual(1234567);
expect(receipt?.data.thread_id).toBeFalsy();
});

it("gracefully handles missing rooms when receiving receipts", async () => {
const roomId = "!room:id";
const alice = "@alice:alice";
const eventId = "$something";
ext.onResponse(
generateReceiptResponse(alice, roomId, eventId, "m.read", 1234567),
);
// we expect it not to crash
});
});
});
64 changes: 52 additions & 12 deletions src/sliding-sync-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,29 +246,55 @@ class ExtensionTyping implements Extension {

public onRequest(isInitial: boolean): object | undefined {
if (!isInitial) {
return undefined;
return undefined; // don't send a JSON object for subsequent requests, we don't need to.
}
return {
enabled: true,
};
}

public onResponse(data: {rooms: Record<string, Event[]>}): void {
public onResponse(data: {rooms: Record<string, IMinimalEvent>}): void {
if (!data || !data.rooms) {
return;
}

for (const roomId in data.rooms) {
const ephemeralEvents = mapEvents(this.client, roomId, [data.rooms[roomId]]);
const room = this.client.getRoom(roomId);
if (!room) {
logger.warn("got typing events for room but room doesn't exist on client:", roomId);
continue;
}
room.addEphemeralEvents(ephemeralEvents);
ephemeralEvents.forEach((e) => {
this.client.emit(ClientEvent.Event, e);
});
processEphemeralEvents(
this.client, roomId, [data.rooms[roomId]],
);
}
}
}

class ExtensionReceipts implements Extension {
public constructor(private readonly client: MatrixClient) {}

public name(): string {
return "receipts";
}

public when(): ExtensionState {
return ExtensionState.PostProcess;
}

public onRequest(isInitial: boolean): object | undefined {
if (isInitial) {
return {
enabled: true,
};
}
return undefined; // don't send a JSON object for subsequent requests, we don't need to.
}

public onResponse(data: {rooms: Record<string, IMinimalEvent>}): void {
if (!data || !data.rooms) {
return;
}

for (const roomId in data.rooms) {
processEphemeralEvents(
this.client, roomId, [data.rooms[roomId]],
);
}
}
}
Expand Down Expand Up @@ -314,6 +340,7 @@ export class SlidingSyncSdk {
new ExtensionToDevice(this.client),
new ExtensionAccountData(this.client),
new ExtensionTyping(this.client),
new ExtensionReceipts(this.client),
];
if (this.opts.crypto) {
extensions.push(
Expand Down Expand Up @@ -929,3 +956,16 @@ function mapEvents(client: MatrixClient, roomId: string | undefined, events: obj
return mapper(e);
});
}

function processEphemeralEvents(client: MatrixClient, roomId: string, ephEvents: IMinimalEvent[]): void {
const ephemeralEvents = mapEvents(client, roomId, ephEvents);
const room = client.getRoom(roomId);
if (!room) {
logger.warn("got ephemeral events for room but room doesn't exist on client:", roomId);
return;
}
room.addEphemeralEvents(ephemeralEvents);
ephemeralEvents.forEach((e) => {
client.emit(ClientEvent.Event, e);
});
}

0 comments on commit 7fd55a6

Please sign in to comment.