From 02aa3edda465f311f716a173128ff0d1075b8435 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Mon, 23 Jan 2023 12:21:47 +0000 Subject: [PATCH] Revert "refactor: sliding sync: swap to lists-as-keys (#3076)" Reverting because the companion matrix-react-sdk change is not ready so this is breaking our builds. This reverts commit e04ea02c6208a7d261d6348adaad0f26b7c68c41. --- spec/integ/sliding-sync-sdk.spec.ts | 9 +- spec/integ/sliding-sync.spec.ts | 298 ++++++++++++++-------------- src/sliding-sync.ts | 195 ++++++++---------- 3 files changed, 236 insertions(+), 266 deletions(-) diff --git a/spec/integ/sliding-sync-sdk.spec.ts b/spec/integ/sliding-sync-sdk.spec.ts index 98c3879f3..a54cf71cb 100644 --- a/spec/integ/sliding-sync-sdk.spec.ts +++ b/spec/integ/sliding-sync-sdk.spec.ts @@ -52,9 +52,10 @@ describe("SlidingSyncSdk", () => { const selfAccessToken = "aseukfgwef"; const mockifySlidingSync = (s: SlidingSync): SlidingSync => { - s.getListParams = jest.fn(); + s.getList = jest.fn(); s.getListData = jest.fn(); s.getRoomSubscriptions = jest.fn(); + s.listLength = jest.fn(); s.modifyRoomSubscriptionInfo = jest.fn(); s.modifyRoomSubscriptions = jest.fn(); s.registerExtension = jest.fn(); @@ -114,7 +115,7 @@ describe("SlidingSyncSdk", () => { const testClient = new TestClient(selfUserId, "DEVICE", selfAccessToken); httpBackend = testClient.httpBackend; client = testClient.client; - mockSlidingSync = mockifySlidingSync(new SlidingSync("", new Map(), {}, client, 0)); + mockSlidingSync = mockifySlidingSync(new SlidingSync("", [], {}, client, 0)); if (testOpts.withCrypto) { httpBackend!.when("GET", "/room_keys/version").respond(404, {}); await client!.initCrypto(); @@ -548,7 +549,7 @@ describe("SlidingSyncSdk", () => { it("emits SyncState.Reconnecting when < FAILED_SYNC_ERROR_THRESHOLD & SyncState.Error when over", async () => { mockSlidingSync!.emit(SlidingSyncEvent.Lifecycle, SlidingSyncState.Complete, { pos: "h", - lists: {}, + lists: [], rooms: {}, extensions: {}, }); @@ -576,7 +577,7 @@ describe("SlidingSyncSdk", () => { it("emits SyncState.Syncing after a previous SyncState.Error", async () => { mockSlidingSync!.emit(SlidingSyncEvent.Lifecycle, SlidingSyncState.Complete, { pos: "i", - lists: {}, + lists: [], rooms: {}, extensions: {}, }); diff --git a/spec/integ/sliding-sync.spec.ts b/spec/integ/sliding-sync.spec.ts index 38094be45..368ace4e8 100644 --- a/spec/integ/sliding-sync.spec.ts +++ b/spec/integ/sliding-sync.spec.ts @@ -64,10 +64,10 @@ describe("SlidingSync", () => { let slidingSync: SlidingSync; it("should start the sync loop upon calling start()", async () => { - slidingSync = new SlidingSync(proxyBaseUrl, new Map(), {}, client!, 1); + slidingSync = new SlidingSync(proxyBaseUrl, [], {}, client!, 1); const fakeResp = { pos: "a", - lists: {}, + lists: [], rooms: {}, extensions: {}, }; @@ -90,7 +90,7 @@ describe("SlidingSync", () => { it("should reset the connection on HTTP 400 and send everything again", async () => { // seed the connection with some lists, extensions and subscriptions to verify they are sent again - slidingSync = new SlidingSync(proxyBaseUrl, new Map(), {}, client!, 1); + slidingSync = new SlidingSync(proxyBaseUrl, [], {}, client!, 1); const roomId = "!sub:localhost"; const subInfo = { timeline_limit: 42, @@ -114,7 +114,7 @@ describe("SlidingSync", () => { }; slidingSync.modifyRoomSubscriptions(new Set([roomId])); slidingSync.modifyRoomSubscriptionInfo(subInfo); - slidingSync.setList("a", listInfo); + slidingSync.setList(0, listInfo); slidingSync.registerExtension(ext); slidingSync.start(); @@ -128,7 +128,7 @@ describe("SlidingSync", () => { expect(body.room_subscriptions).toEqual({ [roomId]: subInfo, }); - expect(body.lists["a"]).toEqual(listInfo); + expect(body.lists[0]).toEqual(listInfo); expect(body.extensions).toBeTruthy(); expect(body.extensions["custom_extension"]).toEqual({ initial: true }); expect(req.queryParams!["pos"]).toBeUndefined(); @@ -137,7 +137,7 @@ describe("SlidingSync", () => { .respond(200, function () { return { pos: "11", - lists: { a: { count: 5 } }, + lists: [{ count: 5 }], extensions: {}, txn_id: txnId, }; @@ -151,7 +151,7 @@ describe("SlidingSync", () => { const body = req.data; logger.debug("got ", body); expect(body.room_subscriptions).toBeFalsy(); - expect(body.lists["a"]).toEqual({ + expect(body.lists[0]).toEqual({ ranges: [[0, 10]], }); expect(body.extensions).toBeTruthy(); @@ -161,7 +161,7 @@ describe("SlidingSync", () => { .respond(200, function () { return { pos: "12", - lists: { a: { count: 5 } }, + lists: [{ count: 5 }], extensions: {}, }; }); @@ -185,7 +185,7 @@ describe("SlidingSync", () => { expect(body.room_subscriptions).toEqual({ [roomId]: subInfo, }); - expect(body.lists["a"]).toEqual(listInfo); + expect(body.lists[0]).toEqual(listInfo); expect(body.extensions).toBeTruthy(); expect(body.extensions["custom_extension"]).toEqual({ initial: true }); expect(req.queryParams!["pos"]).toBeUndefined(); @@ -193,7 +193,7 @@ describe("SlidingSync", () => { .respond(200, function () { return { pos: "1", - lists: { a: { count: 6 } }, + lists: [{ count: 6 }], extensions: {}, }; }); @@ -221,7 +221,7 @@ describe("SlidingSync", () => { it("should be able to subscribe to a room", async () => { // add the subscription - slidingSync = new SlidingSync(proxyBaseUrl, new Map(), roomSubInfo, client!, 1); + slidingSync = new SlidingSync(proxyBaseUrl, [], roomSubInfo, client!, 1); slidingSync.modifyRoomSubscriptions(new Set([roomId])); httpBackend! .when("POST", syncUrl) @@ -233,7 +233,7 @@ describe("SlidingSync", () => { }) .respond(200, { pos: "a", - lists: {}, + lists: [], extensions: {}, rooms: { [roomId]: wantRoomData, @@ -266,7 +266,7 @@ describe("SlidingSync", () => { }) .respond(200, { pos: "a", - lists: {}, + lists: [], extensions: {}, rooms: { [roomId]: wantRoomData, @@ -313,7 +313,7 @@ describe("SlidingSync", () => { }) .respond(200, { pos: "b", - lists: {}, + lists: [], extensions: {}, rooms: { [anotherRoomID]: anotherRoomData, @@ -344,7 +344,7 @@ describe("SlidingSync", () => { }) .respond(200, { pos: "b", - lists: {}, + lists: [], }); const p = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => { @@ -402,19 +402,19 @@ describe("SlidingSync", () => { is_dm: true, }, }; - slidingSync = new SlidingSync(proxyBaseUrl, new Map([["a", listReq]]), {}, client!, 1); + slidingSync = new SlidingSync(proxyBaseUrl, [listReq], {}, client!, 1); httpBackend! .when("POST", syncUrl) .check(function (req) { const body = req.data; logger.log("list", body); expect(body.lists).toBeTruthy(); - expect(body.lists["a"]).toEqual(listReq); + expect(body.lists[0]).toEqual(listReq); }) .respond(200, { pos: "a", - lists: { - a: { + lists: [ + { count: 500, ops: [ { @@ -424,7 +424,7 @@ describe("SlidingSync", () => { }, ], }, - }, + ], rooms: rooms, }); const listenerData: Record = {}; @@ -444,14 +444,15 @@ describe("SlidingSync", () => { expect(listenerData[roomB]).toEqual(rooms[roomB]); expect(listenerData[roomC]).toEqual(rooms[roomC]); + expect(slidingSync.listLength()).toEqual(1); slidingSync.off(SlidingSyncEvent.RoomData, dataListener); }); it("should be possible to retrieve list data", () => { - expect(slidingSync.getListParams("a")).toBeDefined(); - expect(slidingSync.getListParams("b")).toBeNull(); - expect(slidingSync.getListData("b")).toBeNull(); - const syncData = slidingSync.getListData("a")!; + expect(slidingSync.getList(0)).toBeDefined(); + expect(slidingSync.getList(5)).toBeNull(); + expect(slidingSync.getListData(5)).toBeNull(); + const syncData = slidingSync.getListData(0)!; expect(syncData.joinedCount).toEqual(500); // from previous test expect(syncData.roomIndexToRoomId).toEqual({ 0: roomA, @@ -466,17 +467,17 @@ describe("SlidingSync", () => { .when("POST", syncUrl) .check(function (req) { const body = req.data; - logger.log("next ranges", body.lists["a"].ranges); + logger.log("next ranges", body.lists[0].ranges); expect(body.lists).toBeTruthy(); - expect(body.lists["a"]).toEqual({ + expect(body.lists[0]).toEqual({ // only the ranges should be sent as the rest are unchanged and sticky ranges: newRanges, }); }) .respond(200, { pos: "b", - lists: { - a: { + lists: [ + { count: 500, ops: [ { @@ -486,17 +487,15 @@ describe("SlidingSync", () => { }, ], }, - }, + ], }); const responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => { return state === SlidingSyncState.RequestFinished; }); - slidingSync.setListRanges("a", newRanges); + slidingSync.setListRanges(0, newRanges); await httpBackend!.flushAllExpected(); await responseProcessed; - // setListRanges for an invalid list key returns an error - await expect(slidingSync.setListRanges("idontexist", newRanges)).rejects.toBeTruthy(); }); it("should be possible to add an extra list", async () => { @@ -514,19 +513,19 @@ describe("SlidingSync", () => { const body = req.data; logger.log("extra list", body); expect(body.lists).toBeTruthy(); - expect(body.lists["a"]).toEqual({ + expect(body.lists[0]).toEqual({ // only the ranges should be sent as the rest are unchanged and sticky ranges: newRanges, }); - expect(body.lists["b"]).toEqual(extraListReq); + expect(body.lists[1]).toEqual(extraListReq); }) .respond(200, { pos: "c", - lists: { - a: { + lists: [ + { count: 500, }, - b: { + { count: 50, ops: [ { @@ -536,10 +535,10 @@ describe("SlidingSync", () => { }, ], }, - }, + ], }); - listenUntil(slidingSync, "SlidingSync.List", (listKey, joinedCount, roomIndexToRoomId) => { - expect(listKey).toEqual("b"); + listenUntil(slidingSync, "SlidingSync.List", (listIndex, joinedCount, roomIndexToRoomId) => { + expect(listIndex).toEqual(1); expect(joinedCount).toEqual(50); expect(roomIndexToRoomId).toEqual({ 0: roomA, @@ -551,7 +550,7 @@ describe("SlidingSync", () => { const responseProcessed = listenUntil(slidingSync, "SlidingSync.Lifecycle", (state) => { return state === SlidingSyncState.Complete; }); - slidingSync.setList("b", extraListReq); + slidingSync.setList(1, extraListReq); await httpBackend!.flushAllExpected(); await responseProcessed; }); @@ -560,8 +559,8 @@ describe("SlidingSync", () => { // move C (2) to A (0) httpBackend!.when("POST", syncUrl).respond(200, { pos: "e", - lists: { - a: { + lists: [ + { count: 500, ops: [ { @@ -575,16 +574,16 @@ describe("SlidingSync", () => { }, ], }, - b: { + { count: 50, }, - }, + ], }); let listPromise = listenUntil( slidingSync, "SlidingSync.List", - (listKey, joinedCount, roomIndexToRoomId) => { - expect(listKey).toEqual("a"); + (listIndex, joinedCount, roomIndexToRoomId) => { + expect(listIndex).toEqual(0); expect(joinedCount).toEqual(500); expect(roomIndexToRoomId).toEqual({ 0: roomC, @@ -604,8 +603,8 @@ describe("SlidingSync", () => { // move C (0) back to A (2) httpBackend!.when("POST", syncUrl).respond(200, { pos: "f", - lists: { - a: { + lists: [ + { count: 500, ops: [ { @@ -619,13 +618,13 @@ describe("SlidingSync", () => { }, ], }, - b: { + { count: 50, }, - }, + ], }); - listPromise = listenUntil(slidingSync, "SlidingSync.List", (listKey, joinedCount, roomIndexToRoomId) => { - expect(listKey).toEqual("a"); + listPromise = listenUntil(slidingSync, "SlidingSync.List", (listIndex, joinedCount, roomIndexToRoomId) => { + expect(listIndex).toEqual(0); expect(joinedCount).toEqual(500); expect(roomIndexToRoomId).toEqual({ 0: roomA, @@ -645,8 +644,8 @@ describe("SlidingSync", () => { it("should ignore invalid list indexes", async () => { httpBackend!.when("POST", syncUrl).respond(200, { pos: "e", - lists: { - a: { + lists: [ + { count: 500, ops: [ { @@ -655,16 +654,16 @@ describe("SlidingSync", () => { }, ], }, - b: { + { count: 50, }, - }, + ], }); const listPromise = listenUntil( slidingSync, "SlidingSync.List", - (listKey, joinedCount, roomIndexToRoomId) => { - expect(listKey).toEqual("a"); + (listIndex, joinedCount, roomIndexToRoomId) => { + expect(listIndex).toEqual(0); expect(joinedCount).toEqual(500); expect(roomIndexToRoomId).toEqual({ 0: roomA, @@ -685,8 +684,8 @@ describe("SlidingSync", () => { it("should be possible to update a list", async () => { httpBackend!.when("POST", syncUrl).respond(200, { pos: "g", - lists: { - a: { + lists: [ + { count: 42, ops: [ { @@ -700,13 +699,13 @@ describe("SlidingSync", () => { }, ], }, - b: { + { count: 50, }, - }, + ], }); // update the list with a new filter - slidingSync.setList("a", { + slidingSync.setList(0, { filters: { is_encrypted: true, }, @@ -715,8 +714,8 @@ describe("SlidingSync", () => { const listPromise = listenUntil( slidingSync, "SlidingSync.List", - (listKey, joinedCount, roomIndexToRoomId) => { - expect(listKey).toEqual("a"); + (listIndex, joinedCount, roomIndexToRoomId) => { + expect(listIndex).toEqual(0); expect(joinedCount).toEqual(42); expect(roomIndexToRoomId).toEqual({ 0: roomB, @@ -739,12 +738,12 @@ describe("SlidingSync", () => { 0: roomB, 1: roomC, }; - expect(slidingSync.getListData("a")!.roomIndexToRoomId).toEqual(indexToRoomId); + expect(slidingSync.getListData(0)!.roomIndexToRoomId).toEqual(indexToRoomId); httpBackend!.when("POST", syncUrl).respond(200, { pos: "f", // currently the list is [B,C] so we will insert D then immediately delete it - lists: { - a: { + lists: [ + { count: 500, ops: [ { @@ -762,16 +761,16 @@ describe("SlidingSync", () => { }, ], }, - b: { + { count: 50, }, - }, + ], }); const listPromise = listenUntil( slidingSync, "SlidingSync.List", - (listKey, joinedCount, roomIndexToRoomId) => { - expect(listKey).toEqual("a"); + (listIndex, joinedCount, roomIndexToRoomId) => { + expect(listIndex).toEqual(0); expect(joinedCount).toEqual(500); expect(roomIndexToRoomId).toEqual(indexToRoomId); return true; @@ -786,14 +785,14 @@ describe("SlidingSync", () => { }); it("should handle deletions correctly", async () => { - expect(slidingSync.getListData("a")!.roomIndexToRoomId).toEqual({ + expect(slidingSync.getListData(0)!.roomIndexToRoomId).toEqual({ 0: roomB, 1: roomC, }); httpBackend!.when("POST", syncUrl).respond(200, { pos: "g", - lists: { - a: { + lists: [ + { count: 499, ops: [ { @@ -802,16 +801,16 @@ describe("SlidingSync", () => { }, ], }, - b: { + { count: 50, }, - }, + ], }); const listPromise = listenUntil( slidingSync, "SlidingSync.List", - (listKey, joinedCount, roomIndexToRoomId) => { - expect(listKey).toEqual("a"); + (listIndex, joinedCount, roomIndexToRoomId) => { + expect(listIndex).toEqual(0); expect(joinedCount).toEqual(499); expect(roomIndexToRoomId).toEqual({ 0: roomC, @@ -828,13 +827,13 @@ describe("SlidingSync", () => { }); it("should handle insertions correctly", async () => { - expect(slidingSync.getListData("a")!.roomIndexToRoomId).toEqual({ + expect(slidingSync.getListData(0)!.roomIndexToRoomId).toEqual({ 0: roomC, }); httpBackend!.when("POST", syncUrl).respond(200, { pos: "h", - lists: { - a: { + lists: [ + { count: 500, ops: [ { @@ -844,16 +843,16 @@ describe("SlidingSync", () => { }, ], }, - b: { + { count: 50, }, - }, + ], }); let listPromise = listenUntil( slidingSync, "SlidingSync.List", - (listKey, joinedCount, roomIndexToRoomId) => { - expect(listKey).toEqual("a"); + (listIndex, joinedCount, roomIndexToRoomId) => { + expect(listIndex).toEqual(0); expect(joinedCount).toEqual(500); expect(roomIndexToRoomId).toEqual({ 0: roomC, @@ -871,8 +870,8 @@ describe("SlidingSync", () => { httpBackend!.when("POST", syncUrl).respond(200, { pos: "h", - lists: { - a: { + lists: [ + { count: 501, ops: [ { @@ -882,13 +881,13 @@ describe("SlidingSync", () => { }, ], }, - b: { + { count: 50, }, - }, + ], }); - listPromise = listenUntil(slidingSync, "SlidingSync.List", (listKey, joinedCount, roomIndexToRoomId) => { - expect(listKey).toEqual("a"); + listPromise = listenUntil(slidingSync, "SlidingSync.List", (listIndex, joinedCount, roomIndexToRoomId) => { + expect(listIndex).toEqual(0); expect(joinedCount).toEqual(501); expect(roomIndexToRoomId).toEqual({ 0: roomC, @@ -911,14 +910,11 @@ describe("SlidingSync", () => { it("should handle insertions with a spurious DELETE correctly", async () => { slidingSync = new SlidingSync( proxyBaseUrl, - new Map([ - [ - "a", - { - ranges: [[0, 20]], - }, - ], - ]), + [ + { + ranges: [[0, 20]], + }, + ], {}, client!, 1, @@ -926,22 +922,22 @@ describe("SlidingSync", () => { // initially start with nothing httpBackend!.when("POST", syncUrl).respond(200, { pos: "a", - lists: { - a: { + lists: [ + { count: 0, ops: [], }, - }, + ], }); slidingSync.start(); await httpBackend!.flushAllExpected(); - expect(slidingSync.getListData("a")!.roomIndexToRoomId).toEqual({}); + expect(slidingSync.getListData(0)!.roomIndexToRoomId).toEqual({}); // insert a room httpBackend!.when("POST", syncUrl).respond(200, { pos: "b", - lists: { - a: { + lists: [ + { count: 1, ops: [ { @@ -955,18 +951,18 @@ describe("SlidingSync", () => { }, ], }, - }, + ], }); await httpBackend!.flushAllExpected(); - expect(slidingSync.getListData("a")!.roomIndexToRoomId).toEqual({ + expect(slidingSync.getListData(0)!.roomIndexToRoomId).toEqual({ 0: roomA, }); // insert another room httpBackend!.when("POST", syncUrl).respond(200, { pos: "c", - lists: { - a: { + lists: [ + { count: 1, ops: [ { @@ -980,10 +976,10 @@ describe("SlidingSync", () => { }, ], }, - }, + ], }); await httpBackend!.flushAllExpected(); - expect(slidingSync.getListData("a")!.roomIndexToRoomId).toEqual({ + expect(slidingSync.getListData(0)!.roomIndexToRoomId).toEqual({ 0: roomB, 1: roomA, }); @@ -991,8 +987,8 @@ describe("SlidingSync", () => { // insert a final room httpBackend!.when("POST", syncUrl).respond(200, { pos: "c", - lists: { - a: { + lists: [ + { count: 1, ops: [ { @@ -1006,10 +1002,10 @@ describe("SlidingSync", () => { }, ], }, - }, + ], }); await httpBackend!.flushAllExpected(); - expect(slidingSync.getListData("a")!.roomIndexToRoomId).toEqual({ + expect(slidingSync.getListData(0)!.roomIndexToRoomId).toEqual({ 0: roomC, 1: roomB, 2: roomA, @@ -1032,7 +1028,7 @@ describe("SlidingSync", () => { required_state: [["m.room.name", ""]], }; // add the subscription - slidingSync = new SlidingSync(proxyBaseUrl, new Map(), roomSubInfo, client!, 1); + slidingSync = new SlidingSync(proxyBaseUrl, [], roomSubInfo, client!, 1); // modification before SlidingSync.start() const subscribePromise = slidingSync.modifyRoomSubscriptions(new Set([roomId])); let txnId: string | undefined; @@ -1050,7 +1046,7 @@ describe("SlidingSync", () => { return { pos: "aaa", txn_id: txnId, - lists: {}, + lists: [], extensions: {}, rooms: { [roomId]: { @@ -1069,7 +1065,7 @@ describe("SlidingSync", () => { const newList = { ranges: [[0, 20]], }; - const promise = slidingSync.setList("a", newList); + const promise = slidingSync.setList(0, newList); let txnId: string | undefined; httpBackend! .when("POST", syncUrl) @@ -1077,7 +1073,7 @@ describe("SlidingSync", () => { const body = req.data; logger.debug("got ", body); expect(body.room_subscriptions).toBeFalsy(); - expect(body.lists["a"]).toEqual(newList); + expect(body.lists[0]).toEqual(newList); expect(body.txn_id).toBeTruthy(); txnId = body.txn_id; }) @@ -1085,7 +1081,7 @@ describe("SlidingSync", () => { return { pos: "bbb", txn_id: txnId, - lists: { a: { count: 5 } }, + lists: [{ count: 5 }], extensions: {}, }; }); @@ -1094,7 +1090,7 @@ describe("SlidingSync", () => { expect(txnId).toBeDefined(); }); it("should resolve setListRanges during a connection", async () => { - const promise = slidingSync.setListRanges("a", [[20, 40]]); + const promise = slidingSync.setListRanges(0, [[20, 40]]); let txnId: string | undefined; httpBackend! .when("POST", syncUrl) @@ -1102,7 +1098,7 @@ describe("SlidingSync", () => { const body = req.data; logger.debug("got ", body); expect(body.room_subscriptions).toBeFalsy(); - expect(body.lists["a"]).toEqual({ + expect(body.lists[0]).toEqual({ ranges: [[20, 40]], }); expect(body.txn_id).toBeTruthy(); @@ -1112,7 +1108,7 @@ describe("SlidingSync", () => { return { pos: "ccc", txn_id: txnId, - lists: { a: { count: 5 } }, + lists: [{ count: 5 }], extensions: {}, }; }); @@ -1154,10 +1150,10 @@ describe("SlidingSync", () => { const pushTxn = function (req: MockHttpBackend["requests"][0]) { gotTxnIds.push(req.data.txn_id); }; - const failPromise = slidingSync.setListRanges("a", [[20, 40]]); + const failPromise = slidingSync.setListRanges(0, [[20, 40]]); httpBackend!.when("POST", syncUrl).check(pushTxn).respond(200, { pos: "e" }); // missing txn_id await httpBackend!.flushAllExpected(); - const failPromise2 = slidingSync.setListRanges("a", [[60, 70]]); + const failPromise2 = slidingSync.setListRanges(0, [[60, 70]]); httpBackend!.when("POST", syncUrl).check(pushTxn).respond(200, { pos: "f" }); // missing txn_id await httpBackend!.flushAllExpected(); @@ -1166,7 +1162,7 @@ describe("SlidingSync", () => { expect(failPromise).rejects.toEqual(gotTxnIds[0]); expect(failPromise2).rejects.toEqual(gotTxnIds[1]); - const okPromise = slidingSync.setListRanges("a", [[0, 20]]); + const okPromise = slidingSync.setListRanges(0, [[0, 20]]); let txnId: string | undefined; httpBackend! .when("POST", syncUrl) @@ -1191,10 +1187,10 @@ describe("SlidingSync", () => { const pushTxn = function (req: MockHttpBackend["requests"][0]) { gotTxnIds.push(req.data?.txn_id); }; - const A = slidingSync.setListRanges("a", [[20, 40]]); + const A = slidingSync.setListRanges(0, [[20, 40]]); httpBackend!.when("POST", syncUrl).check(pushTxn).respond(200, { pos: "A" }); await httpBackend!.flushAllExpected(); - const B = slidingSync.setListRanges("a", [[60, 70]]); + const B = slidingSync.setListRanges(0, [[60, 70]]); httpBackend!.when("POST", syncUrl).check(pushTxn).respond(200, { pos: "B" }); // missing txn_id await httpBackend!.flushAllExpected(); @@ -1202,7 +1198,7 @@ describe("SlidingSync", () => { // which is a fail. expect(A).rejects.toEqual(gotTxnIds[0]); - const C = slidingSync.setListRanges("a", [[0, 20]]); + const C = slidingSync.setListRanges(0, [[0, 20]]); let pendingC = true; C.finally(() => { pendingC = false; @@ -1223,7 +1219,7 @@ describe("SlidingSync", () => { expect(pendingC).toBe(true); // C is pending still }); it("should do nothing for unknown txn_ids", async () => { - const promise = slidingSync.setListRanges("a", [[20, 40]]); + const promise = slidingSync.setListRanges(0, [[20, 40]]); let pending = true; promise.finally(() => { pending = false; @@ -1235,7 +1231,7 @@ describe("SlidingSync", () => { const body = req.data; logger.debug("got ", body); expect(body.room_subscriptions).toBeFalsy(); - expect(body.lists["a"]).toEqual({ + expect(body.lists[0]).toEqual({ ranges: [[20, 40]], }); expect(body.txn_id).toBeTruthy(); @@ -1245,7 +1241,7 @@ describe("SlidingSync", () => { return { pos: "ccc", txn_id: "bogus transaction id", - lists: { a: { count: 5 } }, + lists: [{ count: 5 }], extensions: {}, }; }); @@ -1283,7 +1279,7 @@ describe("SlidingSync", () => { }; it("should be possible to use custom subscriptions on startup", async () => { - const slidingSync = new SlidingSync(proxyBaseUrl, new Map(), defaultSub, client!, 1); + const slidingSync = new SlidingSync(proxyBaseUrl, [], defaultSub, client!, 1); // the intention is for clients to set this up at startup slidingSync.addCustomSubscription(customSubName1, customSub1); slidingSync.addCustomSubscription(customSubName2, customSub2); @@ -1306,7 +1302,7 @@ describe("SlidingSync", () => { }) .respond(200, { pos: "b", - lists: {}, + lists: [], extensions: {}, rooms: {}, }); @@ -1316,7 +1312,7 @@ describe("SlidingSync", () => { }); it("should be possible to use custom subscriptions mid-connection", async () => { - const slidingSync = new SlidingSync(proxyBaseUrl, new Map(), defaultSub, client!, 1); + const slidingSync = new SlidingSync(proxyBaseUrl, [], defaultSub, client!, 1); // the intention is for clients to set this up at startup slidingSync.addCustomSubscription(customSubName1, customSub1); slidingSync.addCustomSubscription(customSubName2, customSub2); @@ -1330,7 +1326,7 @@ describe("SlidingSync", () => { }) .respond(200, { pos: "b", - lists: {}, + lists: [], extensions: {}, rooms: {}, }); @@ -1348,7 +1344,7 @@ describe("SlidingSync", () => { }) .respond(200, { pos: "b", - lists: {}, + lists: [], extensions: {}, rooms: {}, }); @@ -1367,7 +1363,7 @@ describe("SlidingSync", () => { }) .respond(200, { pos: "b", - lists: {}, + lists: [], extensions: {}, rooms: {}, }); @@ -1387,7 +1383,7 @@ describe("SlidingSync", () => { }) .respond(200, { pos: "b", - lists: {}, + lists: [], extensions: {}, rooms: {}, }); @@ -1399,7 +1395,7 @@ describe("SlidingSync", () => { }); it("uses the default subscription for unknown subscription names", async () => { - const slidingSync = new SlidingSync(proxyBaseUrl, new Map(), defaultSub, client!, 1); + const slidingSync = new SlidingSync(proxyBaseUrl, [], defaultSub, client!, 1); slidingSync.addCustomSubscription(customSubName1, customSub1); slidingSync.useCustomSubscription(roomA, "unknown name"); slidingSync.modifyRoomSubscriptions(new Set([roomA])); @@ -1414,7 +1410,7 @@ describe("SlidingSync", () => { }) .respond(200, { pos: "b", - lists: {}, + lists: [], extensions: {}, rooms: {}, }); @@ -1424,7 +1420,7 @@ describe("SlidingSync", () => { }); it("should not be possible to add/modify an already added custom subscription", async () => { - const slidingSync = new SlidingSync(proxyBaseUrl, new Map(), defaultSub, client!, 1); + const slidingSync = new SlidingSync(proxyBaseUrl, [], defaultSub, client!, 1); slidingSync.addCustomSubscription(customSubName1, customSub1); slidingSync.addCustomSubscription(customSubName1, customSub2); slidingSync.useCustomSubscription(roomA, customSubName1); @@ -1440,7 +1436,7 @@ describe("SlidingSync", () => { }) .respond(200, { pos: "b", - lists: {}, + lists: [], extensions: {}, rooms: {}, }); @@ -1450,7 +1446,7 @@ describe("SlidingSync", () => { }); it("should change the custom subscription if they are different", async () => { - const slidingSync = new SlidingSync(proxyBaseUrl, new Map(), defaultSub, client!, 1); + const slidingSync = new SlidingSync(proxyBaseUrl, [], defaultSub, client!, 1); slidingSync.addCustomSubscription(customSubName1, customSub1); slidingSync.addCustomSubscription(customSubName2, customSub2); slidingSync.useCustomSubscription(roomA, customSubName1); @@ -1467,7 +1463,7 @@ describe("SlidingSync", () => { }) .respond(200, { pos: "b", - lists: {}, + lists: [], extensions: {}, rooms: {}, }); @@ -1488,7 +1484,7 @@ describe("SlidingSync", () => { }) .respond(200, { pos: "b", - lists: {}, + lists: [], extensions: {}, rooms: {}, }); @@ -1510,7 +1506,7 @@ describe("SlidingSync", () => { }) .respond(200, { pos: "b", - lists: {}, + lists: [], extensions: {}, rooms: {}, }); @@ -1563,7 +1559,7 @@ describe("SlidingSync", () => { }; it("should be able to register an extension", async () => { - slidingSync = new SlidingSync(proxyBaseUrl, new Map(), {}, client!, 1); + slidingSync = new SlidingSync(proxyBaseUrl, [], {}, client!, 1); slidingSync.registerExtension(extPre); const callbackOrder: string[] = []; @@ -1688,7 +1684,7 @@ describe("SlidingSync", () => { }); it("is not possible to register the same extension name twice", async () => { - slidingSync = new SlidingSync(proxyBaseUrl, new Map(), {}, client!, 1); + slidingSync = new SlidingSync(proxyBaseUrl, [], {}, client!, 1); slidingSync.registerExtension(extPre); expect(() => { slidingSync.registerExtension(extPre); diff --git a/src/sliding-sync.ts b/src/sliding-sync.ts index dde5f1be7..3a2f67391 100644 --- a/src/sliding-sync.ts +++ b/src/sliding-sync.ts @@ -70,7 +70,7 @@ export interface MSC3575List extends MSC3575RoomSubscription { */ export interface MSC3575SlidingSyncRequest { // json body params - lists?: Record; + lists?: MSC3575List[]; unsubscribe_rooms?: string[]; room_subscriptions?: Record; extensions?: object; @@ -137,7 +137,7 @@ type Operation = DeleteOperation | InsertOperation | InvalidateOperation | SyncO export interface MSC3575SlidingSyncResponse { pos: string; txn_id?: string; - lists: Record; + lists: ListResponse[]; rooms: Record; extensions: Record; } @@ -332,7 +332,11 @@ export type SlidingSyncEventHandlerMap = { resp: MSC3575SlidingSyncResponse | null, err?: Error, ) => void; - [SlidingSyncEvent.List]: (listKey: string, joinedCount: number, roomIndexToRoomId: Record) => void; + [SlidingSyncEvent.List]: ( + listIndex: number, + joinedCount: number, + roomIndexToRoomId: Record, + ) => void; }; /** @@ -342,7 +346,7 @@ export type SlidingSyncEventHandlerMap = { * To hook this up with the JS SDK, you need to use SlidingSyncSdk. */ export class SlidingSync extends TypedEventEmitter { - private lists: Map; + private lists: SlidingList[]; private listModifiedCount = 0; private terminated = false; // flag set when resend() is called because we cannot rely on detecting AbortError in JS SDK :( @@ -376,16 +380,13 @@ export class SlidingSync extends TypedEventEmitter, + lists: MSC3575List[], private roomSubscriptionInfo: MSC3575RoomSubscription, private readonly client: MatrixClient, private readonly timeoutMS: number, ) { super(); - this.lists = new Map(); - lists.forEach((list, key) => { - this.lists.set(key, new SlidingList(list)); - }); + this.lists = lists.map((l) => new SlidingList(l)); } /** @@ -422,70 +423,70 @@ export class SlidingSync extends TypedEventEmitter } | null { - const data = this.lists.get(key); - if (!data) { + public getListData(index: number): { joinedCount: number; roomIndexToRoomId: Record } | null { + if (!this.lists[index]) { return null; } return { - joinedCount: data.joinedCount, - roomIndexToRoomId: Object.assign({}, data.roomIndexToRoomId), + joinedCount: this.lists[index].joinedCount, + roomIndexToRoomId: Object.assign({}, this.lists[index].roomIndexToRoomId), }; } /** - * Get the full request list parameters for a list index. This function is provided for callers to use + * Get the full list parameters for a list index. This function is provided for callers to use * in conjunction with setList to update fields on an existing list. - * @param key - The list key to get the params for. - * @returns A copy of the list params or undefined. + * @param index - The list index to get the list for. + * @returns A copy of the list or undefined. */ - public getListParams(key: string): MSC3575List | null { - const params = this.lists.get(key); - if (!params) { + public getList(index: number): MSC3575List | null { + if (!this.lists[index]) { return null; } - return params.getList(true); + return this.lists[index].getList(true); } /** * Set new ranges for an existing list. Calling this function when _only_ the ranges have changed * is more efficient than calling setList(index,list) as this function won't resend sticky params, * whereas setList always will. - * @param key - The list key to modify + * @param index - The list index to modify * @param ranges - The new ranges to apply. * @returns A promise which resolves to the transaction ID when it has been received down sync * (or rejects with the transaction ID if the action was not applied e.g the request was cancelled * immediately after sending, in which case the action will be applied in the subsequent request) */ - public setListRanges(key: string, ranges: number[][]): Promise { - const list = this.lists.get(key); - if (!list) { - return Promise.reject(new Error("no list with key " + key)); - } - list.updateListRange(ranges); + public setListRanges(index: number, ranges: number[][]): Promise { + this.lists[index].updateListRange(ranges); return this.resend(); } /** * Add or replace a list. Calling this function will interrupt the /sync request to resend new * lists. - * @param key - The key to modify + * @param index - The index to modify * @param list - The new list parameters. * @returns A promise which resolves to the transaction ID when it has been received down sync * (or rejects with the transaction ID if the action was not applied e.g the request was cancelled * immediately after sending, in which case the action will be applied in the subsequent request) */ - public setList(key: string, list: MSC3575List): Promise { - const existingList = this.lists.get(key); - if (existingList) { - existingList.replaceList(list); - this.lists.set(key, existingList); + public setList(index: number, list: MSC3575List): Promise { + if (this.lists[index]) { + this.lists[index].replaceList(list); } else { - this.lists.set(key, new SlidingList(list)); + this.lists[index] = new SlidingList(list); } this.listModifiedCount += 1; return this.resend(); @@ -591,44 +592,32 @@ export class SlidingSync extends TypedEventEmitter low; i--) { - if (list.isIndexInRange(i)) { - list.roomIndexToRoomId[i] = list.roomIndexToRoomId[i - 1]; + if (this.lists[listIndex].isIndexInRange(i)) { + this.lists[listIndex].roomIndexToRoomId[i] = this.lists[listIndex].roomIndexToRoomId[i - 1]; } } } - private shiftLeft(listKey: string, hi: number, low: number): void { - const list = this.lists.get(listKey); - if (!list) { - return; - } + private shiftLeft(listIndex: number, hi: number, low: number): void { // l h // 0,1,2,3,4 <- before // 0,1,3,4,4 <- after, low is deleted and hi is duplicated for (let i = low; i < hi; i++) { - if (list.isIndexInRange(i)) { - list.roomIndexToRoomId[i] = list.roomIndexToRoomId[i + 1]; + if (this.lists[listIndex].isIndexInRange(i)) { + this.lists[listIndex].roomIndexToRoomId[i] = this.lists[listIndex].roomIndexToRoomId[i + 1]; } } } - private removeEntry(listKey: string, index: number): void { - const list = this.lists.get(listKey); - if (!list) { - return; - } + private removeEntry(listIndex: number, index: number): void { // work out the max index let max = -1; - for (const n in list.roomIndexToRoomId) { + for (const n in this.lists[listIndex].roomIndexToRoomId) { if (Number(n) > max) { max = Number(n); } @@ -637,18 +626,14 @@ export class SlidingSync extends TypedEventEmitter max) { max = Number(n); } @@ -657,37 +642,30 @@ export class SlidingSync extends TypedEventEmitter { - if (!listData) { - return; - } switch (op.op) { case "DELETE": { - logger.debug("DELETE", listKey, op.index, ";"); - delete listData.roomIndexToRoomId[op.index]; + logger.debug("DELETE", listIndex, op.index, ";"); + delete this.lists[listIndex].roomIndexToRoomId[op.index]; if (gapIndex !== -1) { // we already have a DELETE operation to process, so process it. - this.removeEntry(listKey, gapIndex); + this.removeEntry(listIndex, gapIndex); } gapIndex = op.index; break; } case "INSERT": { - logger.debug("INSERT", listKey, op.index, op.room_id, ";"); - if (listData.roomIndexToRoomId[op.index]) { + logger.debug("INSERT", listIndex, op.index, op.room_id, ";"); + if (this.lists[listIndex].roomIndexToRoomId[op.index]) { // something is in this space, shift items out of the way if (gapIndex < 0) { // we haven't been told where to shift from, so make way for a new room entry. - this.addEntry(listKey, op.index); + this.addEntry(listIndex, op.index); } else if (gapIndex > op.index) { // the gap is further down the list, shift every element to the right // starting at the gap so we can just shift each element in turn: @@ -696,11 +674,11 @@ export class SlidingSync extends TypedEventEmitter = {}; - this.lists.forEach((l: SlidingList, key: string) => { - reqLists[key] = l.getList(false); - }); const reqBody: MSC3575SlidingSyncRequest = { - lists: reqLists, + lists: this.lists.map((l) => { + return l.getList(false); + }), pos: currentPos, timeout: this.timeoutMS, clientTimeout: this.timeoutMS + BUFFER_PERIOD_MS, @@ -890,15 +866,11 @@ export class SlidingSync extends TypedEventEmitter { - const list = this.lists.get(key); - if (!list || !resp) { - return; - } - list.joinedCount = resp.lists[key].count; + resp.lists.forEach((val, i) => { + this.lists[i].joinedCount = val.count; }); this.invokeLifecycleListeners(SlidingSyncState.RequestFinished, resp); } catch (err) { @@ -927,24 +899,25 @@ export class SlidingSync extends TypedEventEmitter = new Set(); + const listIndexesWithUpdates: Set = new Set(); if (!doNotUpdateList) { - for (const [key, list] of Object.entries(resp.lists)) { + resp.lists.forEach((list, listIndex) => { list.ops = list.ops || []; if (list.ops.length > 0) { - listKeysWithUpdates.add(key); + listIndexesWithUpdates.add(listIndex); } - this.processListOps(list, key); - } + this.processListOps(list, listIndex); + }); } this.invokeLifecycleListeners(SlidingSyncState.Complete, resp); this.onPostExtensionsResponse(resp.extensions); - listKeysWithUpdates.forEach((listKey: string) => { - const list = this.lists.get(listKey); - if (!list) { - return; - } - this.emit(SlidingSyncEvent.List, listKey, list.joinedCount, Object.assign({}, list.roomIndexToRoomId)); + listIndexesWithUpdates.forEach((i) => { + this.emit( + SlidingSyncEvent.List, + i, + this.lists[i].joinedCount, + Object.assign({}, this.lists[i].roomIndexToRoomId), + ); }); this.resolveTransactionDefers(resp.txn_id);