You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-06 12:02:40 +03:00
Fix sync init when thread unread notif is not supported (#2739)
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
62
spec/unit/feature.spec.ts
Normal file
62
spec/unit/feature.spec.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { buildFeatureSupportMap, Feature, ServerSupport } from "../../src/feature";
|
||||
|
||||
describe("Feature detection", () => {
|
||||
it("checks the matrix version", async () => {
|
||||
const support = await buildFeatureSupportMap({
|
||||
versions: ["v1.3"],
|
||||
unstable_features: {},
|
||||
});
|
||||
|
||||
expect(support.get(Feature.Thread)).toBe(ServerSupport.Stable);
|
||||
expect(support.get(Feature.ThreadUnreadNotifications)).toBe(ServerSupport.Unsupported);
|
||||
});
|
||||
|
||||
it("checks the matrix msc number", async () => {
|
||||
const support = await buildFeatureSupportMap({
|
||||
versions: ["v1.2"],
|
||||
unstable_features: {
|
||||
"org.matrix.msc3771": true,
|
||||
"org.matrix.msc3773": true,
|
||||
},
|
||||
});
|
||||
expect(support.get(Feature.ThreadUnreadNotifications)).toBe(ServerSupport.Unstable);
|
||||
});
|
||||
|
||||
it("requires two MSCs to pass", async () => {
|
||||
const support = await buildFeatureSupportMap({
|
||||
versions: ["v1.2"],
|
||||
unstable_features: {
|
||||
"org.matrix.msc3771": false,
|
||||
"org.matrix.msc3773": true,
|
||||
},
|
||||
});
|
||||
expect(support.get(Feature.ThreadUnreadNotifications)).toBe(ServerSupport.Unsupported);
|
||||
});
|
||||
|
||||
it("requires two MSCs OR matrix versions to pass", async () => {
|
||||
const support = await buildFeatureSupportMap({
|
||||
versions: ["v1.4"],
|
||||
unstable_features: {
|
||||
"org.matrix.msc3771": false,
|
||||
"org.matrix.msc3773": true,
|
||||
},
|
||||
});
|
||||
expect(support.get(Feature.ThreadUnreadNotifications)).toBe(ServerSupport.Stable);
|
||||
});
|
||||
});
|
@@ -1,3 +1,4 @@
|
||||
import { UNREAD_THREAD_NOTIFICATIONS } from "../../src/@types/sync";
|
||||
import { Filter, IFilterDefinition } from "../../src/filter";
|
||||
|
||||
describe("Filter", function() {
|
||||
@@ -50,7 +51,7 @@ describe("Filter", function() {
|
||||
expect(filter.getDefinition()).toEqual({
|
||||
room: {
|
||||
timeline: {
|
||||
unread_thread_notifications: true,
|
||||
[UNREAD_THREAD_NOTIFICATIONS.name]: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { UnstableValue } from "matrix-events-sdk/lib/NamespacedValue";
|
||||
import { ServerControlledNamespacedValue } from "../NamespacedValue";
|
||||
|
||||
/**
|
||||
* https://github.com/matrix-org/matrix-doc/pull/3773
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export const UNREAD_THREAD_NOTIFICATIONS = new UnstableValue(
|
||||
export const UNREAD_THREAD_NOTIFICATIONS = new ServerControlledNamespacedValue(
|
||||
"unread_thread_notifications",
|
||||
"org.matrix.msc3773.unread_thread_notifications");
|
||||
|
@@ -204,6 +204,8 @@ import { MAIN_ROOM_TIMELINE } from "./models/read-receipt";
|
||||
import { IgnoredInvites } from "./models/invites-ignorer";
|
||||
import { UIARequest, UIAResponse } from "./@types/uia";
|
||||
import { LocalNotificationSettings } from "./@types/local_notifications";
|
||||
import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync";
|
||||
import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature";
|
||||
|
||||
export type Store = IStore;
|
||||
|
||||
@@ -528,7 +530,7 @@ export interface ITurnServer {
|
||||
credential: string;
|
||||
}
|
||||
|
||||
interface IServerVersions {
|
||||
export interface IServerVersions {
|
||||
versions: string[];
|
||||
unstable_features: Record<string, boolean>;
|
||||
}
|
||||
@@ -967,6 +969,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
protected clientWellKnownIntervalID: ReturnType<typeof setInterval>;
|
||||
protected canResetTimelineCallback: ResetTimelineCallback;
|
||||
|
||||
public canSupport = new Map<Feature, ServerSupport>();
|
||||
|
||||
// The pushprocessor caches useful things, so keep one and re-use it
|
||||
protected pushProcessor = new PushProcessor(this);
|
||||
|
||||
@@ -1197,6 +1201,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
this.syncApi.stop();
|
||||
}
|
||||
|
||||
const serverVersions = await this.getVersions();
|
||||
this.canSupport = await buildFeatureSupportMap(serverVersions);
|
||||
|
||||
const support = this.canSupport.get(Feature.ThreadUnreadNotifications);
|
||||
UNREAD_THREAD_NOTIFICATIONS.setPreferUnstable(support === ServerSupport.Unstable);
|
||||
|
||||
const { threads, list } = await this.doesServerSupportThread();
|
||||
Thread.setServerSideSupport(threads);
|
||||
Thread.setServerSideListSupport(list);
|
||||
|
62
src/feature.ts
Normal file
62
src/feature.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { IServerVersions } from "./client";
|
||||
|
||||
export enum ServerSupport {
|
||||
Stable,
|
||||
Unstable,
|
||||
Unsupported
|
||||
}
|
||||
|
||||
export enum Feature {
|
||||
Thread = "Thread",
|
||||
ThreadUnreadNotifications = "ThreadUnreadNotifications",
|
||||
}
|
||||
|
||||
type FeatureSupportCondition = {
|
||||
unstablePrefixes?: string[];
|
||||
matrixVersion?: string;
|
||||
};
|
||||
|
||||
const featureSupportResolver: Record<string, FeatureSupportCondition> = {
|
||||
[Feature.Thread]: {
|
||||
unstablePrefixes: ["org.matrix.msc3440"],
|
||||
matrixVersion: "v1.3",
|
||||
},
|
||||
[Feature.ThreadUnreadNotifications]: {
|
||||
unstablePrefixes: ["org.matrix.msc3771", "org.matrix.msc3773"],
|
||||
matrixVersion: "v1.4",
|
||||
},
|
||||
};
|
||||
|
||||
export async function buildFeatureSupportMap(versions: IServerVersions): Promise<Map<Feature, ServerSupport>> {
|
||||
const supportMap = new Map<Feature, ServerSupport>();
|
||||
for (const [feature, supportCondition] of Object.entries(featureSupportResolver)) {
|
||||
const supportMatrixVersion = versions.versions?.includes(supportCondition.matrixVersion || "") ?? false;
|
||||
const supportUnstablePrefixes = supportCondition.unstablePrefixes?.every(unstablePrefix => {
|
||||
return versions.unstable_features?.[unstablePrefix] === true;
|
||||
}) ?? false;
|
||||
if (supportMatrixVersion) {
|
||||
supportMap.set(feature as Feature, ServerSupport.Stable);
|
||||
} else if (supportUnstablePrefixes) {
|
||||
supportMap.set(feature as Feature, ServerSupport.Unstable);
|
||||
} else {
|
||||
supportMap.set(feature as Feature, ServerSupport.Unsupported);
|
||||
}
|
||||
}
|
||||
return supportMap;
|
||||
}
|
@@ -73,7 +73,7 @@ export interface IFilterComponent {
|
||||
* @param {Object} filterJson the definition of this filter JSON, e.g. { 'contains_url': true }
|
||||
*/
|
||||
export class FilterComponent {
|
||||
constructor(private filterJson: IFilterComponent, public readonly userId?: string) {}
|
||||
constructor(private filterJson: IFilterComponent, public readonly userId?: string | undefined | null) {}
|
||||
|
||||
/**
|
||||
* Checks with the filter component matches the given event
|
||||
|
@@ -22,6 +22,7 @@ import {
|
||||
EventType,
|
||||
RelationType,
|
||||
} from "./@types/event";
|
||||
import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync";
|
||||
import { FilterComponent, IFilterComponent } from "./filter-component";
|
||||
import { MatrixEvent } from "./models/event";
|
||||
|
||||
@@ -99,7 +100,7 @@ export class Filter {
|
||||
* @param {Object} jsonObj
|
||||
* @return {Filter}
|
||||
*/
|
||||
public static fromJson(userId: string, filterId: string, jsonObj: IFilterDefinition): Filter {
|
||||
public static fromJson(userId: string | undefined | null, filterId: string, jsonObj: IFilterDefinition): Filter {
|
||||
const filter = new Filter(userId, filterId);
|
||||
filter.setDefinition(jsonObj);
|
||||
return filter;
|
||||
@@ -109,7 +110,7 @@ export class Filter {
|
||||
private roomFilter: FilterComponent;
|
||||
private roomTimelineFilter: FilterComponent;
|
||||
|
||||
constructor(public readonly userId: string, public filterId?: string) {}
|
||||
constructor(public readonly userId: string | undefined | null, public filterId?: string) {}
|
||||
|
||||
/**
|
||||
* Get the ID of this filter on your homeserver (if known)
|
||||
@@ -227,7 +228,16 @@ export class Filter {
|
||||
* @param {boolean} enabled
|
||||
*/
|
||||
public setUnreadThreadNotifications(enabled: boolean): void {
|
||||
setProp(this.definition, "room.timeline.unread_thread_notifications", !!enabled);
|
||||
this.definition = {
|
||||
...this.definition,
|
||||
room: {
|
||||
...this.definition?.room,
|
||||
timeline: {
|
||||
...this.definition?.room?.timeline,
|
||||
[UNREAD_THREAD_NOTIFICATIONS.name]: !!enabled,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
setLazyLoadMembers(enabled: boolean): void {
|
||||
|
@@ -227,7 +227,7 @@ export class MemoryStore implements IStore {
|
||||
* @param {Filter} filter
|
||||
*/
|
||||
public storeFilter(filter: Filter): void {
|
||||
if (!filter) {
|
||||
if (!filter?.userId) {
|
||||
return;
|
||||
}
|
||||
if (!this.filters[filter.userId]) {
|
||||
|
11
src/sync.ts
11
src/sync.ts
@@ -59,6 +59,7 @@ import { BeaconEvent } from "./models/beacon";
|
||||
import { IEventsResponse } from "./@types/requests";
|
||||
import { IAbortablePromise } from "./@types/partials";
|
||||
import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync";
|
||||
import { Feature, ServerSupport } from "./feature";
|
||||
|
||||
const DEBUG = true;
|
||||
|
||||
@@ -562,7 +563,11 @@ export class SyncApi {
|
||||
};
|
||||
|
||||
private buildDefaultFilter = () => {
|
||||
return new Filter(this.client.credentials.userId);
|
||||
const filter = new Filter(this.client.credentials.userId);
|
||||
if (this.client.canSupport.get(Feature.ThreadUnreadNotifications) !== ServerSupport.Unsupported) {
|
||||
filter.setUnreadThreadNotifications(true);
|
||||
}
|
||||
return filter;
|
||||
};
|
||||
|
||||
private checkLazyLoadStatus = async () => {
|
||||
@@ -706,10 +711,6 @@ export class SyncApi {
|
||||
const initialFilter = this.buildDefaultFilter();
|
||||
initialFilter.setDefinition(filter.getDefinition());
|
||||
initialFilter.setTimelineLimit(this.opts.initialSyncLimit);
|
||||
const supportsThreadNotifications =
|
||||
await this.client.doesServerSupportUnstableFeature("org.matrix.msc3773")
|
||||
|| await this.client.isVersionSupported("v1.4");
|
||||
initialFilter.setUnreadThreadNotifications(supportsThreadNotifications);
|
||||
// Use an inline filter, no point uploading it for a single usage
|
||||
firstSyncFilter = JSON.stringify(initialFilter.getDefinition());
|
||||
}
|
||||
|
Reference in New Issue
Block a user