Skip to content

Commit 62007ec

Browse files
Germaint3chguy
andauthored
Fix sync init when thread unread notif is not supported (#2739)
Co-authored-by: Michael Telatynski <[email protected]>
1 parent 029280b commit 62007ec

File tree

9 files changed

+160
-14
lines changed

9 files changed

+160
-14
lines changed

spec/unit/feature.spec.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { buildFeatureSupportMap, Feature, ServerSupport } from "../../src/feature";
18+
19+
describe("Feature detection", () => {
20+
it("checks the matrix version", async () => {
21+
const support = await buildFeatureSupportMap({
22+
versions: ["v1.3"],
23+
unstable_features: {},
24+
});
25+
26+
expect(support.get(Feature.Thread)).toBe(ServerSupport.Stable);
27+
expect(support.get(Feature.ThreadUnreadNotifications)).toBe(ServerSupport.Unsupported);
28+
});
29+
30+
it("checks the matrix msc number", async () => {
31+
const support = await buildFeatureSupportMap({
32+
versions: ["v1.2"],
33+
unstable_features: {
34+
"org.matrix.msc3771": true,
35+
"org.matrix.msc3773": true,
36+
},
37+
});
38+
expect(support.get(Feature.ThreadUnreadNotifications)).toBe(ServerSupport.Unstable);
39+
});
40+
41+
it("requires two MSCs to pass", async () => {
42+
const support = await buildFeatureSupportMap({
43+
versions: ["v1.2"],
44+
unstable_features: {
45+
"org.matrix.msc3771": false,
46+
"org.matrix.msc3773": true,
47+
},
48+
});
49+
expect(support.get(Feature.ThreadUnreadNotifications)).toBe(ServerSupport.Unsupported);
50+
});
51+
52+
it("requires two MSCs OR matrix versions to pass", async () => {
53+
const support = await buildFeatureSupportMap({
54+
versions: ["v1.4"],
55+
unstable_features: {
56+
"org.matrix.msc3771": false,
57+
"org.matrix.msc3773": true,
58+
},
59+
});
60+
expect(support.get(Feature.ThreadUnreadNotifications)).toBe(ServerSupport.Stable);
61+
});
62+
});

spec/unit/filter.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { UNREAD_THREAD_NOTIFICATIONS } from "../../src/@types/sync";
12
import { Filter, IFilterDefinition } from "../../src/filter";
23

34
describe("Filter", function() {
@@ -50,7 +51,7 @@ describe("Filter", function() {
5051
expect(filter.getDefinition()).toEqual({
5152
room: {
5253
timeline: {
53-
unread_thread_notifications: true,
54+
[UNREAD_THREAD_NOTIFICATIONS.name]: true,
5455
},
5556
},
5657
});

src/@types/sync.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { UnstableValue } from "matrix-events-sdk/lib/NamespacedValue";
17+
import { ServerControlledNamespacedValue } from "../NamespacedValue";
1818

1919
/**
2020
* https://github.com/matrix-org/matrix-doc/pull/3773
2121
*
2222
* @experimental
2323
*/
24-
export const UNREAD_THREAD_NOTIFICATIONS = new UnstableValue(
24+
export const UNREAD_THREAD_NOTIFICATIONS = new ServerControlledNamespacedValue(
2525
"unread_thread_notifications",
2626
"org.matrix.msc3773.unread_thread_notifications");

src/client.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ import { MAIN_ROOM_TIMELINE } from "./models/read-receipt";
204204
import { IgnoredInvites } from "./models/invites-ignorer";
205205
import { UIARequest, UIAResponse } from "./@types/uia";
206206
import { LocalNotificationSettings } from "./@types/local_notifications";
207+
import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync";
208+
import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature";
207209

208210
export type Store = IStore;
209211

@@ -528,7 +530,7 @@ export interface ITurnServer {
528530
credential: string;
529531
}
530532

531-
interface IServerVersions {
533+
export interface IServerVersions {
532534
versions: string[];
533535
unstable_features: Record<string, boolean>;
534536
}
@@ -967,6 +969,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
967969
protected clientWellKnownIntervalID: ReturnType<typeof setInterval>;
968970
protected canResetTimelineCallback: ResetTimelineCallback;
969971

972+
public canSupport = new Map<Feature, ServerSupport>();
973+
970974
// The pushprocessor caches useful things, so keep one and re-use it
971975
protected pushProcessor = new PushProcessor(this);
972976

@@ -1197,6 +1201,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
11971201
this.syncApi.stop();
11981202
}
11991203

1204+
const serverVersions = await this.getVersions();
1205+
this.canSupport = await buildFeatureSupportMap(serverVersions);
1206+
1207+
const support = this.canSupport.get(Feature.ThreadUnreadNotifications);
1208+
UNREAD_THREAD_NOTIFICATIONS.setPreferUnstable(support === ServerSupport.Unstable);
1209+
12001210
const { threads, list } = await this.doesServerSupportThread();
12011211
Thread.setServerSideSupport(threads);
12021212
Thread.setServerSideListSupport(list);

src/feature.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { IServerVersions } from "./client";
18+
19+
export enum ServerSupport {
20+
Stable,
21+
Unstable,
22+
Unsupported
23+
}
24+
25+
export enum Feature {
26+
Thread = "Thread",
27+
ThreadUnreadNotifications = "ThreadUnreadNotifications",
28+
}
29+
30+
type FeatureSupportCondition = {
31+
unstablePrefixes?: string[];
32+
matrixVersion?: string;
33+
};
34+
35+
const featureSupportResolver: Record<string, FeatureSupportCondition> = {
36+
[Feature.Thread]: {
37+
unstablePrefixes: ["org.matrix.msc3440"],
38+
matrixVersion: "v1.3",
39+
},
40+
[Feature.ThreadUnreadNotifications]: {
41+
unstablePrefixes: ["org.matrix.msc3771", "org.matrix.msc3773"],
42+
matrixVersion: "v1.4",
43+
},
44+
};
45+
46+
export async function buildFeatureSupportMap(versions: IServerVersions): Promise<Map<Feature, ServerSupport>> {
47+
const supportMap = new Map<Feature, ServerSupport>();
48+
for (const [feature, supportCondition] of Object.entries(featureSupportResolver)) {
49+
const supportMatrixVersion = versions.versions?.includes(supportCondition.matrixVersion || "") ?? false;
50+
const supportUnstablePrefixes = supportCondition.unstablePrefixes?.every(unstablePrefix => {
51+
return versions.unstable_features?.[unstablePrefix] === true;
52+
}) ?? false;
53+
if (supportMatrixVersion) {
54+
supportMap.set(feature as Feature, ServerSupport.Stable);
55+
} else if (supportUnstablePrefixes) {
56+
supportMap.set(feature as Feature, ServerSupport.Unstable);
57+
} else {
58+
supportMap.set(feature as Feature, ServerSupport.Unsupported);
59+
}
60+
}
61+
return supportMap;
62+
}

src/filter-component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export interface IFilterComponent {
7373
* @param {Object} filterJson the definition of this filter JSON, e.g. { 'contains_url': true }
7474
*/
7575
export class FilterComponent {
76-
constructor(private filterJson: IFilterComponent, public readonly userId?: string) {}
76+
constructor(private filterJson: IFilterComponent, public readonly userId?: string | undefined | null) {}
7777

7878
/**
7979
* Checks with the filter component matches the given event

src/filter.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
EventType,
2323
RelationType,
2424
} from "./@types/event";
25+
import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync";
2526
import { FilterComponent, IFilterComponent } from "./filter-component";
2627
import { MatrixEvent } from "./models/event";
2728

@@ -99,7 +100,7 @@ export class Filter {
99100
* @param {Object} jsonObj
100101
* @return {Filter}
101102
*/
102-
public static fromJson(userId: string, filterId: string, jsonObj: IFilterDefinition): Filter {
103+
public static fromJson(userId: string | undefined | null, filterId: string, jsonObj: IFilterDefinition): Filter {
103104
const filter = new Filter(userId, filterId);
104105
filter.setDefinition(jsonObj);
105106
return filter;
@@ -109,7 +110,7 @@ export class Filter {
109110
private roomFilter: FilterComponent;
110111
private roomTimelineFilter: FilterComponent;
111112

112-
constructor(public readonly userId: string, public filterId?: string) {}
113+
constructor(public readonly userId: string | undefined | null, public filterId?: string) {}
113114

114115
/**
115116
* Get the ID of this filter on your homeserver (if known)
@@ -227,7 +228,16 @@ export class Filter {
227228
* @param {boolean} enabled
228229
*/
229230
public setUnreadThreadNotifications(enabled: boolean): void {
230-
setProp(this.definition, "room.timeline.unread_thread_notifications", !!enabled);
231+
this.definition = {
232+
...this.definition,
233+
room: {
234+
...this.definition?.room,
235+
timeline: {
236+
...this.definition?.room?.timeline,
237+
[UNREAD_THREAD_NOTIFICATIONS.name]: !!enabled,
238+
},
239+
},
240+
};
231241
}
232242

233243
setLazyLoadMembers(enabled: boolean): void {

src/store/memory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ export class MemoryStore implements IStore {
227227
* @param {Filter} filter
228228
*/
229229
public storeFilter(filter: Filter): void {
230-
if (!filter) {
230+
if (!filter?.userId) {
231231
return;
232232
}
233233
if (!this.filters[filter.userId]) {

src/sync.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import { BeaconEvent } from "./models/beacon";
5959
import { IEventsResponse } from "./@types/requests";
6060
import { IAbortablePromise } from "./@types/partials";
6161
import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync";
62+
import { Feature, ServerSupport } from "./feature";
6263

6364
const DEBUG = true;
6465

@@ -562,7 +563,11 @@ export class SyncApi {
562563
};
563564

564565
private buildDefaultFilter = () => {
565-
return new Filter(this.client.credentials.userId);
566+
const filter = new Filter(this.client.credentials.userId);
567+
if (this.client.canSupport.get(Feature.ThreadUnreadNotifications) !== ServerSupport.Unsupported) {
568+
filter.setUnreadThreadNotifications(true);
569+
}
570+
return filter;
566571
};
567572

568573
private checkLazyLoadStatus = async () => {
@@ -706,10 +711,6 @@ export class SyncApi {
706711
const initialFilter = this.buildDefaultFilter();
707712
initialFilter.setDefinition(filter.getDefinition());
708713
initialFilter.setTimelineLimit(this.opts.initialSyncLimit);
709-
const supportsThreadNotifications =
710-
await this.client.doesServerSupportUnstableFeature("org.matrix.msc3773")
711-
|| await this.client.isVersionSupported("v1.4");
712-
initialFilter.setUnreadThreadNotifications(supportsThreadNotifications);
713714
// Use an inline filter, no point uploading it for a single usage
714715
firstSyncFilter = JSON.stringify(initialFilter.getDefinition());
715716
}

0 commit comments

Comments
 (0)