You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-09 10:22:46 +03:00
Merge pull request #2912 from matrix-org/kegan/ss-receipts
sliding sync: add receipts extension
This commit is contained in:
@@ -928,4 +928,90 @@ describe("SlidingSyncSdk", () => {
|
|||||||
expect(room.getMember(selfUserId)?.typing).toEqual(false);
|
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
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -246,29 +246,55 @@ class ExtensionTyping implements Extension {
|
|||||||
|
|
||||||
public onRequest(isInitial: boolean): object | undefined {
|
public onRequest(isInitial: boolean): object | undefined {
|
||||||
if (!isInitial) {
|
if (!isInitial) {
|
||||||
return undefined;
|
return undefined; // don't send a JSON object for subsequent requests, we don't need to.
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public onResponse(data: {rooms: Record<string, Event[]>}): void {
|
public onResponse(data: {rooms: Record<string, IMinimalEvent>}): void {
|
||||||
if (!data || !data.rooms) {
|
if (!data || !data.rooms) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const roomId in data.rooms) {
|
for (const roomId in data.rooms) {
|
||||||
const ephemeralEvents = mapEvents(this.client, roomId, [data.rooms[roomId]]);
|
processEphemeralEvents(
|
||||||
const room = this.client.getRoom(roomId);
|
this.client, roomId, [data.rooms[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);
|
|
||||||
});
|
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]],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -314,6 +340,7 @@ export class SlidingSyncSdk {
|
|||||||
new ExtensionToDevice(this.client),
|
new ExtensionToDevice(this.client),
|
||||||
new ExtensionAccountData(this.client),
|
new ExtensionAccountData(this.client),
|
||||||
new ExtensionTyping(this.client),
|
new ExtensionTyping(this.client),
|
||||||
|
new ExtensionReceipts(this.client),
|
||||||
];
|
];
|
||||||
if (this.opts.crypto) {
|
if (this.opts.crypto) {
|
||||||
extensions.push(
|
extensions.push(
|
||||||
@@ -929,3 +956,16 @@ function mapEvents(client: MatrixClient, roomId: string | undefined, events: obj
|
|||||||
return mapper(e);
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user