You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-26 17:03:12 +03:00
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.
This commit is contained in:
@@ -80,6 +80,103 @@ describe("SlidingSync", () => {
|
|||||||
slidingSync.stop();
|
slidingSync.stop();
|
||||||
httpBackend.verifyNoOutstandingExpectation();
|
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", () => {
|
describe("room subscriptions", () => {
|
||||||
|
|||||||
@@ -694,6 +694,26 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|||||||
this.removeAllListeners(SlidingSyncEvent.RoomData);
|
this.removeAllListeners(SlidingSyncEvent.RoomData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-setup this connection e.g in the event of an expired session.
|
||||||
|
*/
|
||||||
|
private resetup(): void {
|
||||||
|
logger.warn("SlidingSync: resetting connection info");
|
||||||
|
// any pending txn ID defers will be forgotten already by the server, so clear them out
|
||||||
|
this.txnIdDefers.forEach((d) => {
|
||||||
|
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<string>(); // 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.
|
* Start syncing with the server. Blocks until stopped.
|
||||||
*/
|
*/
|
||||||
@@ -732,7 +752,6 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|||||||
}
|
}
|
||||||
this.pendingReq = this.client.slidingSync(reqBody, this.proxyBaseUrl);
|
this.pendingReq = this.client.slidingSync(reqBody, this.proxyBaseUrl);
|
||||||
resp = await this.pendingReq;
|
resp = await this.pendingReq;
|
||||||
logger.debug(resp);
|
|
||||||
currentPos = resp.pos;
|
currentPos = resp.pos;
|
||||||
// update what we think we're subscribed to.
|
// update what we think we're subscribed to.
|
||||||
for (const roomId of newSubscriptions) {
|
for (const roomId of newSubscriptions) {
|
||||||
@@ -770,7 +789,15 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|||||||
null,
|
null,
|
||||||
err,
|
err,
|
||||||
);
|
);
|
||||||
await sleep(3000);
|
if (err.httpStatus === 400) {
|
||||||
|
// session probably expired TODO: assign an errcode
|
||||||
|
// so drop state and re-request
|
||||||
|
this.resetup();
|
||||||
|
currentPos = undefined;
|
||||||
|
await sleep(50); // in case the 400 was for something else; don't tightloop
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await sleep(5000);
|
||||||
} else if (this.needsResend || err === "aborted") {
|
} else if (this.needsResend || err === "aborted") {
|
||||||
// don't sleep as we caused this error by abort()ing the request.
|
// don't sleep as we caused this error by abort()ing the request.
|
||||||
// we check for 'aborted' because that's the error Jest returns and without it
|
// we check for 'aborted' because that's the error Jest returns and without it
|
||||||
@@ -778,7 +805,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
await sleep(3000);
|
await sleep(5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!resp) {
|
if (!resp) {
|
||||||
|
|||||||
Reference in New Issue
Block a user