diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index c1a7423ab..6b2188a0c 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -5,6 +5,11 @@ on: secrets: SONAR_TOKEN: required: true + inputs: + extra_args: + type: string + required: false + description: "Extra args to pass to SonarCloud" jobs: sonarqube: runs-on: ubuntu-latest @@ -22,7 +27,7 @@ jobs: - name: "🩻 SonarCloud Scan" id: sonarcloud - uses: matrix-org/sonarcloud-workflow-action@v2.2 + uses: matrix-org/sonarcloud-workflow-action@v2.3 with: repository: ${{ github.event.workflow_run.head_repository.full_name }} is_pr: ${{ github.event.workflow_run.event == 'pull_request' }} @@ -33,8 +38,8 @@ jobs: coverage_run_id: ${{ github.event.workflow_run.id }} coverage_workflow_name: tests.yml coverage_extract_path: coverage - - + extra_args: ${{ inputs.extra_args }} + - uses: Sibz/github-status-action@v1 if: always() with: diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index a5360c64f..61768adad 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -8,8 +8,36 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch }} cancel-in-progress: true jobs: + # This is a workaround for https://github.com/SonarSource/SonarJS/issues/578 + prepare: + name: Prepare + runs-on: ubuntu-latest + outputs: + reportPaths: ${{ steps.extra_args.outputs.reportPaths }} + testExecutionReportPaths: ${{ steps.extra_args.outputs.testExecutionReportPaths }} + steps: + # There's a 'download artifact' action, but it hasn't been updated for the workflow_run action + # (https://github.com/actions/download-artifact/issues/60) so instead we get this mess: + - name: 📥 Download artifact + uses: dawidd6/action-download-artifact@v2 + with: + workflow: tests.yaml + run_id: ${{ github.event.workflow_run.id }} + name: coverage + path: coverage + + - id: extra_args + run: | + coverage=$(find coverage -type f -name '*lcov.info' | tr '\n' ',' | sed 's/,$//g') + echo "reportPaths=$coverage" >> $GITHUB_OUTPUT + reports=$(find coverage -type f -name 'jest-sonar-report*.xml' | tr '\n' ',' | sed 's/,$//g') + echo "testExecutionReportPaths=$reports" >> $GITHUB_OUTPUT + sonarqube: name: 🩻 SonarQube + needs: prepare uses: matrix-org/matrix-js-sdk/.github/workflows/sonarcloud.yml@develop secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + with: + extra_args: -Dsonar.javascript.lcov.reportPaths=${{ needs.prepare.outputs.reportPaths }} -Dsonar.testExecutionReportPaths=${{ needs.prepare.outputs.testExecutionReportPaths }} diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 44ccbf495..6b6de7f2c 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -64,38 +64,3 @@ jobs: - name: Generate Docs run: "yarn run gendoc" - - tsc-strict: - name: Typescript Strict Error Checker - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - permissions: - pull-requests: read - checks: write - steps: - - uses: actions/checkout@v3 - - - name: Get diff lines - id: diff - uses: Equip-Collaboration/diff-line-numbers@v1.0.0 - with: - include: '["\\.tsx?$"]' - - - name: Detecting files changed - id: files - uses: futuratrepadeira/changed-files@v4.0.0 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - pattern: '^.*\.tsx?$' - - - uses: t3chguy/typescript-check-action@main - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - use-check: false - check-fail-mode: added - output-behaviour: annotate - ts-extra-args: '--strict' - files-changed: ${{ steps.files.outputs.files_updated }} - files-added: ${{ steps.files.outputs.files_created }} - files-deleted: ${{ steps.files.outputs.files_deleted }} - line-numbers: ${{ steps.diff.outputs.lineNumbers }} diff --git a/.gitignore b/.gitignore index 4fc51ab85..8609e9cdb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /.jsdocbuild /.jsdoc +.DS_Store node_modules /.npmrc diff --git a/package.json b/package.json index a6fadbd91..ac83abf6f 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ }, "scripts": { "prepublishOnly": "yarn build", + "postinstall": "patch-package", "start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && babel src -w -s -d lib --verbose --extensions \".ts,.js\"", "dist": "echo 'This is for the release script so it can make assets (browser bundle).' && yarn build", "clean": "rimraf lib dist", @@ -59,7 +60,7 @@ "bs58": "^5.0.0", "content-type": "^1.0.4", "loglevel": "^1.7.1", - "matrix-events-sdk": "^0.0.1-beta.7", + "matrix-events-sdk": "0.0.1-beta.7", "matrix-widget-api": "^1.0.0", "p-retry": "4", "qs": "^6.9.6", @@ -79,6 +80,7 @@ "@babel/preset-env": "^7.12.11", "@babel/preset-typescript": "^7.12.7", "@babel/register": "^7.12.10", + "@casualbot/jest-sonar-reporter": "^2.2.5", "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.13.tgz", "@types/bs58": "^4.0.1", "@types/content-type": "^1.1.5", @@ -106,9 +108,10 @@ "jest-environment-jsdom": "^28.1.3", "jest-localstorage-mock": "^2.4.6", "jest-mock": "^29.0.0", - "jest-sonar-reporter": "^2.0.0", "jsdoc": "^3.6.6", "matrix-mock-request": "^2.5.0", + "patch-package": "^6.5.0", + "postinstall-postinstall": "^2.1.0", "rimraf": "^3.0.2", "terser": "^5.5.1", "tsify": "^5.0.2", @@ -129,10 +132,11 @@ "text-summary", "lcov" ], - "testResultsProcessor": "jest-sonar-reporter" + "testResultsProcessor": "@casualbot/jest-sonar-reporter" }, - "jestSonar": { - "reportPath": "coverage", - "sonar56x": true + "@casualbot/jest-sonar-reporter": { + "outputDirectory": "coverage", + "outputName": "jest-sonar-report.xml", + "relativePaths": true } } diff --git a/patches/matrix-events-sdk+0.0.1-beta.7.patch b/patches/matrix-events-sdk+0.0.1-beta.7.patch new file mode 100644 index 000000000..1d6165363 --- /dev/null +++ b/patches/matrix-events-sdk+0.0.1-beta.7.patch @@ -0,0 +1,12 @@ +diff --git a/node_modules/matrix-events-sdk/lib/NamespacedMap.d.ts b/node_modules/matrix-events-sdk/lib/NamespacedMap.d.ts +index c141b11..461f528 100644 +--- a/node_modules/matrix-events-sdk/lib/NamespacedMap.d.ts ++++ b/node_modules/matrix-events-sdk/lib/NamespacedMap.d.ts +@@ -1,6 +1,6 @@ + import { NamespacedValue } from "./NamespacedValue"; + import { Optional } from "./types"; +-declare type NS = NamespacedValue, Optional>; ++declare type NS = NamespacedValue; + /** + * A `Map` implementation which accepts a NamespacedValue as a key, and arbitrary value. The + * namespaced value must be a string type. diff --git a/sonar-project.properties b/sonar-project.properties index 4f5b65e38..5cfec433c 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -11,6 +11,6 @@ sonar.exclusions=docs,examples,git-hooks sonar.typescript.tsconfigPath=./tsconfig.json sonar.javascript.lcov.reportPaths=coverage/lcov.info sonar.coverage.exclusions=spec/**/* -sonar.testExecutionReportPaths=coverage/test-report.xml +sonar.testExecutionReportPaths=coverage/jest-sonar-report.xml sonar.lang.patterns.ts=**/*.ts,**/*.tsx diff --git a/spec/integ/matrix-client-syncing.spec.ts b/spec/integ/matrix-client-syncing.spec.ts index 418e2d16a..216fbdbb1 100644 --- a/spec/integ/matrix-client-syncing.spec.ts +++ b/spec/integ/matrix-client-syncing.spec.ts @@ -707,15 +707,11 @@ describe("MatrixClient syncing", () => { awaitSyncEvent(2), ]).then(() => { const room = client!.getRoom(roomOne)!; - const stateAtStart = room.getLiveTimeline().getState( - EventTimeline.BACKWARDS, - ); + const stateAtStart = room.getLiveTimeline().getState(EventTimeline.BACKWARDS)!; const startRoomNameEvent = stateAtStart.getStateEvents('m.room.name', ''); expect(startRoomNameEvent.getContent().name).toEqual('Old room name'); - const stateAtEnd = room.getLiveTimeline().getState( - EventTimeline.FORWARDS, - ); + const stateAtEnd = room.getLiveTimeline().getState(EventTimeline.FORWARDS)!; const endRoomNameEvent = stateAtEnd.getStateEvents('m.room.name', ''); expect(endRoomNameEvent.getContent().name).toEqual('A new room name'); }); diff --git a/spec/unit/crypto/dehydration.spec.ts b/spec/unit/crypto/dehydration.spec.ts new file mode 100644 index 000000000..7d3227117 --- /dev/null +++ b/spec/unit/crypto/dehydration.spec.ts @@ -0,0 +1,143 @@ +/* +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 '../../olm-loader'; +import { TestClient } from '../../TestClient'; +import { logger } from '../../../src/logger'; +import { DEHYDRATION_ALGORITHM } from '../../../src/crypto/dehydration'; + +const Olm = global.Olm; + +describe("Dehydration", () => { + if (!global.Olm) { + logger.warn('Not running dehydration unit tests: libolm not present'); + return; + } + + beforeAll(function() { + return global.Olm.init(); + }); + + it("should rehydrate a dehydrated device", async () => { + const key = new Uint8Array([1, 2, 3]); + const alice = new TestClient( + "@alice:example.com", "Osborne2", undefined, undefined, + { + cryptoCallbacks: { + getDehydrationKey: async t => key, + }, + }, + ); + + const dehydratedDevice = new Olm.Account(); + dehydratedDevice.create(); + + alice.httpBackend.when("GET", "/dehydrated_device").respond(200, { + device_id: "ABCDEFG", + device_data: { + algorithm: DEHYDRATION_ALGORITHM, + account: dehydratedDevice.pickle(new Uint8Array(key)), + }, + }); + alice.httpBackend.when("POST", "/dehydrated_device/claim").respond(200, { + success: true, + }); + + expect((await Promise.all([ + alice.client.rehydrateDevice(), + alice.httpBackend.flushAllExpected(), + ]))[0]) + .toEqual("ABCDEFG"); + + expect(alice.client.getDeviceId()).toEqual("ABCDEFG"); + }); + + it("should dehydrate a device", async () => { + const key = new Uint8Array([1, 2, 3]); + const alice = new TestClient( + "@alice:example.com", "Osborne2", undefined, undefined, + { + cryptoCallbacks: { + getDehydrationKey: async t => key, + }, + }, + ); + + await alice.client.initCrypto(); + + alice.httpBackend.when("GET", "/room_keys/version").respond(404, { + errcode: "M_NOT_FOUND", + }); + + let pickledAccount = ""; + + alice.httpBackend.when("PUT", "/dehydrated_device") + .check((req) => { + expect(req.data.device_data).toMatchObject({ + algorithm: DEHYDRATION_ALGORITHM, + account: expect.any(String), + }); + pickledAccount = req.data.device_data.account; + }) + .respond(200, { + device_id: "ABCDEFG", + }); + alice.httpBackend.when("POST", "/keys/upload/ABCDEFG") + .check((req) => { + expect(req.data).toMatchObject({ + "device_keys": expect.objectContaining({ + algorithms: expect.any(Array), + device_id: "ABCDEFG", + user_id: "@alice:example.com", + keys: expect.objectContaining({ + "ed25519:ABCDEFG": expect.any(String), + "curve25519:ABCDEFG": expect.any(String), + }), + signatures: expect.objectContaining({ + "@alice:example.com": expect.objectContaining({ + "ed25519:ABCDEFG": expect.any(String), + }), + }), + }), + "one_time_keys": expect.any(Object), + "org.matrix.msc2732.fallback_keys": expect.any(Object), + }); + }) + .respond(200, {}); + + try { + const deviceId = + (await Promise.all([ + alice.client.createDehydratedDevice(new Uint8Array(key), {}), + alice.httpBackend.flushAllExpected(), + ]))[0]; + + expect(deviceId).toEqual("ABCDEFG"); + expect(deviceId).not.toEqual(""); + + // try to rehydrate the dehydrated device + const rehydrated = new Olm.Account(); + try { + rehydrated.unpickle(new Uint8Array(key), pickledAccount); + } finally { + rehydrated.free(); + } + } finally { + alice.client?.crypto?.dehydrationManager?.stop(); + alice.client?.crypto?.deviceList.stop(); + } + }); +}); diff --git a/spec/unit/event-timeline.spec.ts b/spec/unit/event-timeline.spec.ts index 59f35dcce..21434ec71 100644 --- a/spec/unit/event-timeline.spec.ts +++ b/spec/unit/event-timeline.spec.ts @@ -55,13 +55,13 @@ describe("EventTimeline", function() { ]; timeline.initialiseState(events); // @ts-ignore private prop - const timelineStartState = timeline.startState; + const timelineStartState = timeline.startState!; expect(mocked(timelineStartState).setStateEvents).toHaveBeenCalledWith( events, { timelineWasEmpty: undefined }, ); // @ts-ignore private prop - const timelineEndState = timeline.endState; + const timelineEndState = timeline.endState!; expect(mocked(timelineEndState).setStateEvents).toHaveBeenCalledWith( events, { timelineWasEmpty: undefined }, @@ -185,14 +185,14 @@ describe("EventTimeline", function() { sentinel.name = "Old Alice"; sentinel.membership = "join"; - mocked(timeline.getState(EventTimeline.FORWARDS)).getSentinelMember + mocked(timeline.getState(EventTimeline.FORWARDS)!).getSentinelMember .mockImplementation(function(uid) { if (uid === userA) { return sentinel; } return null; }); - mocked(timeline.getState(EventTimeline.BACKWARDS)).getSentinelMember + mocked(timeline.getState(EventTimeline.BACKWARDS)!).getSentinelMember .mockImplementation(function(uid) { if (uid === userA) { return oldSentinel; @@ -225,14 +225,14 @@ describe("EventTimeline", function() { sentinel.name = "Old Alice"; sentinel.membership = "join"; - mocked(timeline.getState(EventTimeline.FORWARDS)).getSentinelMember + mocked(timeline.getState(EventTimeline.FORWARDS)!).getSentinelMember .mockImplementation(function(uid) { if (uid === userA) { return sentinel; } return null; }); - mocked(timeline.getState(EventTimeline.BACKWARDS)).getSentinelMember + mocked(timeline.getState(EventTimeline.BACKWARDS)!).getSentinelMember .mockImplementation(function(uid) { if (uid === userA) { return oldSentinel; @@ -269,15 +269,15 @@ describe("EventTimeline", function() { timeline.addEvent(events[0], { toStartOfTimeline: false }); timeline.addEvent(events[1], { toStartOfTimeline: false }); - expect(timeline.getState(EventTimeline.FORWARDS).setStateEvents). + expect(timeline.getState(EventTimeline.FORWARDS)!.setStateEvents). toHaveBeenCalledWith([events[0]], { timelineWasEmpty: undefined }); - expect(timeline.getState(EventTimeline.FORWARDS).setStateEvents). + expect(timeline.getState(EventTimeline.FORWARDS)!.setStateEvents). toHaveBeenCalledWith([events[1]], { timelineWasEmpty: undefined }); expect(events[0].forwardLooking).toBe(true); expect(events[1].forwardLooking).toBe(true); - expect(timeline.getState(EventTimeline.BACKWARDS).setStateEvents). + expect(timeline.getState(EventTimeline.BACKWARDS)!.setStateEvents). not.toHaveBeenCalled(); }); @@ -298,15 +298,15 @@ describe("EventTimeline", function() { timeline.addEvent(events[0], { toStartOfTimeline: true }); timeline.addEvent(events[1], { toStartOfTimeline: true }); - expect(timeline.getState(EventTimeline.BACKWARDS).setStateEvents). + expect(timeline.getState(EventTimeline.BACKWARDS)!.setStateEvents). toHaveBeenCalledWith([events[0]], { timelineWasEmpty: undefined }); - expect(timeline.getState(EventTimeline.BACKWARDS).setStateEvents). + expect(timeline.getState(EventTimeline.BACKWARDS)!.setStateEvents). toHaveBeenCalledWith([events[1]], { timelineWasEmpty: undefined }); expect(events[0].forwardLooking).toBe(false); expect(events[1].forwardLooking).toBe(false); - expect(timeline.getState(EventTimeline.FORWARDS).setStateEvents). + expect(timeline.getState(EventTimeline.FORWARDS)!.setStateEvents). not.toHaveBeenCalled(); }); diff --git a/spec/unit/queueToDevice.spec.ts b/spec/unit/queueToDevice.spec.ts index 7f4504eaf..298f5341c 100644 --- a/spec/unit/queueToDevice.spec.ts +++ b/spec/unit/queueToDevice.spec.ts @@ -23,6 +23,7 @@ import { ToDeviceBatch } from '../../src/models/ToDeviceMessage'; import { logger } from '../../src/logger'; import { IStore } from '../../src/store'; import { flushPromises } from '../test-utils/flushPromises'; +import { removeElement } from "../../src/utils"; const FAKE_USER = "@alice:example.org"; const FAKE_DEVICE_ID = "AAAAAAAA"; @@ -63,6 +64,8 @@ describe.each([ let client: MatrixClient; beforeEach(async function() { + jest.runOnlyPendingTimers(); + jest.useRealTimers(); httpBackend = new MockHttpBackend(); let store: IStore; @@ -288,7 +291,7 @@ describe.each([ ], }); - expect(await httpBackend.flush(undefined, 1, 1)).toEqual(1); + expect(await httpBackend.flush(undefined, 1, 20)).toEqual(1); await flushPromises(); const dummyEvent = new MatrixEvent({ @@ -316,12 +319,12 @@ describe.each([ }); } + const expectedCounts = [20, 1]; httpBackend.when( "PUT", "/sendToDevice/org.example.foo/", ).check((request) => { - expect(Object.keys(request.data.messages).length).toEqual(20); + expect(removeElement(expectedCounts, c => c === Object.keys(request.data.messages).length)).toBeTruthy(); }).respond(200, {}); - httpBackend.when( "PUT", "/sendToDevice/org.example.foo/", ).check((request) => { diff --git a/spec/unit/webrtc/call.spec.ts b/spec/unit/webrtc/call.spec.ts index 331830d18..030e6a6ba 100644 --- a/spec/unit/webrtc/call.spec.ts +++ b/spec/unit/webrtc/call.spec.ts @@ -768,6 +768,19 @@ describe('Call', function() { }); }); + describe("transferToCall", () => { + it("should send the required events", async () => { + const targetCall = new MatrixCall({ client: client.client, roomId: "!roomId:server" }); + const sendEvent = jest.spyOn(client.client, "sendEvent"); + await call.transferToCall(targetCall); + + const newCallId = (sendEvent.mock.calls[0][2] as any)!.await_call; + expect(sendEvent).toHaveBeenCalledWith(call.roomId, EventType.CallReplaces, expect.objectContaining({ + create_call: newCallId, + })); + }); + }); + describe("muting", () => { let mockSendVoipEvent: jest.Mock, [string, object]>; beforeEach(async () => { diff --git a/src/@types/PushRules.ts b/src/@types/PushRules.ts index f3aeca156..59af49060 100644 --- a/src/@types/PushRules.ts +++ b/src/@types/PushRules.ts @@ -30,7 +30,7 @@ export enum TweakName { export type Tweak = { set_tweak: N; - value: V; + value?: V; }; export type TweakHighlight = Tweak; @@ -76,7 +76,8 @@ export interface IPushRuleCondition { export interface IEventMatchCondition extends IPushRuleCondition { key: string; - pattern: string; + pattern?: string; + value?: string; } export interface IContainsDisplayNameCondition extends IPushRuleCondition { diff --git a/src/autodiscovery.ts b/src/autodiscovery.ts index f2128c0d5..d26e4d5c6 100644 --- a/src/autodiscovery.ts +++ b/src/autodiscovery.ts @@ -32,6 +32,28 @@ export enum AutoDiscoveryAction { FAIL_ERROR = "FAIL_ERROR", } +enum AutoDiscoveryError { + Invalid = "Invalid homeserver discovery response", + GenericFailure = "Failed to get autodiscovery configuration from server", + InvalidHsBaseUrl = "Invalid base_url for m.homeserver", + InvalidHomeserver = "Homeserver URL does not appear to be a valid Matrix homeserver", + InvalidIsBaseUrl = "Invalid base_url for m.identity_server", + InvalidIdentityServer = "Identity server URL does not appear to be a valid identity server", + InvalidIs = "Invalid identity server discovery response", + MissingWellknown = "No .well-known JSON file found", + InvalidJson = "Invalid JSON", +} + +interface WellKnownConfig extends Omit { + state: AutoDiscoveryAction; + error?: IWellKnownConfig["error"] | null; +} + +interface ClientConfig { + "m.homeserver": WellKnownConfig; + "m.identity_server": WellKnownConfig; +} + /** * Utilities for automatically discovery resources, such as homeservers * for users to log in to. @@ -42,36 +64,25 @@ export class AutoDiscovery { // translate the meaning of the states in the spec, but also // support our own if needed. - public static readonly ERROR_INVALID = "Invalid homeserver discovery response"; + public static readonly ERROR_INVALID = AutoDiscoveryError.Invalid; - public static readonly ERROR_GENERIC_FAILURE = "Failed to get autodiscovery configuration from server"; + public static readonly ERROR_GENERIC_FAILURE = AutoDiscoveryError.GenericFailure; - public static readonly ERROR_INVALID_HS_BASE_URL = "Invalid base_url for m.homeserver"; + public static readonly ERROR_INVALID_HS_BASE_URL = AutoDiscoveryError.InvalidHsBaseUrl; - public static readonly ERROR_INVALID_HOMESERVER = "Homeserver URL does not appear to be a valid Matrix homeserver"; + public static readonly ERROR_INVALID_HOMESERVER = AutoDiscoveryError.InvalidHomeserver; - public static readonly ERROR_INVALID_IS_BASE_URL = "Invalid base_url for m.identity_server"; + public static readonly ERROR_INVALID_IS_BASE_URL = AutoDiscoveryError.InvalidIsBaseUrl; - // eslint-disable-next-line - public static readonly ERROR_INVALID_IDENTITY_SERVER = "Identity server URL does not appear to be a valid identity server"; + public static readonly ERROR_INVALID_IDENTITY_SERVER = AutoDiscoveryError.InvalidIdentityServer; - public static readonly ERROR_INVALID_IS = "Invalid identity server discovery response"; + public static readonly ERROR_INVALID_IS = AutoDiscoveryError.InvalidIs; - public static readonly ERROR_MISSING_WELLKNOWN = "No .well-known JSON file found"; + public static readonly ERROR_MISSING_WELLKNOWN = AutoDiscoveryError.MissingWellknown; - public static readonly ERROR_INVALID_JSON = "Invalid JSON"; + public static readonly ERROR_INVALID_JSON = AutoDiscoveryError.InvalidJson; - public static readonly ALL_ERRORS = [ - AutoDiscovery.ERROR_INVALID, - AutoDiscovery.ERROR_GENERIC_FAILURE, - AutoDiscovery.ERROR_INVALID_HS_BASE_URL, - AutoDiscovery.ERROR_INVALID_HOMESERVER, - AutoDiscovery.ERROR_INVALID_IS_BASE_URL, - AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER, - AutoDiscovery.ERROR_INVALID_IS, - AutoDiscovery.ERROR_MISSING_WELLKNOWN, - AutoDiscovery.ERROR_INVALID_JSON, - ]; + public static readonly ALL_ERRORS = Object.keys(AutoDiscoveryError); /** * The auto discovery failed. The client is expected to communicate @@ -120,13 +131,13 @@ export class AutoDiscovery { * configuration, which may include error states. Rejects on unexpected * failure, not when verification fails. */ - public static async fromDiscoveryConfig(wellknown: any): Promise { + public static async fromDiscoveryConfig(wellknown: any): Promise { // Step 1 is to get the config, which is provided to us here. // We default to an error state to make the first few checks easier to // write. We'll update the properties of this object over the duration // of this function. - const clientConfig = { + const clientConfig: ClientConfig = { "m.homeserver": { state: AutoDiscovery.FAIL_ERROR, error: AutoDiscovery.ERROR_INVALID, @@ -197,7 +208,7 @@ export class AutoDiscovery { if (wellknown["m.identity_server"]) { // We prepare a failing identity server response to save lines later // in this branch. - const failingClientConfig = { + const failingClientConfig: ClientConfig = { "m.homeserver": clientConfig["m.homeserver"], "m.identity_server": { state: AutoDiscovery.FAIL_PROMPT, @@ -279,7 +290,7 @@ export class AutoDiscovery { * configuration, which may include error states. Rejects on unexpected * failure, not when discovery fails. */ - public static async findClientConfig(domain: string): Promise { + public static async findClientConfig(domain: string): Promise { if (!domain || typeof(domain) !== "string" || domain.length === 0) { throw new Error("'domain' must be a string of non-zero length"); } @@ -298,7 +309,7 @@ export class AutoDiscovery { // We default to an error state to make the first few checks easier to // write. We'll update the properties of this object over the duration // of this function. - const clientConfig = { + const clientConfig: ClientConfig = { "m.homeserver": { state: AutoDiscovery.FAIL_ERROR, error: AutoDiscovery.ERROR_INVALID, @@ -367,18 +378,18 @@ export class AutoDiscovery { * @return {string|boolean} The sanitized URL or a falsey value if the URL is invalid. * @private */ - private static sanitizeWellKnownUrl(url: string): string | boolean { + private static sanitizeWellKnownUrl(url: string): string | false { if (!url) return false; try { - let parsed = null; + let parsed: URL | undefined; try { parsed = new URL(url); } catch (e) { logger.error("Could not parse url", e); } - if (!parsed || !parsed.hostname) return false; + if (!parsed?.hostname) return false; if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false; const port = parsed.port ? `:${parsed.port}` : ""; @@ -448,12 +459,17 @@ export class AutoDiscovery { }; } } catch (err) { - const error = err as Error | string | undefined; + const error = err as AutoDiscoveryError | string | undefined; + let reason = ""; + if (typeof error === "object") { + reason = (error)?.message; + } + return { error, raw: {}, action: AutoDiscoveryAction.FAIL_PROMPT, - reason: (error)?.message || "General failure", + reason: reason || "General failure", }; } @@ -463,7 +479,7 @@ export class AutoDiscovery { action: AutoDiscoveryAction.SUCCESS, }; } catch (err) { - const error = err as Error | string | undefined; + const error = err as Error; return { error, raw: {}, diff --git a/src/client.ts b/src/client.ts index 11c9ae1bd..04e1696f8 100644 --- a/src/client.ts +++ b/src/client.ts @@ -518,7 +518,7 @@ export interface IUploadKeySignaturesResponse { } export interface IPreviewUrlResponse { - [key: string]: string | number; + [key: string]: undefined | string | number; "og:title": string; "og:type": string; "og:url": string; @@ -716,8 +716,9 @@ export interface IMyDevice { display_name?: string; last_seen_ip?: string; last_seen_ts?: number; - [UNSTABLE_MSC3852_LAST_SEEN_UA.stable]?: string; - [UNSTABLE_MSC3852_LAST_SEEN_UA.unstable]?: string; + // UNSTABLE_MSC3852_LAST_SEEN_UA + last_seen_user_agent?: string; + "org.matrix.msc3852.last_seen_user_agent"?: string; } export interface Keys { @@ -3225,7 +3226,7 @@ export class MatrixClient extends TypedEventEmitter { const privKey = decodeRecoveryKey(recoveryKey); - return this.restoreKeyBackup(privKey, targetRoomId, targetSessionId, backupInfo, opts); + return this.restoreKeyBackup(privKey, targetRoomId!, targetSessionId!, backupInfo, opts); } public async restoreKeyBackupWithCache( @@ -3387,7 +3388,7 @@ export class MatrixClient extends TypedEventEmitter { if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) { @@ -3891,10 +3892,10 @@ export class MatrixClient extends TypedEventEmitter { return ev.isRelation(THREAD_RELATION_TYPE.name) && !ev.status; })?.getId() ?? threadId, @@ -4200,7 +4201,7 @@ export class MatrixClient extends TypedEventEmitter { if (typeof threadId !== "string" && threadId !== null) { @@ -4417,7 +4418,7 @@ export class MatrixClient extends TypedEventEmitter { @@ -4461,7 +4462,7 @@ export class MatrixClient extends TypedEventEmitter { @@ -5329,11 +5330,11 @@ export class MatrixClient extends TypedEventEmitter { if (res.state) { - const roomState = eventTimeline.getState(dir); + const roomState = eventTimeline.getState(dir)!; const stateEvents = res.state.map(this.getEventMapper()); roomState.setUnknownStateEvents(stateEvents); } @@ -5861,7 +5862,7 @@ export class MatrixClient extends TypedEventEmitter { if (res.state) { - const roomState = eventTimeline.getState(dir); + const roomState = eventTimeline.getState(dir)!; const stateEvents = res.state.map(this.getEventMapper()); roomState.setUnknownStateEvents(stateEvents); } diff --git a/src/content-helpers.ts b/src/content-helpers.ts index f6763fb8c..4dbb37b21 100644 --- a/src/content-helpers.ts +++ b/src/content-helpers.ts @@ -118,12 +118,12 @@ export function makeEmoteMessage(body: string) { /** Location content helpers */ export const getTextForLocationEvent = ( - uri: string, + uri: string | undefined, assetType: LocationAssetType, - timestamp: number, - description?: string, + timestamp?: number, + description?: string | null, ): string => { - const date = `at ${new Date(timestamp).toISOString()}`; + const date = `at ${new Date(timestamp!).toISOString()}`; const assetName = assetType === LocationAssetType.Self ? 'User' : undefined; const quotedDescription = description ? `"${description}"` : undefined; @@ -147,10 +147,10 @@ export const getTextForLocationEvent = ( export const makeLocationContent = ( // this is first but optional // to avoid a breaking change - text: string | undefined, - uri: string, - timestamp: number, - description?: string, + text?: string, + uri?: string, + timestamp?: number, + description?: string | null, assetType?: LocationAssetType, ): LegacyLocationEventContent & MLocationEventContent => { const defaultedText = text ?? @@ -187,7 +187,7 @@ export const parseLocationEvent = (wireEventContent: LocationEventWireContent): const assetType = asset?.type ?? LocationAssetType.Self; const fallbackText = text ?? wireEventContent.body; - return makeLocationContent(fallbackText, geoUri, timestamp, description, assetType); + return makeLocationContent(fallbackText, geoUri, timestamp ?? undefined, description, assetType); }; /** @@ -201,7 +201,7 @@ export type MakeTopicContent = ( export const makeTopicContent: MakeTopicContent = (topic, htmlTopic) => { const renderings = [{ body: topic, mimetype: "text/plain" }]; if (isProvided(htmlTopic)) { - renderings.push({ body: htmlTopic, mimetype: "text/html" }); + renderings.push({ body: htmlTopic!, mimetype: "text/html" }); } return { topic, [M_TOPIC.name]: renderings }; }; @@ -247,14 +247,14 @@ export const makeBeaconInfoContent: MakeBeaconInfoContent = ( export type BeaconInfoState = MBeaconInfoContent & { assetType?: LocationAssetType; - timestamp: number; + timestamp?: number; }; /** * Flatten beacon info event content */ export const parseBeaconInfoContent = (content: MBeaconInfoEventContent): BeaconInfoState => { const { description, timeout, live } = content; - const timestamp = M_TIMESTAMP.findIn(content); + const timestamp = M_TIMESTAMP.findIn(content) ?? undefined; const asset = M_ASSET.findIn(content); return { @@ -290,14 +290,14 @@ export const makeBeaconContent: MakeBeaconContent = ( }, }); -export type BeaconLocationState = MLocationContent & { +export type BeaconLocationState = Omit & { uri?: string; // override from MLocationContent to allow optionals timestamp?: number; }; export const parseBeaconContent = (content: MBeaconEventContent): BeaconLocationState => { const location = M_LOCATION.findIn(content); - const timestamp = M_TIMESTAMP.findIn(content); + const timestamp = M_TIMESTAMP.findIn(content) ?? undefined; return { description: location?.description, diff --git a/src/crypto/backup.ts b/src/crypto/backup.ts index f57cfe293..fd4377e14 100644 --- a/src/crypto/backup.ts +++ b/src/crypto/backup.ts @@ -302,7 +302,7 @@ export class BackupManager { || now - this.sessionLastCheckAttemptedTime[targetSessionId!] > KEY_BACKUP_CHECK_RATE_LIMIT ) { this.sessionLastCheckAttemptedTime[targetSessionId!] = now; - await this.baseApis.restoreKeyBackupWithCache(targetRoomId, targetSessionId, this.backupInfo, {}); + await this.baseApis.restoreKeyBackupWithCache(targetRoomId!, targetSessionId!, this.backupInfo, {}); } } diff --git a/src/crypto/store/indexeddb-crypto-store-backend.ts b/src/crypto/store/indexeddb-crypto-store-backend.ts index faeb5c829..f525c0c95 100644 --- a/src/crypto/store/indexeddb-crypto-store-backend.ts +++ b/src/crypto/store/indexeddb-crypto-store-backend.ts @@ -201,7 +201,7 @@ export class Backend implements CryptoStore { let stateIndex = 0; let result: OutgoingRoomKeyRequest; - function onsuccess(this: IDBRequest) { + function onsuccess(this: IDBRequest) { const cursor = this.result; if (cursor) { // got a match @@ -256,7 +256,7 @@ export class Backend implements CryptoStore { let stateIndex = 0; const results: OutgoingRoomKeyRequest[] = []; - function onsuccess(this: IDBRequest) { + function onsuccess(this: IDBRequest) { const cursor = this.result; if (cursor) { const keyReq = cursor.value; @@ -309,7 +309,7 @@ export class Backend implements CryptoStore { ): Promise { let result: OutgoingRoomKeyRequest | null = null; - function onsuccess(this: IDBRequest) { + function onsuccess(this: IDBRequest) { const cursor = this.result; if (!cursor) { return; diff --git a/src/http-api/errors.ts b/src/http-api/errors.ts index faa379784..be63c94fd 100644 --- a/src/http-api/errors.ts +++ b/src/http-api/errors.ts @@ -49,7 +49,7 @@ export class HTTPError extends Error { */ export class MatrixError extends HTTPError { public readonly errcode?: string; - public readonly data: IErrorJson; + public data: IErrorJson; constructor( errorJson: IErrorJson = {}, diff --git a/src/interactive-auth.ts b/src/interactive-auth.ts index ef3bc8bd8..5ba595669 100644 --- a/src/interactive-auth.ts +++ b/src/interactive-auth.ts @@ -21,6 +21,7 @@ limitations under the License. import { logger } from './logger'; import { MatrixClient } from "./client"; import { defer, IDeferred } from "./utils"; +import { MatrixError } from "./http-api"; const EMAIL_STAGE_TYPE = "m.login.email.identity"; const MSISDN_STAGE_TYPE = "m.login.msisdn"; @@ -111,7 +112,7 @@ interface IOpts { sessionId?: string; clientSecret?: string; emailSid?: string; - doRequest(auth: IAuthData, background: boolean): Promise; + doRequest(auth: IAuthData | null, background: boolean): Promise; stateUpdated(nextStage: AuthType, status: IStageStatus): void; requestEmailToken(email: string, secret: string, attempt: number, session: string): Promise<{ sid: string }>; busyChanged?(busy: boolean): void; @@ -328,7 +329,7 @@ export class InteractiveAuth { * @param {string} loginType login type for the stage * @return {object?} any parameters from the server for this stage */ - public getStageParams(loginType: string): Record { + public getStageParams(loginType: string): Record | undefined { return this.data.params?.[loginType]; } @@ -428,10 +429,10 @@ export class InteractiveAuth { this.requestingEmailToken = true; try { const requestTokenResult = await this.requestEmailTokenCallback( - this.inputs.emailAddress, + this.inputs.emailAddress!, this.clientSecret, this.emailAttempt++, - this.data.session, + this.data.session!, ); this.emailSid = requestTokenResult.sid; logger.trace("Email token request succeeded"); @@ -454,16 +455,16 @@ export class InteractiveAuth { * This can be set to true for requests that just poll to see if auth has * been completed elsewhere. */ - private async doRequest(auth: IAuthData, background = false): Promise { + private async doRequest(auth: IAuthData | null, background = false): Promise { try { const result = await this.requestCallback(auth, background); this.attemptAuthDeferred!.resolve(result); this.attemptAuthDeferred = null; } catch (error) { // sometimes UI auth errors don't come with flows - const errorFlows = error.data?.flows ?? null; + const errorFlows = (error).data?.flows ?? null; const haveFlows = this.data.flows || Boolean(errorFlows); - if (error.httpStatus !== 401 || !error.data || !haveFlows) { + if ((error).httpStatus !== 401 || !(error).data || !haveFlows) { // doesn't look like an interactive-auth failure. if (!background) { this.attemptAuthDeferred?.reject(error); @@ -474,20 +475,23 @@ export class InteractiveAuth { logger.log("Background poll request failed doing UI auth: ignoring", error); } } - if (!error.data) { - error.data = {}; + if (!(error).data) { + (error).data = {}; } // if the error didn't come with flows, completed flows or session ID, // copy over the ones we have. Synapse sometimes sends responses without // any UI auth data (eg. when polling for email validation, if the email // has not yet been validated). This appears to be a Synapse bug, which // we workaround here. - if (!error.data.flows && !error.data.completed && !error.data.session) { - error.data.flows = this.data.flows; - error.data.completed = this.data.completed; - error.data.session = this.data.session; + if (!(error).data.flows && + !(error).data.completed && + !(error).data.session + ) { + (error).data.flows = this.data.flows; + (error).data.completed = this.data.completed; + (error).data.session = this.data.session; } - this.data = error.data; + this.data = (error).data; try { this.startNextAuthStage(); } catch (e) { diff --git a/src/models/MSC3089Branch.ts b/src/models/MSC3089Branch.ts index e8c5af14a..25ce51a20 100644 --- a/src/models/MSC3089Branch.ts +++ b/src/models/MSC3089Branch.ts @@ -145,7 +145,7 @@ export class MSC3089Branch { let event: MatrixEvent | undefined = room.getUnfilteredTimelineSet().findEventById(this.id); // keep scrolling back if needed until we find the event or reach the start of the room: - while (!event && room.getLiveTimeline().getState(EventTimeline.BACKWARDS).paginationToken) { + while (!event && room.getLiveTimeline().getState(EventTimeline.BACKWARDS)!.paginationToken) { await this.client.scrollback(room, 100); event = room.getUnfilteredTimelineSet().findEventById(this.id); } diff --git a/src/models/beacon.ts b/src/models/beacon.ts index 26748b31f..fc817b05e 100644 --- a/src/models/beacon.ts +++ b/src/models/beacon.ts @@ -132,19 +132,19 @@ export class Beacon extends TypedEventEmitter 1) { this.livenessWatchTimeout = setTimeout( () => { this.monitorLiveness(); }, expiryInMs, ); } - } else if (this.beaconInfo.timestamp > Date.now()) { + } else if (this.beaconInfo.timestamp! > Date.now()) { // beacon start timestamp is in the future // check liveness again then this.livenessWatchTimeout = setTimeout( () => { this.monitorLiveness(); }, - this.beaconInfo.timestamp - Date.now(), + this.beaconInfo.timestamp! - Date.now(), ); } } @@ -165,6 +165,7 @@ export class Beacon extends TypedEventEmitter Date.now() ? - this.beaconInfo.timestamp - 360000 /* 6min */ : + const startTimestamp = this.beaconInfo.timestamp! > Date.now() ? + this.beaconInfo.timestamp! - 360000 /* 6min */ : this.beaconInfo.timestamp; - this._isLive = !!this._beaconInfo?.live && + this._isLive = !!this._beaconInfo?.live && !!startTimestamp && isTimestampInDuration(startTimestamp, this._beaconInfo?.timeout, Date.now()); if (prevLiveness !== this.isLive) { diff --git a/src/models/event-timeline-set.ts b/src/models/event-timeline-set.ts index abf20ebf8..e41eec158 100644 --- a/src/models/event-timeline-set.ts +++ b/src/models/event-timeline-set.ts @@ -625,7 +625,7 @@ export class EventTimelineSet extends TypedEventEmitter | null> = { @@ -126,10 +126,12 @@ export class EventTimeline { */ constructor(private readonly eventTimelineSet: EventTimelineSet) { this.roomId = eventTimelineSet.room?.roomId ?? null; - this.startState = new RoomState(this.roomId); - this.startState.paginationToken = null; - this.endState = new RoomState(this.roomId); - this.endState.paginationToken = null; + if (this.roomId) { + this.startState = new RoomState(this.roomId); + this.startState.paginationToken = null; + this.endState = new RoomState(this.roomId); + this.endState.paginationToken = null; + } // this is used by client.js this.paginationRequests = { 'b': null, 'f': null }; @@ -167,12 +169,8 @@ export class EventTimeline { Object.freeze(e); } - this.startState.setStateEvents(stateEvents, { - timelineWasEmpty, - }); - this.endState.setStateEvents(stateEvents, { - timelineWasEmpty, - }); + this.startState?.setStateEvents(stateEvents, { timelineWasEmpty }); + this.endState?.setStateEvents(stateEvents, { timelineWasEmpty }); } /** @@ -190,7 +188,7 @@ export class EventTimeline { public forkLive(direction: Direction): EventTimeline { const forkState = this.getState(direction); const timeline = new EventTimeline(this.eventTimelineSet); - timeline.startState = forkState.clone(); + timeline.startState = forkState?.clone(); // Now clobber the end state of the new live timeline with that from the // previous live timeline. It will be identical except that we'll keep // using the same RoomMember objects for the 'live' set of members with any @@ -198,7 +196,7 @@ export class EventTimeline { timeline.endState = forkState; // Firstly, we just stole the current timeline's end state, so it needs a new one. // Make an immutable copy of the state so back pagination will get the correct sentinels. - this.endState = forkState.clone(); + this.endState = forkState?.clone(); return timeline; } @@ -214,8 +212,8 @@ export class EventTimeline { public fork(direction: Direction): EventTimeline { const forkState = this.getState(direction); const timeline = new EventTimeline(this.eventTimelineSet); - timeline.startState = forkState.clone(); - timeline.endState = forkState.clone(); + timeline.startState = forkState?.clone(); + timeline.endState = forkState?.clone(); return timeline; } @@ -276,7 +274,7 @@ export class EventTimeline { * * @return {RoomState} state at the start/end of the timeline */ - public getState(direction: Direction): RoomState { + public getState(direction: Direction): RoomState | undefined { if (direction == EventTimeline.BACKWARDS) { return this.startState; } else if (direction == EventTimeline.FORWARDS) { @@ -296,7 +294,7 @@ export class EventTimeline { * @return {?string} pagination token */ public getPaginationToken(direction: Direction): string | null { - return this.getState(direction).paginationToken; + return this.getState(direction)?.paginationToken ?? null; } /** @@ -304,12 +302,15 @@ export class EventTimeline { * * @param {?string} token pagination token * - * @param {string} direction EventTimeline.BACKWARDS to set the paginatio + * @param {string} direction EventTimeline.BACKWARDS to set the pagination * token for going backwards in time; EventTimeline.FORWARDS to set the * pagination token for going forwards in time. */ public setPaginationToken(token: string | null, direction: Direction): void { - this.getState(direction).paginationToken = token; + const state = this.getState(direction); + if (state) { + state.paginationToken = token; + } } /** @@ -408,16 +409,14 @@ export class EventTimeline { const timelineSet = this.getTimelineSet(); if (timelineSet.room) { - EventTimeline.setEventMetadata(event, roomState, toStartOfTimeline); + EventTimeline.setEventMetadata(event, roomState!, toStartOfTimeline); // modify state but only on unfiltered timelineSets if ( event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet ) { - roomState.setStateEvents([event], { - timelineWasEmpty, - }); + roomState?.setStateEvents([event], { timelineWasEmpty }); // it is possible that the act of setting the state event means we // can set more metadata (specifically sender/target props), so try // it again if the prop wasn't previously set. It may also mean that @@ -428,8 +427,8 @@ export class EventTimeline { // back in time, else we'll set the .sender value for BEFORE the given // member event, whereas we want to set the .sender value for the ACTUAL // member event itself. - if (!event.sender || (event.getType() === "m.room.member" && !toStartOfTimeline)) { - EventTimeline.setEventMetadata(event, roomState, toStartOfTimeline); + if (!event.sender || (event.getType() === EventType.RoomMember && !toStartOfTimeline)) { + EventTimeline.setEventMetadata(event, roomState!, toStartOfTimeline); } } } diff --git a/src/models/event.ts b/src/models/event.ts index 6fbe6a391..6ba8a6511 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -413,7 +413,7 @@ export class MatrixEvent extends TypedEventEmitter1433502692297 */ public getTs(): number { - return this.event.origin_server_ts; + return this.event.origin_server_ts!; } /** @@ -625,8 +625,8 @@ export class MatrixEvent extends TypedEventEmitter { // state at the start and end of that timeline. These are more // for backwards-compatibility than anything else. this.timeline = this.getLiveTimeline().getEvents(); - this.oldState = this.getLiveTimeline() - .getState(EventTimeline.BACKWARDS); - this.currentState = this.getLiveTimeline() - .getState(EventTimeline.FORWARDS); + this.oldState = this.getLiveTimeline().getState(EventTimeline.BACKWARDS)!; + this.currentState = this.getLiveTimeline().getState(EventTimeline.FORWARDS)!; // Let people know to register new listeners for the new state // references. The reference won't necessarily change every time so only @@ -1564,8 +1562,8 @@ export class Room extends ReadReceipt { pendingEvents = true, }: ICreateFilterOpts = {}, ): EventTimelineSet { - if (this.filteredTimelineSets[filter.filterId]) { - return this.filteredTimelineSets[filter.filterId]; + if (this.filteredTimelineSets[filter.filterId!]) { + return this.filteredTimelineSets[filter.filterId!]; } const opts = Object.assign({ filter, pendingEvents }, this.opts); const timelineSet = new EventTimelineSet(this, opts); @@ -1574,7 +1572,7 @@ export class Room extends ReadReceipt { RoomEvent.TimelineReset, ]); if (useSyncEvents) { - this.filteredTimelineSets[filter.filterId] = timelineSet; + this.filteredTimelineSets[filter.filterId!] = timelineSet; this.timelineSets.push(timelineSet); } @@ -1623,7 +1621,7 @@ export class Room extends ReadReceipt { } private async getThreadListFilter(filterType = ThreadFilterType.All): Promise { - const myUserId = this.client.getUserId(); + const myUserId = this.client.getUserId()!; const filter = new Filter(myUserId); const definition: IFilterDefinition = { @@ -1635,7 +1633,7 @@ export class Room extends ReadReceipt { }; if (filterType === ThreadFilterType.My) { - definition.room.timeline[FILTER_RELATED_BY_SENDERS.name] = [myUserId]; + definition!.room!.timeline![FILTER_RELATED_BY_SENDERS.name] = [myUserId]; } filter.setDefinition(definition); @@ -1681,7 +1679,7 @@ export class Room extends ReadReceipt { return event.getSender() === this.client.getUserId(); }); if (filterType !== ThreadFilterType.My || currentUserParticipated) { - timelineSet.getLiveTimeline().addEvent(thread.rootEvent, { + timelineSet.getLiveTimeline().addEvent(thread.rootEvent!, { toStartOfTimeline: false, }); } @@ -1851,8 +1849,8 @@ export class Room extends ReadReceipt { * @param {Filter} filter the filter whose timelineSet is to be forgotten */ public removeFilteredTimelineSet(filter: Filter): void { - const timelineSet = this.filteredTimelineSets[filter.filterId]; - delete this.filteredTimelineSets[filter.filterId]; + const timelineSet = this.filteredTimelineSets[filter.filterId!]; + delete this.filteredTimelineSets[filter.filterId!]; const i = this.timelineSets.indexOf(timelineSet); if (i > -1) { this.timelineSets.splice(i, 1); @@ -2187,7 +2185,7 @@ export class Room extends ReadReceipt { // call setEventMetadata to set up event.sender etc // as event is shared over all timelineSets, we set up its metadata based // on the unfiltered timelineSet. - EventTimeline.setEventMetadata(event, this.getLiveTimeline().getState(EventTimeline.FORWARDS), false); + EventTimeline.setEventMetadata(event, this.getLiveTimeline().getState(EventTimeline.FORWARDS)!, false); this.txnToEvent[txnId] = event; if (this.pendingEventList) { diff --git a/src/models/thread.ts b/src/models/thread.ts index 31a0690ff..384500aed 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -201,7 +201,7 @@ export class Thread extends ReadReceipt { }; public get roomState(): RoomState { - return this.room.getLiveTimeline().getState(EventTimeline.FORWARDS); + return this.room.getLiveTimeline().getState(EventTimeline.FORWARDS)!; } private addEventToTimeline(event: MatrixEvent, toStartOfTimeline: boolean): void { diff --git a/src/pushprocessor.ts b/src/pushprocessor.ts index 2976417fd..e979032d8 100644 --- a/src/pushprocessor.ts +++ b/src/pushprocessor.ts @@ -219,8 +219,11 @@ export class PushProcessor { return null; } - private templateRuleToRaw(kind: PushRuleKind, tprule: any): any { - const rawrule = { + private templateRuleToRaw( + kind: PushRuleKind, + tprule: IPushRule, + ): Pick | null { + const rawrule: Pick = { 'rule_id': tprule.rule_id, 'actions': tprule.actions, 'conditions': [], @@ -234,7 +237,7 @@ export class PushProcessor { if (!tprule.rule_id) { return null; } - rawrule.conditions.push({ + rawrule.conditions!.push({ 'kind': ConditionKind.EventMatch, 'key': 'room_id', 'value': tprule.rule_id, @@ -244,7 +247,7 @@ export class PushProcessor { if (!tprule.rule_id) { return null; } - rawrule.conditions.push({ + rawrule.conditions!.push({ 'kind': ConditionKind.EventMatch, 'key': 'user_id', 'value': tprule.rule_id, @@ -254,7 +257,7 @@ export class PushProcessor { if (!tprule.pattern) { return null; } - rawrule.conditions.push({ + rawrule.conditions!.push({ 'kind': ConditionKind.EventMatch, 'key': 'content.body', 'pattern': tprule.pattern, @@ -474,7 +477,7 @@ export class PushProcessor { return actionObj; } - public ruleMatchesEvent(rule: IPushRule, ev: MatrixEvent): boolean { + public ruleMatchesEvent(rule: Partial & Pick, ev: MatrixEvent): boolean { if (!rule.conditions?.length) return true; let ret = true; diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 6e43b48be..304588b18 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -347,7 +347,7 @@ export class MatrixCall extends TypedEventEmitter; // A queue for candidates waiting to go out. // We try to amalgamate candidates into a single candidate message where @@ -979,9 +979,9 @@ export class MatrixCall extends TypedEventEmitter