From 4db913cccb479d1ae93b1f8caf2945fe8dfa3379 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 5 Jun 2023 13:17:17 +0100 Subject: [PATCH 1/5] Make sliding sync linearize processing of sync requests --- src/sliding-sync-sdk.ts | 7 ++++--- src/sliding-sync.ts | 27 ++++++++++++++------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/sliding-sync-sdk.ts b/src/sliding-sync-sdk.ts index 27eae2d94b..9532406116 100644 --- a/src/sliding-sync-sdk.ts +++ b/src/sliding-sync-sdk.ts @@ -17,7 +17,7 @@ limitations under the License. import type { SyncCryptoCallbacks } from "./common-crypto/CryptoBackend"; import { NotificationCountType, Room, RoomEvent } from "./models/room"; import { logger } from "./logger"; -import { promiseMapSeries } from "./utils"; +import { IDeferred, promiseMapSeries } from "./utils"; import { EventTimeline } from "./models/event-timeline"; import { ClientEvent, IStoredClientOpts, MatrixClient } from "./client"; import { @@ -376,7 +376,7 @@ export class SlidingSyncSdk { }); } - private onRoomData(roomId: string, roomData: MSC3575RoomData): void { + private async onRoomData(roomId: string, roomData: MSC3575RoomData, deferred: IDeferred): Promise { let room = this.client.store.getRoom(roomId); if (!room) { if (!roomData.initial) { @@ -385,7 +385,8 @@ export class SlidingSyncSdk { } room = _createAndReEmitRoom(this.client, roomId, this.opts); } - this.processRoomData(this.client, room, roomData); + await this.processRoomData(this.client, room!, roomData); + deferred.resolve(); } private onLifecycle(state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, err?: Error): void { diff --git a/src/sliding-sync.ts b/src/sliding-sync.ts index dde5f1be73..2b98982e1b 100644 --- a/src/sliding-sync.ts +++ b/src/sliding-sync.ts @@ -326,7 +326,12 @@ export enum SlidingSyncEvent { } export type SlidingSyncEventHandlerMap = { - [SlidingSyncEvent.RoomData]: (roomId: string, roomData: MSC3575RoomData) => void; + // The deferred must be resolved for the next sync request to be made + [SlidingSyncEvent.RoomData]: ( + roomId: string, + roomData: MSC3575RoomData, + deferred: IDeferred, + ) => Promise; [SlidingSyncEvent.Lifecycle]: ( state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, @@ -567,14 +572,16 @@ export class SlidingSync extends TypedEventEmitter { if (!roomData.required_state) { roomData.required_state = []; } if (!roomData.timeline) { roomData.timeline = []; } - this.emit(SlidingSyncEvent.RoomData, roomId, roomData); + const deferred = defer(); + this.emit(SlidingSyncEvent.RoomData, roomId, roomData, deferred); + await deferred.promise; } /** @@ -767,13 +774,7 @@ export class SlidingSync extends TypedEventEmitter txnIdDefer.txnId === txnId); if (txnIndex === -1) { // this shouldn't happen; we shouldn't be seeing txn_ids for things we don't know about, // whine about it. @@ -923,9 +924,9 @@ export class SlidingSync extends TypedEventEmitter { - this.invokeRoomDataListeners(roomId, resp!.rooms[roomId]); - }); + for (const roomId in resp.rooms) { + await this.invokeRoomDataListeners(roomId, resp!.rooms[roomId]); + } const listKeysWithUpdates: Set = new Set(); if (!doNotUpdateList) { From aaba9c38355e59f9e02f96c00fda21135244533f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 5 Jun 2023 13:21:55 +0100 Subject: [PATCH 2/5] Iterate --- spec/integ/sliding-sync-sdk.spec.ts | 267 +++++++++++++++++----------- 1 file changed, 161 insertions(+), 106 deletions(-) diff --git a/spec/integ/sliding-sync-sdk.spec.ts b/spec/integ/sliding-sync-sdk.spec.ts index dfec79e158..d3b778b099 100644 --- a/spec/integ/sliding-sync-sdk.spec.ts +++ b/spec/integ/sliding-sync-sdk.spec.ts @@ -20,7 +20,7 @@ import { fail } from "assert"; import { SlidingSync, SlidingSyncEvent, MSC3575RoomData, SlidingSyncState, Extension } from "../../src/sliding-sync"; import { TestClient } from "../TestClient"; -import { IRoomEvent, IStateEvent } from "../../src/sync-accumulator"; +import { IRoomEvent, IStateEvent } from "../../src"; import { MatrixClient, MatrixEvent, @@ -303,7 +303,7 @@ describe("SlidingSyncSdk", () => { }; it("can be created with required_state and timeline", async () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomA, data[roomA]); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomA, data[roomA], defer()); await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomA); expect(gotRoom).toBeTruthy(); @@ -313,7 +313,7 @@ describe("SlidingSyncSdk", () => { }); it("can be created with timeline only", async () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomB, data[roomB]); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomB, data[roomB], defer()); await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomB); expect(gotRoom).toBeTruthy(); @@ -323,7 +323,7 @@ describe("SlidingSyncSdk", () => { }); it("can be created with a highlight_count", async () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomC, data[roomC]); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomC, data[roomC], defer()); await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomC); expect(gotRoom).toBeTruthy(); @@ -333,7 +333,7 @@ describe("SlidingSyncSdk", () => { }); it("can be created with a notification_count", async () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomD, data[roomD]); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomD, data[roomD], defer()); await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomD); expect(gotRoom).toBeTruthy(); @@ -343,7 +343,7 @@ describe("SlidingSyncSdk", () => { }); it("can be created with an invited/joined_count", async () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomG, data[roomG]); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomG, data[roomG], defer()); await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomG); expect(gotRoom).toBeTruthy(); @@ -366,7 +366,7 @@ describe("SlidingSyncSdk", () => { } }; client!.on(RoomEvent.Timeline, listener); - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomH, data[roomH]); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomH, data[roomH], defer()); await emitPromise(client!, ClientEvent.Room); client!.off(RoomEvent.Timeline, listener); const gotRoom = client!.getRoom(roomH); @@ -379,7 +379,7 @@ describe("SlidingSyncSdk", () => { }); it("can be created with invite_state", async () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomE, data[roomE]); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomE, data[roomE], defer()); await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomE); expect(gotRoom).toBeTruthy(); @@ -388,7 +388,7 @@ describe("SlidingSyncSdk", () => { }); it("uses the 'name' field to caluclate the room name", async () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomF, data[roomF]); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomF, data[roomF], defer()); await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomF); expect(gotRoom).toBeTruthy(); @@ -398,11 +398,16 @@ describe("SlidingSyncSdk", () => { describe("updating", () => { it("can update with a new timeline event", async () => { const newEvent = mkOwnEvent(EventType.RoomMessage, { body: "new event A" }); - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomA, { - timeline: [newEvent], - required_state: [], - name: data[roomA].name, - }); + mockSlidingSync!.emit( + SlidingSyncEvent.RoomData, + roomA, + { + timeline: [newEvent], + required_state: [], + name: data[roomA].name, + }, + defer(), + ); const gotRoom = client!.getRoom(roomA); expect(gotRoom).toBeTruthy(); if (gotRoom == null) { @@ -420,11 +425,16 @@ describe("SlidingSyncSdk", () => { return; } expect(gotRoom!.getJoinRule()).toEqual(JoinRule.Invite); // default - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomB, { - required_state: [mkOwnStateEvent("m.room.join_rules", { join_rule: "restricted" }, "")], - timeline: [], - name: data[roomB].name, - }); + mockSlidingSync!.emit( + SlidingSyncEvent.RoomData, + roomB, + { + required_state: [mkOwnStateEvent("m.room.join_rules", { join_rule: "restricted" }, "")], + timeline: [], + name: data[roomB].name, + }, + defer(), + ); gotRoom = client!.getRoom(roomB); expect(gotRoom).toBeTruthy(); if (gotRoom == null) { @@ -434,12 +444,17 @@ describe("SlidingSyncSdk", () => { }); it("can update with a new highlight_count", async () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomC, { - name: data[roomC].name, - required_state: [], - timeline: [], - highlight_count: 1, - }); + mockSlidingSync!.emit( + SlidingSyncEvent.RoomData, + roomC, + { + name: data[roomC].name, + required_state: [], + timeline: [], + highlight_count: 1, + }, + defer(), + ); const gotRoom = client!.getRoom(roomC); expect(gotRoom).toBeTruthy(); if (gotRoom == null) { @@ -449,12 +464,17 @@ describe("SlidingSyncSdk", () => { }); it("can update with a new notification_count", async () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomD, { - name: data[roomD].name, - required_state: [], - timeline: [], - notification_count: 1, - }); + mockSlidingSync!.emit( + SlidingSyncEvent.RoomData, + roomD, + { + name: data[roomD].name, + required_state: [], + timeline: [], + notification_count: 1, + }, + defer(), + ); const gotRoom = client!.getRoom(roomD); expect(gotRoom).toBeTruthy(); if (gotRoom == null) { @@ -464,12 +484,17 @@ describe("SlidingSyncSdk", () => { }); it("can update with a new joined_count", () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomG, { - name: data[roomD].name, - required_state: [], - timeline: [], - joined_count: 1, - }); + mockSlidingSync!.emit( + SlidingSyncEvent.RoomData, + roomG, + { + name: data[roomD].name, + required_state: [], + timeline: [], + joined_count: 1, + }, + defer(), + ); const gotRoom = client!.getRoom(roomG); expect(gotRoom).toBeTruthy(); if (gotRoom == null) { @@ -490,12 +515,17 @@ describe("SlidingSyncSdk", () => { mkOwnEvent(EventType.RoomMessage, { body: "old event C" }), ...timeline, ]; - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomA, { - timeline: oldTimeline, - required_state: [], - name: data[roomA].name, - initial: true, // e.g requested via room subscription - }); + mockSlidingSync!.emit( + SlidingSyncEvent.RoomData, + roomA, + { + timeline: oldTimeline, + required_state: [], + name: data[roomA].name, + initial: true, // e.g requested via room subscription + }, + defer(), + ); const gotRoom = client!.getRoom(roomA); expect(gotRoom).toBeTruthy(); if (gotRoom == null) { @@ -597,17 +627,22 @@ describe("SlidingSyncSdk", () => { displayname: "The Invitee", }; httpBackend!.when("GET", "/profile").respond(200, inviteeProfile); - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomId, { - initial: true, - name: "Room with Invite", - required_state: [], - timeline: [ - mkOwnStateEvent(EventType.RoomCreate, { creator: selfUserId }, ""), - mkOwnStateEvent(EventType.RoomMember, { membership: "join" }, selfUserId), - mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""), - mkOwnStateEvent(EventType.RoomMember, { membership: "invite" }, invitee), - ], - }); + mockSlidingSync!.emit( + SlidingSyncEvent.RoomData, + roomId, + { + initial: true, + name: "Room with Invite", + required_state: [], + timeline: [ + mkOwnStateEvent(EventType.RoomCreate, { creator: selfUserId }, ""), + mkOwnStateEvent(EventType.RoomMember, { membership: "join" }, selfUserId), + mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""), + mkOwnStateEvent(EventType.RoomMember, { membership: "invite" }, invitee), + ], + }, + defer(), + ); await httpBackend!.flush("/profile", 1, 1000); await emitPromise(client!, RoomMemberEvent.Name); const room = client!.getRoom(roomId)!; @@ -714,17 +749,22 @@ describe("SlidingSyncSdk", () => { it("processes rooms account data", async () => { const roomId = "!room:id"; - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomId, { - name: "Room with account data", - 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, - }); + mockSlidingSync!.emit( + SlidingSyncEvent.RoomData, + roomId, + { + name: "Room with account data", + 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, + }, + defer(), + ); const roomContent = { foo: "bar", }; @@ -918,17 +958,22 @@ describe("SlidingSyncSdk", () => { 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, - }); + 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, + }, + defer(), + ); await emitPromise(client!, ClientEvent.Room); const room = client!.getRoom(roomId)!; expect(room).toBeTruthy(); @@ -959,17 +1004,22 @@ describe("SlidingSyncSdk", () => { 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, - }); + 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, + }, + defer(), + ); const room = client!.getRoom(roomId)!; expect(room).toBeTruthy(); expect(room.getMember(selfUserId)?.typing).toEqual(false); @@ -1045,25 +1095,30 @@ describe("SlidingSyncSdk", () => { 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, - }); + 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, + }, + defer(), + ); await emitPromise(client!, ClientEvent.Room); const room = client!.getRoom(roomId)!; expect(room).toBeTruthy(); From 645441a4d86fef35cf2ac67d03df60a0c926750d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 5 Jun 2023 13:24:36 +0100 Subject: [PATCH 3/5] Iterate --- src/sliding-sync.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/sliding-sync.ts b/src/sliding-sync.ts index 2b98982e1b..d9947b6f9d 100644 --- a/src/sliding-sync.ts +++ b/src/sliding-sync.ts @@ -327,11 +327,7 @@ export enum SlidingSyncEvent { export type SlidingSyncEventHandlerMap = { // The deferred must be resolved for the next sync request to be made - [SlidingSyncEvent.RoomData]: ( - roomId: string, - roomData: MSC3575RoomData, - deferred: IDeferred, - ) => Promise; + [SlidingSyncEvent.RoomData]: (roomId: string, roomData: MSC3575RoomData, deferred: IDeferred) => void; [SlidingSyncEvent.Lifecycle]: ( state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, From 049ed88c6d94ce4bde986801e6dea07a54e17462 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 5 Jun 2023 14:06:02 +0100 Subject: [PATCH 4/5] Iterate --- spec/integ/sliding-sync-sdk.spec.ts | 267 +++++++++++----------------- src/models/typed-event-emitter.ts | 18 ++ src/sliding-sync-sdk.ts | 5 +- src/sliding-sync.ts | 15 +- 4 files changed, 135 insertions(+), 170 deletions(-) diff --git a/spec/integ/sliding-sync-sdk.spec.ts b/spec/integ/sliding-sync-sdk.spec.ts index d3b778b099..a55316b5fa 100644 --- a/spec/integ/sliding-sync-sdk.spec.ts +++ b/spec/integ/sliding-sync-sdk.spec.ts @@ -39,7 +39,7 @@ import { } from "../../src"; import { SlidingSyncSdk } from "../../src/sliding-sync-sdk"; import { SyncApiOptions, SyncState } from "../../src/sync"; -import { IStoredClientOpts } from "../../src/client"; +import { IStoredClientOpts } from "../../src"; import { logger } from "../../src/logger"; import { emitPromise } from "../test-utils/test-utils"; import { defer } from "../../src/utils"; @@ -303,7 +303,7 @@ describe("SlidingSyncSdk", () => { }; it("can be created with required_state and timeline", async () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomA, data[roomA], defer()); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomA, data[roomA]); await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomA); expect(gotRoom).toBeTruthy(); @@ -313,7 +313,7 @@ describe("SlidingSyncSdk", () => { }); it("can be created with timeline only", async () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomB, data[roomB], defer()); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomB, data[roomB]); await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomB); expect(gotRoom).toBeTruthy(); @@ -323,7 +323,7 @@ describe("SlidingSyncSdk", () => { }); it("can be created with a highlight_count", async () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomC, data[roomC], defer()); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomC, data[roomC]); await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomC); expect(gotRoom).toBeTruthy(); @@ -333,7 +333,7 @@ describe("SlidingSyncSdk", () => { }); it("can be created with a notification_count", async () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomD, data[roomD], defer()); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomD, data[roomD]); await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomD); expect(gotRoom).toBeTruthy(); @@ -343,7 +343,7 @@ describe("SlidingSyncSdk", () => { }); it("can be created with an invited/joined_count", async () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomG, data[roomG], defer()); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomG, data[roomG]); await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomG); expect(gotRoom).toBeTruthy(); @@ -366,7 +366,7 @@ describe("SlidingSyncSdk", () => { } }; client!.on(RoomEvent.Timeline, listener); - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomH, data[roomH], defer()); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomH, data[roomH]); await emitPromise(client!, ClientEvent.Room); client!.off(RoomEvent.Timeline, listener); const gotRoom = client!.getRoom(roomH); @@ -379,7 +379,7 @@ describe("SlidingSyncSdk", () => { }); it("can be created with invite_state", async () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomE, data[roomE], defer()); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomE, data[roomE]); await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomE); expect(gotRoom).toBeTruthy(); @@ -388,7 +388,7 @@ describe("SlidingSyncSdk", () => { }); it("uses the 'name' field to caluclate the room name", async () => { - mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomF, data[roomF], defer()); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomF, data[roomF]); await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomF); expect(gotRoom).toBeTruthy(); @@ -398,16 +398,11 @@ describe("SlidingSyncSdk", () => { describe("updating", () => { it("can update with a new timeline event", async () => { const newEvent = mkOwnEvent(EventType.RoomMessage, { body: "new event A" }); - mockSlidingSync!.emit( - SlidingSyncEvent.RoomData, - roomA, - { - timeline: [newEvent], - required_state: [], - name: data[roomA].name, - }, - defer(), - ); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomA, { + timeline: [newEvent], + required_state: [], + name: data[roomA].name, + }); const gotRoom = client!.getRoom(roomA); expect(gotRoom).toBeTruthy(); if (gotRoom == null) { @@ -425,16 +420,11 @@ describe("SlidingSyncSdk", () => { return; } expect(gotRoom!.getJoinRule()).toEqual(JoinRule.Invite); // default - mockSlidingSync!.emit( - SlidingSyncEvent.RoomData, - roomB, - { - required_state: [mkOwnStateEvent("m.room.join_rules", { join_rule: "restricted" }, "")], - timeline: [], - name: data[roomB].name, - }, - defer(), - ); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomB, { + required_state: [mkOwnStateEvent("m.room.join_rules", { join_rule: "restricted" }, "")], + timeline: [], + name: data[roomB].name, + }); gotRoom = client!.getRoom(roomB); expect(gotRoom).toBeTruthy(); if (gotRoom == null) { @@ -444,17 +434,12 @@ describe("SlidingSyncSdk", () => { }); it("can update with a new highlight_count", async () => { - mockSlidingSync!.emit( - SlidingSyncEvent.RoomData, - roomC, - { - name: data[roomC].name, - required_state: [], - timeline: [], - highlight_count: 1, - }, - defer(), - ); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomC, { + name: data[roomC].name, + required_state: [], + timeline: [], + highlight_count: 1, + }); const gotRoom = client!.getRoom(roomC); expect(gotRoom).toBeTruthy(); if (gotRoom == null) { @@ -464,17 +449,12 @@ describe("SlidingSyncSdk", () => { }); it("can update with a new notification_count", async () => { - mockSlidingSync!.emit( - SlidingSyncEvent.RoomData, - roomD, - { - name: data[roomD].name, - required_state: [], - timeline: [], - notification_count: 1, - }, - defer(), - ); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomD, { + name: data[roomD].name, + required_state: [], + timeline: [], + notification_count: 1, + }); const gotRoom = client!.getRoom(roomD); expect(gotRoom).toBeTruthy(); if (gotRoom == null) { @@ -484,17 +464,12 @@ describe("SlidingSyncSdk", () => { }); it("can update with a new joined_count", () => { - mockSlidingSync!.emit( - SlidingSyncEvent.RoomData, - roomG, - { - name: data[roomD].name, - required_state: [], - timeline: [], - joined_count: 1, - }, - defer(), - ); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomG, { + name: data[roomD].name, + required_state: [], + timeline: [], + joined_count: 1, + }); const gotRoom = client!.getRoom(roomG); expect(gotRoom).toBeTruthy(); if (gotRoom == null) { @@ -515,17 +490,12 @@ describe("SlidingSyncSdk", () => { mkOwnEvent(EventType.RoomMessage, { body: "old event C" }), ...timeline, ]; - mockSlidingSync!.emit( - SlidingSyncEvent.RoomData, - roomA, - { - timeline: oldTimeline, - required_state: [], - name: data[roomA].name, - initial: true, // e.g requested via room subscription - }, - defer(), - ); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomA, { + timeline: oldTimeline, + required_state: [], + name: data[roomA].name, + initial: true, // e.g requested via room subscription + }); const gotRoom = client!.getRoom(roomA); expect(gotRoom).toBeTruthy(); if (gotRoom == null) { @@ -627,22 +597,17 @@ describe("SlidingSyncSdk", () => { displayname: "The Invitee", }; httpBackend!.when("GET", "/profile").respond(200, inviteeProfile); - mockSlidingSync!.emit( - SlidingSyncEvent.RoomData, - roomId, - { - initial: true, - name: "Room with Invite", - required_state: [], - timeline: [ - mkOwnStateEvent(EventType.RoomCreate, { creator: selfUserId }, ""), - mkOwnStateEvent(EventType.RoomMember, { membership: "join" }, selfUserId), - mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""), - mkOwnStateEvent(EventType.RoomMember, { membership: "invite" }, invitee), - ], - }, - defer(), - ); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomId, { + initial: true, + name: "Room with Invite", + required_state: [], + timeline: [ + mkOwnStateEvent(EventType.RoomCreate, { creator: selfUserId }, ""), + mkOwnStateEvent(EventType.RoomMember, { membership: "join" }, selfUserId), + mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""), + mkOwnStateEvent(EventType.RoomMember, { membership: "invite" }, invitee), + ], + }); await httpBackend!.flush("/profile", 1, 1000); await emitPromise(client!, RoomMemberEvent.Name); const room = client!.getRoom(roomId)!; @@ -749,22 +714,17 @@ describe("SlidingSyncSdk", () => { it("processes rooms account data", async () => { const roomId = "!room:id"; - mockSlidingSync!.emit( - SlidingSyncEvent.RoomData, - roomId, - { - name: "Room with account data", - 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, - }, - defer(), - ); + mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomId, { + name: "Room with account data", + 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 roomContent = { foo: "bar", }; @@ -958,22 +918,17 @@ describe("SlidingSyncSdk", () => { 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, - }, - defer(), - ); + 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, + }); await emitPromise(client!, ClientEvent.Room); const room = client!.getRoom(roomId)!; expect(room).toBeTruthy(); @@ -1004,22 +959,17 @@ describe("SlidingSyncSdk", () => { 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, - }, - defer(), - ); + 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).toBeTruthy(); expect(room.getMember(selfUserId)?.typing).toEqual(false); @@ -1095,30 +1045,25 @@ describe("SlidingSyncSdk", () => { 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, - }, - defer(), - ); + 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, + }); await emitPromise(client!, ClientEvent.Room); const room = client!.getRoom(roomId)!; expect(room).toBeTruthy(); diff --git a/src/models/typed-event-emitter.ts b/src/models/typed-event-emitter.ts index 7eac48b962..de0dd2b869 100644 --- a/src/models/typed-event-emitter.ts +++ b/src/models/typed-event-emitter.ts @@ -89,6 +89,24 @@ export class TypedEventEmitter< return super.emit(event, ...args); } + /** + * Similar to `emit` but calls all listeners within a `Promise.all` and returns the promise chain + * @param event - The name of the event to emit + * @param args - Arguments to pass to the listener + * @returns `true` if the event had listeners, `false` otherwise. + */ + public async emitPromised( + event: T, + ...args: Parameters + ): Promise; + public async emitPromised(event: T, ...args: Parameters): Promise; + public async emitPromised(event: T, ...args: any[]): Promise { + const listeners = this.listeners(event); + return Promise.allSettled(listeners.map((l) => l(...args))).then(() => { + return listeners.length > 0; + }); + } + /** * Returns the number of listeners listening to the event named `event`. * diff --git a/src/sliding-sync-sdk.ts b/src/sliding-sync-sdk.ts index 9532406116..36eed5d73e 100644 --- a/src/sliding-sync-sdk.ts +++ b/src/sliding-sync-sdk.ts @@ -17,7 +17,7 @@ limitations under the License. import type { SyncCryptoCallbacks } from "./common-crypto/CryptoBackend"; import { NotificationCountType, Room, RoomEvent } from "./models/room"; import { logger } from "./logger"; -import { IDeferred, promiseMapSeries } from "./utils"; +import { promiseMapSeries } from "./utils"; import { EventTimeline } from "./models/event-timeline"; import { ClientEvent, IStoredClientOpts, MatrixClient } from "./client"; import { @@ -376,7 +376,7 @@ export class SlidingSyncSdk { }); } - private async onRoomData(roomId: string, roomData: MSC3575RoomData, deferred: IDeferred): Promise { + private async onRoomData(roomId: string, roomData: MSC3575RoomData): Promise { let room = this.client.store.getRoom(roomId); if (!room) { if (!roomData.initial) { @@ -386,7 +386,6 @@ export class SlidingSyncSdk { room = _createAndReEmitRoom(this.client, roomId, this.opts); } await this.processRoomData(this.client, room!, roomData); - deferred.resolve(); } private onLifecycle(state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, err?: Error): void { diff --git a/src/sliding-sync.ts b/src/sliding-sync.ts index d9947b6f9d..933e253559 100644 --- a/src/sliding-sync.ts +++ b/src/sliding-sync.ts @@ -326,8 +326,7 @@ export enum SlidingSyncEvent { } export type SlidingSyncEventHandlerMap = { - // The deferred must be resolved for the next sync request to be made - [SlidingSyncEvent.RoomData]: (roomId: string, roomData: MSC3575RoomData, deferred: IDeferred) => void; + [SlidingSyncEvent.RoomData]: (roomId: string, roomData: MSC3575RoomData) => void; [SlidingSyncEvent.Lifecycle]: ( state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, @@ -575,9 +574,7 @@ export class SlidingSync extends TypedEventEmitter(); - this.emit(SlidingSyncEvent.RoomData, roomId, roomData, deferred); - await deferred.promise; + this.emitPromised(SlidingSyncEvent.RoomData, roomId, roomData); } /** @@ -770,7 +767,13 @@ export class SlidingSync extends TypedEventEmitter txnIdDefer.txnId === txnId); + let txnIndex = -1; + for (let i = 0; i < this.txnIdDefers.length; i++) { + if (this.txnIdDefers[i].txnId === txnId) { + txnIndex = i; + break; + } + } if (txnIndex === -1) { // this shouldn't happen; we shouldn't be seeing txn_ids for things we don't know about, // whine about it. From 7436c5e6098484f0d301d087c014c22e5ab4d549 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 5 Jun 2023 14:14:34 +0100 Subject: [PATCH 5/5] Iterate --- src/sliding-sync.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sliding-sync.ts b/src/sliding-sync.ts index 933e253559..a45a142d58 100644 --- a/src/sliding-sync.ts +++ b/src/sliding-sync.ts @@ -326,7 +326,7 @@ export enum SlidingSyncEvent { } export type SlidingSyncEventHandlerMap = { - [SlidingSyncEvent.RoomData]: (roomId: string, roomData: MSC3575RoomData) => void; + [SlidingSyncEvent.RoomData]: (roomId: string, roomData: MSC3575RoomData) => Promise | void; [SlidingSyncEvent.Lifecycle]: ( state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, @@ -574,7 +574,7 @@ export class SlidingSync extends TypedEventEmitter