Compare commits
34 Commits
v17.2.0
...
v18.0.0-rc.1
| Author | SHA1 | Date | |
|---|---|---|---|
| ba371f7468 | |||
| 761facf98a | |||
| c6af997542 | |||
| e9e8e90a94 | |||
| f44510e65f | |||
| ba1f6ffc84 | |||
| 3e4f02b41e | |||
| af17fb27b8 | |||
| 72013341db | |||
| 6f445ca99a | |||
| e2af78d8d3 | |||
| 4721aa1d24 | |||
| 4d4d6e1411 | |||
| 67f5293d6c | |||
| 923ff4b282 | |||
| 49dd76b91e | |||
| 34cfa51104 | |||
| 685276f056 | |||
| 03a79dc6dd | |||
| 83a4881498 | |||
| 62d77231af | |||
| 706b4d6054 | |||
| da69ca215b | |||
| 95a94cdbe3 | |||
| 6b5f4aa0a9 | |||
| dea3f52fe9 | |||
| a388fde3e2 | |||
| 1cde686a13 | |||
| 592f2931dc | |||
| 030fcb57a5 | |||
| 8be30acb11 | |||
| b86630f0e3 | |||
| 81c89f69c5 | |||
| 9b633251d5 |
@@ -2,13 +2,24 @@ name: Notify Downstream Projects
|
||||
on:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
notify-matrix-react-sdk:
|
||||
notify-downstream:
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- repo: vector-im/element-web
|
||||
event: element-web-notify
|
||||
- repo: matrix-org/matrix-react-sdk
|
||||
event: upstream-sdk-notify
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Notify matrix-react-sdk repo that a new SDK build is on develop so it can CI against it
|
||||
uses: peter-evans/repository-dispatch@v1
|
||||
with:
|
||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
repository: vector-im/element-web
|
||||
event-type: upstream-sdk-notify
|
||||
repository: ${{ matrix.repo }}
|
||||
event-type: ${{ matrix.event }}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# Find details about the PR associated with this ref
|
||||
# Outputs:
|
||||
# prnumber: the ID number of the associated PR
|
||||
# headref: the name of the head branch of the PR
|
||||
# baseref: the name of the base branch of the PR
|
||||
name: PR Details
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
owner:
|
||||
type: string
|
||||
required: true
|
||||
description: The github username of the owner of the head branch
|
||||
branch:
|
||||
type: string
|
||||
required: true
|
||||
description: The name of the head branch
|
||||
outputs:
|
||||
pr_id:
|
||||
description: The ID of the PR found
|
||||
value: ${{ jobs.prdetails.outputs.pr_id }}
|
||||
head_branch:
|
||||
description: The head branch of the PR found
|
||||
value: ${{ jobs.prdetails.outputs.head_branch }}
|
||||
base_branch:
|
||||
description: The base branch of the PR found
|
||||
value: ${{ jobs.prdetails.outputs.base_branch }}
|
||||
|
||||
jobs:
|
||||
prdetails:
|
||||
name: Find PR Details
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "🔍 Read PR details"
|
||||
id: details
|
||||
# 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='${{ inputs.owner }}:${{ inputs.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"
|
||||
outputs:
|
||||
pr_id: ${{ steps.details.outputs.prnumber }}
|
||||
head_branch: ${{ steps.details.outputs.headref }}
|
||||
base_branch: ${{ steps.details.outputs.baseref }}
|
||||
@@ -1,10 +1,14 @@
|
||||
name: Pull Request
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [ opened, edited, labeled, unlabeled ]
|
||||
types: [ opened, edited, labeled, unlabeled, synchronize ]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
changelog:
|
||||
name: Preview Changelog
|
||||
if: github.event.action != 'synchronize'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: matrix-org/allchange@main
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
name: SonarCloud
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
repo:
|
||||
type: string
|
||||
required: true
|
||||
description: The full name of the repo in org/repo format
|
||||
head_branch:
|
||||
type: string
|
||||
required: true
|
||||
description: The name of the head branch
|
||||
# We cannot use ${{ github.sha }} here as for pull requests it'll be a simulated merge commit instead
|
||||
revision:
|
||||
type: string
|
||||
required: true
|
||||
description: The git revision with which this sonar run should be associated
|
||||
|
||||
# Coverage specific parameters, assumes coverage reports live in a /coverage/ directory
|
||||
coverage_workflow_name:
|
||||
type: string
|
||||
required: false
|
||||
description: The name of the workflow which uploaded the `coverage` artifact, if any
|
||||
coverage_run_id:
|
||||
type: string
|
||||
required: false
|
||||
description: The run_id of the workflow which upload the coverage relevant to this run
|
||||
|
||||
# PR specific parameters
|
||||
pr_id:
|
||||
type: string
|
||||
required: false
|
||||
description: The ID number of the PR if this workflow is being triggered due to one
|
||||
base_branch:
|
||||
type: string
|
||||
required: false
|
||||
description: The base branch of the PR if this workflow is being triggered due to one
|
||||
|
||||
# Org specific parameters
|
||||
main_branch:
|
||||
type: string
|
||||
required: false
|
||||
description: The default branch of the repository
|
||||
default: "develop"
|
||||
secrets:
|
||||
SONAR_TOKEN:
|
||||
required: true
|
||||
jobs:
|
||||
analysis:
|
||||
name: Analysis
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "🧮 Checkout code"
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: ${{ inputs.repo }}
|
||||
ref: ${{ inputs.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 ${{ inputs.main_branch }}"
|
||||
if: inputs.head_branch != inputs.main_branch
|
||||
run: git rev-parse HEAD && git fetch origin ${{ inputs.main_branch }}:${{ inputs.main_branch }} && 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"
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
if: inputs.coverage_workflow_name
|
||||
with:
|
||||
workflow: ${{ inputs.coverage_workflow_name }}
|
||||
run_id: ${{ inputs.coverage_run_id }}
|
||||
name: coverage
|
||||
path: coverage
|
||||
|
||||
- name: "🔍 Read package.json version"
|
||||
id: version
|
||||
uses: martinbeentjes/npm-get-version-action@main
|
||||
|
||||
- name: "🩻 SonarCloud Scan"
|
||||
uses: SonarSource/sonarcloud-github-action@master
|
||||
with:
|
||||
args: >
|
||||
-Dsonar.projectVersion=${{ steps.version.outputs.current-version }}
|
||||
-Dsonar.scm.revision=${{ inputs.revision }}
|
||||
-Dsonar.pullrequest.key=${{ inputs.pr_id }}
|
||||
-Dsonar.pullrequest.branch=${{ inputs.pr_id && inputs.head_branch }}
|
||||
-Dsonar.pullrequest.base=${{ inputs.pr_id && inputs.base_branch }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
@@ -4,44 +4,34 @@ on:
|
||||
workflows: [ "Tests" ]
|
||||
types:
|
||||
- completed
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
prdetails:
|
||||
name: ℹ️ PR Details
|
||||
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request'
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/pr_details.yml@develop
|
||||
with:
|
||||
owner: ${{ github.event.workflow_run.head_repository.owner.login }}
|
||||
branch: ${{ github.event.workflow_run.head_branch }}
|
||||
|
||||
sonarqube:
|
||||
name: SonarQube
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.workflow_run.conclusion == 'success'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
|
||||
# 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
|
||||
uses: actions/github-script@v3.1.0
|
||||
with:
|
||||
script: |
|
||||
const artifacts = await github.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: ${{ github.event.workflow_run.id }},
|
||||
});
|
||||
const matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "coverage"
|
||||
})[0];
|
||||
const download = await github.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
const fs = require('fs');
|
||||
fs.writeFileSync('${{github.workspace}}/coverage.zip', Buffer.from(download.data));
|
||||
|
||||
- name: Extract Coverage Report
|
||||
run: unzip -d coverage coverage.zip && rm coverage.zip
|
||||
|
||||
- name: SonarCloud Scan
|
||||
uses: SonarSource/sonarcloud-github-action@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
name: 🩻 SonarQube
|
||||
needs: prdetails
|
||||
# Only wait for prdetails if it isn't skipped
|
||||
if: |
|
||||
always() &&
|
||||
(needs.prdetails.result == 'success' || needs.prdetails.result == 'skipped') &&
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/sonarcloud.yml@develop
|
||||
with:
|
||||
repo: ${{ github.event.workflow_run.head_repository.full_name }}
|
||||
pr_id: ${{ needs.prdetails.outputs.pr_id }}
|
||||
head_branch: ${{ needs.prdetails.outputs.head_branch || github.event.workflow_run.head_branch }}
|
||||
base_branch: ${{ needs.prdetails.outputs.base_branch }}
|
||||
revision: ${{ github.event.workflow_run.head_sha }}
|
||||
coverage_workflow_name: tests.yml
|
||||
coverage_run_id: ${{ github.event.workflow_run.id }}
|
||||
secrets:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
|
||||
@@ -3,6 +3,9 @@ on:
|
||||
pull_request: { }
|
||||
push:
|
||||
branches: [ develop, master ]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
ts_lint:
|
||||
name: "Typescript Syntax Check"
|
||||
|
||||
@@ -2,7 +2,10 @@ name: Tests
|
||||
on:
|
||||
pull_request: { }
|
||||
push:
|
||||
branches: [ develop, main, master ]
|
||||
branches: [ develop, master ]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
jest:
|
||||
name: Jest
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
name: Upgrade Dependencies
|
||||
on:
|
||||
workflow_dispatch: { }
|
||||
workflow_call:
|
||||
secrets:
|
||||
ELEMENT_BOT_TOKEN:
|
||||
required: true
|
||||
jobs:
|
||||
upgrade:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Upgrade
|
||||
run: yarn upgrade && yarn install
|
||||
|
||||
- name: Create Pull Request
|
||||
id: cpr
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
branch: actions/upgrade-deps
|
||||
delete-branch: true
|
||||
title: Upgrade dependencies
|
||||
labels: |
|
||||
Dependencies
|
||||
T-Task
|
||||
|
||||
- name: Enable automerge
|
||||
uses: peter-evans/enable-pull-request-automerge@v2
|
||||
if: steps.cpr.outputs.pull-request-operation == 'created'
|
||||
with:
|
||||
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
|
||||
@@ -1,3 +1,6 @@
|
||||
Changes in [18.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v18.0.0-rc.1) (2022-05-17)
|
||||
============================================================================================================
|
||||
|
||||
Changes in [17.2.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v17.2.0) (2022-05-10)
|
||||
==================================================================================================
|
||||
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
[](https://www.npmjs.com/package/matrix-js-sdk)
|
||||

|
||||

|
||||
[](https://sonarcloud.io/summary/new_code?id=matrix-js-sdk)
|
||||
[](https://sonarcloud.io/summary/new_code?id=matrix-js-sdk)
|
||||
[](https://sonarcloud.io/summary/new_code?id=matrix-js-sdk)
|
||||
[](https://sonarcloud.io/summary/new_code?id=matrix-js-sdk)
|
||||
|
||||
Matrix Javascript SDK
|
||||
=====================
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "17.2.0",
|
||||
"version": "18.0.0-rc.1",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"engines": {
|
||||
"node": ">=12.9.0"
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
sonar.projectKey=matrix-js-sdk
|
||||
sonar.organization=matrix-org
|
||||
|
||||
# This is the name and version displayed in the SonarCloud UI.
|
||||
#sonar.projectName=matrix-js-sdk
|
||||
#sonar.projectVersion=1.0
|
||||
|
||||
# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
|
||||
#sonar.sources=.
|
||||
|
||||
# Encoding of the source code. Default is default system encoding
|
||||
#sonar.sourceEncoding=UTF-8
|
||||
|
||||
@@ -17,5 +10,7 @@ sonar.exclusions=docs,examples,git-hooks
|
||||
|
||||
sonar.typescript.tsconfigPath=./tsconfig.json
|
||||
sonar.javascript.lcov.reportPaths=coverage/lcov.info
|
||||
sonar.coverage.exclusions=spec/*.ts
|
||||
sonar.coverage.exclusions=spec/**/*
|
||||
sonar.testExecutionReportPaths=coverage/test-report.xml
|
||||
|
||||
sonar.lang.patterns.ts=**/*.ts,**/*.tsx
|
||||
|
||||
@@ -47,7 +47,7 @@ describe("Browserify Test", function() {
|
||||
httpBackend.stop();
|
||||
});
|
||||
|
||||
it("Sync", async function() {
|
||||
it("Sync", function() {
|
||||
const event = utils.mkMembership({
|
||||
room: ROOM_ID,
|
||||
mship: "join",
|
||||
@@ -71,7 +71,7 @@ describe("Browserify Test", function() {
|
||||
};
|
||||
|
||||
httpBackend.when("GET", "/sync").respond(200, syncData);
|
||||
return await Promise.race([
|
||||
return Promise.race([
|
||||
httpBackend.flushAllExpected(),
|
||||
new Promise((_, reject) => {
|
||||
client.once("sync.unexpectedError", reject);
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
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 * as utils from "../test-utils/test-utils";
|
||||
import { CRYPTO_ENABLED } from "../../src/client";
|
||||
import { MatrixEvent } from "../../src/models/event";
|
||||
@@ -823,6 +839,171 @@ describe("MatrixClient", function() {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getThirdpartyUser", () => {
|
||||
it("should hit the expected API endpoint", async () => {
|
||||
const response = [{
|
||||
userid: "@Bob",
|
||||
protocol: "irc",
|
||||
fields: {},
|
||||
}];
|
||||
|
||||
const prom = client.getThirdpartyUser("irc", {});
|
||||
httpBackend.when("GET", "/thirdparty/user/irc").respond(200, response);
|
||||
await httpBackend.flush();
|
||||
expect(await prom).toStrictEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getThirdpartyLocation", () => {
|
||||
it("should hit the expected API endpoint", async () => {
|
||||
const response = [{
|
||||
alias: "#alias",
|
||||
protocol: "irc",
|
||||
fields: {},
|
||||
}];
|
||||
|
||||
const prom = client.getThirdpartyLocation("irc", {});
|
||||
httpBackend.when("GET", "/thirdparty/location/irc").respond(200, response);
|
||||
await httpBackend.flush();
|
||||
expect(await prom).toStrictEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getPushers", () => {
|
||||
it("should hit the expected API endpoint", async () => {
|
||||
const response = {
|
||||
pushers: [],
|
||||
};
|
||||
|
||||
const prom = client.getPushers();
|
||||
httpBackend.when("GET", "/pushers").respond(200, response);
|
||||
await httpBackend.flush();
|
||||
expect(await prom).toStrictEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getKeyChanges", () => {
|
||||
it("should hit the expected API endpoint", async () => {
|
||||
const response = {
|
||||
changed: [],
|
||||
left: [],
|
||||
};
|
||||
|
||||
const prom = client.getKeyChanges("old", "new");
|
||||
httpBackend.when("GET", "/keys/changes").check((req) => {
|
||||
expect(req.queryParams.from).toEqual("old");
|
||||
expect(req.queryParams.to).toEqual("new");
|
||||
}).respond(200, response);
|
||||
await httpBackend.flush();
|
||||
expect(await prom).toStrictEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getDevices", () => {
|
||||
it("should hit the expected API endpoint", async () => {
|
||||
const response = {
|
||||
devices: [],
|
||||
};
|
||||
|
||||
const prom = client.getDevices();
|
||||
httpBackend.when("GET", "/devices").respond(200, response);
|
||||
await httpBackend.flush();
|
||||
expect(await prom).toStrictEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getDevice", () => {
|
||||
it("should hit the expected API endpoint", async () => {
|
||||
const response = {
|
||||
device_id: "DEADBEEF",
|
||||
display_name: "NotAPhone",
|
||||
last_seen_ip: "127.0.0.1",
|
||||
last_seen_ts: 1,
|
||||
};
|
||||
|
||||
const prom = client.getDevice("DEADBEEF");
|
||||
httpBackend.when("GET", "/devices/DEADBEEF").respond(200, response);
|
||||
await httpBackend.flush();
|
||||
expect(await prom).toStrictEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getThreePids", () => {
|
||||
it("should hit the expected API endpoint", async () => {
|
||||
const response = {
|
||||
threepids: [],
|
||||
};
|
||||
|
||||
const prom = client.getThreePids();
|
||||
httpBackend.when("GET", "/account/3pid").respond(200, response);
|
||||
await httpBackend.flush();
|
||||
expect(await prom).toStrictEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteAlias", () => {
|
||||
it("should hit the expected API endpoint", async () => {
|
||||
const response = {};
|
||||
const prom = client.deleteAlias("#foo:bar");
|
||||
httpBackend.when("DELETE", "/directory/room/" + encodeURIComponent("#foo:bar")).respond(200, response);
|
||||
await httpBackend.flush();
|
||||
expect(await prom).toStrictEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteRoomTag", () => {
|
||||
it("should hit the expected API endpoint", async () => {
|
||||
const response = {};
|
||||
const prom = client.deleteRoomTag("!roomId:server", "u.tag");
|
||||
const url = `/user/${encodeURIComponent(userId)}/rooms/${encodeURIComponent("!roomId:server")}/tags/u.tag`;
|
||||
httpBackend.when("DELETE", url).respond(200, response);
|
||||
await httpBackend.flush();
|
||||
expect(await prom).toStrictEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getRoomTags", () => {
|
||||
it("should hit the expected API endpoint", async () => {
|
||||
const response = {
|
||||
tags: {
|
||||
"u.tag": {
|
||||
order: 0.5,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const prom = client.getRoomTags("!roomId:server");
|
||||
const url = `/user/${encodeURIComponent(userId)}/rooms/${encodeURIComponent("!roomId:server")}/tags`;
|
||||
httpBackend.when("GET", url).respond(200, response);
|
||||
await httpBackend.flush();
|
||||
expect(await prom).toStrictEqual(response);
|
||||
});
|
||||
});
|
||||
|
||||
describe("requestRegisterEmailToken", () => {
|
||||
it("should hit the expected API endpoint", async () => {
|
||||
const response = {
|
||||
sid: "random_sid",
|
||||
submit_url: "https://foobar.matrix/_matrix/matrix",
|
||||
};
|
||||
|
||||
httpBackend.when("GET", "/_matrix/client/versions").respond(200, {
|
||||
versions: ["r0.5.0"],
|
||||
});
|
||||
|
||||
const prom = client.requestRegisterEmailToken("bob@email", "secret", 1);
|
||||
httpBackend.when("POST", "/register/email/requestToken").check(req => {
|
||||
expect(req.data).toStrictEqual({
|
||||
email: "bob@email",
|
||||
client_secret: "secret",
|
||||
send_attempt: 1,
|
||||
});
|
||||
}).respond(200, response);
|
||||
await httpBackend.flush();
|
||||
expect(await prom).toStrictEqual(response);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function withThreadId(event, newThreadId) {
|
||||
|
||||
@@ -17,7 +17,13 @@ limitations under the License.
|
||||
import { REFERENCE_RELATION } from "matrix-events-sdk";
|
||||
|
||||
import { LocationAssetType, M_ASSET, M_LOCATION, M_TIMESTAMP } from "../../src/@types/location";
|
||||
import { makeBeaconContent, makeBeaconInfoContent } from "../../src/content-helpers";
|
||||
import { M_TOPIC } from "../../src/@types/topic";
|
||||
import {
|
||||
makeBeaconContent,
|
||||
makeBeaconInfoContent,
|
||||
makeTopicContent,
|
||||
parseTopicContent,
|
||||
} from "../../src/content-helpers";
|
||||
|
||||
describe('Beacon content helpers', () => {
|
||||
describe('makeBeaconInfoContent()', () => {
|
||||
@@ -122,3 +128,68 @@ describe('Beacon content helpers', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Topic content helpers', () => {
|
||||
describe('makeTopicContent()', () => {
|
||||
it('creates fully defined event content without html', () => {
|
||||
expect(makeTopicContent("pizza")).toEqual({
|
||||
topic: "pizza",
|
||||
[M_TOPIC.name]: [{
|
||||
body: "pizza",
|
||||
mimetype: "text/plain",
|
||||
}],
|
||||
});
|
||||
});
|
||||
|
||||
it('creates fully defined event content with html', () => {
|
||||
expect(makeTopicContent("pizza", "<b>pizza</b>")).toEqual({
|
||||
topic: "pizza",
|
||||
[M_TOPIC.name]: [{
|
||||
body: "pizza",
|
||||
mimetype: "text/plain",
|
||||
}, {
|
||||
body: "<b>pizza</b>",
|
||||
mimetype: "text/html",
|
||||
}],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseTopicContent()', () => {
|
||||
it('parses event content with plain text topic without mimetype', () => {
|
||||
expect(parseTopicContent({
|
||||
topic: "pizza",
|
||||
[M_TOPIC.name]: [{
|
||||
body: "pizza",
|
||||
}],
|
||||
})).toEqual({
|
||||
text: "pizza",
|
||||
});
|
||||
});
|
||||
|
||||
it('parses event content with plain text topic', () => {
|
||||
expect(parseTopicContent({
|
||||
topic: "pizza",
|
||||
[M_TOPIC.name]: [{
|
||||
body: "pizza",
|
||||
mimetype: "text/plain",
|
||||
}],
|
||||
})).toEqual({
|
||||
text: "pizza",
|
||||
});
|
||||
});
|
||||
|
||||
it('parses event content with html topic', () => {
|
||||
expect(parseTopicContent({
|
||||
topic: "pizza",
|
||||
[M_TOPIC.name]: [{
|
||||
body: "<b>pizza</b>",
|
||||
mimetype: "text/html",
|
||||
}],
|
||||
})).toEqual({
|
||||
text: "pizza",
|
||||
html: "<b>pizza</b>",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,10 +29,11 @@ import {
|
||||
import { MEGOLM_ALGORITHM } from "../../src/crypto/olmlib";
|
||||
import { EventStatus, MatrixEvent } from "../../src/models/event";
|
||||
import { Preset } from "../../src/@types/partials";
|
||||
import { ReceiptType } from "../../src/@types/read_receipts";
|
||||
import * as testUtils from "../test-utils/test-utils";
|
||||
import { makeBeaconInfoContent } from "../../src/content-helpers";
|
||||
import { M_BEACON_INFO } from "../../src/@types/beacon";
|
||||
import { Room } from "../../src";
|
||||
import { ContentHelpers, Room } from "../../src";
|
||||
import { makeBeaconEvent } from "../test-utils/beacon";
|
||||
|
||||
jest.useFakeTimers();
|
||||
@@ -90,7 +91,12 @@ describe("MatrixClient", function() {
|
||||
let pendingLookup = null;
|
||||
function httpReq(cb, method, path, qp, data, prefix) {
|
||||
if (path === KEEP_ALIVE_PATH && acceptKeepalives) {
|
||||
return Promise.resolve();
|
||||
return Promise.resolve({
|
||||
unstable_features: {
|
||||
"org.matrix.msc3440.stable": true,
|
||||
},
|
||||
versions: ["r0.6.0", "r0.6.1"],
|
||||
});
|
||||
}
|
||||
const next = httpLookups.shift();
|
||||
const logLine = (
|
||||
@@ -992,6 +998,46 @@ describe("MatrixClient", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("read-markers and read-receipts", () => {
|
||||
it("setRoomReadMarkers", () => {
|
||||
client.setRoomReadMarkersHttpRequest = jest.fn();
|
||||
const room = {
|
||||
hasPendingEvent: jest.fn().mockReturnValue(false),
|
||||
addLocalEchoReceipt: jest.fn(),
|
||||
};
|
||||
const rrEvent = new MatrixEvent({ event_id: "read_event_id" });
|
||||
const rpEvent = new MatrixEvent({ event_id: "read_private_event_id" });
|
||||
client.getRoom = () => room;
|
||||
|
||||
client.setRoomReadMarkers(
|
||||
"room_id",
|
||||
"read_marker_event_id",
|
||||
rrEvent,
|
||||
rpEvent,
|
||||
);
|
||||
|
||||
expect(client.setRoomReadMarkersHttpRequest).toHaveBeenCalledWith(
|
||||
"room_id",
|
||||
"read_marker_event_id",
|
||||
"read_event_id",
|
||||
"read_private_event_id",
|
||||
);
|
||||
expect(room.addLocalEchoReceipt).toHaveBeenCalledTimes(2);
|
||||
expect(room.addLocalEchoReceipt).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
client.credentials.userId,
|
||||
rrEvent,
|
||||
ReceiptType.Read,
|
||||
);
|
||||
expect(room.addLocalEchoReceipt).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
client.credentials.userId,
|
||||
rpEvent,
|
||||
ReceiptType.ReadPrivate,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("beacons", () => {
|
||||
const roomId = '!room:server.org';
|
||||
const content = makeBeaconInfoContent(100, true);
|
||||
@@ -1058,6 +1104,41 @@ describe("MatrixClient", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("setRoomTopic", () => {
|
||||
const roomId = "!foofoofoofoofoofoo:matrix.org";
|
||||
const createSendStateEventMock = (topic: string, htmlTopic?: string) => {
|
||||
return jest.fn()
|
||||
.mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
|
||||
expect(roomId).toEqual(roomId);
|
||||
expect(eventType).toEqual(EventType.RoomTopic);
|
||||
expect(content).toMatchObject(ContentHelpers.makeTopicContent(topic, htmlTopic));
|
||||
expect(stateKey).toBeUndefined();
|
||||
return Promise.resolve();
|
||||
});
|
||||
};
|
||||
|
||||
it("is called with plain text topic and sends state event", async () => {
|
||||
const sendStateEvent = createSendStateEventMock("pizza");
|
||||
client.sendStateEvent = sendStateEvent;
|
||||
await client.setRoomTopic(roomId, "pizza");
|
||||
expect(sendStateEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("is called with plain text topic and callback and sends state event", async () => {
|
||||
const sendStateEvent = createSendStateEventMock("pizza");
|
||||
client.sendStateEvent = sendStateEvent;
|
||||
await client.setRoomTopic(roomId, "pizza", () => {});
|
||||
expect(sendStateEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("is called with plain text and HTML topic and sends state event", async () => {
|
||||
const sendStateEvent = createSendStateEventMock("pizza", "<b>pizza</b>");
|
||||
client.sendStateEvent = sendStateEvent;
|
||||
await client.setRoomTopic(roomId, "pizza", "<b>pizza</b>");
|
||||
expect(sendStateEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setPassword", () => {
|
||||
const auth = { session: 'abcdef', type: 'foo' };
|
||||
const newPassword = 'newpassword';
|
||||
|
||||
@@ -57,4 +57,31 @@ describe('MatrixEvent', () => {
|
||||
expect(a.toSnapshot().isEquivalentTo(a)).toBe(true);
|
||||
expect(a.toSnapshot().isEquivalentTo(b)).toBe(false);
|
||||
});
|
||||
|
||||
it("should prune clearEvent when being redacted", () => {
|
||||
const ev = new MatrixEvent({
|
||||
type: "m.room.message",
|
||||
content: {
|
||||
body: "Test",
|
||||
},
|
||||
event_id: "$event1:server",
|
||||
});
|
||||
|
||||
expect(ev.getContent().body).toBe("Test");
|
||||
expect(ev.getWireContent().body).toBe("Test");
|
||||
ev.makeEncrypted("m.room.encrypted", { ciphertext: "xyz" }, "", "");
|
||||
expect(ev.getContent().body).toBe("Test");
|
||||
expect(ev.getWireContent().body).toBeUndefined();
|
||||
expect(ev.getWireContent().ciphertext).toBe("xyz");
|
||||
|
||||
const redaction = new MatrixEvent({
|
||||
type: "m.room.redaction",
|
||||
redacts: ev.getId(),
|
||||
});
|
||||
|
||||
ev.makeRedacted(redaction);
|
||||
expect(ev.getContent().body).toBeUndefined();
|
||||
expect(ev.getWireContent().body).toBeUndefined();
|
||||
expect(ev.getWireContent().ciphertext).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
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 { Thread } from "../../../src/models/thread";
|
||||
|
||||
describe('Thread', () => {
|
||||
describe("constructor", () => {
|
||||
it("should explode for element-web#22141 logging", () => {
|
||||
// Logging/debugging for https://github.com/vector-im/element-web/issues/22141
|
||||
expect(() => {
|
||||
new Thread("$event", undefined, {} as any); // deliberate cast to test error case
|
||||
}).toThrow("element-web#22141: A thread requires a room in order to function");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as utils from "../test-utils/test-utils";
|
||||
import { PushProcessor } from "../../src/pushprocessor";
|
||||
import { EventType } from "../../src";
|
||||
|
||||
describe('NotificationService', function() {
|
||||
const testUserId = "@ali:matrix.org";
|
||||
@@ -208,6 +209,7 @@ describe('NotificationService', function() {
|
||||
msgtype: "m.text",
|
||||
},
|
||||
});
|
||||
matrixClient.pushRules = PushProcessor.rewriteDefaultRules(matrixClient.pushRules);
|
||||
pushProcessor = new PushProcessor(matrixClient);
|
||||
});
|
||||
|
||||
@@ -295,6 +297,21 @@ describe('NotificationService', function() {
|
||||
expect(actions.tweaks.highlight).toEqual(false);
|
||||
});
|
||||
|
||||
it('should not bing on room server ACL changes', function() {
|
||||
testEvent = utils.mkEvent({
|
||||
type: EventType.RoomServerAcl,
|
||||
room: testRoomId,
|
||||
user: "@alfred:localhost",
|
||||
event: true,
|
||||
content: {},
|
||||
});
|
||||
|
||||
const actions = pushProcessor.actionsForEvent(testEvent);
|
||||
expect(actions.tweaks.highlight).toBeFalsy();
|
||||
expect(actions.tweaks.sound).toBeFalsy();
|
||||
expect(actions.notify).toBeFalsy();
|
||||
});
|
||||
|
||||
// invalid
|
||||
|
||||
it('should gracefully handle bad input.', function() {
|
||||
|
||||
+38
-4
@@ -23,6 +23,7 @@ import * as utils from "../test-utils/test-utils";
|
||||
import {
|
||||
DuplicateStrategy,
|
||||
EventStatus,
|
||||
EventTimelineSet,
|
||||
EventType,
|
||||
JoinRule,
|
||||
MatrixEvent,
|
||||
@@ -31,11 +32,12 @@ import {
|
||||
RoomEvent,
|
||||
} from "../../src";
|
||||
import { EventTimeline } from "../../src/models/event-timeline";
|
||||
import { Room } from "../../src/models/room";
|
||||
import { IWrappedReceipt, Room } from "../../src/models/room";
|
||||
import { RoomState } from "../../src/models/room-state";
|
||||
import { UNSTABLE_ELEMENT_FUNCTIONAL_USERS } from "../../src/@types/event";
|
||||
import { TestClient } from "../TestClient";
|
||||
import { emitPromise } from "../test-utils/test-utils";
|
||||
import { ReceiptType } from "../../src/@types/read_receipts";
|
||||
import { Thread, ThreadEvent } from "../../src/models/thread";
|
||||
|
||||
describe("Room", function() {
|
||||
@@ -1935,6 +1937,15 @@ describe("Room", function() {
|
||||
expect(() => room.createThread(rootEvent.getId(), rootEvent, [])).not.toThrow();
|
||||
});
|
||||
|
||||
it("creating thread from edited event should not conflate old versions of the event", () => {
|
||||
const message = mkMessage();
|
||||
const edit = mkEdit(message);
|
||||
message.makeReplaced(edit);
|
||||
|
||||
const thread = room.createThread("$000", message, [], true);
|
||||
expect(thread).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("Edits update the lastReply event", async () => {
|
||||
room.client.supportsExperimentalThreads = () => true;
|
||||
|
||||
@@ -2034,17 +2045,15 @@ describe("Room", function() {
|
||||
},
|
||||
});
|
||||
|
||||
let prom = emitPromise(room, ThreadEvent.New);
|
||||
const prom = emitPromise(room, ThreadEvent.New);
|
||||
room.addLiveEvents([threadRoot, threadResponse1, threadResponse2, threadResponse2Reaction]);
|
||||
const thread = await prom;
|
||||
|
||||
expect(thread).toHaveLength(2);
|
||||
expect(thread.replyToEvent.getId()).toBe(threadResponse2.getId());
|
||||
|
||||
prom = emitPromise(thread, ThreadEvent.Update);
|
||||
const threadResponse2ReactionRedaction = mkRedaction(threadResponse2Reaction);
|
||||
room.addLiveEvents([threadResponse2ReactionRedaction]);
|
||||
await prom;
|
||||
expect(thread).toHaveLength(2);
|
||||
expect(thread.replyToEvent.getId()).toBe(threadResponse2.getId());
|
||||
});
|
||||
@@ -2286,4 +2295,29 @@ describe("Room", function() {
|
||||
expect(responseRelations[0][1].has(threadReaction)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getEventReadUpTo()", () => {
|
||||
const client = new TestClient(userA).client;
|
||||
const room = new Room(roomId, client, userA);
|
||||
|
||||
it("handles missing receipt type", () => {
|
||||
room.getReadReceiptForUserId = (userId, ignore, receiptType) => {
|
||||
return receiptType === ReceiptType.ReadPrivate ? { eventId: "eventId" } as IWrappedReceipt : null;
|
||||
};
|
||||
|
||||
expect(room.getEventReadUpTo(userA)).toEqual("eventId");
|
||||
});
|
||||
|
||||
it("prefers older receipt", () => {
|
||||
room.getReadReceiptForUserId = (userId, ignore, receiptType) => {
|
||||
return (receiptType === ReceiptType.Read
|
||||
? { eventId: "eventId1" }
|
||||
: { eventId: "eventId2" }
|
||||
) as IWrappedReceipt;
|
||||
};
|
||||
room.getUnfilteredTimelineSet = () => ({ compareEventOrdering: (event1, event2) => 1 } as EventTimelineSet);
|
||||
|
||||
expect(room.getEventReadUpTo(userA)).toEqual("eventId1");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ReceiptType } from "../../src/@types/read_receipts";
|
||||
import { SyncAccumulator } from "../../src/sync-accumulator";
|
||||
|
||||
// The event body & unsigned object get frozen to assert that they don't get altered
|
||||
@@ -294,10 +295,13 @@ describe("SyncAccumulator", function() {
|
||||
room_id: "!foo:bar",
|
||||
content: {
|
||||
"$event1:localhost": {
|
||||
"m.read": {
|
||||
[ReceiptType.Read]: {
|
||||
"@alice:localhost": { ts: 1 },
|
||||
"@bob:localhost": { ts: 2 },
|
||||
},
|
||||
[ReceiptType.ReadPrivate]: {
|
||||
"@dan:localhost": { ts: 4 },
|
||||
},
|
||||
"some.other.receipt.type": {
|
||||
"@should_be_ignored:localhost": { key: "val" },
|
||||
},
|
||||
@@ -309,7 +313,7 @@ describe("SyncAccumulator", function() {
|
||||
room_id: "!foo:bar",
|
||||
content: {
|
||||
"$event2:localhost": {
|
||||
"m.read": {
|
||||
[ReceiptType.Read]: {
|
||||
"@bob:localhost": { ts: 2 }, // clobbers event1 receipt
|
||||
"@charlie:localhost": { ts: 3 },
|
||||
},
|
||||
@@ -337,12 +341,15 @@ describe("SyncAccumulator", function() {
|
||||
room_id: "!foo:bar",
|
||||
content: {
|
||||
"$event1:localhost": {
|
||||
"m.read": {
|
||||
[ReceiptType.Read]: {
|
||||
"@alice:localhost": { ts: 1 },
|
||||
},
|
||||
[ReceiptType.ReadPrivate]: {
|
||||
"@dan:localhost": { ts: 4 },
|
||||
},
|
||||
},
|
||||
"$event2:localhost": {
|
||||
"m.read": {
|
||||
[ReceiptType.Read]: {
|
||||
"@bob:localhost": { ts: 2 },
|
||||
"@charlie:localhost": { ts: 3 },
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { TestClient } from '../../TestClient';
|
||||
import { MatrixCall, CallErrorCode, CallEvent } from '../../../src/webrtc/call';
|
||||
import { MatrixCall, CallErrorCode, CallEvent, supportsMatrixCall } from '../../../src/webrtc/call';
|
||||
import { SDPStreamMetadataKey, SDPStreamMetadataPurpose } from '../../../src/webrtc/callEventTypes';
|
||||
import { RoomMember } from "../../../src";
|
||||
|
||||
@@ -505,4 +505,40 @@ describe('Call', function() {
|
||||
return sender?.track?.kind === "video";
|
||||
}).track.id).toBe("video_track");
|
||||
});
|
||||
|
||||
describe("supportsMatrixCall", () => {
|
||||
it("should return true when the environment is right", () => {
|
||||
expect(supportsMatrixCall()).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false if window or document are undefined", () => {
|
||||
global.window = undefined;
|
||||
expect(supportsMatrixCall()).toBe(false);
|
||||
global.window = prevWindow;
|
||||
global.document = undefined;
|
||||
expect(supportsMatrixCall()).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false if RTCPeerConnection throws", () => {
|
||||
// @ts-ignore - writing to window as we are simulating browser edge-cases
|
||||
global.window = {};
|
||||
Object.defineProperty(global.window, "RTCPeerConnection", {
|
||||
get: () => {
|
||||
throw Error("Secure mode, naaah!");
|
||||
},
|
||||
});
|
||||
expect(supportsMatrixCall()).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false if RTCPeerConnection & RTCSessionDescription " +
|
||||
"& RTCIceCandidate & mediaDevices are unavailable",
|
||||
() => {
|
||||
global.window.RTCPeerConnection = undefined;
|
||||
global.window.RTCSessionDescription = undefined;
|
||||
global.window.RTCIceCandidate = undefined;
|
||||
// @ts-ignore - writing to a read-only property as we are simulating faulty browsers
|
||||
global.navigator.mediaDevices = undefined;
|
||||
expect(supportsMatrixCall()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
Vendored
+1
@@ -23,6 +23,7 @@ declare global {
|
||||
// use `number` as the return type in all cases for global.set{Interval,Timeout},
|
||||
// so we don't accidentally use the methods on NodeJS.Timeout - they only exist in a subset of environments.
|
||||
// The overload for clear{Interval,Timeout} is resolved as expected.
|
||||
// We use `ReturnType<typeof setTimeout>` in the code to be agnostic of if this definition gets loaded.
|
||||
function setInterval(handler: TimerHandler, timeout: number, ...arguments: any[]): number;
|
||||
function setTimeout(handler: TimerHandler, timeout: number, ...arguments: any[]): number;
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
Copyright 2022 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
export enum ReceiptType {
|
||||
Read = "m.read",
|
||||
FullyRead = "m.fully_read",
|
||||
ReadPrivate = "org.matrix.msc2285.read.private"
|
||||
}
|
||||
+49
-1
@@ -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,50 @@ 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;
|
||||
}
|
||||
|
||||
export interface ITagsResponse {
|
||||
tags: {
|
||||
[tagId: string]: {
|
||||
order: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface IStatusResponse extends IPresenceOpts {
|
||||
currently_active?: boolean;
|
||||
last_active_ago?: number;
|
||||
}
|
||||
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { EitherAnd, IMessageRendering } from "matrix-events-sdk";
|
||||
|
||||
import { UnstableValue } from "../NamespacedValue";
|
||||
|
||||
/**
|
||||
* Extensible topic event type based on MSC3765
|
||||
* https://github.com/matrix-org/matrix-spec-proposals/pull/3765
|
||||
*/
|
||||
|
||||
/**
|
||||
* Eg
|
||||
* {
|
||||
* "type": "m.room.topic,
|
||||
* "state_key": "",
|
||||
* "content": {
|
||||
* "topic": "All about **pizza**",
|
||||
* "m.topic": [{
|
||||
* "body": "All about **pizza**",
|
||||
* "mimetype": "text/plain",
|
||||
* }, {
|
||||
* "body": "All about <b>pizza</b>",
|
||||
* "mimetype": "text/html",
|
||||
* }],
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* The event type for an m.topic event (in content)
|
||||
*/
|
||||
export const M_TOPIC = new UnstableValue("m.topic", "org.matrix.msc3765.topic");
|
||||
|
||||
/**
|
||||
* The event content for an m.topic event (in content)
|
||||
*/
|
||||
export type MTopicContent = IMessageRendering[];
|
||||
|
||||
/**
|
||||
* The event definition for an m.topic event (in content)
|
||||
*/
|
||||
export type MTopicEvent = EitherAnd<{ [M_TOPIC.name]: MTopicContent }, { [M_TOPIC.altName]: MTopicContent }>;
|
||||
|
||||
/**
|
||||
* The event content for an m.room.topic event
|
||||
*/
|
||||
export type MRoomTopicEventContent = { topic: string } & MTopicEvent;
|
||||
+5
-12
@@ -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;
|
||||
@@ -410,14 +403,14 @@ export class AutoDiscovery {
|
||||
* the following properties:
|
||||
* raw: The JSON object returned by the server.
|
||||
* action: One of SUCCESS, IGNORE, or FAIL_PROMPT.
|
||||
* reason: Relatively human readable description of what went wrong.
|
||||
* reason: Relatively human-readable description of what went wrong.
|
||||
* error: The actual Error, if one exists.
|
||||
* @param {string} url The URL to fetch a JSON object from.
|
||||
* @return {Promise<object>} Resolves to the returned state.
|
||||
* @private
|
||||
*/
|
||||
private static async fetchWellKnownObject(url: string): Promise<IWellKnownConfig> {
|
||||
return new Promise(function(resolve, reject) {
|
||||
private static fetchWellKnownObject(url: string): Promise<IWellKnownConfig> {
|
||||
return new Promise(function(resolve) {
|
||||
// eslint-disable-next-line
|
||||
const request = require("./matrix").getRequest();
|
||||
if (!request) throw new Error("No request library available");
|
||||
|
||||
+179
-190
@@ -32,7 +32,7 @@ import {
|
||||
MatrixEventHandlerMap,
|
||||
} from "./models/event";
|
||||
import { StubStore } from "./store/stub";
|
||||
import { CallEvent, CallEventHandlerMap, createNewMatrixCall, MatrixCall } from "./webrtc/call";
|
||||
import { CallEvent, CallEventHandlerMap, createNewMatrixCall, MatrixCall, supportsMatrixCall } from "./webrtc/call";
|
||||
import { Filter, IFilterDefinition } from "./filter";
|
||||
import { CallEventHandlerEvent, CallEventHandler, CallEventHandlerEventHandlerMap } from './webrtc/callEventHandler';
|
||||
import * as utils from './utils';
|
||||
@@ -113,6 +113,13 @@ import {
|
||||
RoomMemberEventHandlerMap,
|
||||
RoomStateEvent,
|
||||
RoomStateEventHandlerMap,
|
||||
INotificationsResponse,
|
||||
IFilterResponse,
|
||||
ITagsResponse,
|
||||
IStatusResponse,
|
||||
IPushRule,
|
||||
PushRuleActionName,
|
||||
IAuthDict,
|
||||
} from "./matrix";
|
||||
import {
|
||||
CrossSigningKey,
|
||||
@@ -132,6 +139,7 @@ import { Room } from "./models/room";
|
||||
import {
|
||||
IAddThreePidOnlyBody,
|
||||
IBindThreePidBody,
|
||||
IContextResponse,
|
||||
ICreateRoomOpts,
|
||||
IEventSearchOpts,
|
||||
IGuestAccessOpts,
|
||||
@@ -181,6 +189,7 @@ import { CryptoStore } from "./crypto/store/base";
|
||||
import { MediaHandler } from "./webrtc/mediaHandler";
|
||||
import { IRefreshTokenResponse } from "./@types/auth";
|
||||
import { TypedEventEmitter } from "./models/typed-event-emitter";
|
||||
import { ReceiptType } from "./@types/read_receipts";
|
||||
import { Thread, THREAD_RELATION_TYPE } from "./models/thread";
|
||||
import { MBeaconInfoEventContent, M_BEACON_INFO } from "./@types/beacon";
|
||||
|
||||
@@ -912,7 +921,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
protected syncLeftRoomsPromise: Promise<Room[]>;
|
||||
protected syncedLeftRooms = false;
|
||||
protected clientOpts: IStoredClientOpts;
|
||||
protected clientWellKnownIntervalID: number;
|
||||
protected clientWellKnownIntervalID: ReturnType<typeof setInterval>;
|
||||
protected canResetTimelineCallback: ResetTimelineCallback;
|
||||
|
||||
// The pushprocessor caches useful things, so keep one and re-use it
|
||||
@@ -930,7 +939,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
protected clientWellKnownPromise: Promise<IClientWellKnown>;
|
||||
protected turnServers: ITurnServer[] = [];
|
||||
protected turnServersExpiry = 0;
|
||||
protected checkTurnServersIntervalID: number;
|
||||
protected checkTurnServersIntervalID: ReturnType<typeof setInterval>;
|
||||
protected exportedOlmDeviceToImport: IOlmDevice;
|
||||
protected txnCtr = 0;
|
||||
protected mediaHandler = new MediaHandler(this);
|
||||
@@ -1004,10 +1013,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
});
|
||||
}
|
||||
|
||||
// try constructing a MatrixCall to see if we are running in an environment
|
||||
// which has WebRTC. If we are, listen for and handle m.call.* events.
|
||||
const call = createNewMatrixCall(this, undefined, undefined);
|
||||
if (call) {
|
||||
if (supportsMatrixCall()) {
|
||||
this.callEventHandler = new CallEventHandler(this);
|
||||
this.canSupportVoip = true;
|
||||
// Start listening for calls after the initial sync is done
|
||||
@@ -1078,7 +1084,13 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
// Figure out if we've read something or if it's just informational
|
||||
const content = event.getContent();
|
||||
const isSelf = Object.keys(content).filter(eid => {
|
||||
return Object.keys(content[eid]['m.read']).includes(this.getUserId());
|
||||
const read = content[eid][ReceiptType.Read];
|
||||
if (read && Object.keys(read).includes(this.getUserId())) return true;
|
||||
|
||||
const readPrivate = content[eid][ReceiptType.ReadPrivate];
|
||||
if (readPrivate && Object.keys(readPrivate).includes(this.getUserId())) return true;
|
||||
|
||||
return false;
|
||||
}).length > 0;
|
||||
|
||||
if (!isSelf) return;
|
||||
@@ -1164,6 +1176,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
const { serverSupport, stable } = await this.doesServerSupportThread();
|
||||
Thread.setServerSideSupport(serverSupport, stable);
|
||||
} catch (e) {
|
||||
// Most likely cause is that `doesServerSupportThread` returned `null` (as it
|
||||
// is allowed to do) and thus we enter "degraded mode" on threads.
|
||||
Thread.setServerSideSupport(false, true);
|
||||
}
|
||||
|
||||
@@ -1192,6 +1206,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* clean shutdown.
|
||||
*/
|
||||
public stopClient() {
|
||||
if (!this.clientRunning) return; // already stopped
|
||||
|
||||
logger.log('stopping MatrixClient');
|
||||
|
||||
this.clientRunning = false;
|
||||
@@ -1324,7 +1340,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* dehydrated device.
|
||||
* @return {Promise} A promise that resolves when the dehydrated device is stored.
|
||||
*/
|
||||
public async setDehydrationKey(
|
||||
public setDehydrationKey(
|
||||
key: Uint8Array,
|
||||
keyInfo: IDehydratedDeviceKeyInfo,
|
||||
deviceDisplayName?: string,
|
||||
@@ -1333,7 +1349,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
logger.warn('not dehydrating device if crypto is not enabled');
|
||||
return;
|
||||
}
|
||||
return await this.crypto.dehydrationManager.setKeyAndQueueDehydration(key, keyInfo, deviceDisplayName);
|
||||
return this.crypto.dehydrationManager.setKeyAndQueueDehydration(key, keyInfo, deviceDisplayName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1354,11 +1370,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
logger.warn('not dehydrating device if crypto is not enabled');
|
||||
return;
|
||||
}
|
||||
await this.crypto.dehydrationManager.setKey(
|
||||
key, keyInfo, deviceDisplayName,
|
||||
);
|
||||
// XXX: Private member access.
|
||||
return await this.crypto.dehydrationManager.dehydrateDevice();
|
||||
await this.crypto.dehydrationManager.setKey(key, keyInfo, deviceDisplayName);
|
||||
return this.crypto.dehydrationManager.dehydrateDevice();
|
||||
}
|
||||
|
||||
public async exportDevice(): Promise<IExportedDevice> {
|
||||
@@ -2382,18 +2395,15 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* The Secure Secret Storage API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @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 isSecretStored(name: string, checkKey: boolean): Promise<Record<string, ISecretStorageKeyInfo> | null> {
|
||||
public isSecretStored(name: string): Promise<Record<string, ISecretStorageKeyInfo> | null> {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
return this.crypto.isSecretStored(name, checkKey);
|
||||
return this.crypto.isSecretStored(name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2605,9 +2615,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @returns {Promise<IKeyBackupInfo | null>} Information object from API or null
|
||||
*/
|
||||
public async getKeyBackupVersion(): Promise<IKeyBackupInfo | null> {
|
||||
let res;
|
||||
let res: IKeyBackupInfo;
|
||||
try {
|
||||
res = await this.http.authedRequest(
|
||||
res = await this.http.authedRequest<IKeyBackupInfo>(
|
||||
undefined, Method.Get, "/room_keys/version", undefined, undefined,
|
||||
{ prefix: PREFIX_UNSTABLE },
|
||||
);
|
||||
@@ -2618,11 +2628,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
try {
|
||||
BackupManager.checkBackupVersion(res);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
BackupManager.checkBackupVersion(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -2726,7 +2732,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* trusted key
|
||||
*/
|
||||
public isKeyBackupKeyStored(): Promise<Record<string, ISecretStorageKeyInfo> | null> {
|
||||
return Promise.resolve(this.isSecretStored("m.megolm_backup.v1", false /* checkKey */));
|
||||
return Promise.resolve(this.isSecretStored("m.megolm_backup.v1"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3144,10 +3150,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
});
|
||||
}
|
||||
|
||||
const res = await this.http.authedRequest(
|
||||
const res = await this.http.authedRequest<IRoomsKeysResponse | IRoomKeysResponse | IKeyBackupSession>(
|
||||
undefined, Method.Get, path.path, path.queryData, undefined,
|
||||
{ prefix: PREFIX_UNSTABLE },
|
||||
) as IRoomsKeysResponse | IRoomKeysResponse | IKeyBackupSession;
|
||||
);
|
||||
|
||||
if ((res as IRoomsKeysResponse).rooms) {
|
||||
const rooms = (res as IRoomsKeysResponse).rooms;
|
||||
@@ -3387,11 +3393,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
$type: eventType,
|
||||
});
|
||||
try {
|
||||
return await this.http.authedRequest(
|
||||
undefined, Method.Get, path, undefined,
|
||||
);
|
||||
return await this.http.authedRequest(undefined, Method.Get, path);
|
||||
} catch (e) {
|
||||
if (e.data && e.data.errcode === 'M_NOT_FOUND') {
|
||||
if (e.data?.errcode === 'M_NOT_FOUND') {
|
||||
return null;
|
||||
}
|
||||
throw e;
|
||||
@@ -3553,28 +3557,45 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
/**
|
||||
* @param {string} roomId
|
||||
* @param {string} topic
|
||||
* @param {string} htmlTopic Optional.
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {Promise} Resolves: TODO
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public setRoomTopic(roomId: string, topic: string, callback?: Callback): Promise<ISendEventResponse> {
|
||||
return this.sendStateEvent(roomId, EventType.RoomTopic, { topic: topic }, undefined, callback);
|
||||
public setRoomTopic(
|
||||
roomId: string,
|
||||
topic: string,
|
||||
htmlTopic?: string,
|
||||
): Promise<ISendEventResponse>;
|
||||
public setRoomTopic(
|
||||
roomId: string,
|
||||
topic: string,
|
||||
callback?: Callback,
|
||||
): Promise<ISendEventResponse>;
|
||||
public setRoomTopic(
|
||||
roomId: string,
|
||||
topic: string,
|
||||
htmlTopicOrCallback?: string | Callback,
|
||||
): Promise<ISendEventResponse> {
|
||||
const isCallback = typeof htmlTopicOrCallback === 'function';
|
||||
const htmlTopic = isCallback ? undefined : htmlTopicOrCallback;
|
||||
const callback = isCallback ? htmlTopicOrCallback : undefined;
|
||||
const content = ContentHelpers.makeTopicContent(topic, htmlTopic);
|
||||
return this.sendStateEvent(roomId, EventType.RoomTopic, content, undefined, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} roomId
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {Promise} Resolves: TODO
|
||||
* @return {Promise} Resolves: to an object keyed by tagId with objects containing a numeric order field.
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public getRoomTags(roomId: string, callback?: Callback): Promise<unknown> { // TODO: Types
|
||||
const path = utils.encodeUri("/user/$userId/rooms/$roomId/tags/", {
|
||||
public getRoomTags(roomId: string, callback?: Callback): Promise<ITagsResponse> {
|
||||
const path = utils.encodeUri("/user/$userId/rooms/$roomId/tags", {
|
||||
$userId: this.credentials.userId,
|
||||
$roomId: roomId,
|
||||
});
|
||||
return this.http.authedRequest(
|
||||
callback, Method.Get, path, undefined,
|
||||
);
|
||||
return this.http.authedRequest(callback, Method.Get, path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3598,7 +3619,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param {string} roomId
|
||||
* @param {string} tagName name of room tag to be removed
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {Promise} Resolves: TODO
|
||||
* @return {Promise} Resolves: void
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public deleteRoomTag(roomId: string, tagName: string, callback?: Callback): Promise<void> {
|
||||
@@ -3607,7 +3628,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
$roomId: roomId,
|
||||
$tag: tagName,
|
||||
});
|
||||
return this.http.authedRequest(callback, Method.Delete, path, undefined, undefined);
|
||||
return this.http.authedRequest(callback, Method.Delete, path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3655,7 +3676,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
if (event?.getType() === EventType.RoomPowerLevels) {
|
||||
// take a copy of the content to ensure we don't corrupt
|
||||
// existing client state with a failed power level change
|
||||
content = utils.deepCopy(event.getContent()) as typeof content;
|
||||
content = utils.deepCopy(event.getContent());
|
||||
}
|
||||
content.users[userId] = powerLevel;
|
||||
const path = utils.encodeUri("/rooms/$roomId/state/m.room.power_levels", {
|
||||
@@ -4500,13 +4521,14 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
/**
|
||||
* Send a receipt.
|
||||
* @param {Event} event The event being acknowledged
|
||||
* @param {string} receiptType The kind of receipt e.g. "m.read"
|
||||
* @param {ReceiptType} receiptType The kind of receipt e.g. "m.read". Other than
|
||||
* ReceiptType.Read are experimental!
|
||||
* @param {object} body Additional content to send alongside the receipt.
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {Promise} Resolves: to an empty object {}
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public sendReceipt(event: MatrixEvent, receiptType: string, body: any, callback?: Callback): Promise<{}> {
|
||||
public sendReceipt(event: MatrixEvent, receiptType: ReceiptType, body: any, callback?: Callback): Promise<{}> {
|
||||
if (typeof (body) === 'function') {
|
||||
callback = body as any as Callback; // legacy
|
||||
body = {};
|
||||
@@ -4533,32 +4555,19 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
/**
|
||||
* Send a read receipt.
|
||||
* @param {Event} event The event that has been read.
|
||||
* @param {object} opts The options for the read receipt.
|
||||
* @param {boolean} opts.hidden True to prevent the receipt from being sent to
|
||||
* other users and homeservers. Default false (send to everyone). <b>This
|
||||
* property is unstable and may change in the future.</b>
|
||||
* @param {ReceiptType} receiptType other than ReceiptType.Read are experimental! Optional.
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {Promise} Resolves: to an empty object
|
||||
* @return {Promise} Resolves: to an empty object {}
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public async sendReadReceipt(event: MatrixEvent, opts?: { hidden?: boolean }, callback?: Callback): Promise<{}> {
|
||||
if (typeof (opts) === 'function') {
|
||||
callback = opts as any as Callback; // legacy
|
||||
opts = {};
|
||||
}
|
||||
if (!opts) opts = {};
|
||||
|
||||
public async sendReadReceipt(event: MatrixEvent, receiptType = ReceiptType.Read, callback?: Callback): Promise<{}> {
|
||||
const eventId = event.getId();
|
||||
const room = this.getRoom(event.getRoomId());
|
||||
if (room && room.hasPendingEvent(eventId)) {
|
||||
throw new Error(`Cannot set read receipt to a pending event (${eventId})`);
|
||||
}
|
||||
|
||||
const addlContent = {
|
||||
"org.matrix.msc2285.hidden": Boolean(opts.hidden),
|
||||
};
|
||||
|
||||
return this.sendReceipt(event, "m.read", addlContent, callback);
|
||||
return this.sendReceipt(event, receiptType, {}, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4571,16 +4580,15 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param {MatrixEvent} rrEvent the event tracked by the read receipt. This is here for
|
||||
* convenience because the RR and the RM are commonly updated at the same time as each
|
||||
* other. The local echo of this receipt will be done if set. Optional.
|
||||
* @param {object} opts Options for the read markers
|
||||
* @param {object} opts.hidden True to hide the receipt from other users and homeservers.
|
||||
* <b>This property is unstable and may change in the future.</b>
|
||||
* @param {MatrixEvent} rpEvent the m.read.private read receipt event for when we don't
|
||||
* want other users to see the read receipts. This is experimental. Optional.
|
||||
* @return {Promise} Resolves: the empty object, {}.
|
||||
*/
|
||||
public async setRoomReadMarkers(
|
||||
roomId: string,
|
||||
rmEventId: string,
|
||||
rrEvent: MatrixEvent,
|
||||
opts: { hidden?: boolean },
|
||||
rrEvent?: MatrixEvent,
|
||||
rpEvent?: MatrixEvent,
|
||||
): Promise<{}> {
|
||||
const room = this.getRoom(roomId);
|
||||
if (room && room.hasPendingEvent(rmEventId)) {
|
||||
@@ -4588,18 +4596,26 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
|
||||
// Add the optional RR update, do local echo like `sendReceipt`
|
||||
let rrEventId;
|
||||
let rrEventId: string;
|
||||
if (rrEvent) {
|
||||
rrEventId = rrEvent.getId();
|
||||
if (room && room.hasPendingEvent(rrEventId)) {
|
||||
if (room?.hasPendingEvent(rrEventId)) {
|
||||
throw new Error(`Cannot set read receipt to a pending event (${rrEventId})`);
|
||||
}
|
||||
if (room) {
|
||||
room.addLocalEchoReceipt(this.credentials.userId, rrEvent, "m.read");
|
||||
}
|
||||
room?.addLocalEchoReceipt(this.credentials.userId, rrEvent, ReceiptType.Read);
|
||||
}
|
||||
|
||||
return this.setRoomReadMarkersHttpRequest(roomId, rmEventId, rrEventId, opts);
|
||||
// Add the optional private RR update, do local echo like `sendReceipt`
|
||||
let rpEventId: string;
|
||||
if (rpEvent) {
|
||||
rpEventId = rpEvent.getId();
|
||||
if (room?.hasPendingEvent(rpEventId)) {
|
||||
throw new Error(`Cannot set read receipt to a pending event (${rpEventId})`);
|
||||
}
|
||||
room?.addLocalEchoReceipt(this.credentials.userId, rpEvent, ReceiptType.ReadPrivate);
|
||||
}
|
||||
|
||||
return this.setRoomReadMarkersHttpRequest(roomId, rmEventId, rrEventId, rpEventId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4858,7 +4874,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
}
|
||||
|
||||
const populationResults: Record<string, Error> = {}; // {roomId: Error}
|
||||
const populationResults: { [roomId: string]: Error } = {};
|
||||
const promises = [];
|
||||
|
||||
const doLeave = (roomId: string) => {
|
||||
@@ -4964,7 +4980,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
membership: string,
|
||||
reason?: string,
|
||||
callback?: Callback,
|
||||
): Promise<{}> {
|
||||
): Promise<{}> { // API returns an empty object
|
||||
if (utils.isFunction(reason)) {
|
||||
callback = reason as any as Callback; // legacy
|
||||
reason = undefined;
|
||||
@@ -5072,26 +5088,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
return getHttpUriForMxc(this.baseUrl, mxcUrl, width, height, resizeMethod, allowDirectLinks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new status message for the user. The message may be null/falsey
|
||||
* to clear the message.
|
||||
* @param {string} newMessage The new message to set.
|
||||
* @return {Promise} Resolves: to nothing
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public _unstable_setStatusMessage(newMessage: string): Promise<void> { // eslint-disable-line
|
||||
const type = "im.vector.user_status";
|
||||
return Promise.all(this.getRooms().map(async (room) => {
|
||||
const isJoined = room.getMyMembership() === "join";
|
||||
const looksLikeDm = room.getInvitedAndJoinedMemberCount() === 2;
|
||||
if (!isJoined || !looksLikeDm) return;
|
||||
// Check power level separately as it's a bit more expensive.
|
||||
const maySend = room.currentState.mayClientSendStateEvent(type, this);
|
||||
if (!maySend) return;
|
||||
await this.sendStateEvent(room.roomId, type, { status: newMessage }, this.getUserId());
|
||||
})).then(); // .then to fix return type
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} opts Options to apply
|
||||
* @param {string} opts.presence One of "online", "offline" or "unavailable"
|
||||
@@ -5125,12 +5121,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {Promise} Resolves: The presence state for this user.
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public getPresence(userId: string, callback?: Callback): Promise<unknown> { // TODO: Types
|
||||
public getPresence(userId: string, callback?: Callback): Promise<IStatusResponse> {
|
||||
const path = utils.encodeUri("/presence/$userId/status", {
|
||||
$userId: userId,
|
||||
});
|
||||
|
||||
return this.http.authedRequest(callback, Method.Get, path, undefined, undefined);
|
||||
return this.http.authedRequest(callback, Method.Get, path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5142,7 +5138,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* when there is no connection).
|
||||
*
|
||||
* @param {Room} room The room to get older messages in.
|
||||
* @param {Integer} limit Optional. The maximum number of previous events to
|
||||
* @param {number} limit Optional. The maximum number of previous events to
|
||||
* pull in. Default: 30.
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {Promise} Resolves: Room. If you are at the beginning
|
||||
@@ -5273,7 +5269,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
|
||||
// TODO: we should implement a backoff (as per scrollback()) to deal more nicely with HTTP errors.
|
||||
const res = await this.http.authedRequest<any>(undefined, Method.Get, path, params); // TODO types
|
||||
const res = await this.http.authedRequest<IContextResponse>(undefined, Method.Get, path, params);
|
||||
if (!res.event) {
|
||||
throw new Error("'event' not in '/context' result - homeserver too old?");
|
||||
}
|
||||
@@ -5452,8 +5448,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
params.from = token;
|
||||
}
|
||||
|
||||
promise = this.http.authedRequest<any>( // TODO types
|
||||
undefined, Method.Get, path, params, undefined,
|
||||
promise = this.http.authedRequest<INotificationsResponse>(
|
||||
undefined, Method.Get, path, params,
|
||||
).then(async (res) => {
|
||||
const token = res.next_token;
|
||||
const matrixEvents = [];
|
||||
@@ -5833,11 +5829,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
const idServerUrl = new URL(this.idBaseUrl);
|
||||
postParams.id_server = idServerUrl.host;
|
||||
|
||||
if (
|
||||
this.identityServer &&
|
||||
this.identityServer.getAccessToken &&
|
||||
await this.doesServerAcceptIdentityAccessToken()
|
||||
) {
|
||||
if (this.identityServer?.getAccessToken && await this.doesServerAcceptIdentityAccessToken()) {
|
||||
const identityAccessToken = await this.identityServer.getAccessToken();
|
||||
if (identityAccessToken) {
|
||||
postParams.id_access_token = identityAccessToken;
|
||||
@@ -5854,7 +5846,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param {string} roomId the id of the room.
|
||||
* @return {object} the rule or undefined.
|
||||
*/
|
||||
public getRoomPushRule(scope: string, roomId: string): any { // TODO: Types
|
||||
public getRoomPushRule(scope: string, roomId: string): IPushRule | undefined {
|
||||
// There can be only room-kind push rule per room
|
||||
// and its id is the room id.
|
||||
if (this.pushRules) {
|
||||
@@ -5881,15 +5873,13 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public setRoomMutePushRule(scope: string, roomId: string, mute: boolean): Promise<void> | void {
|
||||
let promise: Promise<void>;
|
||||
let hasDontNotifyRule;
|
||||
let promise: Promise<unknown>;
|
||||
let hasDontNotifyRule = false;
|
||||
|
||||
// Get the existing room-kind push rule if any
|
||||
const roomPushRule = this.getRoomPushRule(scope, roomId);
|
||||
if (roomPushRule) {
|
||||
if (0 <= roomPushRule.actions.indexOf("dont_notify")) {
|
||||
hasDontNotifyRule = true;
|
||||
}
|
||||
if (roomPushRule?.actions.includes(PushRuleActionName.DontNotify)) {
|
||||
hasDontNotifyRule = true;
|
||||
}
|
||||
|
||||
if (!mute) {
|
||||
@@ -5900,24 +5890,23 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
} else {
|
||||
if (!roomPushRule) {
|
||||
promise = this.addPushRule(scope, PushRuleKind.RoomSpecific, roomId, {
|
||||
actions: ["dont_notify"],
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
});
|
||||
} else if (!hasDontNotifyRule) {
|
||||
// Remove the existing one before setting the mute push rule
|
||||
// This is a workaround to SYN-590 (Push rule update fails)
|
||||
const deferred = utils.defer();
|
||||
this.deletePushRule(scope, PushRuleKind.RoomSpecific, roomPushRule.rule_id)
|
||||
.then(() => {
|
||||
this.addPushRule(scope, PushRuleKind.RoomSpecific, roomId, {
|
||||
actions: ["dont_notify"],
|
||||
}).then(() => {
|
||||
deferred.resolve();
|
||||
}).catch((err) => {
|
||||
deferred.reject(err);
|
||||
});
|
||||
this.deletePushRule(scope, PushRuleKind.RoomSpecific, roomPushRule.rule_id).then(() => {
|
||||
this.addPushRule(scope, PushRuleKind.RoomSpecific, roomId, {
|
||||
actions: [PushRuleActionName.DontNotify],
|
||||
}).then(() => {
|
||||
deferred.resolve();
|
||||
}).catch((err) => {
|
||||
deferred.reject(err);
|
||||
});
|
||||
}).catch((err) => {
|
||||
deferred.reject(err);
|
||||
});
|
||||
|
||||
promise = deferred.promise;
|
||||
}
|
||||
@@ -5935,7 +5924,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
// Update it even if the previous operation fails. This can help the
|
||||
// app to recover when push settings has been modifed from another client
|
||||
// app to recover when push settings has been modified from another client
|
||||
this.getPushRules().then((result) => {
|
||||
this.pushRules = result;
|
||||
reject(err);
|
||||
@@ -6109,7 +6098,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
this.syncLeftRoomsPromise = syncApi.syncLeftRooms();
|
||||
|
||||
// cleanup locks
|
||||
this.syncLeftRoomsPromise.then((res) => {
|
||||
this.syncLeftRoomsPromise.then(() => {
|
||||
logger.log("Marking success of sync left room request");
|
||||
this.syncedLeftRooms = true; // flip the bit on success
|
||||
}).finally(() => {
|
||||
@@ -6129,15 +6118,13 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
const path = utils.encodeUri("/user/$userId/filter", {
|
||||
$userId: this.credentials.userId,
|
||||
});
|
||||
// TODO types
|
||||
return this.http.authedRequest<any>(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<IFilterResponse>(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;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6162,9 +6149,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
$filterId: filterId,
|
||||
});
|
||||
|
||||
return this.http.authedRequest<IFilterDefinition>(
|
||||
undefined, Method.Get, path, undefined, undefined,
|
||||
).then((response) => {
|
||||
return this.http.authedRequest<IFilterDefinition>(undefined, Method.Get, path).then((response) => {
|
||||
// persist the filter
|
||||
const filter = Filter.fromJson(userId, filterId, response);
|
||||
this.store.storeFilter(filter);
|
||||
@@ -6559,8 +6544,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
const versions = response["versions"];
|
||||
const unstableFeatures = response["unstable_features"];
|
||||
|
||||
return (versions && versions.includes("r0.6.0"))
|
||||
|| (unstableFeatures && unstableFeatures["m.separate_add_and_bind"]);
|
||||
return versions?.includes("r0.6.0") || unstableFeatures?.["m.separate_add_and_bind"];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6602,14 +6586,17 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
} | null> {
|
||||
try {
|
||||
const hasUnstableSupport = await this.doesServerSupportUnstableFeature("org.matrix.msc3440");
|
||||
const hasStableSupport = await this.doesServerSupportUnstableFeature("org.matrix.msc3440.stable")
|
||||
|| await this.isVersionSupported("v1.3");
|
||||
const hasStableSupport = await this.doesServerSupportUnstableFeature("org.matrix.msc3440.stable");
|
||||
|
||||
// TODO: Use `this.isVersionSupported("v1.3")` for whatever spec version includes MSC3440 formally.
|
||||
|
||||
return {
|
||||
serverSupport: hasUnstableSupport || hasStableSupport,
|
||||
stable: hasStableSupport,
|
||||
};
|
||||
} catch (e) {
|
||||
// Assume server support and stability aren't available: null/no data return.
|
||||
// XXX: This should just return an object with `false` booleans instead.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -7091,9 +7078,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* it is up to the caller to either reset or destroy the MatrixClient after
|
||||
* this method succeeds.
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {Promise} Resolves: On success, the empty object
|
||||
* @param {boolean} stopClient whether to stop the client before calling /logout to prevent invalid token errors.
|
||||
* @return {Promise} Resolves: On success, the empty object {}
|
||||
*/
|
||||
public async logout(callback?: Callback): Promise<{}> {
|
||||
public async logout(callback?: Callback, stopClient = false): Promise<{}> {
|
||||
if (this.crypto?.backupManager?.getKeyBackupEnabled()) {
|
||||
try {
|
||||
while (await this.crypto.backupManager.backupPendingKeys(200) > 0);
|
||||
@@ -7104,6 +7092,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (stopClient) {
|
||||
this.stopClient();
|
||||
}
|
||||
|
||||
return this.http.authedRequest(
|
||||
callback, Method.Post, '/logout',
|
||||
);
|
||||
@@ -7203,7 +7196,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param {Object} [opts] options with optional values for the request.
|
||||
* @return {Object} the response, with chunk, prev_batch and, next_batch.
|
||||
*/
|
||||
public async fetchRelations(
|
||||
public fetchRelations(
|
||||
roomId: string,
|
||||
eventId: string,
|
||||
relationType?: RelationType | string | null,
|
||||
@@ -7231,7 +7224,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
$relationType: relationType,
|
||||
$eventType: eventType,
|
||||
});
|
||||
return await this.http.authedRequest(
|
||||
return this.http.authedRequest(
|
||||
undefined, Method.Get, path, null, null, {
|
||||
prefix: PREFIX_UNSTABLE,
|
||||
},
|
||||
@@ -7410,25 +7403,24 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param {string} rrEventId ID of the event tracked by the read receipt. This is here
|
||||
* for convenience because the RR and the RM are commonly updated at the same time as
|
||||
* each other. Optional.
|
||||
* @param {object} opts Options for the read markers.
|
||||
* @param {object} opts.hidden True to hide the read receipt from other users. <b>This
|
||||
* property is currently unstable and may change in the future.</b>
|
||||
* @param {string} rpEventId rpEvent the m.read.private read receipt event for when we
|
||||
* don't want other users to see the read receipts. This is experimental. Optional.
|
||||
* @return {Promise} Resolves: the empty object, {}.
|
||||
*/
|
||||
public setRoomReadMarkersHttpRequest(
|
||||
roomId: string,
|
||||
rmEventId: string,
|
||||
rrEventId: string,
|
||||
opts: { hidden?: boolean },
|
||||
rpEventId: string,
|
||||
): Promise<{}> {
|
||||
const path = utils.encodeUri("/rooms/$roomId/read_markers", {
|
||||
$roomId: roomId,
|
||||
});
|
||||
|
||||
const content = {
|
||||
"m.fully_read": rmEventId,
|
||||
"m.read": rrEventId,
|
||||
"org.matrix.msc2285.hidden": Boolean(opts ? opts.hidden : false),
|
||||
[ReceiptType.FullyRead]: rmEventId,
|
||||
[ReceiptType.Read]: rrEventId,
|
||||
[ReceiptType.ReadPrivate]: rpEventId,
|
||||
};
|
||||
|
||||
return this.http.authedRequest(undefined, Method.Post, path, undefined, content);
|
||||
@@ -7511,18 +7503,18 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an alias to room ID mapping. This alias must be on your local server
|
||||
* Delete an alias to room ID mapping. This alias must be on your local server,
|
||||
* and you must have sufficient access to do this operation.
|
||||
* @param {string} alias The room alias to delete.
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {Promise} Resolves: an empty object.
|
||||
* @return {Promise} Resolves: an empty object {}.
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public deleteAlias(alias: string, callback?: Callback): Promise<{}> {
|
||||
const path = utils.encodeUri("/directory/room/$alias", {
|
||||
$alias: alias,
|
||||
});
|
||||
return this.http.authedRequest(callback, Method.Delete, path, undefined, undefined);
|
||||
return this.http.authedRequest(callback, Method.Delete, path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -7590,7 +7582,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* in the public directory, or "private" to make
|
||||
* it invisible.
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {Promise} Resolves: result object
|
||||
* @return {Promise} Resolves: to an empty object {}
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public setRoomDirectoryVisibility(roomId: string, visibility: Visibility, callback?: Callback): Promise<{}> {
|
||||
@@ -7749,8 +7741,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public getThreePids(callback?: Callback): Promise<{ threepids: IThreepid[] }> {
|
||||
const path = "/account/3pid";
|
||||
return this.http.authedRequest(callback, Method.Get, path, undefined, undefined);
|
||||
return this.http.authedRequest(callback, Method.Get, "/account/3pid");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -7786,7 +7777,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @param {Object} data A object with 3PID validation data from having called
|
||||
* `account/3pid/<medium>/requestToken` on the homeserver.
|
||||
* @return {Promise} Resolves: on success
|
||||
* @return {Promise} Resolves: to an empty object {}
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public async addThreePidOnly(data: IAddThreePidOnlyBody): Promise<{}> {
|
||||
@@ -7806,7 +7797,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param {Object} data A object with 3PID validation data from having called
|
||||
* `validate/<medium>/requestToken` on the identity server. It should also
|
||||
* contain `id_server` and `id_access_token` fields as well.
|
||||
* @return {Promise} Resolves: on success
|
||||
* @return {Promise} Resolves: to an empty object {}
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public async bindThreePid(data: IBindThreePidBody): Promise<{}> {
|
||||
@@ -7867,7 +7858,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param {string} newPassword The new desired password.
|
||||
* @param {boolean} logoutDevices Should all sessions be logged out after the password change. Defaults to true.
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {Promise} Resolves: TODO
|
||||
* @return {Promise} Resolves: to an empty object {}
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public setPassword(
|
||||
@@ -7914,7 +7905,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public getDevices(): Promise<{ devices: IMyDevice[] }> {
|
||||
return this.http.authedRequest(undefined, Method.Get, "/devices", undefined, undefined);
|
||||
return this.http.authedRequest(undefined, Method.Get, "/devices");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -7927,7 +7918,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
const path = utils.encodeUri("/devices/$device_id", {
|
||||
$device_id: deviceId,
|
||||
});
|
||||
return this.http.authedRequest(undefined, Method.Get, path, undefined, undefined);
|
||||
return this.http.authedRequest(undefined, Method.Get, path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -7935,7 +7926,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @param {string} deviceId device to update
|
||||
* @param {Object} body body of request
|
||||
* @return {Promise} Resolves: result object
|
||||
* @return {Promise} Resolves: to an empty object {}
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
// eslint-disable-next-line camelcase
|
||||
@@ -7955,7 +7946,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {Promise} Resolves: result object
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public deleteDevice(deviceId: string, auth?: any): Promise<any> { // TODO: Types
|
||||
public deleteDevice(deviceId: string, auth?: IAuthDict): Promise<IAuthData | {}> {
|
||||
const path = utils.encodeUri("/devices/$device_id", {
|
||||
$device_id: deviceId,
|
||||
});
|
||||
@@ -7977,7 +7968,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {Promise} Resolves: result object
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public deleteMultipleDevices(devices: string[], auth?: any): Promise<any> { // TODO: Types
|
||||
public deleteMultipleDevices(devices: string[], auth?: IAuthDict): Promise<IAuthData | {}> {
|
||||
const body: any = { devices };
|
||||
|
||||
if (auth) {
|
||||
@@ -7996,8 +7987,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public getPushers(callback?: Callback): Promise<{ pushers: IPusher[] }> {
|
||||
const path = "/pushers";
|
||||
return this.http.authedRequest(callback, Method.Get, path, undefined, undefined);
|
||||
return this.http.authedRequest(callback, Method.Get, "/pushers");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -8038,9 +8028,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
scope: string,
|
||||
kind: PushRuleKind,
|
||||
ruleId: Exclude<string, RuleId>,
|
||||
body: any,
|
||||
body: Pick<IPushRule, "actions" | "conditions" | "pattern">,
|
||||
callback?: Callback,
|
||||
): Promise<any> { // TODO: Types
|
||||
): Promise<{}> {
|
||||
// NB. Scope not uri encoded because devices need the '/'
|
||||
const path = utils.encodeUri("/pushrules/" + scope + "/$kind/$ruleId", {
|
||||
$kind: kind,
|
||||
@@ -8062,7 +8052,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
kind: PushRuleKind,
|
||||
ruleId: Exclude<string, RuleId>,
|
||||
callback?: Callback,
|
||||
): Promise<any> { // TODO: Types
|
||||
): Promise<{}> {
|
||||
// NB. Scope not uri encoded because devices need the '/'
|
||||
const path = utils.encodeUri("/pushrules/" + scope + "/$kind/$ruleId", {
|
||||
$kind: kind,
|
||||
@@ -8078,7 +8068,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param {string} ruleId
|
||||
* @param {boolean} enabled
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {Promise} Resolves: result object
|
||||
* @return {Promise} Resolves: to an empty object {}
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public setPushRuleEnabled(
|
||||
@@ -8104,7 +8094,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param {string} ruleId
|
||||
* @param {array} actions
|
||||
* @param {module:client.callback} callback Optional.
|
||||
* @return {Promise} Resolves: result object
|
||||
* @return {Promise} Resolves: to an empty object {}
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public setPushRuleActions(
|
||||
@@ -8261,11 +8251,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
to: newToken,
|
||||
};
|
||||
|
||||
const path = "/keys/changes";
|
||||
return this.http.authedRequest(undefined, Method.Get, path, qps, undefined);
|
||||
return this.http.authedRequest(undefined, Method.Get, "/keys/changes", qps);
|
||||
}
|
||||
|
||||
public uploadDeviceSigningKeys(auth?: IAuthData, keys?: CrossSigningKeys): Promise<{}> {
|
||||
public uploadDeviceSigningKeys(auth?: IAuthData, keys?: CrossSigningKeys): Promise<{}> { // API returns empty object
|
||||
const data = Object.assign({}, keys);
|
||||
if (auth) Object.assign(data, { auth });
|
||||
return this.http.authedRequest(
|
||||
@@ -8324,7 +8313,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
* @throws Error if no identity server is set
|
||||
*/
|
||||
public async requestEmailToken(
|
||||
public requestEmailToken(
|
||||
email: string,
|
||||
clientSecret: string,
|
||||
sendAttempt: number,
|
||||
@@ -8339,7 +8328,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
next_link: nextLink,
|
||||
};
|
||||
|
||||
return await this.http.idServerRequest(
|
||||
return this.http.idServerRequest(
|
||||
callback, Method.Post, "/validate/email/requestToken",
|
||||
params, PREFIX_IDENTITY_V2, identityAccessToken,
|
||||
);
|
||||
@@ -8372,7 +8361,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
* @throws Error if no identity server is set
|
||||
*/
|
||||
public async requestMsisdnToken(
|
||||
public requestMsisdnToken(
|
||||
phoneCountry: string,
|
||||
phoneNumber: string,
|
||||
clientSecret: string,
|
||||
@@ -8389,7 +8378,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
next_link: nextLink,
|
||||
};
|
||||
|
||||
return await this.http.idServerRequest(
|
||||
return this.http.idServerRequest(
|
||||
callback, Method.Post, "/validate/msisdn/requestToken",
|
||||
params, PREFIX_IDENTITY_V2, identityAccessToken,
|
||||
);
|
||||
@@ -8414,7 +8403,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
* @throws Error if No identity server is set
|
||||
*/
|
||||
public async submitMsisdnToken(
|
||||
public submitMsisdnToken(
|
||||
sid: string,
|
||||
clientSecret: string,
|
||||
msisdnToken: string,
|
||||
@@ -8426,7 +8415,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
token: msisdnToken,
|
||||
};
|
||||
|
||||
return await this.http.idServerRequest(
|
||||
return this.http.idServerRequest(
|
||||
undefined, Method.Post, "/validate/msisdn/submitToken",
|
||||
params, PREFIX_IDENTITY_V2, identityAccessToken,
|
||||
);
|
||||
@@ -8675,7 +8664,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* content to send. Map from user_id to device_id to content object.
|
||||
* @param {string=} txnId transaction id. One will be made up if not
|
||||
* supplied.
|
||||
* @return {Promise} Resolves to the result object
|
||||
* @return {Promise} Resolves: to an empty object {}
|
||||
*/
|
||||
public sendToDevice(
|
||||
eventType: string,
|
||||
@@ -8707,7 +8696,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*/
|
||||
public getThirdpartyProtocols(): Promise<{ [protocol: string]: IProtocol }> {
|
||||
return this.http.authedRequest<Record<string, IProtocol>>(
|
||||
undefined, Method.Get, "/thirdparty/protocols", undefined, undefined,
|
||||
undefined, Method.Get, "/thirdparty/protocols",
|
||||
).then((response) => {
|
||||
// sanity check
|
||||
if (!response || typeof (response) !== 'object') {
|
||||
@@ -8733,7 +8722,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
$protocol: protocol,
|
||||
});
|
||||
|
||||
return this.http.authedRequest(undefined, Method.Get, path, params, undefined);
|
||||
return this.http.authedRequest(undefined, Method.Get, path, params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -8749,7 +8738,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
$protocol: protocol,
|
||||
});
|
||||
|
||||
return this.http.authedRequest(undefined, Method.Get, path, params, undefined);
|
||||
return this.http.authedRequest(undefined, Method.Get, path, params);
|
||||
}
|
||||
|
||||
public getTerms(serviceType: SERVICE_TYPES, baseUrl: string): Promise<any> { // TODO: Types
|
||||
@@ -8947,7 +8936,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* origin_server_ts of the closest event to the timestamp in the given
|
||||
* direction
|
||||
*/
|
||||
public async timestampToEvent(
|
||||
public timestampToEvent(
|
||||
roomId: string,
|
||||
timestamp: number,
|
||||
dir: Direction,
|
||||
@@ -8956,7 +8945,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
$roomId: roomId,
|
||||
});
|
||||
|
||||
return await this.http.authedRequest(
|
||||
return this.http.authedRequest(
|
||||
undefined,
|
||||
Method.Get,
|
||||
path,
|
||||
|
||||
+30
-1
@@ -16,7 +16,7 @@ limitations under the License.
|
||||
|
||||
/** @module ContentHelpers */
|
||||
|
||||
import { REFERENCE_RELATION } from "matrix-events-sdk";
|
||||
import { isProvided, REFERENCE_RELATION } from "matrix-events-sdk";
|
||||
|
||||
import { MBeaconEventContent, MBeaconInfoContent, MBeaconInfoEventContent } from "./@types/beacon";
|
||||
import { MsgType } from "./@types/event";
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
MAssetContent,
|
||||
LegacyLocationEventContent,
|
||||
} from "./@types/location";
|
||||
import { MRoomTopicEventContent, MTopicContent, M_TOPIC } from "./@types/topic";
|
||||
|
||||
/**
|
||||
* Generates the content for a HTML Message event
|
||||
@@ -190,6 +191,34 @@ export const parseLocationEvent = (wireEventContent: LocationEventWireContent):
|
||||
return makeLocationContent(fallbackText, geoUri, timestamp, description, assetType);
|
||||
};
|
||||
|
||||
/**
|
||||
* Topic event helpers
|
||||
*/
|
||||
export type MakeTopicContent = (
|
||||
topic: string,
|
||||
htmlTopic?: string,
|
||||
) => MRoomTopicEventContent;
|
||||
|
||||
export const makeTopicContent: MakeTopicContent = (topic, htmlTopic) => {
|
||||
const renderings = [{ body: topic, mimetype: "text/plain" }];
|
||||
if (isProvided(htmlTopic)) {
|
||||
renderings.push({ body: htmlTopic, mimetype: "text/html" });
|
||||
}
|
||||
return { topic, [M_TOPIC.name]: renderings };
|
||||
};
|
||||
|
||||
export type TopicState = {
|
||||
text: string;
|
||||
html?: string;
|
||||
};
|
||||
|
||||
export const parseTopicContent = (content: MRoomTopicEventContent): TopicState => {
|
||||
const mtopic = M_TOPIC.findIn<MTopicContent>(content);
|
||||
const text = mtopic?.find(r => !isProvided(r.mimetype) || r.mimetype === "text/plain")?.body ?? content.topic;
|
||||
const html = mtopic?.find(r => r.mimetype === "text/html")?.body;
|
||||
return { text, html };
|
||||
};
|
||||
|
||||
/**
|
||||
* Beacon event helpers
|
||||
*/
|
||||
|
||||
@@ -171,7 +171,7 @@ export class CrossSigningInfo {
|
||||
*/
|
||||
public async isStoredInSecretStorage(secretStorage: SecretStorage): Promise<Record<string, object>> {
|
||||
// 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<string, ISecretStorageKeyInfo>) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
// The time the save is scheduled for
|
||||
private savePromiseTime: number = null;
|
||||
// The timer used to delay the save
|
||||
private saveTimer: number = null;
|
||||
private saveTimer: ReturnType<typeof setTimeout> = null;
|
||||
// True if we have fetched data from the server or loaded a non-empty
|
||||
// set of device data from the store
|
||||
private hasFetched: boolean = null;
|
||||
@@ -122,7 +122,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
'readonly', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
|
||||
this.cryptoStore.getEndToEndDeviceData(txn, (deviceData) => {
|
||||
this.hasFetched = Boolean(deviceData && deviceData.devices);
|
||||
this.devices = deviceData ? deviceData.devices : {},
|
||||
this.devices = deviceData ? deviceData.devices : {};
|
||||
this.crossSigningInfo = deviceData ?
|
||||
deviceData.crossSigningInfo || {} : {};
|
||||
this.deviceTrackingStatus = deviceData ?
|
||||
@@ -190,7 +190,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
|
||||
let savePromise = this.savePromise;
|
||||
if (savePromise === null) {
|
||||
savePromise = new Promise((resolve, reject) => {
|
||||
savePromise = new Promise((resolve) => {
|
||||
this.resolveSavePromise = resolve;
|
||||
});
|
||||
this.savePromise = savePromise;
|
||||
@@ -942,7 +942,7 @@ async function updateStoredDeviceKeysForUser(
|
||||
async function storeDeviceKeys(
|
||||
olmDevice: OlmDevice,
|
||||
userStore: Record<string, DeviceInfo>,
|
||||
deviceResult: any, // TODO types
|
||||
deviceResult: IDownloadKeyResult["device_keys"]["user_id"]["device_id"],
|
||||
): Promise<boolean> {
|
||||
if (!deviceResult.keys) {
|
||||
// no keys?
|
||||
|
||||
@@ -909,12 +909,12 @@ export class OlmDevice {
|
||||
await this.cryptoStore.storeEndToEndSessionProblem(deviceKey, type, fixed);
|
||||
}
|
||||
|
||||
public async sessionMayHaveProblems(deviceKey: string, timestamp: number): Promise<IProblem> {
|
||||
return await this.cryptoStore.getEndToEndSessionProblem(deviceKey, timestamp);
|
||||
public sessionMayHaveProblems(deviceKey: string, timestamp: number): Promise<IProblem> {
|
||||
return this.cryptoStore.getEndToEndSessionProblem(deviceKey, timestamp);
|
||||
}
|
||||
|
||||
public async filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]> {
|
||||
return await this.cryptoStore.filterOutNotifiedErrorDevices(devices);
|
||||
public filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]> {
|
||||
return this.cryptoStore.filterOutNotifiedErrorDevices(devices);
|
||||
}
|
||||
|
||||
// Outbound group session
|
||||
|
||||
@@ -78,7 +78,7 @@ export enum RoomKeyRequestState {
|
||||
export class OutgoingRoomKeyRequestManager {
|
||||
// handle for the delayed call to sendOutgoingRoomKeyRequests. Non-null
|
||||
// if the callback has been set, or if it is still running.
|
||||
private sendOutgoingRoomKeyRequestsTimer: number = null;
|
||||
private sendOutgoingRoomKeyRequestsTimer: ReturnType<typeof setTimeout> = null;
|
||||
|
||||
// sanity check to ensure that we don't end up with two concurrent runs
|
||||
// of sendOutgoingRoomKeyRequests
|
||||
@@ -189,9 +189,7 @@ export class OutgoingRoomKeyRequestManager {
|
||||
// in state ROOM_KEY_REQUEST_STATES.SENT, so we must have
|
||||
// raced with another tab to mark the request cancelled.
|
||||
// Try again, to make sure the request is resent.
|
||||
return await this.queueRoomKeyRequest(
|
||||
requestBody, recipients, resend,
|
||||
);
|
||||
return this.queueRoomKeyRequest(requestBody, recipients, resend);
|
||||
}
|
||||
|
||||
// We don't want to wait for the timer, so we send it
|
||||
|
||||
@@ -329,7 +329,7 @@ export class SecretStorage {
|
||||
// encoded, since this is how a key would normally be stored.
|
||||
if (encInfo.passthrough) return encodeBase64(decryption.get_private_key());
|
||||
|
||||
return await decryption.decrypt(encInfo);
|
||||
return decryption.decrypt(encInfo);
|
||||
} finally {
|
||||
if (decryption && decryption.free) decryption.free();
|
||||
}
|
||||
@@ -339,21 +339,15 @@ 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: boolean): Promise<Record<string, ISecretStorageKeyInfo> | null> {
|
||||
public async isStored(name: string): Promise<Record<string, ISecretStorageKeyInfo> | null> {
|
||||
// check if secret exists
|
||||
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer<ISecretInfo>(name);
|
||||
if (!secretInfo) return null;
|
||||
if (!secretInfo.encrypted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (checkKey === undefined) checkKey = true;
|
||||
if (!secretInfo?.encrypted) return null;
|
||||
|
||||
const ret = {};
|
||||
|
||||
@@ -598,11 +592,11 @@ export class SecretStorage {
|
||||
|
||||
if (keys[keyId].algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
|
||||
const decryption = {
|
||||
encrypt: async function(secret: string): Promise<IEncryptedPayload> {
|
||||
return await encryptAES(secret, privateKey, name);
|
||||
encrypt: function(secret: string): Promise<IEncryptedPayload> {
|
||||
return encryptAES(secret, privateKey, name);
|
||||
},
|
||||
decrypt: async function(encInfo: IEncryptedPayload): Promise<string> {
|
||||
return await decryptAES(encInfo, privateKey, name);
|
||||
decrypt: function(encInfo: IEncryptedPayload): Promise<string> {
|
||||
return decryptAES(encInfo, privateKey, name);
|
||||
},
|
||||
};
|
||||
return [keyId, decryption];
|
||||
|
||||
+1
-1
@@ -250,7 +250,7 @@ async function deriveKeysBrowser(key: Uint8Array, name: string): Promise<[Crypto
|
||||
['sign', 'verify'],
|
||||
);
|
||||
|
||||
return await Promise.all([aesProm, hmacProm]);
|
||||
return Promise.all([aesProm, hmacProm]);
|
||||
}
|
||||
|
||||
export function encryptAES(data: string, key: Uint8Array, name: string, ivStr?: string): Promise<IEncryptedPayload> {
|
||||
|
||||
@@ -70,7 +70,7 @@ class OlmEncryption extends EncryptionAlgorithm {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this.prepPromise = this.crypto.downloadKeys(roomMembers).then((res) => {
|
||||
this.prepPromise = this.crypto.downloadKeys(roomMembers).then(() => {
|
||||
return this.crypto.ensureOlmSessionsForUsers(roomMembers);
|
||||
}).then(() => {
|
||||
this.sessionPrepared = true;
|
||||
@@ -144,7 +144,7 @@ class OlmEncryption extends EncryptionAlgorithm {
|
||||
}
|
||||
}
|
||||
|
||||
return await Promise.all(promises).then(() => encryptedContent);
|
||||
return Promise.all(promises).then(() => encryptedContent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ class OlmDecryption extends DecryptionAlgorithm {
|
||||
*
|
||||
* @return {string} payload, if decrypted successfully.
|
||||
*/
|
||||
private async decryptMessage(theirDeviceIdentityKey: string, message: IMessage): Promise<string> {
|
||||
private decryptMessage(theirDeviceIdentityKey: string, message: IMessage): Promise<string> {
|
||||
// This is a wrapper that serialises decryptions of prekey messages, because
|
||||
// otherwise we race between deciding we have no active sessions for the message
|
||||
// and creating a new one, which we can only do once because it removes the OTK.
|
||||
@@ -274,7 +274,7 @@ class OlmDecryption extends DecryptionAlgorithm {
|
||||
});
|
||||
// we want the error, but don't propagate it to the next decryption
|
||||
this.olmDevice.olmPrekeyPromise = myPromise.catch(() => {});
|
||||
return await myPromise;
|
||||
return myPromise;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-11
@@ -132,18 +132,18 @@ export class BackupManager {
|
||||
if (!Algorithm) {
|
||||
throw new Error("Unknown backup algorithm: " + info.algorithm);
|
||||
}
|
||||
if (!(typeof info.auth_data === "object")) {
|
||||
if (typeof info.auth_data !== "object") {
|
||||
throw new Error("Invalid backup data returned");
|
||||
}
|
||||
return Algorithm.checkBackupVersion(info);
|
||||
}
|
||||
|
||||
public static async makeAlgorithm(info: IKeyBackupInfo, getKey: GetKey): Promise<BackupAlgorithm> {
|
||||
public static makeAlgorithm(info: IKeyBackupInfo, getKey: GetKey): Promise<BackupAlgorithm> {
|
||||
const Algorithm = algorithmsByName[info.algorithm];
|
||||
if (!Algorithm) {
|
||||
throw new Error("Unknown backup algorithm");
|
||||
}
|
||||
return await Algorithm.init(info.auth_data, getKey);
|
||||
return Algorithm.init(info.auth_data, getKey);
|
||||
}
|
||||
|
||||
public async enableKeyBackup(info: IKeyBackupInfo): Promise<void> {
|
||||
@@ -428,7 +428,7 @@ export class BackupManager {
|
||||
// requests from different clients hitting the server all at
|
||||
// the same time when a new key is sent
|
||||
const delay = Math.random() * maxDelay;
|
||||
await sleep(delay, undefined);
|
||||
await sleep(delay);
|
||||
let numFailures = 0; // number of consecutive failures
|
||||
for (;;) {
|
||||
if (!this.algorithm) {
|
||||
@@ -462,7 +462,7 @@ export class BackupManager {
|
||||
}
|
||||
if (numFailures) {
|
||||
// exponential backoff if we have failures
|
||||
await sleep(1000 * Math.pow(2, Math.min(numFailures - 1, 4)), undefined);
|
||||
await sleep(1000 * Math.pow(2, Math.min(numFailures - 1, 4)));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -474,8 +474,8 @@ export class BackupManager {
|
||||
* Take some e2e keys waiting to be backed up and send them
|
||||
* to the backup.
|
||||
*
|
||||
* @param {integer} limit Maximum number of keys to back up
|
||||
* @returns {integer} Number of sessions backed up
|
||||
* @param {number} limit Maximum number of keys to back up
|
||||
* @returns {number} Number of sessions backed up
|
||||
*/
|
||||
public async backupPendingKeys(limit: number): Promise<number> {
|
||||
const sessions = await this.baseApis.crypto.cryptoStore.getSessionsNeedingBackup(limit);
|
||||
@@ -777,15 +777,15 @@ export class Aes256 implements BackupAlgorithm {
|
||||
|
||||
public get untrusted() { return false; }
|
||||
|
||||
async encryptSession(data: Record<string, any>): Promise<any> {
|
||||
public encryptSession(data: Record<string, any>): Promise<any> {
|
||||
const plainText: Record<string, any> = Object.assign({}, data);
|
||||
delete plainText.session_id;
|
||||
delete plainText.room_id;
|
||||
delete plainText.first_known_index;
|
||||
return await encryptAES(JSON.stringify(plainText), this.key, data.session_id);
|
||||
return encryptAES(JSON.stringify(plainText), this.key, data.session_id);
|
||||
}
|
||||
|
||||
async decryptSessions(sessions: Record<string, IKeyBackupSession>): Promise<IMegolmSessionData[]> {
|
||||
public async decryptSessions(sessions: Record<string, IKeyBackupSession>): Promise<IMegolmSessionData[]> {
|
||||
const keys: IMegolmSessionData[] = [];
|
||||
|
||||
for (const [sessionId, sessionData] of Object.entries(sessions)) {
|
||||
@@ -800,7 +800,7 @@ export class Aes256 implements BackupAlgorithm {
|
||||
return keys;
|
||||
}
|
||||
|
||||
async keyMatches(key: Uint8Array): Promise<boolean> {
|
||||
public async keyMatches(key: Uint8Array): Promise<boolean> {
|
||||
if (this.authData.mac) {
|
||||
const { mac } = await calculateKeyCheck(key, this.authData.iv);
|
||||
return this.authData.mac.replace(/=+$/g, '') === mac.replace(/=+/g, '');
|
||||
|
||||
@@ -61,11 +61,13 @@ export class DehydrationManager {
|
||||
private key: Uint8Array;
|
||||
private keyInfo: {[props: string]: any};
|
||||
private deviceDisplayName: string;
|
||||
|
||||
constructor(private readonly crypto: Crypto) {
|
||||
this.getDehydrationKeyFromCache();
|
||||
}
|
||||
async getDehydrationKeyFromCache(): Promise<void> {
|
||||
return await this.crypto.cryptoStore.doTxn(
|
||||
|
||||
public getDehydrationKeyFromCache(): Promise<void> {
|
||||
return this.crypto.cryptoStore.doTxn(
|
||||
'readonly',
|
||||
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||
(txn) => {
|
||||
@@ -93,7 +95,7 @@ export class DehydrationManager {
|
||||
}
|
||||
|
||||
/** set the key, and queue periodic dehydration to the server in the background */
|
||||
async setKeyAndQueueDehydration(
|
||||
public async setKeyAndQueueDehydration(
|
||||
key: Uint8Array, keyInfo: {[props: string]: any} = {},
|
||||
deviceDisplayName: string = undefined,
|
||||
): Promise<void> {
|
||||
@@ -104,7 +106,7 @@ export class DehydrationManager {
|
||||
}
|
||||
}
|
||||
|
||||
async setKey(
|
||||
public async setKey(
|
||||
key: Uint8Array, keyInfo: {[props: string]: any} = {},
|
||||
deviceDisplayName: string = undefined,
|
||||
): Promise<boolean> {
|
||||
@@ -148,7 +150,7 @@ export class DehydrationManager {
|
||||
}
|
||||
|
||||
/** returns the device id of the newly created dehydrated device */
|
||||
async dehydrateDevice(): Promise<string> {
|
||||
public async dehydrateDevice(): Promise<string> {
|
||||
if (this.inProgress) {
|
||||
logger.log("Dehydration already in progress -- not starting new dehydration");
|
||||
return;
|
||||
|
||||
+9
-12
@@ -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';
|
||||
@@ -309,7 +309,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
|
||||
private oneTimeKeyCount: number;
|
||||
private needsNewFallback: boolean;
|
||||
private fallbackCleanup?: number; // setTimeout ID
|
||||
private fallbackCleanup?: ReturnType<typeof setTimeout>;
|
||||
|
||||
/**
|
||||
* Cryptography bits
|
||||
@@ -402,7 +402,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
|
||||
// try to get key from app
|
||||
if (this.baseApis.cryptoCallbacks && this.baseApis.cryptoCallbacks.getBackupKey) {
|
||||
return await this.baseApis.cryptoCallbacks.getBackupKey();
|
||||
return this.baseApis.cryptoCallbacks.getBackupKey();
|
||||
}
|
||||
|
||||
throw new Error("Unable to get private key");
|
||||
@@ -690,7 +690,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
|
||||
// Cross-sign own device
|
||||
const device = this.deviceList.getStoredDevice(this.userId, this.deviceId);
|
||||
const deviceSignature = await crossSigningInfo.signDevice(this.userId, device) as ISignedKey;
|
||||
const deviceSignature = await crossSigningInfo.signDevice(this.userId, device);
|
||||
builder.addKeySignature(this.userId, this.deviceId, deviceSignature);
|
||||
|
||||
// Sign message key backup with cross-signing master key
|
||||
@@ -1076,11 +1076,8 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
return this.secretStorage.get(name);
|
||||
}
|
||||
|
||||
public isSecretStored(
|
||||
name: string,
|
||||
checkKey?: boolean,
|
||||
): Promise<Record<string, ISecretStorageKeyInfo> | null> {
|
||||
return this.secretStorage.isStored(name, checkKey);
|
||||
public isSecretStored(name: string): Promise<Record<string, ISecretStorageKeyInfo> | null> {
|
||||
return this.secretStorage.isStored(name);
|
||||
}
|
||||
|
||||
public requestSecret(name: string, devices: string[]): ISecretRequest {
|
||||
@@ -2321,8 +2318,8 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
transactionId: string = null,
|
||||
): any { // TODO types
|
||||
let request;
|
||||
): VerificationBase<any, any> {
|
||||
let request: Request;
|
||||
if (transactionId) {
|
||||
request = this.toDeviceVerificationRequests.getRequestBySenderAndTxnId(userId, transactionId);
|
||||
if (!request) {
|
||||
@@ -2890,7 +2887,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
} else {
|
||||
const content = event.getWireContent();
|
||||
const alg = this.getRoomDecryptor(event.getRoomId(), content.algorithm);
|
||||
return await alg.decryptEvent(event);
|
||||
return alg.decryptEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ interface IKey {
|
||||
iterations: number;
|
||||
}
|
||||
|
||||
export async function keyFromAuthData(authData: IAuthData, password: string): Promise<Uint8Array> {
|
||||
export function keyFromAuthData(authData: IAuthData, password: string): Promise<Uint8Array> {
|
||||
if (!global.Olm) {
|
||||
throw new Error("Olm is not available");
|
||||
}
|
||||
@@ -50,7 +50,7 @@ export async function keyFromAuthData(authData: IAuthData, password: string): Pr
|
||||
);
|
||||
}
|
||||
|
||||
return await deriveKey(
|
||||
return deriveKey(
|
||||
password, authData.private_key_salt,
|
||||
authData.private_key_iterations,
|
||||
authData.private_key_bits || DEFAULT_BITSIZE,
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import bs58 from 'bs58';
|
||||
import * as bs58 from 'bs58';
|
||||
|
||||
// picked arbitrarily but to try & avoid clashing with any bitcoin ones
|
||||
// (which are also base58 encoded, but bitcoin's involve a lot more hashing)
|
||||
|
||||
@@ -873,7 +873,7 @@ export class Backend implements CryptoStore {
|
||||
|
||||
public doTxn<T>(
|
||||
mode: Mode,
|
||||
stores: Iterable<string>,
|
||||
stores: string | string[],
|
||||
func: (txn: IDBTransaction) => T,
|
||||
log: PrefixedLogger = logger,
|
||||
): Promise<T> {
|
||||
|
||||
@@ -55,7 +55,7 @@ export class VerificationBase<
|
||||
private cancelled = false;
|
||||
private _done = false;
|
||||
private promise: Promise<void> = null;
|
||||
private transactionTimeoutTimer: number = null;
|
||||
private transactionTimeoutTimer: ReturnType<typeof setTimeout> = null;
|
||||
protected expectedEvent: string;
|
||||
private resolve: () => void;
|
||||
private reject: (e: Error | MatrixEvent) => void;
|
||||
|
||||
@@ -184,7 +184,7 @@ export class InRoomChannel implements IVerificationChannel {
|
||||
* @param {boolean} isLiveEvent whether this is an even received through sync or not
|
||||
* @returns {Promise} a promise that resolves when any requests as an answer to the passed-in event are sent.
|
||||
*/
|
||||
public async handleEvent(event: MatrixEvent, request: VerificationRequest, isLiveEvent = false): Promise<void> {
|
||||
public handleEvent(event: MatrixEvent, request: VerificationRequest, isLiveEvent = false): Promise<void> {
|
||||
// prevent processing the same event multiple times, as under
|
||||
// some circumstances Room.timeline can get emitted twice for the same event
|
||||
if (request.hasEventId(event.getId())) {
|
||||
@@ -221,8 +221,7 @@ export class InRoomChannel implements IVerificationChannel {
|
||||
const isRemoteEcho = !!event.getUnsigned().transaction_id;
|
||||
const isSentByUs = event.getSender() === this.client.getUserId();
|
||||
|
||||
return await request.handleEvent(
|
||||
type, event, isLiveEvent, isRemoteEcho, isSentByUs);
|
||||
return request.handleEvent(type, event, isLiveEvent, isRemoteEcho, isSentByUs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -95,7 +95,7 @@ export class VerificationRequest<
|
||||
private eventsByUs = new Map<string, MatrixEvent>();
|
||||
private eventsByThem = new Map<string, MatrixEvent>();
|
||||
private _observeOnly = false;
|
||||
private timeoutTimer: number = null;
|
||||
private timeoutTimer: ReturnType<typeof setTimeout> = null;
|
||||
private _accepting = false;
|
||||
private _declining = false;
|
||||
private verifierHasFinished = false;
|
||||
|
||||
+4
-4
@@ -30,7 +30,7 @@ import type { Request as _Request, CoreOptions } from "request";
|
||||
import * as callbacks from "./realtime-callbacks";
|
||||
import { IUploadOpts } from "./@types/requests";
|
||||
import { IAbortablePromise, IUsageLimit } from "./@types/partials";
|
||||
import { IDeferred } from "./utils";
|
||||
import { IDeferred, sleep } from "./utils";
|
||||
import { Callback } from "./client";
|
||||
import * as utils from "./utils";
|
||||
import { logger } from './logger';
|
||||
@@ -1055,7 +1055,7 @@ interface IErrorJson extends Partial<IUsageLimit> {
|
||||
* @prop {string} name Same as MatrixError.errcode but with a default unknown string.
|
||||
* @prop {string} message The Matrix 'error' value, e.g. "Missing token."
|
||||
* @prop {Object} data The raw Matrix error JSON used to construct this object.
|
||||
* @prop {integer} httpStatus The numeric HTTP status code given
|
||||
* @prop {number} httpStatus The numeric HTTP status code given
|
||||
*/
|
||||
export class MatrixError extends Error {
|
||||
public readonly errcode: string;
|
||||
@@ -1114,9 +1114,9 @@ export async function retryNetworkOperation<T>(maxAttempts: number, callback: ()
|
||||
const timeout = 1000 * Math.pow(2, attempts);
|
||||
logger.log(`network operation failed ${attempts} times,` +
|
||||
` retrying in ${timeout}ms...`);
|
||||
await new Promise(r => setTimeout(r, timeout));
|
||||
await sleep(timeout);
|
||||
}
|
||||
return await callback();
|
||||
return callback();
|
||||
} catch (err) {
|
||||
if (err instanceof ConnectionError) {
|
||||
attempts += 1;
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import request from "request";
|
||||
import * as request from "request";
|
||||
|
||||
import * as matrixcs from "./matrix";
|
||||
import * as utils from "./utils";
|
||||
|
||||
+32
-19
@@ -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<void> = 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -17,8 +17,7 @@ limitations under the License.
|
||||
import { MemoryCryptoStore } from "./crypto/store/memory-crypto-store";
|
||||
import { MemoryStore } from "./store/memory";
|
||||
import { MatrixScheduler } from "./scheduler";
|
||||
import { MatrixClient } from "./client";
|
||||
import { ICreateClientOpts } from "./client";
|
||||
import { MatrixClient, ICreateClientOpts } from "./client";
|
||||
import { DeviceTrustLevel } from "./crypto/CrossSigning";
|
||||
import { ISecretStorageKeyInfo } from "./crypto/api";
|
||||
|
||||
@@ -153,7 +152,7 @@ export interface ICryptoCallbacks {
|
||||
export function createClient(opts: ICreateClientOpts | string) {
|
||||
if (typeof opts === "string") {
|
||||
opts = {
|
||||
"baseUrl": opts as string,
|
||||
"baseUrl": opts,
|
||||
};
|
||||
}
|
||||
opts.request = opts.request || requestInstance;
|
||||
|
||||
@@ -54,7 +54,7 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
|
||||
public readonly roomId: string;
|
||||
private _beaconInfo: BeaconInfoState;
|
||||
private _isLive: boolean;
|
||||
private livenessWatchInterval: number;
|
||||
private livenessWatchInterval: ReturnType<typeof setInterval>;
|
||||
private _latestLocationState: BeaconLocationState | undefined;
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -28,7 +28,6 @@ import { EventType, RelationType } from "../@types/event";
|
||||
import { RoomState } from "./room-state";
|
||||
import { TypedEventEmitter } from "./typed-event-emitter";
|
||||
|
||||
// var DEBUG = false;
|
||||
const DEBUG = true;
|
||||
|
||||
let debuglog: (...args: any[]) => void;
|
||||
|
||||
+12
-16
@@ -1043,7 +1043,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
||||
* caused a change in the actual visibility of this event, either by making it
|
||||
* visible (if it was hidden), by making it hidden (if it was visible) or by
|
||||
* changing the reason (if it was hidden).
|
||||
* @param visibilityEvent event holding a hide/unhide payload, or nothing
|
||||
* @param visibilityChange event holding a hide/unhide payload, or nothing
|
||||
* if the event is being reset to its original visibility (presumably
|
||||
* by a visibility event being redacted).
|
||||
*/
|
||||
@@ -1065,9 +1065,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
||||
reason: reason,
|
||||
});
|
||||
}
|
||||
if (change) {
|
||||
this.emit(MatrixEventEvent.VisibilityChange, this, visible);
|
||||
}
|
||||
this.emit(MatrixEventEvent.VisibilityChange, this, visible);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1112,23 +1110,21 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
||||
}
|
||||
this.event.unsigned.redacted_because = redactionEvent.event as IEvent;
|
||||
|
||||
let key;
|
||||
for (key in this.event) {
|
||||
if (!this.event.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
if (!REDACT_KEEP_KEYS.has(key)) {
|
||||
for (const key in this.event) {
|
||||
if (this.event.hasOwnProperty(key) && !REDACT_KEEP_KEYS.has(key)) {
|
||||
delete this.event[key];
|
||||
}
|
||||
}
|
||||
|
||||
// If the event is encrypted prune the decrypted bits
|
||||
if (this.isEncrypted()) {
|
||||
this.clearEvent = null;
|
||||
}
|
||||
|
||||
const keeps = REDACT_KEEP_CONTENT_MAP[this.getType()] || {};
|
||||
const content = this.getContent();
|
||||
for (key in content) {
|
||||
if (!content.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
if (!keeps[key]) {
|
||||
for (const key in content) {
|
||||
if (content.hasOwnProperty(key) && !keeps[key]) {
|
||||
delete content[key];
|
||||
}
|
||||
}
|
||||
@@ -1589,7 +1585,7 @@ const REDACT_KEEP_KEYS = new Set([
|
||||
'content', 'unsigned', 'origin_server_ts',
|
||||
]);
|
||||
|
||||
// a map from event type to the .content keys we keep when an event is redacted
|
||||
// a map from state event type to the .content keys we keep when an event is redacted
|
||||
const REDACT_KEEP_CONTENT_MAP = {
|
||||
[EventType.RoomMember]: { 'membership': 1 },
|
||||
[EventType.RoomCreate]: { 'creator': 1 },
|
||||
|
||||
@@ -26,10 +26,9 @@ import { MatrixEvent, MatrixEventEvent } from "./event";
|
||||
import { MatrixClient } from "../client";
|
||||
import { GuestAccess, HistoryVisibility, IJoinRuleEventContent, JoinRule } from "../@types/partials";
|
||||
import { TypedEventEmitter } from "./typed-event-emitter";
|
||||
import { Beacon, BeaconEvent, BeaconEventHandlerMap } from "./beacon";
|
||||
import { Beacon, BeaconEvent, BeaconEventHandlerMap, getBeaconInfoIdentifier, BeaconIdentifier } from "./beacon";
|
||||
import { TypedReEmitter } from "../ReEmitter";
|
||||
import { M_BEACON, M_BEACON_INFO } from "../@types/beacon";
|
||||
import { getBeaconInfoIdentifier, BeaconIdentifier } from "./beacon";
|
||||
|
||||
// possible statuses for out-of-band member loading
|
||||
enum OobStatus {
|
||||
|
||||
+54
-25
@@ -47,6 +47,7 @@ import {
|
||||
ThreadFilterType,
|
||||
} from "./thread";
|
||||
import { TypedEventEmitter } from "./typed-event-emitter";
|
||||
import { ReceiptType } from "../@types/read_receipts";
|
||||
import { IStateEventWithRoomId } from "../@types/search";
|
||||
|
||||
// These constants are used as sane defaults when the homeserver doesn't support
|
||||
@@ -58,7 +59,7 @@ import { IStateEventWithRoomId } from "../@types/search";
|
||||
const KNOWN_SAFE_ROOM_VERSION = '9';
|
||||
const SAFE_ROOM_VERSIONS = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||
|
||||
function synthesizeReceipt(userId: string, event: MatrixEvent, receiptType: string): MatrixEvent {
|
||||
function synthesizeReceipt(userId: string, event: MatrixEvent, receiptType: ReceiptType): MatrixEvent {
|
||||
// console.log("synthesizing receipt for "+event.getId());
|
||||
return new MatrixEvent({
|
||||
content: {
|
||||
@@ -93,13 +94,13 @@ interface IReceipt {
|
||||
ts: number;
|
||||
}
|
||||
|
||||
interface IWrappedReceipt {
|
||||
export interface IWrappedReceipt {
|
||||
eventId: string;
|
||||
data: IReceipt;
|
||||
}
|
||||
|
||||
interface ICachedReceipt {
|
||||
type: string;
|
||||
type: ReceiptType;
|
||||
userId: string;
|
||||
data: IReceipt;
|
||||
}
|
||||
@@ -108,7 +109,7 @@ type ReceiptCache = {[eventId: string]: ICachedReceipt[]};
|
||||
|
||||
interface IReceiptContent {
|
||||
[eventId: string]: {
|
||||
[type: string]: {
|
||||
[key in ReceiptType]: {
|
||||
[userId: string]: IReceipt;
|
||||
};
|
||||
};
|
||||
@@ -1124,14 +1125,14 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
||||
* The aliases returned by this function may not necessarily
|
||||
* still point to this room.
|
||||
* @return {array} The room's alias as an array of strings
|
||||
* @deprecated this uses m.room.aliases events, replaced by Room::getAltAliases()
|
||||
*/
|
||||
public getAliases(): string[] {
|
||||
const aliasStrings: string[] = [];
|
||||
|
||||
const aliasEvents = this.currentState.getStateEvents(EventType.RoomAliases);
|
||||
if (aliasEvents) {
|
||||
for (let i = 0; i < aliasEvents.length; ++i) {
|
||||
const aliasEvent = aliasEvents[i];
|
||||
for (const aliasEvent of aliasEvents) {
|
||||
if (Array.isArray(aliasEvent.getContent().aliases)) {
|
||||
const filteredAliases = aliasEvent.getContent<{ aliases: string[] }>().aliases.filter(a => {
|
||||
if (typeof(a) !== "string") return false;
|
||||
@@ -1141,7 +1142,7 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
||||
// It's probably valid by here.
|
||||
return true;
|
||||
});
|
||||
Array.prototype.push.apply(aliasStrings, filteredAliases);
|
||||
aliasStrings.push(...filteredAliases);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1644,8 +1645,8 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
||||
eventsByThread[threadId]?.push(event);
|
||||
}
|
||||
|
||||
Object.entries(eventsByThread).map(([threadId, events]) => (
|
||||
this.addThreadedEvents(threadId, events, toStartOfTimeline)
|
||||
Object.entries(eventsByThread).map(([threadId, threadEvents]) => (
|
||||
this.addThreadedEvents(threadId, threadEvents, toStartOfTimeline)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1658,8 +1659,10 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
||||
if (rootEvent) {
|
||||
const tl = this.getTimelineForEvent(rootEvent.getId());
|
||||
const relatedEvents = tl?.getTimelineSet().getAllRelationsEventForEvent(rootEvent.getId());
|
||||
if (relatedEvents) {
|
||||
events = events.concat(relatedEvents);
|
||||
if (relatedEvents?.length) {
|
||||
// Include all relations of the root event, given it'll be visible in both timelines,
|
||||
// except `m.replace` as that will already be applied atop the event using `MatrixEvent::makeReplaced`
|
||||
events = events.concat(relatedEvents.filter(e => !e.isRelation(RelationType.Replace)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1792,7 +1795,7 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
||||
// Don't synthesize RR for m.room.redaction as this causes the RR to go missing.
|
||||
if (event.sender && event.getType() !== EventType.RoomRedaction) {
|
||||
this.addReceipt(synthesizeReceipt(
|
||||
event.sender.userId, event, "m.read",
|
||||
event.sender.userId, event, ReceiptType.Read,
|
||||
), true);
|
||||
|
||||
// Any live events from a user could be taken as implicit
|
||||
@@ -2143,23 +2146,23 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
||||
const threadRoots = this.findThreadRoots(events);
|
||||
const eventsByThread: { [threadId: string]: MatrixEvent[] } = {};
|
||||
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
for (const event of events) {
|
||||
// TODO: We should have a filter to say "only add state event types X Y Z to the timeline".
|
||||
this.processLiveEvent(events[i]);
|
||||
this.processLiveEvent(event);
|
||||
|
||||
const {
|
||||
shouldLiveInRoom,
|
||||
shouldLiveInThread,
|
||||
threadId,
|
||||
} = this.eventShouldLiveIn(events[i], events, threadRoots);
|
||||
} = this.eventShouldLiveIn(event, events, threadRoots);
|
||||
|
||||
if (shouldLiveInThread && !eventsByThread[threadId]) {
|
||||
eventsByThread[threadId] = [];
|
||||
}
|
||||
eventsByThread[threadId]?.push(events[i]);
|
||||
eventsByThread[threadId]?.push(event);
|
||||
|
||||
if (shouldLiveInRoom) {
|
||||
this.addLiveEvent(events[i], duplicateStrategy, fromCache);
|
||||
this.addLiveEvent(event, duplicateStrategy, fromCache);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2314,14 +2317,23 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
||||
*/
|
||||
public getUsersReadUpTo(event: MatrixEvent): string[] {
|
||||
return this.getReceiptsForEvent(event).filter(function(receipt) {
|
||||
return receipt.type === "m.read";
|
||||
return [ReceiptType.Read, ReceiptType.ReadPrivate].includes(receipt.type);
|
||||
}).map(function(receipt) {
|
||||
return receipt.userId;
|
||||
});
|
||||
}
|
||||
|
||||
public getReadReceiptForUserId(userId: string, ignoreSynthesized = false): IWrappedReceipt | null {
|
||||
const [realReceipt, syntheticReceipt] = this.receipts["m.read"]?.[userId] ?? [];
|
||||
/**
|
||||
* Gets the latest receipt for a given user in the room
|
||||
* @param userId The id of the user for which we want the receipt
|
||||
* @param ignoreSynthesized Whether to ignore synthesized receipts or not
|
||||
* @param receiptType Optional. The type of the receipt we want to get
|
||||
* @returns the latest receipts of the chosen type for the chosen user
|
||||
*/
|
||||
public getReadReceiptForUserId(
|
||||
userId: string, ignoreSynthesized = false, receiptType = ReceiptType.Read,
|
||||
): IWrappedReceipt | null {
|
||||
const [realReceipt, syntheticReceipt] = this.receipts[receiptType]?.[userId] ?? [];
|
||||
if (ignoreSynthesized) {
|
||||
return realReceipt;
|
||||
}
|
||||
@@ -2339,8 +2351,25 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
||||
* @return {String} ID of the latest event that the given user has read, or null.
|
||||
*/
|
||||
public getEventReadUpTo(userId: string, ignoreSynthesized = false): string | null {
|
||||
const readReceipt = this.getReadReceiptForUserId(userId, ignoreSynthesized);
|
||||
return readReceipt?.eventId ?? null;
|
||||
const timelineSet = this.getUnfilteredTimelineSet();
|
||||
const publicReadReceipt = this.getReadReceiptForUserId(userId, ignoreSynthesized, ReceiptType.Read);
|
||||
const privateReadReceipt = this.getReadReceiptForUserId(userId, ignoreSynthesized, ReceiptType.ReadPrivate);
|
||||
|
||||
// If we have both, compare them
|
||||
let comparison: number | undefined;
|
||||
if (publicReadReceipt?.eventId && privateReadReceipt?.eventId) {
|
||||
comparison = timelineSet.compareEventOrdering(publicReadReceipt?.eventId, privateReadReceipt?.eventId);
|
||||
}
|
||||
|
||||
// If we didn't get a comparison try to compare the ts of the receipts
|
||||
if (!comparison) comparison = publicReadReceipt?.data?.ts - privateReadReceipt?.data?.ts;
|
||||
|
||||
// The public receipt is more likely to drift out of date so the private
|
||||
// one has precedence
|
||||
if (!comparison) return privateReadReceipt?.eventId ?? publicReadReceipt?.eventId ?? null;
|
||||
|
||||
// If public read receipt is older, return the private one
|
||||
return (comparison < 0) ? privateReadReceipt?.eventId : publicReadReceipt?.eventId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2493,7 +2522,7 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
||||
}
|
||||
this.receiptCacheByEventId[eventId].push({
|
||||
userId: userId,
|
||||
type: receiptType,
|
||||
type: receiptType as ReceiptType,
|
||||
data: receipt,
|
||||
});
|
||||
});
|
||||
@@ -2506,9 +2535,9 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
|
||||
* client the fact that we've sent one.
|
||||
* @param {string} userId The user ID if the receipt sender
|
||||
* @param {MatrixEvent} e The event that is to be acknowledged
|
||||
* @param {string} receiptType The type of receipt
|
||||
* @param {ReceiptType} receiptType The type of receipt
|
||||
*/
|
||||
public addLocalEchoReceipt(userId: string, e: MatrixEvent, receiptType: string): void {
|
||||
public addLocalEchoReceipt(userId: string, e: MatrixEvent, receiptType: ReceiptType): void {
|
||||
this.addReceipt(synthesizeReceipt(userId, e, receiptType), true);
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,12 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
||||
) {
|
||||
super();
|
||||
|
||||
if (!opts?.room) {
|
||||
// Logging/debugging for https://github.com/vector-im/element-web/issues/22141
|
||||
// Hope is that we end up with a more obvious stack trace.
|
||||
throw new Error("element-web#22141: A thread requires a room in order to function");
|
||||
}
|
||||
|
||||
this.room = opts.room;
|
||||
this.client = opts.client;
|
||||
this.timelineSet = new EventTimelineSet(this.room, {
|
||||
@@ -221,9 +227,10 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
|
||||
this._currentUserParticipated = true;
|
||||
}
|
||||
|
||||
// Add all annotations and replace relations to the timeline so that the relations are processed accordingly
|
||||
if ([RelationType.Annotation, RelationType.Replace].includes(event.getRelation()?.rel_type as RelationType)) {
|
||||
this.addEventToTimeline(event, toStartOfTimeline);
|
||||
// Apply annotations and replace relations to the relations of the timeline only
|
||||
this.timelineSet.setRelationsTarget(event);
|
||||
this.timelineSet.aggregateRelations(event);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,6 @@ export enum UserEvent {
|
||||
Presence = "User.presence",
|
||||
CurrentlyActive = "User.currentlyActive",
|
||||
LastPresenceTs = "User.lastPresenceTs",
|
||||
/* @deprecated */
|
||||
_UnstableStatusMessage = "User.unstable_statusMessage",
|
||||
}
|
||||
|
||||
export type UserEventHandlerMap = {
|
||||
@@ -37,7 +35,6 @@ export type UserEventHandlerMap = {
|
||||
[UserEvent.Presence]: (event: MatrixEvent | undefined, user: User) => void;
|
||||
[UserEvent.CurrentlyActive]: (event: MatrixEvent | undefined, user: User) => void;
|
||||
[UserEvent.LastPresenceTs]: (event: MatrixEvent | undefined, user: User) => void;
|
||||
[UserEvent._UnstableStatusMessage]: (user: User) => void;
|
||||
};
|
||||
|
||||
export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
|
||||
@@ -59,8 +56,6 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
|
||||
presence: null,
|
||||
profile: null,
|
||||
};
|
||||
// eslint-disable-next-line camelcase
|
||||
public unstable_statusMessage = "";
|
||||
|
||||
/**
|
||||
* Construct a new User. A User must have an ID and can optionally have extra
|
||||
@@ -81,9 +76,6 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
|
||||
* when a user was last active.
|
||||
* @prop {Boolean} currentlyActive Whether we should consider lastActiveAgo to be
|
||||
* an approximation and that the user should be seen as active 'now'
|
||||
* @prop {string} unstable_statusMessage The status message for the user, if known. This is
|
||||
* different from the presenceStatusMsg in that this is not tied to
|
||||
* the user's presence, and should be represented differently.
|
||||
* @prop {Object} events The events describing this user.
|
||||
* @prop {MatrixEvent} events.presence The m.presence event for this user.
|
||||
*/
|
||||
@@ -219,19 +211,6 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
|
||||
public getLastActiveTs(): number {
|
||||
return this.lastPresenceTs - this.lastActiveAgo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually set the user's status message.
|
||||
* @param {MatrixEvent} event The <code>im.vector.user_status</code> event.
|
||||
* @fires module:client~MatrixClient#event:"User.unstable_statusMessage"
|
||||
*/
|
||||
// eslint-disable-next-line
|
||||
public unstable_updateStatusMessage(event: MatrixEvent): void {
|
||||
if (!event.getContent()) this.unstable_statusMessage = "";
|
||||
else this.unstable_statusMessage = event.getContent()["status"];
|
||||
this.updateModifiedTime();
|
||||
this.emit(UserEvent._UnstableStatusMessage, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+18
-26
@@ -34,6 +34,7 @@ import {
|
||||
PushRuleSet,
|
||||
TweakName,
|
||||
} from "./@types/PushRules";
|
||||
import { EventType } from "./@types/event";
|
||||
|
||||
/**
|
||||
* @module pushprocessor
|
||||
@@ -55,31 +56,6 @@ const RULEKINDS_IN_ORDER = [
|
||||
// 2. We often want to start using push rules ahead of the server supporting them,
|
||||
// and so we can put them here.
|
||||
const DEFAULT_OVERRIDE_RULES: IPushRule[] = [
|
||||
{
|
||||
// For homeservers which don't support MSC1930 yet
|
||||
rule_id: ".m.rule.tombstone",
|
||||
default: true,
|
||||
enabled: true,
|
||||
conditions: [
|
||||
{
|
||||
kind: ConditionKind.EventMatch,
|
||||
key: "type",
|
||||
pattern: "m.room.tombstone",
|
||||
},
|
||||
{
|
||||
kind: ConditionKind.EventMatch,
|
||||
key: "state_key",
|
||||
pattern: "",
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
PushRuleActionName.Notify,
|
||||
{
|
||||
set_tweak: TweakName.Highlight,
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// For homeservers which don't support MSC2153 yet
|
||||
rule_id: ".m.rule.reaction",
|
||||
@@ -96,6 +72,22 @@ const DEFAULT_OVERRIDE_RULES: IPushRule[] = [
|
||||
PushRuleActionName.DontNotify,
|
||||
],
|
||||
},
|
||||
{
|
||||
// For homeservers which don't support MSC3786 yet
|
||||
rule_id: ".org.matrix.msc3786.rule.room.server_acl",
|
||||
default: true,
|
||||
enabled: true,
|
||||
conditions: [
|
||||
{
|
||||
kind: ConditionKind.EventMatch,
|
||||
key: "type",
|
||||
pattern: EventType.RoomServerAcl,
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
PushRuleActionName.DontNotify,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export interface IActionsObject {
|
||||
@@ -300,7 +292,7 @@ export class PushProcessor {
|
||||
|
||||
const memberCount = room.currentState.getJoinedMemberCount();
|
||||
|
||||
const m = cond.is.match(/^([=<>]*)([0-9]*)$/);
|
||||
const m = cond.is.match(/^([=<>]*)(\d*)$/);
|
||||
if (!m) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+6
-2
@@ -37,7 +37,11 @@ export interface ISavedSync {
|
||||
export interface IStore {
|
||||
readonly accountData: Record<string, MatrixEvent>; // type : content
|
||||
|
||||
/** @return {Promise<bool>} whether or not the database was newly created in this session. */
|
||||
// XXX: The indexeddb store exposes a non-standard emitter for the "degraded" event
|
||||
// for when it falls back to being a memory store due to errors.
|
||||
on?: (event: string, handler: (...args: any[]) => void) => void;
|
||||
|
||||
/** @return {Promise<boolean>} whether or not the database was newly created in this session. */
|
||||
isNewlyCreated(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
@@ -105,7 +109,7 @@ export interface IStore {
|
||||
/**
|
||||
* No-op.
|
||||
* @param {Room} room
|
||||
* @param {integer} limit
|
||||
* @param {number} limit
|
||||
* @return {Array}
|
||||
*/
|
||||
scrollback(room: Room, limit: number): MatrixEvent[];
|
||||
|
||||
+1
-1
@@ -199,7 +199,7 @@ export class MemoryStore implements IStore {
|
||||
/**
|
||||
* Retrieve scrollback for this room.
|
||||
* @param {Room} room The matrix room
|
||||
* @param {integer} limit The max number of old events to retrieve.
|
||||
* @param {number} limit The max number of old events to retrieve.
|
||||
* @return {Array<Object>} An array of objects which will be at most 'limit'
|
||||
* length and at least 0. The objects are the raw event JSON.
|
||||
*/
|
||||
|
||||
+1
-1
@@ -123,7 +123,7 @@ export class StubStore implements IStore {
|
||||
/**
|
||||
* No-op.
|
||||
* @param {Room} room
|
||||
* @param {integer} limit
|
||||
* @param {number} limit
|
||||
* @return {Array}
|
||||
*/
|
||||
public scrollback(room: Room, limit: number): MatrixEvent[] {
|
||||
|
||||
+30
-13
@@ -24,6 +24,7 @@ import { deepCopy } from "./utils";
|
||||
import { IContent, IUnsigned } from "./models/event";
|
||||
import { IRoomSummary } from "./models/room-summary";
|
||||
import { EventType } from "./@types/event";
|
||||
import { ReceiptType } from "./@types/read_receipts";
|
||||
|
||||
interface IOpts {
|
||||
maxTimelineEntries?: number;
|
||||
@@ -49,7 +50,6 @@ export interface IRoomEvent extends IMinimalEvent {
|
||||
event_id: string;
|
||||
sender: string;
|
||||
origin_server_ts: number;
|
||||
unsigned?: IUnsigned;
|
||||
/** @deprecated - legacy field */
|
||||
age?: number;
|
||||
}
|
||||
@@ -157,6 +157,7 @@ interface IRoom {
|
||||
_readReceipts: {
|
||||
[userId: string]: {
|
||||
data: IMinimalEvent;
|
||||
type: ReceiptType;
|
||||
eventId: string;
|
||||
};
|
||||
};
|
||||
@@ -416,16 +417,31 @@ export class SyncAccumulator {
|
||||
// of a hassle to work with. We'll inflate this back out when
|
||||
// getJSON() is called.
|
||||
Object.keys(e.content).forEach((eventId) => {
|
||||
if (!e.content[eventId]["m.read"]) {
|
||||
if (!e.content[eventId][ReceiptType.Read] && !e.content[eventId][ReceiptType.ReadPrivate]) {
|
||||
return;
|
||||
}
|
||||
Object.keys(e.content[eventId]["m.read"]).forEach((userId) => {
|
||||
// clobber on user ID
|
||||
currentData._readReceipts[userId] = {
|
||||
data: e.content[eventId]["m.read"][userId],
|
||||
eventId: eventId,
|
||||
};
|
||||
});
|
||||
const read = e.content[eventId][ReceiptType.Read];
|
||||
if (read) {
|
||||
Object.keys(read).forEach((userId) => {
|
||||
// clobber on user ID
|
||||
currentData._readReceipts[userId] = {
|
||||
data: e.content[eventId][ReceiptType.Read][userId],
|
||||
type: ReceiptType.Read,
|
||||
eventId: eventId,
|
||||
};
|
||||
});
|
||||
}
|
||||
const readPrivate = e.content[eventId][ReceiptType.ReadPrivate];
|
||||
if (readPrivate) {
|
||||
Object.keys(readPrivate).forEach((userId) => {
|
||||
// clobber on user ID
|
||||
currentData._readReceipts[userId] = {
|
||||
data: e.content[eventId][ReceiptType.ReadPrivate][userId],
|
||||
type: ReceiptType.ReadPrivate,
|
||||
eventId: eventId,
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -552,11 +568,12 @@ export class SyncAccumulator {
|
||||
Object.keys(roomData._readReceipts).forEach((userId) => {
|
||||
const receiptData = roomData._readReceipts[userId];
|
||||
if (!receiptEvent.content[receiptData.eventId]) {
|
||||
receiptEvent.content[receiptData.eventId] = {
|
||||
"m.read": {},
|
||||
};
|
||||
receiptEvent.content[receiptData.eventId] = {};
|
||||
}
|
||||
receiptEvent.content[receiptData.eventId]["m.read"][userId] = (
|
||||
if (!receiptEvent.content[receiptData.eventId][receiptData.type]) {
|
||||
receiptEvent.content[receiptData.eventId][receiptData.type] = {};
|
||||
}
|
||||
receiptEvent.content[receiptData.eventId][receiptData.type][userId] = (
|
||||
receiptData.data
|
||||
);
|
||||
});
|
||||
|
||||
+6
-16
@@ -54,6 +54,7 @@ import { IPushRules } from "./@types/PushRules";
|
||||
import { RoomStateEvent } from "./models/room-state";
|
||||
import { RoomMemberEvent } from "./models/room-member";
|
||||
import { BeaconEvent } from "./models/beacon";
|
||||
import { IEventsResponse } from "./@types/requests";
|
||||
|
||||
const DEBUG = true;
|
||||
|
||||
@@ -152,7 +153,7 @@ export class SyncApi {
|
||||
private syncStateData: ISyncStateData = null; // additional data (eg. error object for failed sync)
|
||||
private catchingUp = false;
|
||||
private running = false;
|
||||
private keepAliveTimer: number = null;
|
||||
private keepAliveTimer: ReturnType<typeof setTimeout> = null;
|
||||
private connectionReturnedDefer: IDeferred<boolean> = null;
|
||||
private notifEvents: MatrixEvent[] = []; // accumulator of sync events in the current sync response
|
||||
private failedSyncCount = 0; // Number of consecutive failed /sync requests
|
||||
@@ -274,7 +275,7 @@ export class SyncApi {
|
||||
getFilterName(client.credentials.userId, "LEFT_ROOMS"), filter,
|
||||
).then(function(filterId) {
|
||||
qps.filter = filterId;
|
||||
return client.http.authedRequest<any>( // TODO types
|
||||
return client.http.authedRequest<ISyncResponse>(
|
||||
undefined, Method.Get, "/sync", qps as any, undefined, localTimeoutMs,
|
||||
);
|
||||
}).then(async (data) => {
|
||||
@@ -409,8 +410,7 @@ export class SyncApi {
|
||||
}
|
||||
|
||||
// FIXME: gut wrenching; hard-coded timeout values
|
||||
// TODO types
|
||||
this.client.http.authedRequest<any>(undefined, Method.Get, "/events", {
|
||||
this.client.http.authedRequest<IEventsResponse>(undefined, Method.Get, "/events", {
|
||||
room_id: peekRoom.roomId,
|
||||
timeout: String(30 * 1000),
|
||||
from: token,
|
||||
@@ -874,7 +874,7 @@ export class SyncApi {
|
||||
|
||||
private doSyncRequest(syncOptions: ISyncOptions, syncToken: string): IRequestPromise<ISyncResponse> {
|
||||
const qps = this.getSyncParams(syncOptions, syncToken);
|
||||
return this.client.http.authedRequest( // TODO types
|
||||
return this.client.http.authedRequest<ISyncResponse>(
|
||||
undefined, Method.Get, "/sync", qps as any, undefined,
|
||||
qps.timeout + BUFFER_PERIOD_MS,
|
||||
);
|
||||
@@ -1295,16 +1295,6 @@ export class SyncApi {
|
||||
if (e.isState() && e.getType() == "m.room.encryption" && this.opts.crypto) {
|
||||
await this.opts.crypto.onCryptoEvent(e);
|
||||
}
|
||||
if (e.isState() && e.getType() === "im.vector.user_status") {
|
||||
let user = client.store.getUser(e.getStateKey());
|
||||
if (user) {
|
||||
user.unstable_updateStatusMessage(e);
|
||||
} else {
|
||||
user = createNewUser(client, e.getStateKey());
|
||||
user.unstable_updateStatusMessage(e);
|
||||
client.store.storeUser(user);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await utils.promiseMapSeries(stateEvents, processRoomEvent);
|
||||
@@ -1400,7 +1390,7 @@ export class SyncApi {
|
||||
* Starts polling the connectivity check endpoint
|
||||
* @param {number} delay How long to delay until the first poll.
|
||||
* defaults to a short, randomised interval (to prevent
|
||||
* tightlooping if /versions succeeds but /sync etc. fail).
|
||||
* tight-looping if /versions succeeds but /sync etc. fail).
|
||||
* @return {promise} which resolves once the connection returns
|
||||
*/
|
||||
private startKeepAlives(delay?: number): Promise<boolean> {
|
||||
|
||||
+10
-4
@@ -23,7 +23,7 @@ limitations under the License.
|
||||
import unhomoglyph from "unhomoglyph";
|
||||
import promiseRetry from "p-retry";
|
||||
|
||||
import type NodeCrypto from "crypto";
|
||||
import type * as NodeCrypto from "crypto";
|
||||
import { MatrixEvent } from ".";
|
||||
import { M_TIMESTAMP } from "./@types/location";
|
||||
|
||||
@@ -465,7 +465,7 @@ export function defer<T = void>(): IDeferred<T> {
|
||||
|
||||
export async function promiseMapSeries<T>(
|
||||
promises: Array<T | Promise<T>>,
|
||||
fn: (t: T) => void,
|
||||
fn: (t: T) => Promise<unknown> | void, // if async/promise we don't care about the type as we only await resolution
|
||||
): Promise<void> {
|
||||
for (const o of promises) {
|
||||
await fn(await o);
|
||||
@@ -473,7 +473,7 @@ export async function promiseMapSeries<T>(
|
||||
}
|
||||
|
||||
export function promiseTry<T>(fn: () => T | Promise<T>): Promise<T> {
|
||||
return new Promise((resolve) => resolve(fn()));
|
||||
return Promise.resolve(fn());
|
||||
}
|
||||
|
||||
// Creates and awaits all promises, running no more than `chunkSize` at the same time
|
||||
@@ -676,7 +676,13 @@ export function prevString(s: string, alphabet = DEFAULT_ALPHABET): string {
|
||||
export function lexicographicCompare(a: string, b: string): number {
|
||||
// Dev note: this exists because I'm sad that you can use math operators on strings, so I've
|
||||
// hidden the operation in this function.
|
||||
return (a < b) ? -1 : ((a === b) ? 0 : 1);
|
||||
if (a < b) {
|
||||
return -1;
|
||||
} else if (a > b) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const collator = new Intl.Collator();
|
||||
|
||||
+39
-36
@@ -298,7 +298,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
// yet, null if we have but they didn't send a party ID.
|
||||
private opponentPartyId: string;
|
||||
private opponentCaps: CallCapabilities;
|
||||
private inviteTimeout: number;
|
||||
private inviteTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
// The logic of when & if a call is on hold is nontrivial and explained in is*OnHold
|
||||
// This flag represents whether we want the other party to be on hold
|
||||
@@ -322,7 +322,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
|
||||
private remoteSDPStreamMetadata: SDPStreamMetadata;
|
||||
|
||||
private callLengthInterval: number;
|
||||
private callLengthInterval: ReturnType<typeof setInterval>;
|
||||
private callLength = 0;
|
||||
|
||||
constructor(opts: CallOpts) {
|
||||
@@ -708,9 +708,9 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
|
||||
const statsReport = await this.peerConn.getStats();
|
||||
const stats = [];
|
||||
for (const item of statsReport) {
|
||||
statsReport.forEach(item => {
|
||||
stats.push(item[1]);
|
||||
}
|
||||
});
|
||||
|
||||
return stats;
|
||||
}
|
||||
@@ -988,9 +988,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
* @param {string} desktopCapturerSourceId optional id of the desktop capturer source to use
|
||||
* @returns {boolean} new screensharing state
|
||||
*/
|
||||
public async setScreensharingEnabled(
|
||||
enabled: boolean, desktopCapturerSourceId?: string,
|
||||
): Promise<boolean> {
|
||||
public async setScreensharingEnabled(enabled: boolean, desktopCapturerSourceId?: string): Promise<boolean> {
|
||||
// Skip if there is nothing to do
|
||||
if (enabled && this.isScreensharing()) {
|
||||
logger.warn(`There is already a screensharing stream - there is nothing to do!`);
|
||||
@@ -1002,7 +1000,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
|
||||
// Fallback to replaceTrack()
|
||||
if (!this.opponentSupportsSDPStreamMetadata()) {
|
||||
return await this.setScreensharingEnabledWithoutMetadataSupport(enabled, desktopCapturerSourceId);
|
||||
return this.setScreensharingEnabledWithoutMetadataSupport(enabled, desktopCapturerSourceId);
|
||||
}
|
||||
|
||||
logger.debug(`Set screensharing enabled? ${enabled}`);
|
||||
@@ -2264,6 +2262,37 @@ function setTracksEnabled(tracks: Array<MediaStreamTrack>, enabled: boolean): vo
|
||||
}
|
||||
}
|
||||
|
||||
export function supportsMatrixCall(): boolean {
|
||||
// typeof prevents Node from erroring on an undefined reference
|
||||
if (typeof(window) === 'undefined' || typeof(document) === 'undefined') {
|
||||
// NB. We don't log here as apps try to create a call object as a test for
|
||||
// whether calls are supported, so we shouldn't fill the logs up.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Firefox throws on so little as accessing the RTCPeerConnection when operating in a secure mode.
|
||||
// There's some information at https://bugzilla.mozilla.org/show_bug.cgi?id=1542616 though the concern
|
||||
// is that the browser throwing a SecurityError will brick the client creation process.
|
||||
try {
|
||||
const supported = Boolean(
|
||||
window.RTCPeerConnection || window.RTCSessionDescription ||
|
||||
window.RTCIceCandidate || navigator.mediaDevices,
|
||||
);
|
||||
if (!supported) {
|
||||
/* istanbul ignore if */ // Adds a lot of noise to test runs, so disable logging there.
|
||||
if (process.env.NODE_ENV !== "test") {
|
||||
logger.error("WebRTC is not supported in this browser / environment");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error("Exception thrown when trying to access WebRTC", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED
|
||||
* Use client.createCall()
|
||||
@@ -2277,34 +2306,8 @@ function setTracksEnabled(tracks: Array<MediaStreamTrack>, enabled: boolean): vo
|
||||
* since it's only possible to set this option on outbound calls.
|
||||
* @return {MatrixCall} the call or null if the browser doesn't support calling.
|
||||
*/
|
||||
export function createNewMatrixCall(client: any, roomId: string, options?: CallOpts): MatrixCall {
|
||||
// typeof prevents Node from erroring on an undefined reference
|
||||
if (typeof(window) === 'undefined' || typeof(document) === 'undefined') {
|
||||
// NB. We don't log here as apps try to create a call object as a test for
|
||||
// whether calls are supported, so we shouldn't fill the logs up.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Firefox throws on so little as accessing the RTCPeerConnection when operating in
|
||||
// a secure mode. There's some information at https://bugzilla.mozilla.org/show_bug.cgi?id=1542616
|
||||
// though the concern is that the browser throwing a SecurityError will brick the
|
||||
// client creation process.
|
||||
try {
|
||||
const supported = Boolean(
|
||||
window.RTCPeerConnection || window.RTCSessionDescription ||
|
||||
window.RTCIceCandidate || navigator.mediaDevices,
|
||||
);
|
||||
if (!supported) {
|
||||
// Adds a lot of noise to test runs, so disable logging there.
|
||||
if (process.env.NODE_ENV !== "test") {
|
||||
logger.error("WebRTC is not supported in this browser / environment");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error("Exception thrown when trying to access WebRTC", e);
|
||||
return null;
|
||||
}
|
||||
export function createNewMatrixCall(client: any, roomId: string, options?: CallOpts): MatrixCall | null {
|
||||
if (!supportsMatrixCall()) return null;
|
||||
|
||||
const optionsForceTURN = options ? options.forceTURN : false;
|
||||
|
||||
|
||||
@@ -72,21 +72,17 @@ export class CallEventHandler {
|
||||
this.client.decryptEventIfNeeded(event);
|
||||
}));
|
||||
|
||||
const ignoreCallIds = new Set<String>();
|
||||
const ignoreCallIds = new Set<string>();
|
||||
// inspect the buffer and mark all calls which have been answered
|
||||
// or hung up before passing them to the call event handler.
|
||||
for (const ev of this.callEventBuffer) {
|
||||
if (ev.getType() === EventType.CallAnswer ||
|
||||
ev.getType() === EventType.CallHangup) {
|
||||
if (ev.getType() === EventType.CallAnswer || ev.getType() === EventType.CallHangup) {
|
||||
ignoreCallIds.add(ev.getContent().call_id);
|
||||
}
|
||||
}
|
||||
// now loop through the buffer chronologically and inject them
|
||||
for (const e of this.callEventBuffer) {
|
||||
if (
|
||||
e.getType() === EventType.CallInvite &&
|
||||
ignoreCallIds.has(e.getContent().call_id)
|
||||
) {
|
||||
if (e.getType() === EventType.CallInvite && ignoreCallIds.has(e.getContent().call_id)) {
|
||||
// This call has previously been answered or hung up: ignore it
|
||||
continue;
|
||||
}
|
||||
@@ -191,7 +187,7 @@ export class CallEventHandler {
|
||||
}
|
||||
|
||||
// Were we trying to call that user (room)?
|
||||
let existingCall;
|
||||
let existingCall: MatrixCall;
|
||||
for (const thisCall of this.calls.values()) {
|
||||
const isCalling = [CallState.WaitLocalMedia, CallState.CreateOffer, CallState.InviteSent].includes(
|
||||
thisCall.state,
|
||||
|
||||
@@ -69,7 +69,7 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
||||
private frequencyBinCount: Float32Array;
|
||||
private speakingThreshold = SPEAKING_THRESHOLD;
|
||||
private speaking = false;
|
||||
private volumeLooperTimeout: number;
|
||||
private volumeLooperTimeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
constructor(opts: ICallFeedOpts) {
|
||||
super();
|
||||
|
||||
Reference in New Issue
Block a user