Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sliding sync: add custom room subscriptions support #2834

Merged
merged 5 commits into from
Nov 14, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions spec/integ/sliding-sync.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,156 @@ describe("SlidingSync", () => {
});
});

describe("custom room subscriptions", () => {
beforeAll(setupClient);
afterAll(teardownClient);

const roomA = "!a";
const roomB = "!b";
const roomC = "!c";
const roomD = "!d";

const defaultSub = {
timeline_limit: 1,
required_state: [["m.room.create", ""]],
};

const customSubName1 = "sub1";
const customSub1 = {
timeline_limit: 2,
required_state: [["*", "*"]],
};

const customSubName2 = "sub2";
const customSub2 = {
timeline_limit: 3,
required_state: [["*", "*"]],
};

it("should be possible to use custom subscriptions on startup", async () => {
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);
// then call these depending on the kind of room / context
slidingSync.useCustomSubscription(roomA, customSubName1);
slidingSync.useCustomSubscription(roomB, customSubName1);
slidingSync.useCustomSubscription(roomC, customSubName2);
slidingSync.modifyRoomSubscriptions(new Set<string>([roomA, roomB, roomC, roomD]));

httpBackend!.when("POST", syncUrl).check(function(req) {
const body = req.data;
logger.log("custom subs", body);
expect(body.room_subscriptions).toBeTruthy();
expect(body.room_subscriptions[roomA]).toEqual(customSub1);
expect(body.room_subscriptions[roomB]).toEqual(customSub1);
expect(body.room_subscriptions[roomC]).toEqual(customSub2);
expect(body.room_subscriptions[roomD]).toEqual(defaultSub);
}).respond(200, {
pos: "b",
lists: [],
extensions: {},
rooms: {},
});
slidingSync.start();
await httpBackend!.flushAllExpected();
slidingSync.stop();
});

it("should be possible to use custom subscriptions mid-connection", async () => {
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);
// initially no subs
httpBackend!.when("POST", syncUrl).check(function(req) {
const body = req.data;
logger.log("custom subs", body);
expect(body.room_subscriptions).toBeFalsy();
}).respond(200, {
pos: "b",
lists: [],
extensions: {},
rooms: {},
});
slidingSync.start();
await httpBackend!.flushAllExpected();

// now the user clicks on a room which uses the default sub
httpBackend!.when("POST", syncUrl).check(function(req) {
const body = req.data;
logger.log("custom subs", body);
expect(body.room_subscriptions).toBeTruthy();
expect(body.room_subscriptions[roomA]).toEqual(defaultSub);
}).respond(200, {
pos: "b",
lists: [],
extensions: {},
rooms: {},
});
slidingSync.modifyRoomSubscriptions(new Set<string>([roomA]));
await httpBackend!.flushAllExpected();

// now the user clicks on a room which uses a custom sub
httpBackend!.when("POST", syncUrl).check(function(req) {
const body = req.data;
logger.log("custom subs", body);
expect(body.room_subscriptions).toBeTruthy();
expect(body.room_subscriptions[roomB]).toEqual(customSub1);
expect(body.unsubscribe_rooms).toEqual([roomA]);
}).respond(200, {
pos: "b",
lists: [],
extensions: {},
rooms: {},
});
slidingSync.useCustomSubscription(roomB, customSubName1);
slidingSync.modifyRoomSubscriptions(new Set<string>([roomB]));
await httpBackend!.flushAllExpected();

// now the user uses a different sub for the same room: we don't unsub but just resend
httpBackend!.when("POST", syncUrl).check(function(req) {
const body = req.data;
logger.log("custom subs", body);
expect(body.room_subscriptions).toBeTruthy();
expect(body.room_subscriptions[roomB]).toEqual(customSub2);
expect(body.unsubscribe_rooms).toBeFalsy();
}).respond(200, {
pos: "b",
lists: [],
extensions: {},
rooms: {},
});
slidingSync.useCustomSubscription(roomB, customSubName2);
slidingSync.modifyRoomSubscriptions(new Set<string>([roomB]));
await httpBackend!.flushAllExpected();

slidingSync.stop();
});

it("uses the default subscription for unknown subscription names", async () => {
const slidingSync = new SlidingSync(proxyBaseUrl, [], defaultSub, client!, 1);
slidingSync.addCustomSubscription(customSubName1, customSub1);
slidingSync.useCustomSubscription(roomA, "unknown name");
slidingSync.modifyRoomSubscriptions(new Set<string>([roomA]));

httpBackend!.when("POST", syncUrl).check(function(req) {
const body = req.data;
logger.log("custom subs", body);
expect(body.room_subscriptions).toBeTruthy();
expect(body.room_subscriptions[roomA]).toEqual(defaultSub);
}).respond(200, {
pos: "b",
lists: [],
extensions: {},
rooms: {},
});
slidingSync.start();
await httpBackend!.flushAllExpected();
slidingSync.stop();
});
});

describe("extensions", () => {
beforeAll(setupClient);
afterAll(teardownClient);
Expand Down
36 changes: 35 additions & 1 deletion src/sliding-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,11 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
private desiredRoomSubscriptions = new Set<string>(); // the *desired* room subscriptions
private confirmedRoomSubscriptions = new Set<string>();

// map of custom subscription name to the subscription
private customSubscriptions: Map<string, MSC3575RoomSubscription> = new Map();
// map of room ID to custom subscription name
private roomIdToCustomSubscription: Map<string, string> = new Map();

private pendingReq?: Promise<MSC3575SlidingSyncResponse>;
private abortController?: AbortController;

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

/**
* Add a custom room subscription, referred to by an arbitrary name. If a subscription with this
* name already exists, it is replaced. No requests are sent by calling this method.
* @param name The name of the subscription. Only used to reference this subscription in
* useCustomSubscription.
* @param sub The subscription information.
*/
public addCustomSubscription(name: string, sub: MSC3575RoomSubscription) {
this.customSubscriptions.set(name, sub);
}

/**
* Use a custom subscription previously added via addCustomSubscription. No requests are sent
* by calling this method. Use modifyRoomSubscriptions to resend subscription information.
* @param roomId The room to use the subscription in.
* @param name The name of the subscription. If this name is unknown, the default subscription
* will be used.
*/
public useCustomSubscription(roomId: string, name: string) {
this.roomIdToCustomSubscription.set(roomId, name);
// unconfirm this subscription so a resend() will send it up afresh.
this.confirmedRoomSubscriptions.delete(roomId);
}

/**
* Get the length of the sliding lists.
* @returns The number of lists in the sync request
Expand Down Expand Up @@ -806,7 +835,12 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
if (newSubscriptions.size > 0) {
reqBody.room_subscriptions = {};
for (const roomId of newSubscriptions) {
reqBody.room_subscriptions[roomId] = this.roomSubscriptionInfo;
const customSubName = this.roomIdToCustomSubscription.get(roomId);
let sub = this.roomSubscriptionInfo;
if (customSubName && this.customSubscriptions.has(customSubName)) {
sub = this.customSubscriptions.get(customSubName)!;
}
reqBody.room_subscriptions[roomId] = sub;
}
}
if (this.txnId) {
Expand Down