From d9f070404830059d2ef75497995b163be45ecf46 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sun, 20 Mar 2022 20:10:45 +0000 Subject: [PATCH 01/93] reduce flakiness of e2e verif test it's completely valid to receive a `ready` event after having received a `start` event as messages may be received or decrypted in any order. partial (but possibly sufficient?) fix for https://github.com/vector-im/element-web/issues/21488 --- src/crypto/verification/request/VerificationRequest.ts | 6 +++--- src/webrtc/callEventHandler.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/crypto/verification/request/VerificationRequest.ts b/src/crypto/verification/request/VerificationRequest.ts index 49256b0b1..f250d7df5 100644 --- a/src/crypto/verification/request/VerificationRequest.ts +++ b/src/crypto/verification/request/VerificationRequest.ts @@ -842,11 +842,11 @@ export class VerificationRequest< } const isUnexpectedRequest = type === REQUEST_TYPE && this.phase !== PHASE_UNSENT; - const isUnexpectedReady = type === READY_TYPE && this.phase !== PHASE_REQUESTED; + const isUnexpectedReady = type === READY_TYPE && this.phase !== PHASE_REQUESTED && this.phase !== PHASE_STARTED; // only if phase has passed from PHASE_UNSENT should we cancel, because events // are allowed to come in in any order (at least with InRoomChannel). So we only know - // we're dealing with a valid request we should participate in once we've moved to PHASE_REQUESTED - // before that, we could be looking at somebody elses verification request and we just + // we're dealing with a valid request we should participate in once we've moved to PHASE_REQUESTED. + // Before that, we could be looking at somebody else's verification request and we just // happen to be in the room if (this.phase !== PHASE_UNSENT && (isUnexpectedRequest || isUnexpectedReady)) { logger.warn(`Cancelling, unexpected ${type} verification ` + diff --git a/src/webrtc/callEventHandler.ts b/src/webrtc/callEventHandler.ts index f190bde60..9053f8f41 100644 --- a/src/webrtc/callEventHandler.ts +++ b/src/webrtc/callEventHandler.ts @@ -279,7 +279,7 @@ export class CallEventHandler { // The following events need a call and a peer connection if (!call || !call.hasPeerConnection) { - logger.warn("Discarding an event, we don't have a call/peerConn", type); + logger.info(`Discarding possible call event ${event.getId()} as we don't have a call/peerConn`, type); return; } // Ignore remote echo From 49dd76b91e0a081a4ab3c7ab3ddc66bbf9fff1a2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 11 May 2022 10:53:52 +0100 Subject: [PATCH 02/93] Remove redundant checkKey param on isSecretStored (#2360) --- src/client.ts | 9 +++------ src/crypto/CrossSigning.ts | 4 ++-- src/crypto/SecretStorage.ts | 3 +-- src/crypto/index.ts | 7 ++----- 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/client.ts b/src/client.ts index ecec0ead1..12e02a227 100644 --- a/src/client.ts +++ b/src/client.ts @@ -2388,18 +2388,15 @@ export class MatrixClient extends TypedEventEmitter | null> { + public isSecretStored(name: string): Promise | null> { if (!this.crypto) { throw new Error("End-to-end encryption disabled"); } - return this.crypto.isSecretStored(name, checkKey); + return this.crypto.isSecretStored(name); } /** @@ -2728,7 +2725,7 @@ export class MatrixClient extends TypedEventEmitter | null> { - return Promise.resolve(this.isSecretStored("m.megolm_backup.v1", false /* checkKey */)); + return Promise.resolve(this.isSecretStored("m.megolm_backup.v1")); } /** diff --git a/src/crypto/CrossSigning.ts b/src/crypto/CrossSigning.ts index 21dd0ee16..4e5c9f452 100644 --- a/src/crypto/CrossSigning.ts +++ b/src/crypto/CrossSigning.ts @@ -171,7 +171,7 @@ export class CrossSigningInfo { */ public async isStoredInSecretStorage(secretStorage: SecretStorage): Promise> { // check what SSSS keys have encrypted the master key (if any) - const stored = await secretStorage.isStored("m.cross_signing.master", false) || {}; + const stored = await secretStorage.isStored("m.cross_signing.master") || {}; // then check which of those SSSS keys have also encrypted the SSK and USK function intersect(s: Record) { for (const k of Object.keys(stored)) { @@ -181,7 +181,7 @@ export class CrossSigningInfo { } } for (const type of ["self_signing", "user_signing"]) { - intersect(await secretStorage.isStored(`m.cross_signing.${type}`, false) || {}); + intersect(await secretStorage.isStored(`m.cross_signing.${type}`) || {}); } return Object.keys(stored).length ? stored : null; } diff --git a/src/crypto/SecretStorage.ts b/src/crypto/SecretStorage.ts index f36b66c92..0eef2ee7d 100644 --- a/src/crypto/SecretStorage.ts +++ b/src/crypto/SecretStorage.ts @@ -339,13 +339,12 @@ export class SecretStorage { * Check if a secret is stored on the server. * * @param {string} name the name of the secret - * @param {boolean} checkKey check if the secret is encrypted by a trusted key * * @return {object?} map of key name to key info the secret is encrypted * with, or null if it is not present or not encrypted with a trusted * key */ - public async isStored(name: string, checkKey = true): Promise | null> { + public async isStored(name: string): Promise | null> { // check if secret exists const secretInfo = await this.accountDataAdapter.getAccountDataFromServer(name); if (!secretInfo?.encrypted) return null; diff --git a/src/crypto/index.ts b/src/crypto/index.ts index df18d83a0..a83702385 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -1076,11 +1076,8 @@ export class Crypto extends TypedEventEmitter | null> { - return this.secretStorage.isStored(name, checkKey); + public isSecretStored(name: string): Promise | null> { + return this.secretStorage.isStored(name); } public requestSecret(name: string, devices: string[]): ISecretRequest { From 923ff4b282b7c28060a6714cbbbb4d9ee3d72878 Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski Date: Wed, 11 May 2022 13:17:36 +0200 Subject: [PATCH 03/93] registration: add function to re-request email token (#2357) --- spec/unit/interactive-auth.spec.js | 105 +++++++++++++++++++++++++++++ src/interactive-auth.ts | 51 ++++++++------ 2 files changed, 137 insertions(+), 19 deletions(-) diff --git a/spec/unit/interactive-auth.spec.js b/spec/unit/interactive-auth.spec.js index da2bf1917..6742d0590 100644 --- a/spec/unit/interactive-auth.spec.js +++ b/spec/unit/interactive-auth.spec.js @@ -18,6 +18,8 @@ limitations under the License. import { logger } from "../../src/logger"; import { InteractiveAuth } from "../../src/interactive-auth"; import { MatrixError } from "../../src/http-api"; +import { sleep } from "../../src/utils"; +import { randomString } from "../../src/randomstring"; // Trivial client object to test interactive auth // (we do not need TestClient here) @@ -172,4 +174,107 @@ describe("InteractiveAuth", function() { expect(error.message).toBe('No appropriate authentication flow found'); }); }); + + describe("requestEmailToken", () => { + it("increases auth attempts", async () => { + const doRequest = jest.fn(); + const stateUpdated = jest.fn(); + const requestEmailToken = jest.fn(); + requestEmailToken.mockImplementation(async () => ({ sid: "" })); + + const ia = new InteractiveAuth({ + matrixClient: new FakeClient(), + doRequest, stateUpdated, requestEmailToken, + }); + + await ia.requestEmailToken(); + expect(requestEmailToken).toHaveBeenLastCalledWith(undefined, ia.getClientSecret(), 1, undefined); + requestEmailToken.mockClear(); + await ia.requestEmailToken(); + expect(requestEmailToken).toHaveBeenLastCalledWith(undefined, ia.getClientSecret(), 2, undefined); + requestEmailToken.mockClear(); + await ia.requestEmailToken(); + expect(requestEmailToken).toHaveBeenLastCalledWith(undefined, ia.getClientSecret(), 3, undefined); + requestEmailToken.mockClear(); + await ia.requestEmailToken(); + expect(requestEmailToken).toHaveBeenLastCalledWith(undefined, ia.getClientSecret(), 4, undefined); + requestEmailToken.mockClear(); + await ia.requestEmailToken(); + expect(requestEmailToken).toHaveBeenLastCalledWith(undefined, ia.getClientSecret(), 5, undefined); + }); + + it("increases auth attempts", async () => { + const doRequest = jest.fn(); + const stateUpdated = jest.fn(); + const requestEmailToken = jest.fn(); + requestEmailToken.mockImplementation(async () => ({ sid: "" })); + + const ia = new InteractiveAuth({ + matrixClient: new FakeClient(), + doRequest, stateUpdated, requestEmailToken, + }); + + await ia.requestEmailToken(); + expect(requestEmailToken).toHaveBeenLastCalledWith(undefined, ia.getClientSecret(), 1, undefined); + requestEmailToken.mockClear(); + await ia.requestEmailToken(); + expect(requestEmailToken).toHaveBeenLastCalledWith(undefined, ia.getClientSecret(), 2, undefined); + requestEmailToken.mockClear(); + await ia.requestEmailToken(); + expect(requestEmailToken).toHaveBeenLastCalledWith(undefined, ia.getClientSecret(), 3, undefined); + requestEmailToken.mockClear(); + await ia.requestEmailToken(); + expect(requestEmailToken).toHaveBeenLastCalledWith(undefined, ia.getClientSecret(), 4, undefined); + requestEmailToken.mockClear(); + await ia.requestEmailToken(); + expect(requestEmailToken).toHaveBeenLastCalledWith(undefined, ia.getClientSecret(), 5, undefined); + }); + + it("passes errors through", async () => { + const doRequest = jest.fn(); + const stateUpdated = jest.fn(); + const requestEmailToken = jest.fn(); + requestEmailToken.mockImplementation(async () => { + throw new Error("unspecific network error"); + }); + + const ia = new InteractiveAuth({ + matrixClient: new FakeClient(), + doRequest, stateUpdated, requestEmailToken, + }); + + expect(async () => await ia.requestEmailToken()).rejects.toThrowError("unspecific network error"); + }); + + it("only starts one request at a time", async () => { + const doRequest = jest.fn(); + const stateUpdated = jest.fn(); + const requestEmailToken = jest.fn(); + requestEmailToken.mockImplementation(() => sleep(500, { sid: "" })); + + const ia = new InteractiveAuth({ + matrixClient: new FakeClient(), + doRequest, stateUpdated, requestEmailToken, + }); + + await Promise.all([ia.requestEmailToken(), ia.requestEmailToken(), ia.requestEmailToken()]); + expect(requestEmailToken).toHaveBeenCalledTimes(1); + }); + + it("stores result in email sid", async () => { + const doRequest = jest.fn(); + const stateUpdated = jest.fn(); + const requestEmailToken = jest.fn(); + const sid = randomString(24); + requestEmailToken.mockImplementation(() => sleep(500, { sid })); + + const ia = new InteractiveAuth({ + matrixClient: new FakeClient(), + doRequest, stateUpdated, requestEmailToken, + }); + + await ia.requestEmailToken(); + expect(ia.getEmailSid()).toEqual(sid); + }); + }); }); diff --git a/src/interactive-auth.ts b/src/interactive-auth.ts index f06369fe7..2732023d4 100644 --- a/src/interactive-auth.ts +++ b/src/interactive-auth.ts @@ -203,6 +203,8 @@ export class InteractiveAuth { private chosenFlow: IFlow = null; private currentStage: string = null; + private emailAttempt = 1; + // if we are currently trying to submit an auth dict (which includes polling) // the promise the will resolve/reject when it completes private submitPromise: Promise = null; @@ -408,6 +410,34 @@ export class InteractiveAuth { this.emailSid = sid; } + /** + * Requests a new email token and sets the email sid for the validation session + */ + public requestEmailToken = async () => { + if (!this.requestingEmailToken) { + logger.trace("Requesting email token. Attempt: " + this.emailAttempt); + // If we've picked a flow with email auth, we send the email + // now because we want the request to fail as soon as possible + // if the email address is not valid (ie. already taken or not + // registered, depending on what the operation is). + this.requestingEmailToken = true; + try { + const requestTokenResult = await this.requestEmailTokenCallback( + this.inputs.emailAddress, + this.clientSecret, + this.emailAttempt++, + this.data.session, + ); + this.emailSid = requestTokenResult.sid; + logger.trace("Email token request succeeded"); + } finally { + this.requestingEmailToken = false; + } + } else { + logger.warn("Could not request email token: Already requesting"); + } + }; + /** * Fire off a request, and either resolve the promise, or call * startAuthStage. @@ -458,24 +488,9 @@ export class InteractiveAuth { return; } - if ( - !this.emailSid && - !this.requestingEmailToken && - this.chosenFlow.stages.includes(AuthType.Email) - ) { - // If we've picked a flow with email auth, we send the email - // now because we want the request to fail as soon as possible - // if the email address is not valid (ie. already taken or not - // registered, depending on what the operation is). - this.requestingEmailToken = true; + if (!this.emailSid && this.chosenFlow.stages.includes(AuthType.Email)) { try { - const requestTokenResult = await this.requestEmailTokenCallback( - this.inputs.emailAddress, - this.clientSecret, - 1, // TODO: Multiple send attempts? - this.data.session, - ); - this.emailSid = requestTokenResult.sid; + await this.requestEmailToken(); // NB. promise is not resolved here - at some point, doRequest // will be called again and if the user has jumped through all // the hoops correctly, auth will be complete and the request @@ -491,8 +506,6 @@ export class InteractiveAuth { // send the email, for whatever reason. this.attemptAuthDeferred.reject(e); this.attemptAuthDeferred = null; - } finally { - this.requestingEmailToken = false; } } } From 67f5293d6c9c8a9eda37885a5569ed428aad44f3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 11 May 2022 16:42:25 +0100 Subject: [PATCH 04/93] Tweaks for sonar to correctly report on forked PRs (#2359) --- .github/workflows/sonarqube.yml | 43 ++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 61238ed1b..acaf3d1bf 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -10,20 +10,21 @@ jobs: runs-on: ubuntu-latest if: github.event.workflow_run.conclusion == 'success' steps: - - uses: actions/checkout@v3 + - name: "🧮 Checkout code" + uses: actions/checkout@v3 with: repository: ${{ github.event.workflow_run.head_repository.full_name }} ref: ${{ github.event.workflow_run.head_branch }} # checkout commit that triggered this workflow fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis # fetch develop so that Sonar can identify new issues in PR builds - - name: Fetch develop + - name: "📕 Fetch develop" if: "github.event.workflow_run.head_branch != 'develop'" run: git rev-parse HEAD && git fetch origin develop:develop && git status && git rev-parse HEAD # 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 Coverage Report + - name: "📥 Download Coverage Report" uses: actions/github-script@v3.1.0 with: script: | @@ -44,22 +45,46 @@ jobs: const fs = require('fs'); fs.writeFileSync('${{github.workspace}}/coverage.zip', Buffer.from(download.data)); - - name: Extract Coverage Report + - name: "🗃️ Extract Coverage Report" run: unzip -d coverage coverage.zip && rm coverage.zip - - name: Read version + - name: "🔍 Read latest tag" id: version uses: WyriHaximus/github-action-get-previous-tag@v1 - - name: SonarCloud Scan + - name: "🔍 Read PR details" + id: prdetails + if: github.event.workflow_run.event == 'pull_request' + # We need to find the PR number that corresponds to the branch, which we do by searching the GH API + # The workflow_run event includes a list of pull requests, but it doesn't get populated for + # forked PRs: https://docs.github.com/en/rest/reference/checks#create-a-check-run + run: | + head_branch='${{github.event.workflow_run.head_repository.owner.login}}:${{github.event.workflow_run.head_branch}}' + echo "Head branch: $head_branch" + pulls_uri="https://api.github.com/repos/${{ github.repository }}/pulls?head=$(jq -Rr '@uri' <<<$head_branch)" + pr_data=$(curl -s -H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' "$pulls_uri") + + pr_number=$(jq -r '.[] | .number' <<< "$pr_data") + echo "PR number: $pr_number" + echo "::set-output name=prnumber::$pr_number" + + head_ref=$(jq -r '.[] | .head.ref' <<< "$pr_data") + echo "Head ref: $head_ref" + echo "::set-output name=headref::$head_ref" + + base_ref=$(jq -r '.[] | .base.ref' <<< "$pr_data") + echo "Base ref: $base_ref" + echo "::set-output name=baseref::$base_ref" + + - name: "🩻 SonarCloud Scan" uses: SonarSource/sonarcloud-github-action@master with: args: > -Dsonar.projectVersion=${{ steps.version.outputs.tag }} -Dsonar.scm.revision=${{ github.event.workflow_run.head_sha }} - -Dsonar.pullrequest.key=${{ github.event.workflow_run.pull_requests[0].number }} - -Dsonar.pullrequest.branch=${{ github.event.workflow_run.pull_requests[0].head.ref }} - -Dsonar.pullrequest.base=${{ github.event.workflow_run.pull_requests[0].base.ref }} + -Dsonar.pullrequest.key=${{ steps.prdetails.outputs.prnumber }} + -Dsonar.pullrequest.branch=${{ steps.prdetails.outputs.headref }} + -Dsonar.pullrequest.base=${{ steps.prdetails.outputs.baseref }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} From 4d4d6e1411b6cf29b029f4f82cb67508404b8c36 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 11 May 2022 17:04:10 +0100 Subject: [PATCH 05/93] NodeURL isn't needed as Node exports the standard URL c'tor as global (#2361) Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/autodiscovery.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/autodiscovery.ts b/src/autodiscovery.ts index 5f875cc68..58c4cb157 100644 --- a/src/autodiscovery.ts +++ b/src/autodiscovery.ts @@ -17,8 +17,6 @@ limitations under the License. /** @module auto-discovery */ -import { URL as NodeURL } from "url"; - import { IClientWellKnown, IWellKnownConfig } from "./client"; import { logger } from './logger'; @@ -372,16 +370,11 @@ export class AutoDiscovery { if (!url) return false; try { - // We have to try and parse the URL using the NodeJS URL - // library if we're on NodeJS and use the browser's URL - // library when we're in a browser. To accomplish this, we - // try the NodeJS version first and fall back to the browser. let parsed = null; try { - if (NodeURL) parsed = new NodeURL(url); - else parsed = new URL(url); - } catch (e) { parsed = new URL(url); + } catch (e) { + logger.error("Could not parse url", e); } if (!parsed || !parsed.hostname) return false; From 4721aa1d241a46601601259ec7ca6db9ff1bb5fb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 12 May 2022 10:12:39 +0100 Subject: [PATCH 06/93] Fix up more types & Sonar warnings (#2363) * Fix up more types & Sonar warnings * Fix test * Add first test for callEventHandler --- .../matrix-client-event-timeline.spec.js | 7 ++- spec/unit/webrtc/callEventHandler.spec.ts | 54 +++++++++++++++++++ src/@types/requests.ts | 37 ++++++++++++- src/client.ts | 23 ++++---- src/crypto/DeviceList.ts | 2 +- src/crypto/index.ts | 6 +-- src/models/event.ts | 6 +-- src/sync-accumulator.ts | 1 - src/sync.ts | 8 +-- src/webrtc/callEventHandler.ts | 12 ++--- 10 files changed, 119 insertions(+), 37 deletions(-) create mode 100644 spec/unit/webrtc/callEventHandler.spec.ts diff --git a/spec/integ/matrix-client-event-timeline.spec.js b/spec/integ/matrix-client-event-timeline.spec.js index 6e93a063a..6df4a9a81 100644 --- a/spec/integ/matrix-client-event-timeline.spec.js +++ b/spec/integ/matrix-client-event-timeline.spec.js @@ -526,8 +526,7 @@ describe("MatrixClient event timelines", function() { return { original_event: THREAD_ROOT, chunk: [THREAD_REPLY], - next_batch: "next_batch_token0", - prev_batch: "prev_batch_token0", + // no next batch as this is the oldest end of the timeline }; }); @@ -536,8 +535,8 @@ describe("MatrixClient event timelines", function() { const timeline = await timelinePromise; - expect(timeline.getEvents().find(e => e.getId() === THREAD_ROOT.event_id)); - expect(timeline.getEvents().find(e => e.getId() === THREAD_REPLY.event_id)); + expect(timeline.getEvents().find(e => e.getId() === THREAD_ROOT.event_id)).toBeTruthy(); + expect(timeline.getEvents().find(e => e.getId() === THREAD_REPLY.event_id)).toBeTruthy(); }); }); diff --git a/spec/unit/webrtc/callEventHandler.spec.ts b/spec/unit/webrtc/callEventHandler.spec.ts new file mode 100644 index 000000000..d60ae2997 --- /dev/null +++ b/spec/unit/webrtc/callEventHandler.spec.ts @@ -0,0 +1,54 @@ +/* +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 { TestClient } from '../../TestClient'; +import { ClientEvent, EventType, MatrixEvent, RoomEvent } from "../../../src"; +import { CallEventHandler, CallEventHandlerEvent } from "../../../src/webrtc/callEventHandler"; +import { SyncState } from "../../../src/sync"; + +describe("callEventHandler", () => { + it("should ignore a call if invite & hangup come within a single sync", () => { + const testClient = new TestClient(); + const client = testClient.client; + client.callEventHandler = new CallEventHandler(client); + client.callEventHandler.start(); + + // Fire off call invite then hangup within a single sync + const callInvite = new MatrixEvent({ + type: EventType.CallInvite, + content: { + call_id: "123", + }, + }); + client.emit(RoomEvent.Timeline, callInvite); + + const callHangup = new MatrixEvent({ + type: EventType.CallHangup, + content: { + call_id: "123", + }, + }); + client.emit(RoomEvent.Timeline, callHangup); + + const incomingCallEmitted = jest.fn(); + client.on(CallEventHandlerEvent.Incoming, incomingCallEmitted); + + client.getSyncState = jest.fn().mockReturnValue(SyncState.Syncing); + client.emit(ClientEvent.Sync); + + expect(incomingCallEmitted).not.toHaveBeenCalled(); + }); +}); diff --git a/src/@types/requests.ts b/src/@types/requests.ts index a3c950ab1..f3ff33e4a 100644 --- a/src/@types/requests.ts +++ b/src/@types/requests.ts @@ -17,9 +17,11 @@ limitations under the License. import { Callback } from "../client"; import { IContent, IEvent } from "../models/event"; import { Preset, Visibility } from "./partials"; -import { SearchKey } from "./search"; +import { IEventWithRoomId, SearchKey } from "./search"; import { IRoomEventFilter } from "../filter"; import { Direction } from "../models/event-timeline"; +import { PushRuleAction } from "./PushRules"; +import { IRoomEvent } from "../sync-accumulator"; // allow camelcase as these are things that go onto the wire /* eslint-disable camelcase */ @@ -155,4 +157,37 @@ export interface IRelationsResponse { prev_batch?: string; } +export interface IContextResponse { + end: string; + start: string; + state: IEventWithRoomId[]; + events_before: IEventWithRoomId[]; + events_after: IEventWithRoomId[]; + event: IEventWithRoomId; +} + +export interface IEventsResponse { + chunk: IEventWithRoomId[]; + end: string; + start: string; +} + +export interface INotification { + actions: PushRuleAction[]; + event: IRoomEvent; + profile_tag?: string; + read: boolean; + room_id: string; + ts: number; +} + +export interface INotificationsResponse { + next_token: string; + notifications: INotification[]; +} + +export interface IFilterResponse { + filter_id: string; +} + /* eslint-enable camelcase */ diff --git a/src/client.ts b/src/client.ts index 12e02a227..386152f92 100644 --- a/src/client.ts +++ b/src/client.ts @@ -113,6 +113,8 @@ import { RoomMemberEventHandlerMap, RoomStateEvent, RoomStateEventHandlerMap, + INotificationsResponse, + IFilterResponse, } from "./matrix"; import { CrossSigningKey, @@ -132,6 +134,7 @@ import { Room } from "./models/room"; import { IAddThreePidOnlyBody, IBindThreePidBody, + IContextResponse, ICreateRoomOpts, IEventSearchOpts, IGuestAccessOpts, @@ -5245,7 +5248,7 @@ export class MatrixClient extends TypedEventEmitter(undefined, Method.Get, path, params); // TODO types + const res = await this.http.authedRequest(undefined, Method.Get, path, params); if (!res.event) { throw new Error("'event' not in '/context' result - homeserver too old?"); } @@ -5424,7 +5427,7 @@ export class MatrixClient extends TypedEventEmitter( // TODO types + promise = this.http.authedRequest( undefined, Method.Get, path, params, undefined, ).then(async (res) => { const token = res.next_token; @@ -6101,15 +6104,13 @@ export class MatrixClient extends TypedEventEmitter(undefined, Method.Post, path, undefined, content).then((response) => { - // persist the filter - const filter = Filter.fromJson( - this.credentials.userId, response.filter_id, content, - ); - this.store.storeFilter(filter); - return filter; - }); + return this.http.authedRequest(undefined, Method.Post, path, undefined, content) + .then((response) => { + // persist the filter + const filter = Filter.fromJson(this.credentials.userId, response.filter_id, content); + this.store.storeFilter(filter); + return filter; + }); } /** diff --git a/src/crypto/DeviceList.ts b/src/crypto/DeviceList.ts index 000e79f93..c203ce5da 100644 --- a/src/crypto/DeviceList.ts +++ b/src/crypto/DeviceList.ts @@ -942,7 +942,7 @@ async function updateStoredDeviceKeysForUser( async function storeDeviceKeys( olmDevice: OlmDevice, userStore: Record, - deviceResult: any, // TODO types + deviceResult: IDownloadKeyResult["device_keys"]["user_id"]["device_id"], ): Promise { if (!deviceResult.keys) { // no keys? diff --git a/src/crypto/index.ts b/src/crypto/index.ts index a83702385..474bfd3f6 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -58,7 +58,7 @@ import { keyFromPassphrase } from './key_passphrase'; import { decodeRecoveryKey, encodeRecoveryKey } from './recoverykey'; import { VerificationRequest } from "./verification/request/VerificationRequest"; import { InRoomChannel, InRoomRequests } from "./verification/request/InRoomChannel"; -import { ToDeviceChannel, ToDeviceRequests } from "./verification/request/ToDeviceChannel"; +import { ToDeviceChannel, ToDeviceRequests, Request } from "./verification/request/ToDeviceChannel"; import { IllegalMethod } from "./verification/IllegalMethod"; import { KeySignatureUploadError } from "../errors"; import { calculateKeyCheck, decryptAES, encryptAES } from './aes'; @@ -2318,8 +2318,8 @@ export class Crypto extends TypedEventEmitter { + let request: Request; if (transactionId) { request = this.toDeviceVerificationRequests.getRequestBySenderAndTxnId(userId, transactionId); if (!request) { diff --git a/src/models/event.ts b/src/models/event.ts index 227036be5..e188e8607 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -1043,7 +1043,7 @@ export class MatrixEvent extends TypedEventEmitter