Skip to content

Commit ba6a001

Browse files
authored
Merge pull request #2834 from matrix-org/kegan/custom-room-subs
sliding sync: add custom room subscriptions support
2 parents 67f343d + d0c71ec commit ba6a001

File tree

2 files changed

+185
-1
lines changed

2 files changed

+185
-1
lines changed

spec/integ/sliding-sync.spec.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,6 +1112,156 @@ describe("SlidingSync", () => {
11121112
});
11131113
});
11141114

1115+
describe("custom room subscriptions", () => {
1116+
beforeAll(setupClient);
1117+
afterAll(teardownClient);
1118+
1119+
const roomA = "!a";
1120+
const roomB = "!b";
1121+
const roomC = "!c";
1122+
const roomD = "!d";
1123+
1124+
const defaultSub = {
1125+
timeline_limit: 1,
1126+
required_state: [["m.room.create", ""]],
1127+
};
1128+
1129+
const customSubName1 = "sub1";
1130+
const customSub1 = {
1131+
timeline_limit: 2,
1132+
required_state: [["*", "*"]],
1133+
};
1134+
1135+
const customSubName2 = "sub2";
1136+
const customSub2 = {
1137+
timeline_limit: 3,
1138+
required_state: [["*", "*"]],
1139+
};
1140+
1141+
it("should be possible to use custom subscriptions on startup", async () => {
1142+
const slidingSync = new SlidingSync(proxyBaseUrl, [], defaultSub, client!, 1);
1143+
// the intention is for clients to set this up at startup
1144+
slidingSync.addCustomSubscription(customSubName1, customSub1);
1145+
slidingSync.addCustomSubscription(customSubName2, customSub2);
1146+
// then call these depending on the kind of room / context
1147+
slidingSync.useCustomSubscription(roomA, customSubName1);
1148+
slidingSync.useCustomSubscription(roomB, customSubName1);
1149+
slidingSync.useCustomSubscription(roomC, customSubName2);
1150+
slidingSync.modifyRoomSubscriptions(new Set<string>([roomA, roomB, roomC, roomD]));
1151+
1152+
httpBackend!.when("POST", syncUrl).check(function(req) {
1153+
const body = req.data;
1154+
logger.log("custom subs", body);
1155+
expect(body.room_subscriptions).toBeTruthy();
1156+
expect(body.room_subscriptions[roomA]).toEqual(customSub1);
1157+
expect(body.room_subscriptions[roomB]).toEqual(customSub1);
1158+
expect(body.room_subscriptions[roomC]).toEqual(customSub2);
1159+
expect(body.room_subscriptions[roomD]).toEqual(defaultSub);
1160+
}).respond(200, {
1161+
pos: "b",
1162+
lists: [],
1163+
extensions: {},
1164+
rooms: {},
1165+
});
1166+
slidingSync.start();
1167+
await httpBackend!.flushAllExpected();
1168+
slidingSync.stop();
1169+
});
1170+
1171+
it("should be possible to use custom subscriptions mid-connection", async () => {
1172+
const slidingSync = new SlidingSync(proxyBaseUrl, [], defaultSub, client!, 1);
1173+
// the intention is for clients to set this up at startup
1174+
slidingSync.addCustomSubscription(customSubName1, customSub1);
1175+
slidingSync.addCustomSubscription(customSubName2, customSub2);
1176+
// initially no subs
1177+
httpBackend!.when("POST", syncUrl).check(function(req) {
1178+
const body = req.data;
1179+
logger.log("custom subs", body);
1180+
expect(body.room_subscriptions).toBeFalsy();
1181+
}).respond(200, {
1182+
pos: "b",
1183+
lists: [],
1184+
extensions: {},
1185+
rooms: {},
1186+
});
1187+
slidingSync.start();
1188+
await httpBackend!.flushAllExpected();
1189+
1190+
// now the user clicks on a room which uses the default sub
1191+
httpBackend!.when("POST", syncUrl).check(function(req) {
1192+
const body = req.data;
1193+
logger.log("custom subs", body);
1194+
expect(body.room_subscriptions).toBeTruthy();
1195+
expect(body.room_subscriptions[roomA]).toEqual(defaultSub);
1196+
}).respond(200, {
1197+
pos: "b",
1198+
lists: [],
1199+
extensions: {},
1200+
rooms: {},
1201+
});
1202+
slidingSync.modifyRoomSubscriptions(new Set<string>([roomA]));
1203+
await httpBackend!.flushAllExpected();
1204+
1205+
// now the user clicks on a room which uses a custom sub
1206+
httpBackend!.when("POST", syncUrl).check(function(req) {
1207+
const body = req.data;
1208+
logger.log("custom subs", body);
1209+
expect(body.room_subscriptions).toBeTruthy();
1210+
expect(body.room_subscriptions[roomB]).toEqual(customSub1);
1211+
expect(body.unsubscribe_rooms).toEqual([roomA]);
1212+
}).respond(200, {
1213+
pos: "b",
1214+
lists: [],
1215+
extensions: {},
1216+
rooms: {},
1217+
});
1218+
slidingSync.useCustomSubscription(roomB, customSubName1);
1219+
slidingSync.modifyRoomSubscriptions(new Set<string>([roomB]));
1220+
await httpBackend!.flushAllExpected();
1221+
1222+
// now the user uses a different sub for the same room: we don't unsub but just resend
1223+
httpBackend!.when("POST", syncUrl).check(function(req) {
1224+
const body = req.data;
1225+
logger.log("custom subs", body);
1226+
expect(body.room_subscriptions).toBeTruthy();
1227+
expect(body.room_subscriptions[roomB]).toEqual(customSub2);
1228+
expect(body.unsubscribe_rooms).toBeFalsy();
1229+
}).respond(200, {
1230+
pos: "b",
1231+
lists: [],
1232+
extensions: {},
1233+
rooms: {},
1234+
});
1235+
slidingSync.useCustomSubscription(roomB, customSubName2);
1236+
slidingSync.modifyRoomSubscriptions(new Set<string>([roomB]));
1237+
await httpBackend!.flushAllExpected();
1238+
1239+
slidingSync.stop();
1240+
});
1241+
1242+
it("uses the default subscription for unknown subscription names", async () => {
1243+
const slidingSync = new SlidingSync(proxyBaseUrl, [], defaultSub, client!, 1);
1244+
slidingSync.addCustomSubscription(customSubName1, customSub1);
1245+
slidingSync.useCustomSubscription(roomA, "unknown name");
1246+
slidingSync.modifyRoomSubscriptions(new Set<string>([roomA]));
1247+
1248+
httpBackend!.when("POST", syncUrl).check(function(req) {
1249+
const body = req.data;
1250+
logger.log("custom subs", body);
1251+
expect(body.room_subscriptions).toBeTruthy();
1252+
expect(body.room_subscriptions[roomA]).toEqual(defaultSub);
1253+
}).respond(200, {
1254+
pos: "b",
1255+
lists: [],
1256+
extensions: {},
1257+
rooms: {},
1258+
});
1259+
slidingSync.start();
1260+
await httpBackend!.flushAllExpected();
1261+
slidingSync.stop();
1262+
});
1263+
});
1264+
11151265
describe("extensions", () => {
11161266
beforeAll(setupClient);
11171267
afterAll(teardownClient);

src/sliding-sync.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,11 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
353353
private desiredRoomSubscriptions = new Set<string>(); // the *desired* room subscriptions
354354
private confirmedRoomSubscriptions = new Set<string>();
355355

356+
// map of custom subscription name to the subscription
357+
private customSubscriptions: Map<string, MSC3575RoomSubscription> = new Map();
358+
// map of room ID to custom subscription name
359+
private roomIdToCustomSubscription: Map<string, string> = new Map();
360+
356361
private pendingReq?: Promise<MSC3575SlidingSyncResponse>;
357362
private abortController?: AbortController;
358363

@@ -375,6 +380,30 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
375380
this.lists = lists.map((l) => new SlidingList(l));
376381
}
377382

383+
/**
384+
* Add a custom room subscription, referred to by an arbitrary name. If a subscription with this
385+
* name already exists, it is replaced. No requests are sent by calling this method.
386+
* @param name The name of the subscription. Only used to reference this subscription in
387+
* useCustomSubscription.
388+
* @param sub The subscription information.
389+
*/
390+
public addCustomSubscription(name: string, sub: MSC3575RoomSubscription) {
391+
this.customSubscriptions.set(name, sub);
392+
}
393+
394+
/**
395+
* Use a custom subscription previously added via addCustomSubscription. No requests are sent
396+
* by calling this method. Use modifyRoomSubscriptions to resend subscription information.
397+
* @param roomId The room to use the subscription in.
398+
* @param name The name of the subscription. If this name is unknown, the default subscription
399+
* will be used.
400+
*/
401+
public useCustomSubscription(roomId: string, name: string) {
402+
this.roomIdToCustomSubscription.set(roomId, name);
403+
// unconfirm this subscription so a resend() will send it up afresh.
404+
this.confirmedRoomSubscriptions.delete(roomId);
405+
}
406+
378407
/**
379408
* Get the length of the sliding lists.
380409
* @returns The number of lists in the sync request
@@ -806,7 +835,12 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
806835
if (newSubscriptions.size > 0) {
807836
reqBody.room_subscriptions = {};
808837
for (const roomId of newSubscriptions) {
809-
reqBody.room_subscriptions[roomId] = this.roomSubscriptionInfo;
838+
const customSubName = this.roomIdToCustomSubscription.get(roomId);
839+
let sub = this.roomSubscriptionInfo;
840+
if (customSubName && this.customSubscriptions.has(customSubName)) {
841+
sub = this.customSubscriptions.get(customSubName)!;
842+
}
843+
reqBody.room_subscriptions[roomId] = sub;
810844
}
811845
}
812846
if (this.txnId) {

0 commit comments

Comments
 (0)