From 0a35f2e2c74bca0cefc5d4f56f06765640d7a326 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Nov 2022 11:45:35 +0000 Subject: [PATCH 01/21] Fix queueToDevice.spec.ts test flakiness (#2841) --- spec/unit/queueToDevice.spec.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/spec/unit/queueToDevice.spec.ts b/spec/unit/queueToDevice.spec.ts index 4b3050a15..afdd7fdd5 100644 --- a/spec/unit/queueToDevice.spec.ts +++ b/spec/unit/queueToDevice.spec.ts @@ -22,6 +22,7 @@ import { MatrixClient } from "../../src/client"; import { ToDeviceBatch } from '../../src/models/ToDeviceMessage'; import { logger } from '../../src/logger'; import { IStore } from '../../src/store'; +import { removeElement } from "../../src/utils"; const FAKE_USER = "@alice:example.org"; const FAKE_DEVICE_ID = "AAAAAAAA"; @@ -75,6 +76,8 @@ describe.each([ let client: MatrixClient; beforeEach(async function() { + jest.runOnlyPendingTimers(); + jest.useRealTimers(); httpBackend = new MockHttpBackend(); let store: IStore; @@ -300,7 +303,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({ @@ -328,12 +331,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) => { From a07fe4456556b48b3ccd68358e3878bb28b419ce Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Nov 2022 11:46:31 +0000 Subject: [PATCH 02/21] Update matrix-org/sonarcloud-workflow-action (#2845) --- .github/workflows/sonarcloud.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index c1a7423ab..3c0c4b943 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -5,6 +5,11 @@ on: secrets: SONAR_TOKEN: required: true + inputs: + extraArgs: + 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 - - + extraArgs: ${{ inputs.extraArgs }} + - uses: Sibz/github-status-action@v1 if: always() with: From dee2b60c3d95e8793cab110ccf88b47afd48915d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Nov 2022 11:59:54 +0000 Subject: [PATCH 03/21] Update sonarqube.yml --- .github/workflows/sonarqube.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index a5360c64f..227182028 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -8,8 +8,34 @@ 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.extraArgs.outputs.reportPaths }} + testExecutionReportPaths: ${{ steps.extraArgs.outputs.testExecutionReportPaths }} + steps: + - name: Download Artifact + uses: actions/download-artifact@v3 + with: + name: coverage + path: coverage + + - id: extraArgs + run: | + coverage=$(find coverage -type f -name '*lcov.info' -printf "%p,") + echo "{reportPaths}={$coverage}" >> $GITHUB_OUTPUT + testResults=$(find coverage -type f -name '*test-result.xml' -printf "%p,") + echo "{testExecutionReportPaths}={$testResults}" >> $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: + extraArgs: | + -Dsonar.javascript.lcov.reportPaths=${{ needs.prepare.outputs.reportPaths }} + -Dsonar.testExecutionReportPaths=${{ needs.prepare.outputs.testExecutionReportPaths }} From b6e97fcecb0c6ca2f4efb4dc841b1c29a13bd7b4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Nov 2022 12:01:20 +0000 Subject: [PATCH 04/21] Update sonarqube.yml --- .github/workflows/sonarqube.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 227182028..c2d679eb2 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -13,8 +13,8 @@ jobs: name: Prepare runs-on: ubuntu-latest outputs: - reportPaths: ${{ steps.extraArgs.outputs.reportPaths }} - testExecutionReportPaths: ${{ steps.extraArgs.outputs.testExecutionReportPaths }} + reportPaths: ${{ steps.extra_args.outputs.reportPaths }} + testExecutionReportPaths: ${{ steps.extra_args.outputs.testExecutionReportPaths }} steps: - name: Download Artifact uses: actions/download-artifact@v3 @@ -22,7 +22,7 @@ jobs: name: coverage path: coverage - - id: extraArgs + - id: extra_args run: | coverage=$(find coverage -type f -name '*lcov.info' -printf "%p,") echo "{reportPaths}={$coverage}" >> $GITHUB_OUTPUT @@ -36,6 +36,6 @@ jobs: secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} with: - extraArgs: | + extra_args: | -Dsonar.javascript.lcov.reportPaths=${{ needs.prepare.outputs.reportPaths }} -Dsonar.testExecutionReportPaths=${{ needs.prepare.outputs.testExecutionReportPaths }} From e4dd7bcc879c98f034d7b112ea313740e96d287a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Nov 2022 12:01:33 +0000 Subject: [PATCH 05/21] Update sonarcloud.yml --- .github/workflows/sonarcloud.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 3c0c4b943..6b2188a0c 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -6,7 +6,7 @@ on: SONAR_TOKEN: required: true inputs: - extraArgs: + extra_args: type: string required: false description: "Extra args to pass to SonarCloud" @@ -38,7 +38,7 @@ jobs: coverage_run_id: ${{ github.event.workflow_run.id }} coverage_workflow_name: tests.yml coverage_extract_path: coverage - extraArgs: ${{ inputs.extraArgs }} + extra_args: ${{ inputs.extra_args }} - uses: Sibz/github-status-action@v1 if: always() From b6633ad4b018df24f321d5b6fe2067d39d176a10 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Nov 2022 12:06:52 +0000 Subject: [PATCH 06/21] Update sonarqube.yml --- .github/workflows/sonarqube.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index c2d679eb2..2e18cc898 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -12,6 +12,7 @@ jobs: prepare: name: Prepare runs-on: ubuntu-latest + if: github.event.workflow_run.conclusion == 'success' outputs: reportPaths: ${{ steps.extra_args.outputs.reportPaths }} testExecutionReportPaths: ${{ steps.extra_args.outputs.testExecutionReportPaths }} From 6cd60e32dc1fab73932bf15b705020951f8112e5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Nov 2022 12:14:16 +0000 Subject: [PATCH 07/21] Update sonarqube.yml --- .github/workflows/sonarqube.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 2e18cc898..070a21a5b 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -12,18 +12,21 @@ jobs: prepare: name: Prepare runs-on: ubuntu-latest - if: github.event.workflow_run.conclusion == 'success' outputs: - reportPaths: ${{ steps.extra_args.outputs.reportPaths }} - testExecutionReportPaths: ${{ steps.extra_args.outputs.testExecutionReportPaths }} + reportPaths: ${{ steps.extraArgs.outputs.reportPaths }} + testExecutionReportPaths: ${{ steps.extraArgs.outputs.testExecutionReportPaths }} steps: - - name: Download Artifact - uses: actions/download-artifact@v3 + # 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 + - id: extraArgs run: | coverage=$(find coverage -type f -name '*lcov.info' -printf "%p,") echo "{reportPaths}={$coverage}" >> $GITHUB_OUTPUT @@ -37,6 +40,6 @@ jobs: secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} with: - extra_args: | + extraArgs: | -Dsonar.javascript.lcov.reportPaths=${{ needs.prepare.outputs.reportPaths }} -Dsonar.testExecutionReportPaths=${{ needs.prepare.outputs.testExecutionReportPaths }} From a92c148f152db5b2ade3be827c8c3f5681ddfe83 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Nov 2022 12:23:04 +0000 Subject: [PATCH 08/21] Update sonarqube.yml --- .github/workflows/sonarqube.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 070a21a5b..ada26478b 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -13,8 +13,8 @@ jobs: name: Prepare runs-on: ubuntu-latest outputs: - reportPaths: ${{ steps.extraArgs.outputs.reportPaths }} - testExecutionReportPaths: ${{ steps.extraArgs.outputs.testExecutionReportPaths }} + 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: @@ -26,7 +26,7 @@ jobs: name: coverage path: coverage - - id: extraArgs + - id: extra_args run: | coverage=$(find coverage -type f -name '*lcov.info' -printf "%p,") echo "{reportPaths}={$coverage}" >> $GITHUB_OUTPUT @@ -40,6 +40,6 @@ jobs: secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} with: - extraArgs: | + extra_args: | -Dsonar.javascript.lcov.reportPaths=${{ needs.prepare.outputs.reportPaths }} -Dsonar.testExecutionReportPaths=${{ needs.prepare.outputs.testExecutionReportPaths }} From 42b08eca5787d190a4ed78818bff14ac31c2992c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Nov 2022 12:49:38 +0000 Subject: [PATCH 09/21] Update sonarqube.yml --- .github/workflows/sonarqube.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index ada26478b..6699dde32 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -30,7 +30,7 @@ jobs: run: | coverage=$(find coverage -type f -name '*lcov.info' -printf "%p,") echo "{reportPaths}={$coverage}" >> $GITHUB_OUTPUT - testResults=$(find coverage -type f -name '*test-result.xml' -printf "%p,") + testResults=$(find coverage -type f -name '*test-report.xml' -printf "%p,") echo "{testExecutionReportPaths}={$testResults}" >> $GITHUB_OUTPUT sonarqube: From db49cd8d13950e8cf0689a2e5348aa570d7e99a1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Nov 2022 12:50:05 +0000 Subject: [PATCH 10/21] Make the js-sdk conform to tsc --strict (#2835) Co-authored-by: Faye Duxovni --- package.json | 5 +- patches/matrix-events-sdk+0.0.1-beta.7.patch | 12 ++ spec/integ/matrix-client-syncing.spec.ts | 8 +- spec/unit/event-timeline.spec.ts | 24 +-- spec/unit/webrtc/call.spec.ts | 2 +- src/@types/PushRules.ts | 5 +- src/autodiscovery.ts | 80 +++++---- src/client.ts | 43 ++--- src/content-helpers.ts | 28 +-- src/crypto/backup.ts | 2 +- .../store/indexeddb-crypto-store-backend.ts | 6 +- src/http-api/errors.ts | 2 +- src/interactive-auth.ts | 32 ++-- src/models/MSC3089Branch.ts | 2 +- src/models/beacon.ts | 13 +- src/models/event-timeline-set.ts | 2 +- src/models/event-timeline.ts | 53 +++--- src/models/event.ts | 29 ++-- src/models/invites-ignorer.ts | 2 +- src/models/room.ts | 24 ++- src/models/thread.ts | 2 +- src/pushprocessor.ts | 15 +- src/webrtc/call.ts | 20 +-- tsconfig.json | 3 +- yarn.lock | 161 +++++++++++++++++- 25 files changed, 378 insertions(+), 197 deletions(-) create mode 100644 patches/matrix-events-sdk+0.0.1-beta.7.patch diff --git a/package.json b/package.json index 6ad57b04a..262cbecc4 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", @@ -58,7 +59,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", "p-retry": "4", "qs": "^6.9.6", "unhomoglyph": "^1.0.6" @@ -105,6 +106,8 @@ "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", 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/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/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/webrtc/call.spec.ts b/spec/unit/webrtc/call.spec.ts index 01b110b4f..8b42d9fe6 100644 --- a/spec/unit/webrtc/call.spec.ts +++ b/spec/unit/webrtc/call.spec.ts @@ -757,7 +757,7 @@ describe('Call', function() { describe("transferToCall", () => { it("should send the required events", async () => { - const targetCall = new MatrixCall({ client: client.client }); + const targetCall = new MatrixCall({ client: client.client, roomId: "!roomId:server" }); const sendEvent = jest.spyOn(client.client, "sendEvent"); await call.transferToCall(targetCall); 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 7e37c22af..341b2cbec 100644 --- a/src/client.ts +++ b/src/client.ts @@ -507,7 +507,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; @@ -705,8 +705,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 { @@ -3121,7 +3122,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( @@ -3283,7 +3284,7 @@ export class MatrixClient extends TypedEventEmitter { if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) { @@ -3787,10 +3788,10 @@ export class MatrixClient extends TypedEventEmitter { return ev.isRelation(THREAD_RELATION_TYPE.name) && !ev.status; })?.getId() ?? threadId, @@ -4097,7 +4098,7 @@ export class MatrixClient extends TypedEventEmitter { if (typeof threadId !== "string" && threadId !== null) { @@ -4314,7 +4315,7 @@ export class MatrixClient extends TypedEventEmitter { @@ -4358,7 +4359,7 @@ export class MatrixClient extends TypedEventEmitter { @@ -5226,11 +5227,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); } @@ -5758,7 +5759,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 0ec955ffa..79876c469 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 117100d69..0e3b6c3b5 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -200,7 +200,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 b495dfec6..93f5e18a8 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -68,7 +68,7 @@ import { MatrixError } from "../http-api"; */ interface CallOpts { - roomId?: string; + roomId: string; client: MatrixClient; forceTURN?: boolean; turnServers?: Array; @@ -278,7 +278,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 @@ -291,7 +291,7 @@ export class MatrixCall extends TypedEventEmitter = []; private screensharingSenders: Array = []; private inviteOrAnswerSent = false; - private waitForLocalAVStream: boolean; + private waitForLocalAVStream?: boolean; private successor?: MatrixCall; private opponentMember?: RoomMember; private opponentVersion?: number | string; @@ -312,7 +312,7 @@ export class MatrixCall extends TypedEventEmitter { stats.push(item); }); @@ -797,9 +797,9 @@ export class MatrixCall extends TypedEventEmitter Date: Thu, 3 Nov 2022 12:59:16 +0000 Subject: [PATCH 11/21] Update sonarqube.yml --- .github/workflows/sonarqube.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 6699dde32..c66463f25 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -29,9 +29,9 @@ jobs: - id: extra_args run: | coverage=$(find coverage -type f -name '*lcov.info' -printf "%p,") - echo "{reportPaths}={$coverage}" >> $GITHUB_OUTPUT + echo "reportPaths=$coverage" >> $GITHUB_OUTPUT testResults=$(find coverage -type f -name '*test-report.xml' -printf "%p,") - echo "{testExecutionReportPaths}={$testResults}" >> $GITHUB_OUTPUT + echo "testExecutionReportPaths=$testResults" >> $GITHUB_OUTPUT sonarqube: name: 🩻 SonarQube From 82d942dcc576097a6aa6a5b0f42edb41a09d5e1c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Nov 2022 13:17:48 +0000 Subject: [PATCH 12/21] Update sonarqube.yml --- .github/workflows/sonarqube.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index c66463f25..bfb8480d1 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -28,9 +28,9 @@ jobs: - id: extra_args run: | - coverage=$(find coverage -type f -name '*lcov.info' -printf "%p,") + coverage=$(find coverage -type f -name '*lcov.info' | tr '\n' ',') echo "reportPaths=$coverage" >> $GITHUB_OUTPUT - testResults=$(find coverage -type f -name '*test-report.xml' -printf "%p,") + testResults=$(find coverage -type f -name '*test-report.xml' | tr '\n' ',') echo "testExecutionReportPaths=$testResults" >> $GITHUB_OUTPUT sonarqube: @@ -41,5 +41,5 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} with: extra_args: | - -Dsonar.javascript.lcov.reportPaths=${{ needs.prepare.outputs.reportPaths }} - -Dsonar.testExecutionReportPaths=${{ needs.prepare.outputs.testExecutionReportPaths }} + -D"sonar.javascript.lcov.reportPaths=${{ needs.prepare.outputs.reportPaths }}" + -D"sonar.testExecutionReportPaths=${{ needs.prepare.outputs.testExecutionReportPaths }}" From 27bb79a29a514ee57be389f8032a60fd2cddd8c4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Nov 2022 13:47:36 +0000 Subject: [PATCH 13/21] Switch to @casualbot/jest-sonar-reporter --- .github/workflows/sonarqube.yml | 4 ++-- package.json | 11 ++++++----- sonar-project.properties | 2 +- yarn.lock | 22 ++++++++++++---------- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index bfb8480d1..95035deed 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -30,8 +30,8 @@ jobs: run: | coverage=$(find coverage -type f -name '*lcov.info' | tr '\n' ',') echo "reportPaths=$coverage" >> $GITHUB_OUTPUT - testResults=$(find coverage -type f -name '*test-report.xml' | tr '\n' ',') - echo "testExecutionReportPaths=$testResults" >> $GITHUB_OUTPUT + reports=$(find coverage -type f -name 'jest-sonar-report*.xml' | tr '\n' ',') + echo "testExecutionReportPaths=$reports" >> $GITHUB_OUTPUT sonarqube: name: 🩻 SonarQube diff --git a/package.json b/package.json index 262cbecc4..3fcea052a 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,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", @@ -103,7 +104,6 @@ "jest": "^29.0.0", "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", @@ -128,10 +128,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/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/yarn.lock b/yarn.lock index 67db5e346..6daa4bbc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1092,6 +1092,15 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@casualbot/jest-sonar-reporter@^2.2.5": + version "2.2.5" + resolved "https://registry.yarnpkg.com/@casualbot/jest-sonar-reporter/-/jest-sonar-reporter-2.2.5.tgz#23d187ddb8d65129a3c8d8239b0540a52c4dc82a" + integrity sha512-Pmb4aEtJudz9G0VsmEUzuDm5iWGOCDsmulzi6AP/RgAXEcmsSxVdxjcgA+2SHC005diU4mXnPLiQyiiMIAtUjA== + dependencies: + mkdirp "1.0.4" + uuid "8.3.2" + xml "1.0.1" + "@eslint/eslintrc@^1.3.3": version "1.3.3" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.3.tgz#2b044ab39fdfa75b4688184f9e573ce3c5b0ff95" @@ -4597,13 +4606,6 @@ jest-snapshot@^29.2.2: pretty-format "^29.2.1" semver "^7.3.5" -jest-sonar-reporter@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/jest-sonar-reporter/-/jest-sonar-reporter-2.0.0.tgz#faa54a7d2af7198767ee246a82b78c576789cf08" - integrity sha512-ZervDCgEX5gdUbdtWsjdipLN3bKJwpxbvhkYNXTAYvAckCihobSLr9OT/IuyNIRT1EZMDDwR6DroWtrq+IL64w== - dependencies: - xml "^1.0.1" - jest-util@^28.1.3: version "28.1.3" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.3.tgz#f4f932aa0074f0679943220ff9cbba7e497028b0" @@ -5095,7 +5097,7 @@ mkdirp-classic@^0.5.2: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== -mkdirp@^1.0.4: +mkdirp@1.0.4, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -6806,7 +6808,7 @@ util@~0.12.0: is-typed-array "^1.1.3" which-typed-array "^1.1.2" -uuid@^8.3.2: +uuid@8.3.2, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== @@ -6995,7 +6997,7 @@ write-file-atomic@^4.0.1: imurmurhash "^0.1.4" signal-exit "^3.0.7" -xml@^1.0.1: +xml@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== From 76458d3a403a29eec1c9f84513ca357c8ad1a638 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Nov 2022 13:58:50 +0000 Subject: [PATCH 14/21] Update sonarqube.yml --- .github/workflows/sonarqube.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 95035deed..287e59cc5 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -40,6 +40,4 @@ jobs: secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} with: - extra_args: | - -D"sonar.javascript.lcov.reportPaths=${{ needs.prepare.outputs.reportPaths }}" - -D"sonar.testExecutionReportPaths=${{ needs.prepare.outputs.testExecutionReportPaths }}" + extra_args: -D"sonar.javascript.lcov.reportPaths=${{ needs.prepare.outputs.reportPaths }}" -D"sonar.testExecutionReportPaths=${{ needs.prepare.outputs.testExecutionReportPaths }}" From 5df9705bae9a749dfd90a1599022da0df3799f72 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Nov 2022 14:12:42 +0000 Subject: [PATCH 15/21] Update sonarqube.yml --- .github/workflows/sonarqube.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 95035deed..61768adad 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -28,9 +28,9 @@ jobs: - id: extra_args run: | - coverage=$(find coverage -type f -name '*lcov.info' | tr '\n' ',') + 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' ',') + reports=$(find coverage -type f -name 'jest-sonar-report*.xml' | tr '\n' ',' | sed 's/,$//g') echo "testExecutionReportPaths=$reports" >> $GITHUB_OUTPUT sonarqube: @@ -40,6 +40,4 @@ jobs: secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} with: - extra_args: | - -D"sonar.javascript.lcov.reportPaths=${{ needs.prepare.outputs.reportPaths }}" - -D"sonar.testExecutionReportPaths=${{ needs.prepare.outputs.testExecutionReportPaths }}" + extra_args: -Dsonar.javascript.lcov.reportPaths=${{ needs.prepare.outputs.reportPaths }} -Dsonar.testExecutionReportPaths=${{ needs.prepare.outputs.testExecutionReportPaths }} From 4a33e584b01d0dd2f905a29a48bd98aa1458f155 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Thu, 3 Nov 2022 13:12:57 -0400 Subject: [PATCH 16/21] Add unit test for device de-/rehydration (#2821) --- spec/unit/crypto/dehydration.spec.ts | 143 +++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 spec/unit/crypto/dehydration.spec.ts 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(); + } + }); +}); From 777cf1f1358964f1563d03e1f98c48814328fabd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 4 Nov 2022 09:12:07 +0000 Subject: [PATCH 17/21] Remove tsc-strict ci, it has served its purpose (#2847) --- .github/workflows/static_analysis.yml | 35 --------------------------- 1 file changed, 35 deletions(-) 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 }} From 433b7afd7118bdf49917c247e63ce799884ecda5 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 4 Nov 2022 05:54:23 -0500 Subject: [PATCH 18/21] Extra insurance that we don't mix events in the wrong timelines (#2848) Add checks to `addEventToTimeline` as extra insurance that we don't mix events in the wrong timelines (main timeline vs thread timeline). Split out from https://github.com/matrix-org/matrix-js-sdk/pull/2521 Previously, we just relied on the callers to make sure they're doing the right thing and since it's easy to get it wrong, we mixed and bugs happened. Call stacks for how events get added to a timeline: - `TimelineSet.addEventsToTimeline` -> `TimelineSet.addEventToTimeline` -> `Timeline.addEvent` - `TimelineSet.addEventToTimeline` -> `Timeline.addEvent` - `TimelineSet.addLiveEvent` -> `TimelineSet.addEventToTimeline` -> `Timeline.addEvent` --- .DS_Store | Bin 0 -> 6148 bytes spec/unit/event-timeline-set.spec.ts | 59 +++++++++++++++++++++++++++ src/models/event-timeline-set.ts | 13 ++++++ src/models/event.ts | 2 +- src/models/room.ts | 2 +- 5 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..8c6875c741d7013ebf68f1a62758ebca25db1fed GIT binary patch literal 6148 zcmeHK%}T>S5Z<*_6N-?7LXQhx3%1l&ikDF93mDOZN=;1BV9b^zHHT8jSzpK}@p+ut z-GIfMMeGdhe)GGV{UH0p7~|tb*kjCLj9JhSIVv@R?%L3nNk-&2Mm7&(8G!W>%uVdC z1AcphWh`Y6LGk_j<0#9!-A}&NXm0Q9T9(za?z|^ic)6c1vaz4upmix_98|g=Tt%~D z>g=D%B=@6ama2j%oI%R%b(Dm%7|TTxW~$cH0n4`SsncFAhbR4x=nV&}j#!RHqmCH# zk5?<(IygK!y_i17FNu89baG%@$)3Rq-a#>|dG*pHmdPV{s_ZI@kQg8ahyh|?vl%ew zg4Nn=8ff*z05MR*0PYV08lr2k)Tp)&=7p`UpzfkFnyBet{28e-m1{&IQ@cciAU#9kvzg|KX zF+dFbGX{8b;!Vb|D08;{SRS6W0@^(^6wE780ResO5&#D7BW)Gbae+GIxduy(I12hz QIUrpG6d}|R1HZt)7X)QVng9R* literal 0 HcmV?d00001 diff --git a/spec/unit/event-timeline-set.spec.ts b/spec/unit/event-timeline-set.spec.ts index ce8fbf32d..d4b0f6213 100644 --- a/spec/unit/event-timeline-set.spec.ts +++ b/spec/unit/event-timeline-set.spec.ts @@ -55,6 +55,23 @@ describe('EventTimelineSet', () => { }); }; + const mkThreadResponse = (root: MatrixEvent) => utils.mkEvent({ + event: true, + type: EventType.RoomMessage, + user: userA, + room: roomId, + content: { + "body": "Thread response :: " + Math.random(), + "m.relates_to": { + "event_id": root.getId(), + "m.in_reply_to": { + "event_id": root.getId(), + }, + "rel_type": "m.thread", + }, + }, + }, room.client); + beforeEach(() => { client = utils.mock(MatrixClient, 'MatrixClient'); client.reEmitter = utils.mock(ReEmitter, 'ReEmitter'); @@ -117,6 +134,13 @@ describe('EventTimelineSet', () => { }); describe('addEventToTimeline', () => { + let thread: Thread; + + beforeEach(() => { + (client.supportsExperimentalThreads as jest.Mock).mockReturnValue(true); + thread = new Thread("!thread_id:server", messageEvent, { room, client }); + }); + it("Adds event to timeline", () => { const liveTimeline = eventTimelineSet.getLiveTimeline(); expect(liveTimeline.getEvents().length).toStrictEqual(0); @@ -144,6 +168,41 @@ describe('EventTimelineSet', () => { ); }).not.toThrow(); }); + + it("should not add an event to a timeline that does not belong to the timelineSet", () => { + const eventTimelineSet2 = new EventTimelineSet(room); + const liveTimeline2 = eventTimelineSet2.getLiveTimeline(); + expect(liveTimeline2.getEvents().length).toStrictEqual(0); + + expect(() => { + eventTimelineSet.addEventToTimeline(messageEvent, liveTimeline2, { + toStartOfTimeline: true, + }); + }).toThrowError(); + }); + + it("should not add a threaded reply to the main room timeline", () => { + const liveTimeline = eventTimelineSet.getLiveTimeline(); + expect(liveTimeline.getEvents().length).toStrictEqual(0); + + const threadedReplyEvent = mkThreadResponse(messageEvent); + + eventTimelineSet.addEventToTimeline(threadedReplyEvent, liveTimeline, { + toStartOfTimeline: true, + }); + expect(liveTimeline.getEvents().length).toStrictEqual(0); + }); + + it("should not add a normal message to the timelineSet representing a thread", () => { + const eventTimelineSetForThread = new EventTimelineSet(room, {}, client, thread); + const liveTimeline = eventTimelineSetForThread.getLiveTimeline(); + expect(liveTimeline.getEvents().length).toStrictEqual(0); + + eventTimelineSetForThread.addEventToTimeline(messageEvent, liveTimeline, { + toStartOfTimeline: true, + }); + expect(liveTimeline.getEvents().length).toStrictEqual(0); + }); }); describe('aggregateRelations', () => { diff --git a/src/models/event-timeline-set.ts b/src/models/event-timeline-set.ts index e41eec158..fd9dc1ff8 100644 --- a/src/models/event-timeline-set.ts +++ b/src/models/event-timeline-set.ts @@ -702,6 +702,19 @@ export class EventTimelineSet extends TypedEventEmitter { shouldLiveInThread: boolean; threadId?: string; } { - if (!this.client.supportsExperimentalThreads()) { + if (!this.client?.supportsExperimentalThreads()) { return { shouldLiveInRoom: true, shouldLiveInThread: false, From 9459a95134c0b94254b84c1b1fe8300afef1cc4f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 4 Nov 2022 10:59:23 +0000 Subject: [PATCH 19/21] Delete .DS_Store --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 8c6875c741d7013ebf68f1a62758ebca25db1fed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5Z<*_6N-?7LXQhx3%1l&ikDF93mDOZN=;1BV9b^zHHT8jSzpK}@p+ut z-GIfMMeGdhe)GGV{UH0p7~|tb*kjCLj9JhSIVv@R?%L3nNk-&2Mm7&(8G!W>%uVdC z1AcphWh`Y6LGk_j<0#9!-A}&NXm0Q9T9(za?z|^ic)6c1vaz4upmix_98|g=Tt%~D z>g=D%B=@6ama2j%oI%R%b(Dm%7|TTxW~$cH0n4`SsncFAhbR4x=nV&}j#!RHqmCH# zk5?<(IygK!y_i17FNu89baG%@$)3Rq-a#>|dG*pHmdPV{s_ZI@kQg8ahyh|?vl%ew zg4Nn=8ff*z05MR*0PYV08lr2k)Tp)&=7p`UpzfkFnyBet{28e-m1{&IQ@cciAU#9kvzg|KX zF+dFbGX{8b;!Vb|D08;{SRS6W0@^(^6wE780ResO5&#D7BW)Gbae+GIxduy(I12hz QIUrpG6d}|R1HZt)7X)QVng9R* From 38adbaf9236907b906b184143def2b2483eeb2b4 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 4 Nov 2022 06:25:10 -0500 Subject: [PATCH 20/21] Ignore random macOS cruft (`.DS_Store`) (#2851) --- .gitignore | 1 + 1 file changed, 1 insertion(+) 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 From c1a3b9507325d73a2245b96b7ccd38c5c9628fa9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 4 Nov 2022 12:21:04 +0000 Subject: [PATCH 21/21] Revert "Extra insurance that we don't mix events in the wrong timelines" (#2853) This reverts commit 433b7afd7118bdf49917c247e63ce799884ecda5. --- spec/unit/event-timeline-set.spec.ts | 59 ---------------------------- src/models/event-timeline-set.ts | 13 ------ src/models/event.ts | 2 +- src/models/room.ts | 2 +- 4 files changed, 2 insertions(+), 74 deletions(-) diff --git a/spec/unit/event-timeline-set.spec.ts b/spec/unit/event-timeline-set.spec.ts index d4b0f6213..ce8fbf32d 100644 --- a/spec/unit/event-timeline-set.spec.ts +++ b/spec/unit/event-timeline-set.spec.ts @@ -55,23 +55,6 @@ describe('EventTimelineSet', () => { }); }; - const mkThreadResponse = (root: MatrixEvent) => utils.mkEvent({ - event: true, - type: EventType.RoomMessage, - user: userA, - room: roomId, - content: { - "body": "Thread response :: " + Math.random(), - "m.relates_to": { - "event_id": root.getId(), - "m.in_reply_to": { - "event_id": root.getId(), - }, - "rel_type": "m.thread", - }, - }, - }, room.client); - beforeEach(() => { client = utils.mock(MatrixClient, 'MatrixClient'); client.reEmitter = utils.mock(ReEmitter, 'ReEmitter'); @@ -134,13 +117,6 @@ describe('EventTimelineSet', () => { }); describe('addEventToTimeline', () => { - let thread: Thread; - - beforeEach(() => { - (client.supportsExperimentalThreads as jest.Mock).mockReturnValue(true); - thread = new Thread("!thread_id:server", messageEvent, { room, client }); - }); - it("Adds event to timeline", () => { const liveTimeline = eventTimelineSet.getLiveTimeline(); expect(liveTimeline.getEvents().length).toStrictEqual(0); @@ -168,41 +144,6 @@ describe('EventTimelineSet', () => { ); }).not.toThrow(); }); - - it("should not add an event to a timeline that does not belong to the timelineSet", () => { - const eventTimelineSet2 = new EventTimelineSet(room); - const liveTimeline2 = eventTimelineSet2.getLiveTimeline(); - expect(liveTimeline2.getEvents().length).toStrictEqual(0); - - expect(() => { - eventTimelineSet.addEventToTimeline(messageEvent, liveTimeline2, { - toStartOfTimeline: true, - }); - }).toThrowError(); - }); - - it("should not add a threaded reply to the main room timeline", () => { - const liveTimeline = eventTimelineSet.getLiveTimeline(); - expect(liveTimeline.getEvents().length).toStrictEqual(0); - - const threadedReplyEvent = mkThreadResponse(messageEvent); - - eventTimelineSet.addEventToTimeline(threadedReplyEvent, liveTimeline, { - toStartOfTimeline: true, - }); - expect(liveTimeline.getEvents().length).toStrictEqual(0); - }); - - it("should not add a normal message to the timelineSet representing a thread", () => { - const eventTimelineSetForThread = new EventTimelineSet(room, {}, client, thread); - const liveTimeline = eventTimelineSetForThread.getLiveTimeline(); - expect(liveTimeline.getEvents().length).toStrictEqual(0); - - eventTimelineSetForThread.addEventToTimeline(messageEvent, liveTimeline, { - toStartOfTimeline: true, - }); - expect(liveTimeline.getEvents().length).toStrictEqual(0); - }); }); describe('aggregateRelations', () => { diff --git a/src/models/event-timeline-set.ts b/src/models/event-timeline-set.ts index fd9dc1ff8..e41eec158 100644 --- a/src/models/event-timeline-set.ts +++ b/src/models/event-timeline-set.ts @@ -702,19 +702,6 @@ export class EventTimelineSet extends TypedEventEmitter { shouldLiveInThread: boolean; threadId?: string; } { - if (!this.client?.supportsExperimentalThreads()) { + if (!this.client.supportsExperimentalThreads()) { return { shouldLiveInRoom: true, shouldLiveInThread: false,