Skip to content

Commit

Permalink
Merge pull request #2893 from matrix-org/kegan/ss-typing
Browse files Browse the repository at this point in the history
sliding sync: add support for typing extension
  • Loading branch information
kegsay authored Nov 21, 2022
2 parents 7217f83 + 305b83f commit f3dc1c4
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 0 deletions.
119 changes: 119 additions & 0 deletions spec/integ/sliding-sync-sdk.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ describe("SlidingSyncSdk", () => {

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

beforeAll(async () => {
await setupClient({
withCrypto: true,
Expand All @@ -551,18 +552,21 @@ describe("SlidingSyncSdk", () => {
await hasSynced;
ext = findExtension("e2ee");
});

afterAll(async () => {
// needed else we do some async operations in the background which can cause Jest to whine:
// "Cannot log after tests are done. Did you forget to wait for something async in your test?"
// Attempted to log "Saving device tracking data null"."
client!.crypto!.stop();
});

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

it("can update device lists", () => {
ext.onResponse({
device_lists: {
Expand All @@ -572,6 +576,7 @@ describe("SlidingSyncSdk", () => {
});
// TODO: more assertions?
});

it("can update OTK counts", () => {
client!.crypto!.updateOneTimeKeyCount = jest.fn();
ext.onResponse({
Expand All @@ -588,6 +593,7 @@ describe("SlidingSyncSdk", () => {
});
expect(client!.crypto!.updateOneTimeKeyCount).toHaveBeenCalledWith(0);
});

it("can update fallback keys", () => {
ext.onResponse({
device_unused_fallback_key_types: ["signed_curve25519"],
Expand All @@ -599,21 +605,25 @@ describe("SlidingSyncSdk", () => {
expect(client!.crypto!.getNeedsNewFallback()).toEqual(true);
});
});

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

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

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

it("processes global account data", async () => {
const globalType = "global_test";
const globalContent = {
Expand All @@ -633,6 +643,7 @@ describe("SlidingSyncSdk", () => {
expect(globalData).toBeDefined();
expect(globalData.getContent()).toEqual(globalContent);
});

it("processes rooms account data", async () => {
const roomId = "!room:id";
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomId, {
Expand Down Expand Up @@ -667,6 +678,7 @@ describe("SlidingSyncSdk", () => {
expect(event).toBeDefined();
expect(event.getContent()).toEqual(roomContent);
});

it("doesn't crash for unknown room account data", async () => {
const unknownRoomId = "!unknown:id";
const roomType = "tester";
Expand All @@ -686,6 +698,7 @@ describe("SlidingSyncSdk", () => {
expect(room).toBeNull();
expect(client!.getAccountData(roomType)).toBeUndefined();
});

it("can update push rules via account data", async () => {
const roomId = "!foo:bar";
const pushRulesContent: IPushRules = {
Expand Down Expand Up @@ -718,21 +731,25 @@ describe("SlidingSyncSdk", () => {
expect(pushRule).toEqual(pushRulesContent.global[PushRuleKind.RoomSpecific]![0]);
});
});

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

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

it("gets enabled with a limit on the initial request only", () => {
const reqJson: any = ext.onRequest(true);
expect(reqJson.enabled).toEqual(true);
expect(reqJson.limit).toBeGreaterThan(0);
expect(reqJson.since).toBeUndefined();
});

it("updates the since value", async () => {
ext.onResponse({
next_batch: "12345",
Expand All @@ -742,12 +759,14 @@ describe("SlidingSyncSdk", () => {
since: "12345",
});
});

it("can handle missing fields", async () => {
ext.onResponse({
next_batch: "23456",
// no events array
});
});

it("emits to-device events on the client", async () => {
const toDeviceType = "custom_test";
const toDeviceContent = {
Expand All @@ -770,6 +789,7 @@ describe("SlidingSyncSdk", () => {
});
expect(called).toBe(true);
});

it("can cancel key verification requests", async () => {
const seen: Record<string, boolean> = {};
client!.on(ClientEvent.ToDeviceEvent, (ev) => {
Expand Down Expand Up @@ -809,4 +829,103 @@ describe("SlidingSyncSdk", () => {
});
});
});

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

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

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

it("processes typing notifications", async () => {
const roomId = "!room:id";
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomId, {
name: "Room with typing",
required_state: [],
timeline: [
mkOwnStateEvent(EventType.RoomCreate, { creator: selfUserId }, ""),
mkOwnStateEvent(EventType.RoomMember, { membership: "join" }, selfUserId),
mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""),
mkOwnEvent(EventType.RoomMessage, { body: "hello" }),
],
initial: true,
});
const room = client!.getRoom(roomId)!;
expect(room).toBeDefined();
expect(room.getMember(selfUserId)?.typing).toEqual(false);
ext.onResponse({
rooms: {
[roomId]: {
type: EventType.Typing,
content: {
user_ids: [selfUserId],
},
},
},
});
expect(room.getMember(selfUserId)?.typing).toEqual(true);
ext.onResponse({
rooms: {
[roomId]: {
type: EventType.Typing,
content: {
user_ids: [],
},
},
},
});
expect(room.getMember(selfUserId)?.typing).toEqual(false);
});

it("gracefully handles missing rooms and members when typing", async () => {
const roomId = "!room:id";
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomId, {
name: "Room with typing",
required_state: [],
timeline: [
mkOwnStateEvent(EventType.RoomCreate, { creator: selfUserId }, ""),
mkOwnStateEvent(EventType.RoomMember, { membership: "join" }, selfUserId),
mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""),
mkOwnEvent(EventType.RoomMessage, { body: "hello" }),
],
initial: true,
});
const room = client!.getRoom(roomId)!;
expect(room).toBeDefined();
expect(room.getMember(selfUserId)?.typing).toEqual(false);
ext.onResponse({
rooms: {
[roomId]: {
type: EventType.Typing,
content: {
user_ids: ["@someone:else"],
},
},
},
});
expect(room.getMember(selfUserId)?.typing).toEqual(false);
ext.onResponse({
rooms: {
"!something:else": {
type: EventType.Typing,
content: {
user_ids: [selfUserId],
},
},
},
});
expect(room.getMember(selfUserId)?.typing).toEqual(false);
});
});
});
41 changes: 41 additions & 0 deletions src/sliding-sync-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,46 @@ class ExtensionAccountData implements Extension {
}
}

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

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

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

public onRequest(isInitial: boolean): object | undefined {
if (!isInitial) {
return undefined;
}
return {
enabled: true,
};
}

public onResponse(data: {rooms: Record<string, Event[]>}): 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);
});
}
}
}

/**
* A copy of SyncApi such that it can be used as a drop-in replacement for sync v2. For the actual
* sliding sync API, see sliding-sync.ts or the class SlidingSync.
Expand Down Expand Up @@ -273,6 +313,7 @@ export class SlidingSyncSdk {
const extensions: Extension[] = [
new ExtensionToDevice(this.client),
new ExtensionAccountData(this.client),
new ExtensionTyping(this.client),
];
if (this.opts.crypto) {
extensions.push(
Expand Down

0 comments on commit f3dc1c4

Please sign in to comment.