From 2cda229bc4a5fbbb7399c3758cce14c35d7fb98e Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 19 Aug 2022 17:33:07 +0100 Subject: [PATCH] Automatically reconnect sessions when sliding sync expires them This can happen when you close your laptop overnight, as the server will not hold onto in-memory resources for your connection indefinitely. When this happen, the server will HTTP 400 you with "session expired". At this point, it is no longer safe to remember anything and you must forget everything and resend any sticky parameters. This commit does the sticky parameters and re-establishes the connection, but it may need additional work to make the JS SDK forget now invalid data. --- spec/integ/sliding-sync.spec.ts | 97 +++++++++++++++++++++++++++++++++ src/sliding-sync.ts | 33 ++++++++++- 2 files changed, 127 insertions(+), 3 deletions(-) diff --git a/spec/integ/sliding-sync.spec.ts b/spec/integ/sliding-sync.spec.ts index 8c4a7ad12..0464459c1 100644 --- a/spec/integ/sliding-sync.spec.ts +++ b/spec/integ/sliding-sync.spec.ts @@ -80,6 +80,103 @@ describe("SlidingSync", () => { slidingSync.stop(); httpBackend.verifyNoOutstandingExpectation(); }); + + 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, [], {}, client, 1); + const roomId = "!sub:localhost"; + const subInfo = { + timeline_limit: 42, + required_state: [["m.room.create", ""]], + }; + const listInfo = { + ranges: [[0,10]], + filters: { + is_dm: true, + }, + }; + const ext = { + name: () => "custom_extension", + onRequest: (initial) => { return { initial: initial } }, + onResponse: (res) => { return {} }, + when: () => ExtensionState.PreProcess, + }; + slidingSync.modifyRoomSubscriptions(new Set([roomId])); + slidingSync.modifyRoomSubscriptionInfo(subInfo); + slidingSync.setList(0, listInfo); + slidingSync.registerExtension(ext); + slidingSync.start(); + + // expect everything to be sent + let txnId; + httpBackend.when("POST", syncUrl).check(function(req) { + const body = req.data; + logger.debug("got ", body); + expect(body.room_subscriptions).toEqual({ + [roomId]: subInfo, + }); + expect(body.lists[0]).toEqual(listInfo); + expect(body.extensions).toBeTruthy(); + expect(body.extensions["custom_extension"]).toEqual({initial:true}); + txnId = body.txn_id; + }).respond(200, function() { + return { + pos: "11", + lists: [{ count: 5 }], + extensions: {}, + txn_id: txnId, + }; + }); + await httpBackend.flushAllExpected(); + + // expect nothing but ranges and non-initial extensions to be sent + httpBackend.when("POST", syncUrl).check(function(req) { + const body = req.data; + logger.debug("got ", body); + expect(body.room_subscriptions).toBeFalsy(); + expect(body.lists[0]).toEqual({ + ranges: [[0,10]], + }); + expect(body.extensions).toBeTruthy(); + expect(body.extensions["custom_extension"]).toEqual({initial:false}); + }).respond(200, function() { + return { + pos: "12", + lists: [{ count: 5 }], + extensions: {}, + }; + }); + await httpBackend.flushAllExpected(); + + // now we expire the session + httpBackend.when("POST", syncUrl).respond(400, function() { + logger.debug("sending session expired 400"); + return { + error: "HTTP 400 : session expired", + }; + }); + await httpBackend.flushAllExpected(); + + // ...and everything should be sent again + httpBackend.when("POST", syncUrl).check(function(req) { + const body = req.data; + logger.debug("got ", body); + expect(body.room_subscriptions).toEqual({ + [roomId]: subInfo, + }); + expect(body.lists[0]).toEqual(listInfo); + expect(body.extensions).toBeTruthy(); + expect(body.extensions["custom_extension"]).toEqual({initial:true}); + }).respond(200, function() { + return { + pos: "1", + lists: [{ count: 6 }], + extensions: {}, + }; + }); + await httpBackend.flushAllExpected(); + slidingSync.stop(); + }); }); describe("room subscriptions", () => { diff --git a/src/sliding-sync.ts b/src/sliding-sync.ts index 28026b3a9..5218004d0 100644 --- a/src/sliding-sync.ts +++ b/src/sliding-sync.ts @@ -694,6 +694,26 @@ export class SlidingSync extends TypedEventEmitter { + d.reject(d.txnId); + }); + this.txnIdDefers = []; + // resend sticky params and de-confirm all subscriptions + this.lists.forEach((l) => { + l.setModified(true); + }); + this.confirmedRoomSubscriptions = new Set(); // leave desired ones alone though! + // reset the connection as we might be wedged + this.needsResend = true; + this.pendingReq?.abort(); + } + /** * Start syncing with the server. Blocks until stopped. */ @@ -732,7 +752,6 @@ export class SlidingSync extends TypedEventEmitter