diff --git a/spec/integ/matrix-client-methods.spec.js b/spec/integ/matrix-client-methods.spec.js index 0dd33a02c3d..85b18f93e9c 100644 --- a/spec/integ/matrix-client-methods.spec.js +++ b/spec/integ/matrix-client-methods.spec.js @@ -889,6 +889,7 @@ describe("MatrixClient", function() { }; const prom = client.getPushers(); + httpBackend.when("GET", "/_matrix/client/versions").respond(200, {}); httpBackend.when("GET", "/pushers").respond(200, response); await httpBackend.flush(); expect(await prom).toStrictEqual(response); diff --git a/spec/test-utils/test-utils.ts b/spec/test-utils/test-utils.ts index 16d1cb5655b..0187b12f583 100644 --- a/spec/test-utils/test-utils.ts +++ b/spec/test-utils/test-utils.ts @@ -6,7 +6,7 @@ import '../olm-loader'; import { logger } from '../../src/logger'; import { IContent, IEvent, IUnsigned, MatrixEvent, MatrixEventEvent } from "../../src/models/event"; -import { ClientEvent, EventType, MatrixClient, MsgType } from "../../src"; +import { ClientEvent, EventType, IPusher, MatrixClient, MsgType } from "../../src"; import { SyncState } from "../../src/sync"; import { eventMapperFor } from "../../src/event-mapper"; @@ -371,3 +371,14 @@ export async function awaitDecryption(event: MatrixEvent): Promise } export const emitPromise = (e: EventEmitter, k: string): Promise => new Promise(r => e.once(k, r)); + +export const mkPusher = (extra: Partial = {}): IPusher => ({ + app_display_name: "app", + app_id: "123", + data: {}, + device_display_name: "name", + kind: "http", + lang: "en", + pushkey: "pushpush", + ...extra, +}); diff --git a/spec/unit/pusher.spec.ts b/spec/unit/pusher.spec.ts new file mode 100644 index 00000000000..fc5a85f60ef --- /dev/null +++ b/spec/unit/pusher.spec.ts @@ -0,0 +1,69 @@ +/* +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 MockHttpBackend from 'matrix-mock-request'; + +import { IHttpOpts, MatrixClient } from "../../src/matrix"; +import { mkPusher } from '../test-utils/test-utils'; + +const realSetTimeout = setTimeout; +function flushPromises() { + return new Promise(r => { + realSetTimeout(r, 1); + }); +} + +let client: MatrixClient; +let httpBackend: MockHttpBackend; + +describe("Pushers", () => { + beforeEach(() => { + httpBackend = new MockHttpBackend(); + client = new MatrixClient({ + baseUrl: "https://my.home.server", + accessToken: "my.access.token", + request: httpBackend.requestFn as unknown as IHttpOpts["request"], + }); + }); + + describe("supports remotely toggling push notifications", () => { + it("migration support when connecting to a legacy homeserver", async () => { + httpBackend.when("GET", "/_matrix/client/versions").respond(200, { + unstable_features: { + "org.matrix.msc3881": false, + }, + }); + httpBackend.when("GET", "/pushers").respond(200, { + pushers: [ + mkPusher(), + mkPusher({ enabled: true }), + mkPusher({ enabled: false }), + ], + }); + + const promise = client.getPushers(); + + await httpBackend.flushAllExpected(); + await flushPromises(); + + const response = await promise; + + expect(response.pushers[0].enabled).toBe(true); + expect(response.pushers[1].enabled).toBe(true); + expect(response.pushers[2].enabled).toBe(false); + }); + }); +}); diff --git a/src/@types/PushRules.ts b/src/@types/PushRules.ts index 12d1b0d3150..7af6d148150 100644 --- a/src/@types/PushRules.ts +++ b/src/@types/PushRules.ts @@ -156,9 +156,13 @@ export interface IPusher { lang: string; profile_tag?: string; pushkey: string; + enabled?: boolean | null | undefined; + "org.matrix.msc3881.enabled"?: boolean | null | undefined; + device_id?: string | null; + "org.matrix.msc3881.device_id"?: string | null; } -export interface IPusherRequest extends IPusher { +export interface IPusherRequest extends Omit { append?: boolean; } diff --git a/src/client.ts b/src/client.ts index 5764016e2bf..a722ca34df8 100644 --- a/src/client.ts +++ b/src/client.ts @@ -8133,8 +8133,21 @@ export class MatrixClient extends TypedEventEmitter { - return this.http.authedRequest(callback, Method.Get, "/pushers"); + public async getPushers(callback?: Callback): Promise<{ pushers: IPusher[] }> { + const response = await this.http.authedRequest(callback, Method.Get, "/pushers"); + + // Migration path for clients that connect to a homeserver that does not support + // MSC3881 yet, see https://github.com/matrix-org/matrix-spec-proposals/blob/kerry/remote-push-toggle/proposals/3881-remote-push-notification-toggling.md#migration + if (!await this.doesServerSupportUnstableFeature("org.matrix.msc3881")) { + response.pushers = response.pushers.map(pusher => { + if (!pusher.hasOwnProperty("enabled")) { + pusher.enabled = true; + } + return pusher; + }); + } + + return response; } /**