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";
|
import { Filter, IFilterDefinition } from "../../src/filter";
|
||||||
|
|
||||||
describe("Filter", function() {
|
describe("Filter", function() {
|
||||||
@@ -50,7 +51,7 @@ describe("Filter", function() {
|
|||||||
expect(filter.getDefinition()).toEqual({
|
expect(filter.getDefinition()).toEqual({
|
||||||
room: {
|
room: {
|
||||||
timeline: {
|
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.
|
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
|
* https://github.com/matrix-org/matrix-doc/pull/3773
|
||||||
*
|
*
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
export const UNREAD_THREAD_NOTIFICATIONS = new UnstableValue(
|
export const UNREAD_THREAD_NOTIFICATIONS = new ServerControlledNamespacedValue(
|
||||||
"unread_thread_notifications",
|
"unread_thread_notifications",
|
||||||
"org.matrix.msc3773.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 { IgnoredInvites } from "./models/invites-ignorer";
|
||||||
import { UIARequest, UIAResponse } from "./@types/uia";
|
import { UIARequest, UIAResponse } from "./@types/uia";
|
||||||
import { LocalNotificationSettings } from "./@types/local_notifications";
|
import { LocalNotificationSettings } from "./@types/local_notifications";
|
||||||
|
import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync";
|
||||||
|
import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature";
|
||||||
|
|
||||||
export type Store = IStore;
|
export type Store = IStore;
|
||||||
|
|
||||||
@@ -528,7 +530,7 @@ export interface ITurnServer {
|
|||||||
credential: string;
|
credential: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IServerVersions {
|
export interface IServerVersions {
|
||||||
versions: string[];
|
versions: string[];
|
||||||
unstable_features: Record<string, boolean>;
|
unstable_features: Record<string, boolean>;
|
||||||
}
|
}
|
||||||
@@ -967,6 +969,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
protected clientWellKnownIntervalID: ReturnType<typeof setInterval>;
|
protected clientWellKnownIntervalID: ReturnType<typeof setInterval>;
|
||||||
protected canResetTimelineCallback: ResetTimelineCallback;
|
protected canResetTimelineCallback: ResetTimelineCallback;
|
||||||
|
|
||||||
|
public canSupport = new Map<Feature, ServerSupport>();
|
||||||
|
|
||||||
// The pushprocessor caches useful things, so keep one and re-use it
|
// The pushprocessor caches useful things, so keep one and re-use it
|
||||||
protected pushProcessor = new PushProcessor(this);
|
protected pushProcessor = new PushProcessor(this);
|
||||||
|
|
||||||
@@ -1197,6 +1201,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
this.syncApi.stop();
|
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();
|
const { threads, list } = await this.doesServerSupportThread();
|
||||||
Thread.setServerSideSupport(threads);
|
Thread.setServerSideSupport(threads);
|
||||||
Thread.setServerSideListSupport(list);
|
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 }
|
* @param {Object} filterJson the definition of this filter JSON, e.g. { 'contains_url': true }
|
||||||
*/
|
*/
|
||||||
export class FilterComponent {
|
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
|
* Checks with the filter component matches the given event
|
||||||
|
@@ -22,6 +22,7 @@ import {
|
|||||||
EventType,
|
EventType,
|
||||||
RelationType,
|
RelationType,
|
||||||
} from "./@types/event";
|
} from "./@types/event";
|
||||||
|
import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync";
|
||||||
import { FilterComponent, IFilterComponent } from "./filter-component";
|
import { FilterComponent, IFilterComponent } from "./filter-component";
|
||||||
import { MatrixEvent } from "./models/event";
|
import { MatrixEvent } from "./models/event";
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ export class Filter {
|
|||||||
* @param {Object} jsonObj
|
* @param {Object} jsonObj
|
||||||
* @return {Filter}
|
* @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);
|
const filter = new Filter(userId, filterId);
|
||||||
filter.setDefinition(jsonObj);
|
filter.setDefinition(jsonObj);
|
||||||
return filter;
|
return filter;
|
||||||
@@ -109,7 +110,7 @@ export class Filter {
|
|||||||
private roomFilter: FilterComponent;
|
private roomFilter: FilterComponent;
|
||||||
private roomTimelineFilter: 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)
|
* Get the ID of this filter on your homeserver (if known)
|
||||||
@@ -227,7 +228,16 @@ export class Filter {
|
|||||||
* @param {boolean} enabled
|
* @param {boolean} enabled
|
||||||
*/
|
*/
|
||||||
public setUnreadThreadNotifications(enabled: boolean): void {
|
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 {
|
setLazyLoadMembers(enabled: boolean): void {
|
||||||
|
@@ -227,7 +227,7 @@ export class MemoryStore implements IStore {
|
|||||||
* @param {Filter} filter
|
* @param {Filter} filter
|
||||||
*/
|
*/
|
||||||
public storeFilter(filter: Filter): void {
|
public storeFilter(filter: Filter): void {
|
||||||
if (!filter) {
|
if (!filter?.userId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.filters[filter.userId]) {
|
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 { IEventsResponse } from "./@types/requests";
|
||||||
import { IAbortablePromise } from "./@types/partials";
|
import { IAbortablePromise } from "./@types/partials";
|
||||||
import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync";
|
import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync";
|
||||||
|
import { Feature, ServerSupport } from "./feature";
|
||||||
|
|
||||||
const DEBUG = true;
|
const DEBUG = true;
|
||||||
|
|
||||||
@@ -562,7 +563,11 @@ export class SyncApi {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private buildDefaultFilter = () => {
|
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 () => {
|
private checkLazyLoadStatus = async () => {
|
||||||
@@ -706,10 +711,6 @@ export class SyncApi {
|
|||||||
const initialFilter = this.buildDefaultFilter();
|
const initialFilter = this.buildDefaultFilter();
|
||||||
initialFilter.setDefinition(filter.getDefinition());
|
initialFilter.setDefinition(filter.getDefinition());
|
||||||
initialFilter.setTimelineLimit(this.opts.initialSyncLimit);
|
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
|
// Use an inline filter, no point uploading it for a single usage
|
||||||
firstSyncFilter = JSON.stringify(initialFilter.getDefinition());
|
firstSyncFilter = JSON.stringify(initialFilter.getDefinition());
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user