Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b6369cc2bd | |||
| e6e079f487 | |||
| 83a1e07380 | |||
| 7f3123ed65 | |||
| 5a88a6c62a | |||
| 12cecbdcf1 | |||
| c17deb0806 | |||
| 31c4f6c16b | |||
| 22271d22f8 | |||
| 9d3ac66cf8 | |||
| a4ad4ed2cf | |||
| 7fd55a61bf | |||
| 847766c114 | |||
| c8c39052a7 | |||
| 6592b2c205 | |||
| fc91153be4 | |||
| 5511a6ef8c | |||
| 19e02e894f | |||
| c54d61e158 | |||
| 44da9040f4 | |||
| 995f5bf7d7 | |||
| ad16b26247 | |||
| aaf3702c66 | |||
| 74147b9943 | |||
| 815370c5f9 | |||
| a01d8e3174 | |||
| 007b7dd242 | |||
| 77d6def1cc | |||
| b318a77ece | |||
| 1a90259326 | |||
| f46ecf970c | |||
| dd98d7eb2c | |||
| f3dc1c4ca2 | |||
| 305b83f8ea | |||
| acc488da64 | |||
| 7217f83db9 | |||
| 37ea905faa | |||
| 78de55b835 | |||
| cb410f463a | |||
| 72f9d5e6f9 | |||
| c389de98f3 | |||
| 20745dc9ac | |||
| 9410902049 | |||
| a6badbb7fa | |||
| 0b65b199e3 | |||
| c1138bc085 | |||
| c0f7df8c3b | |||
| e085609572 | |||
| 0a4f86a79e | |||
| 5d6ff6c7f9 | |||
| ffcdfe166e | |||
| e1aa7d335b | |||
| 29643e745c | |||
| 54622ce424 | |||
| ca2ae24d46 |
@@ -85,5 +85,14 @@ module.exports = {
|
||||
// We use a `logger` intermediary module
|
||||
"no-console": "error",
|
||||
},
|
||||
}, {
|
||||
files: [
|
||||
"spec/**/*.ts",
|
||||
],
|
||||
rules: {
|
||||
// We don't need super strict typing in test utilities
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-member-accessibility": "off",
|
||||
},
|
||||
}],
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
# There's a 'download artifact' action, but it hasn't been updated for the workflow_run action
|
||||
# (https://github.com/actions/download-artifact/issues/60) so instead we get this mess:
|
||||
- name: 📥 Download artifact
|
||||
uses: dawidd6/action-download-artifact@b12b127cf24433d14b4f93cee62f5465076ba82a # v2.24.1
|
||||
uses: dawidd6/action-download-artifact@e6e25ac3a2b93187502a8be1ef9e9603afc34925 # v2.24.2
|
||||
with:
|
||||
workflow: static_analysis.yml
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
@@ -66,9 +66,44 @@ jobs:
|
||||
run: "yarn run gendoc"
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docs
|
||||
path: _docs
|
||||
# We'll only use this in a workflow_run, then we're done with it
|
||||
retention-days: 1
|
||||
|
||||
tsc-strict:
|
||||
name: Typescript Strict Error Checker
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: read
|
||||
checks: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Get diff lines
|
||||
id: diff
|
||||
uses: Equip-Collaboration/diff-line-numbers@v1.0.0
|
||||
with:
|
||||
include: '["\\.tsx?$"]'
|
||||
|
||||
- name: Detecting files changed
|
||||
id: files
|
||||
uses: futuratrepadeira/changed-files@v4.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
pattern: '^.*\.tsx?$'
|
||||
|
||||
- uses: t3chguy/typescript-check-action@main
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
use-check: false
|
||||
check-fail-mode: added
|
||||
output-behaviour: annotate
|
||||
ts-extra-args: '--noImplicitAny'
|
||||
files-changed: ${{ steps.files.outputs.files_updated }}
|
||||
files-added: ${{ steps.files.outputs.files_created }}
|
||||
files-deleted: ${{ steps.files.outputs.files_deleted }}
|
||||
line-numbers: ${{ steps.diff.outputs.lineNumbers }}
|
||||
|
||||
@@ -36,7 +36,16 @@ jobs:
|
||||
id: cpu-cores
|
||||
uses: SimenB/github-actions-cpu-cores@v1
|
||||
|
||||
- name: Run tests with coverage and metrics
|
||||
if: github.ref == 'refs/heads/develop'
|
||||
run: |
|
||||
yarn coverage --ci --reporters github-actions '--reporters=<rootDir>/spec/slowReporter.js' --max-workers ${{ steps.cpu-cores.outputs.count }} ./spec/${{ matrix.specs }}
|
||||
mv coverage/lcov.info coverage/${{ matrix.node }}-${{ matrix.specs }}.lcov.info
|
||||
env:
|
||||
JEST_SONAR_UNIQUE_OUTPUT_NAME: true
|
||||
|
||||
- name: Run tests with coverage
|
||||
if: github.ref != 'refs/heads/develop'
|
||||
run: |
|
||||
yarn coverage --ci --reporters github-actions --max-workers ${{ steps.cpu-cores.outputs.count }} ./spec/${{ matrix.specs }}
|
||||
mv coverage/lcov.info coverage/${{ matrix.node }}-${{ matrix.specs }}.lcov.info
|
||||
|
||||
@@ -1,3 +1,28 @@
|
||||
Changes in [22.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v22.0.0) (2022-12-06)
|
||||
==================================================================================================
|
||||
|
||||
## 🚨 BREAKING CHANGES
|
||||
* Enable users to join group calls from multiple devices ([\#2902](https://github.com/matrix-org/matrix-js-sdk/pull/2902)).
|
||||
|
||||
## 🦖 Deprecations
|
||||
* Deprecate a function containing a typo ([\#2904](https://github.com/matrix-org/matrix-js-sdk/pull/2904)).
|
||||
|
||||
## ✨ Features
|
||||
* sliding sync: add receipts extension ([\#2912](https://github.com/matrix-org/matrix-js-sdk/pull/2912)).
|
||||
* Define a spec support policy for the js-sdk ([\#2882](https://github.com/matrix-org/matrix-js-sdk/pull/2882)).
|
||||
* Further improvements to e2ee logging ([\#2900](https://github.com/matrix-org/matrix-js-sdk/pull/2900)).
|
||||
* sliding sync: add support for typing extension ([\#2893](https://github.com/matrix-org/matrix-js-sdk/pull/2893)).
|
||||
* Improve logging on Olm session errors ([\#2885](https://github.com/matrix-org/matrix-js-sdk/pull/2885)).
|
||||
* Improve logging of e2ee messages ([\#2884](https://github.com/matrix-org/matrix-js-sdk/pull/2884)).
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
* Fix 3pid invite acceptance not working due to mxid being sent in body ([\#2907](https://github.com/matrix-org/matrix-js-sdk/pull/2907)). Fixes vector-im/element-web#23823.
|
||||
* Don't hang up calls that haven't started yet ([\#2898](https://github.com/matrix-org/matrix-js-sdk/pull/2898)).
|
||||
* Read receipt accumulation for threads ([\#2881](https://github.com/matrix-org/matrix-js-sdk/pull/2881)).
|
||||
* Make GroupCall work better with widgets ([\#2935](https://github.com/matrix-org/matrix-js-sdk/pull/2935)).
|
||||
* Fix highlight notifications increasing when total notification is zero ([\#2937](https://github.com/matrix-org/matrix-js-sdk/pull/2937)). Fixes vector-im/element-web#23885.
|
||||
* Fix synthesizeReceipt ([\#2916](https://github.com/matrix-org/matrix-js-sdk/pull/2916)). Fixes vector-im/element-web#23827 vector-im/element-web#23754 and vector-im/element-web#23847.
|
||||
|
||||
Changes in [21.2.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v21.2.0) (2022-11-22)
|
||||
==================================================================================================
|
||||
|
||||
|
||||
@@ -9,8 +9,14 @@
|
||||
Matrix Javascript SDK
|
||||
=====================
|
||||
|
||||
This is the [Matrix](https://matrix.org) Client-Server r0 SDK for
|
||||
JavaScript. This SDK can be run in a browser or in Node.js.
|
||||
This is the [Matrix](https://matrix.org) Client-Server SDK for JavaScript and TypeScript. This SDK can be run in a
|
||||
browser or in Node.js.
|
||||
|
||||
The Matrix specification is constantly evolving - while this SDK aims for maximum backwards compatibility, it only
|
||||
guarantees that a feature will be supported for at least 4 spec releases. For example, if a feature the js-sdk supports
|
||||
is removed in v1.4 then the feature is *eligible* for removal from the SDK when v1.8 is released. This SDK has no
|
||||
guarantee on implementing all features of any particular spec release, currently. This can mean that the SDK will call
|
||||
endpoints from before Matrix 1.1, for example.
|
||||
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "21.2.0",
|
||||
"version": "22.0.0",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
@@ -85,7 +85,7 @@
|
||||
"@types/content-type": "^1.1.5",
|
||||
"@types/domexception": "^4.0.0",
|
||||
"@types/jest": "^29.0.0",
|
||||
"@types/node": "16",
|
||||
"@types/node": "18",
|
||||
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
||||
"@typescript-eslint/parser": "^5.6.0",
|
||||
"allchange": "^1.0.6",
|
||||
@@ -93,18 +93,18 @@
|
||||
"babelify": "^10.0.0",
|
||||
"better-docs": "^2.4.0-beta.9",
|
||||
"browserify": "^17.0.0",
|
||||
"docdash": "^1.2.0",
|
||||
"docdash": "^2.0.0",
|
||||
"domexception": "^4.0.0",
|
||||
"eslint": "8.26.0",
|
||||
"eslint": "8.28.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-import-resolver-typescript": "^3.5.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-matrix-org": "^0.7.0",
|
||||
"eslint-plugin-unicorn": "^44.0.2",
|
||||
"eslint-plugin-matrix-org": "^0.8.0",
|
||||
"eslint-plugin-unicorn": "^45.0.0",
|
||||
"exorcist": "^2.0.0",
|
||||
"fake-indexeddb": "^4.0.0",
|
||||
"jest": "^29.0.0",
|
||||
"jest-environment-jsdom": "^28.1.3",
|
||||
"jest-environment-jsdom": "^29.0.0",
|
||||
"jest-localstorage-mock": "^2.4.6",
|
||||
"jest-mock": "^29.0.0",
|
||||
"matrix-mock-request": "^2.5.0",
|
||||
|
||||
@@ -173,7 +173,9 @@ describe("MatrixClient", function() {
|
||||
signatures: {},
|
||||
};
|
||||
|
||||
httpBackend!.when("POST", inviteSignUrl).respond(200, signature);
|
||||
httpBackend!.when("POST", inviteSignUrl).check(request => {
|
||||
expect(request.queryParams?.mxid).toEqual(client!.getUserId());
|
||||
}).respond(200, signature);
|
||||
httpBackend!.when("POST", "/join/" + encodeURIComponent(roomId)).check(request => {
|
||||
expect(request.data.third_party_signed).toEqual(signature);
|
||||
}).respond(200, { room_id: roomId });
|
||||
|
||||
@@ -709,11 +709,11 @@ describe("MatrixClient syncing", () => {
|
||||
const room = client!.getRoom(roomOne)!;
|
||||
const stateAtStart = room.getLiveTimeline().getState(EventTimeline.BACKWARDS)!;
|
||||
const startRoomNameEvent = stateAtStart.getStateEvents('m.room.name', '');
|
||||
expect(startRoomNameEvent.getContent().name).toEqual('Old room name');
|
||||
expect(startRoomNameEvent!.getContent().name).toEqual('Old room name');
|
||||
|
||||
const stateAtEnd = room.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
|
||||
const endRoomNameEvent = stateAtEnd.getStateEvents('m.room.name', '');
|
||||
expect(endRoomNameEvent.getContent().name).toEqual('A new room name');
|
||||
expect(endRoomNameEvent!.getContent().name).toEqual('A new room name');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1599,7 +1599,7 @@ describe("MatrixClient syncing", () => {
|
||||
expect(room.roomId).toBe(roomOne);
|
||||
expect(room.getMyMembership()).toBe("leave");
|
||||
expect(room.name).toBe("Room Name");
|
||||
expect(room.currentState.getStateEvents("m.room.name", "").getId()).toBe("$eventId");
|
||||
expect(room.currentState.getStateEvents("m.room.name", "")?.getId()).toBe("$eventId");
|
||||
expect(room.timeline[0].getContent().body).toBe("Message 1");
|
||||
expect(room.timeline[1].getContent().body).toBe("Message 2");
|
||||
client?.stopPeeking();
|
||||
|
||||
@@ -542,6 +542,7 @@ describe("SlidingSyncSdk", () => {
|
||||
|
||||
describe("ExtensionE2EE", () => {
|
||||
let ext: Extension;
|
||||
|
||||
beforeAll(async () => {
|
||||
await setupClient({
|
||||
withCrypto: true,
|
||||
@@ -551,18 +552,21 @@ describe("SlidingSyncSdk", () => {
|
||||
await hasSynced;
|
||||
ext = findExtension("e2ee");
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
// needed else we do some async operations in the background which can cause Jest to whine:
|
||||
// "Cannot log after tests are done. Did you forget to wait for something async in your test?"
|
||||
// Attempted to log "Saving device tracking data null"."
|
||||
client!.crypto!.stop();
|
||||
});
|
||||
|
||||
it("gets enabled on the initial request only", () => {
|
||||
expect(ext.onRequest(true)).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
expect(ext.onRequest(false)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("can update device lists", () => {
|
||||
ext.onResponse({
|
||||
device_lists: {
|
||||
@@ -572,6 +576,7 @@ describe("SlidingSyncSdk", () => {
|
||||
});
|
||||
// TODO: more assertions?
|
||||
});
|
||||
|
||||
it("can update OTK counts", () => {
|
||||
client!.crypto!.updateOneTimeKeyCount = jest.fn();
|
||||
ext.onResponse({
|
||||
@@ -588,6 +593,7 @@ describe("SlidingSyncSdk", () => {
|
||||
});
|
||||
expect(client!.crypto!.updateOneTimeKeyCount).toHaveBeenCalledWith(0);
|
||||
});
|
||||
|
||||
it("can update fallback keys", () => {
|
||||
ext.onResponse({
|
||||
device_unused_fallback_key_types: ["signed_curve25519"],
|
||||
@@ -599,8 +605,10 @@ describe("SlidingSyncSdk", () => {
|
||||
expect(client!.crypto!.getNeedsNewFallback()).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ExtensionAccountData", () => {
|
||||
let ext: Extension;
|
||||
|
||||
beforeAll(async () => {
|
||||
await setupClient();
|
||||
const hasSynced = sdk!.sync();
|
||||
@@ -608,12 +616,14 @@ describe("SlidingSyncSdk", () => {
|
||||
await hasSynced;
|
||||
ext = findExtension("account_data");
|
||||
});
|
||||
|
||||
it("gets enabled on the initial request only", () => {
|
||||
expect(ext.onRequest(true)).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
expect(ext.onRequest(false)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("processes global account data", async () => {
|
||||
const globalType = "global_test";
|
||||
const globalContent = {
|
||||
@@ -633,6 +643,7 @@ describe("SlidingSyncSdk", () => {
|
||||
expect(globalData).toBeDefined();
|
||||
expect(globalData.getContent()).toEqual(globalContent);
|
||||
});
|
||||
|
||||
it("processes rooms account data", async () => {
|
||||
const roomId = "!room:id";
|
||||
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomId, {
|
||||
@@ -667,6 +678,7 @@ describe("SlidingSyncSdk", () => {
|
||||
expect(event).toBeDefined();
|
||||
expect(event.getContent()).toEqual(roomContent);
|
||||
});
|
||||
|
||||
it("doesn't crash for unknown room account data", async () => {
|
||||
const unknownRoomId = "!unknown:id";
|
||||
const roomType = "tester";
|
||||
@@ -686,6 +698,7 @@ describe("SlidingSyncSdk", () => {
|
||||
expect(room).toBeNull();
|
||||
expect(client!.getAccountData(roomType)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("can update push rules via account data", async () => {
|
||||
const roomId = "!foo:bar";
|
||||
const pushRulesContent: IPushRules = {
|
||||
@@ -718,8 +731,10 @@ describe("SlidingSyncSdk", () => {
|
||||
expect(pushRule).toEqual(pushRulesContent.global[PushRuleKind.RoomSpecific]![0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ExtensionToDevice", () => {
|
||||
let ext: Extension;
|
||||
|
||||
beforeAll(async () => {
|
||||
await setupClient();
|
||||
const hasSynced = sdk!.sync();
|
||||
@@ -727,12 +742,14 @@ describe("SlidingSyncSdk", () => {
|
||||
await hasSynced;
|
||||
ext = findExtension("to_device");
|
||||
});
|
||||
|
||||
it("gets enabled with a limit on the initial request only", () => {
|
||||
const reqJson: any = ext.onRequest(true);
|
||||
expect(reqJson.enabled).toEqual(true);
|
||||
expect(reqJson.limit).toBeGreaterThan(0);
|
||||
expect(reqJson.since).toBeUndefined();
|
||||
});
|
||||
|
||||
it("updates the since value", async () => {
|
||||
ext.onResponse({
|
||||
next_batch: "12345",
|
||||
@@ -742,12 +759,14 @@ describe("SlidingSyncSdk", () => {
|
||||
since: "12345",
|
||||
});
|
||||
});
|
||||
|
||||
it("can handle missing fields", async () => {
|
||||
ext.onResponse({
|
||||
next_batch: "23456",
|
||||
// no events array
|
||||
});
|
||||
});
|
||||
|
||||
it("emits to-device events on the client", async () => {
|
||||
const toDeviceType = "custom_test";
|
||||
const toDeviceContent = {
|
||||
@@ -770,6 +789,7 @@ describe("SlidingSyncSdk", () => {
|
||||
});
|
||||
expect(called).toBe(true);
|
||||
});
|
||||
|
||||
it("can cancel key verification requests", async () => {
|
||||
const seen: Record<string, boolean> = {};
|
||||
client!.on(ClientEvent.ToDeviceEvent, (ev) => {
|
||||
@@ -809,4 +829,189 @@ describe("SlidingSyncSdk", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("ExtensionTyping", () => {
|
||||
let ext: Extension;
|
||||
|
||||
beforeAll(async () => {
|
||||
await setupClient();
|
||||
const hasSynced = sdk!.sync();
|
||||
await httpBackend!.flushAllExpected();
|
||||
await hasSynced;
|
||||
ext = findExtension("typing");
|
||||
});
|
||||
|
||||
it("gets enabled on the initial request only", () => {
|
||||
expect(ext.onRequest(true)).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
expect(ext.onRequest(false)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("processes typing notifications", async () => {
|
||||
const roomId = "!room:id";
|
||||
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomId, {
|
||||
name: "Room with typing",
|
||||
required_state: [],
|
||||
timeline: [
|
||||
mkOwnStateEvent(EventType.RoomCreate, { creator: selfUserId }, ""),
|
||||
mkOwnStateEvent(EventType.RoomMember, { membership: "join" }, selfUserId),
|
||||
mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""),
|
||||
mkOwnEvent(EventType.RoomMessage, { body: "hello" }),
|
||||
],
|
||||
initial: true,
|
||||
});
|
||||
const room = client!.getRoom(roomId)!;
|
||||
expect(room).toBeDefined();
|
||||
expect(room.getMember(selfUserId)?.typing).toEqual(false);
|
||||
ext.onResponse({
|
||||
rooms: {
|
||||
[roomId]: {
|
||||
type: EventType.Typing,
|
||||
content: {
|
||||
user_ids: [selfUserId],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(room.getMember(selfUserId)?.typing).toEqual(true);
|
||||
ext.onResponse({
|
||||
rooms: {
|
||||
[roomId]: {
|
||||
type: EventType.Typing,
|
||||
content: {
|
||||
user_ids: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(room.getMember(selfUserId)?.typing).toEqual(false);
|
||||
});
|
||||
|
||||
it("gracefully handles missing rooms and members when typing", async () => {
|
||||
const roomId = "!room:id";
|
||||
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomId, {
|
||||
name: "Room with typing",
|
||||
required_state: [],
|
||||
timeline: [
|
||||
mkOwnStateEvent(EventType.RoomCreate, { creator: selfUserId }, ""),
|
||||
mkOwnStateEvent(EventType.RoomMember, { membership: "join" }, selfUserId),
|
||||
mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""),
|
||||
mkOwnEvent(EventType.RoomMessage, { body: "hello" }),
|
||||
],
|
||||
initial: true,
|
||||
});
|
||||
const room = client!.getRoom(roomId)!;
|
||||
expect(room).toBeDefined();
|
||||
expect(room.getMember(selfUserId)?.typing).toEqual(false);
|
||||
ext.onResponse({
|
||||
rooms: {
|
||||
[roomId]: {
|
||||
type: EventType.Typing,
|
||||
content: {
|
||||
user_ids: ["@someone:else"],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(room.getMember(selfUserId)?.typing).toEqual(false);
|
||||
ext.onResponse({
|
||||
rooms: {
|
||||
"!something:else": {
|
||||
type: EventType.Typing,
|
||||
content: {
|
||||
user_ids: [selfUserId],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(room.getMember(selfUserId)?.typing).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ExtensionReceipts", () => {
|
||||
let ext: Extension;
|
||||
|
||||
const generateReceiptResponse = (
|
||||
userId: string, roomId: string, eventId: string, recType: string, ts: number,
|
||||
) => {
|
||||
return {
|
||||
rooms: {
|
||||
[roomId]: {
|
||||
type: EventType.Receipt,
|
||||
content: {
|
||||
[eventId]: {
|
||||
[recType]: {
|
||||
[userId]: {
|
||||
ts: ts,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
await setupClient();
|
||||
const hasSynced = sdk!.sync();
|
||||
await httpBackend!.flushAllExpected();
|
||||
await hasSynced;
|
||||
ext = findExtension("receipts");
|
||||
});
|
||||
|
||||
it("gets enabled on the initial request only", () => {
|
||||
expect(ext.onRequest(true)).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
expect(ext.onRequest(false)).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("processes receipts", async () => {
|
||||
const roomId = "!room:id";
|
||||
const alice = "@alice:alice";
|
||||
const lastEvent = mkOwnEvent(EventType.RoomMessage, { body: "hello" });
|
||||
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomId, {
|
||||
name: "Room with receipts",
|
||||
required_state: [],
|
||||
timeline: [
|
||||
mkOwnStateEvent(EventType.RoomCreate, { creator: selfUserId }, ""),
|
||||
mkOwnStateEvent(EventType.RoomMember, { membership: "join" }, selfUserId),
|
||||
mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""),
|
||||
{
|
||||
type: EventType.RoomMember,
|
||||
state_key: alice,
|
||||
content: { membership: "join" },
|
||||
sender: alice,
|
||||
origin_server_ts: Date.now(),
|
||||
event_id: "$alice",
|
||||
},
|
||||
lastEvent,
|
||||
],
|
||||
initial: true,
|
||||
});
|
||||
const room = client!.getRoom(roomId)!;
|
||||
expect(room).toBeDefined();
|
||||
expect(room.getReadReceiptForUserId(alice, true)).toBeNull();
|
||||
ext.onResponse(
|
||||
generateReceiptResponse(alice, roomId, lastEvent.event_id, "m.read", 1234567),
|
||||
);
|
||||
const receipt = room.getReadReceiptForUserId(alice);
|
||||
expect(receipt).toBeDefined();
|
||||
expect(receipt?.eventId).toEqual(lastEvent.event_id);
|
||||
expect(receipt?.data.ts).toEqual(1234567);
|
||||
expect(receipt?.data.thread_id).toBeFalsy();
|
||||
});
|
||||
|
||||
it("gracefully handles missing rooms when receiving receipts", async () => {
|
||||
const roomId = "!room:id";
|
||||
const alice = "@alice:alice";
|
||||
const eventId = "$something";
|
||||
ext.onResponse(
|
||||
generateReceiptResponse(alice, roomId, eventId, "m.read", 1234567),
|
||||
);
|
||||
// we expect it not to crash
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
class JestSlowTestReporter {
|
||||
constructor(globalConfig, options) {
|
||||
this._globalConfig = globalConfig;
|
||||
this._options = options;
|
||||
this._slowTests = [];
|
||||
this._slowTestSuites = [];
|
||||
}
|
||||
|
||||
onRunComplete() {
|
||||
const displayResult = (result, isTestSuite) => {
|
||||
if (!isTestSuite) console.log();
|
||||
|
||||
result.sort((a, b) => b.duration - a.duration);
|
||||
const rootPathRegex = new RegExp(`^${process.cwd()}`);
|
||||
const slowestTests = result.slice(0, this._options.numTests || 10);
|
||||
const slowTestTime = this._slowTestTime(slowestTests);
|
||||
const allTestTime = this._allTestTime(result);
|
||||
const percentTime = (slowTestTime / allTestTime) * 100;
|
||||
|
||||
if (isTestSuite) {
|
||||
console.log(
|
||||
`Top ${slowestTests.length} slowest test suites (${slowTestTime / 1000} seconds,` +
|
||||
` ${percentTime.toFixed(1)}% of total time):`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
`Top ${slowestTests.length} slowest tests (${slowTestTime / 1000} seconds,` +
|
||||
` ${percentTime.toFixed(1)}% of total time):`,
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < slowestTests.length; i++) {
|
||||
const duration = slowestTests[i].duration;
|
||||
const filePath = slowestTests[i].filePath.replace(rootPathRegex, '.');
|
||||
|
||||
if (isTestSuite) {
|
||||
console.log(` ${duration / 1000} seconds ${filePath}`);
|
||||
} else {
|
||||
const fullName = slowestTests[i].fullName;
|
||||
console.log(` ${fullName}`);
|
||||
console.log(` ${duration / 1000} seconds ${filePath}`);
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
};
|
||||
|
||||
displayResult(this._slowTests);
|
||||
displayResult(this._slowTestSuites, true);
|
||||
}
|
||||
|
||||
onTestResult(test, testResult) {
|
||||
this._slowTestSuites.push({
|
||||
duration: testResult.perfStats.runtime,
|
||||
filePath: testResult.testFilePath,
|
||||
});
|
||||
for (let i = 0; i < testResult.testResults.length; i++) {
|
||||
this._slowTests.push({
|
||||
duration: testResult.testResults[i].duration,
|
||||
fullName: testResult.testResults[i].fullName,
|
||||
filePath: testResult.testFilePath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_slowTestTime(slowestTests) {
|
||||
let slowTestTime = 0;
|
||||
for (let i = 0; i < slowestTests.length; i++) {
|
||||
slowTestTime += slowestTests[i].duration;
|
||||
}
|
||||
return slowTestTime;
|
||||
}
|
||||
|
||||
_allTestTime(result) {
|
||||
let allTestTime = 0;
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
allTestTime += result[i].duration;
|
||||
}
|
||||
return allTestTime;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JestSlowTestReporter;
|
||||
@@ -360,7 +360,7 @@ export class MockMediaDevices {
|
||||
Promise.resolve(new MockMediaStream("local_stream").typed()),
|
||||
);
|
||||
|
||||
public getDisplayMedia = jest.fn<Promise<MediaStream>, [DisplayMediaStreamConstraints]>().mockReturnValue(
|
||||
public getDisplayMedia = jest.fn<Promise<MediaStream>, [MediaStreamConstraints]>().mockReturnValue(
|
||||
Promise.resolve(new MockMediaStream("local_display_stream").typed()),
|
||||
);
|
||||
|
||||
@@ -416,6 +416,9 @@ export class MockCallMatrixClient extends TypedEventEmitter<EmittedEvents, Emitt
|
||||
public getRooms = jest.fn<Room[], []>().mockReturnValue([]);
|
||||
public getRoom = jest.fn();
|
||||
|
||||
public supportsExperimentalThreads(): boolean { return true; }
|
||||
public async decryptEventIfNeeded(): Promise<void> {}
|
||||
|
||||
public typed(): MatrixClient { return this as unknown as MatrixClient; }
|
||||
|
||||
public emitRoomState(event: MatrixEvent, state: RoomState): void {
|
||||
@@ -431,6 +434,7 @@ export class MockCallMatrixClient extends TypedEventEmitter<EmittedEvents, Emitt
|
||||
export class MockCallFeed {
|
||||
constructor(
|
||||
public userId: string,
|
||||
public deviceId: string | undefined,
|
||||
public stream: MockMediaStream,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ describe("RoomWidgetClient", () => {
|
||||
// It should've also inserted the event into the room object
|
||||
const room = client.getRoom("!1:example.org");
|
||||
expect(room).not.toBeNull();
|
||||
expect(room!.currentState.getStateEvents("org.example.foo", "bar").getEffectiveEvent()).toEqual(event);
|
||||
expect(room!.currentState.getStateEvents("org.example.foo", "bar")?.getEffectiveEvent()).toEqual(event);
|
||||
});
|
||||
|
||||
it("backfills", async () => {
|
||||
@@ -195,7 +195,7 @@ describe("RoomWidgetClient", () => {
|
||||
|
||||
const room = client.getRoom("!1:example.org");
|
||||
expect(room).not.toBeNull();
|
||||
expect(room!.currentState.getStateEvents("org.example.foo", "bar").getEffectiveEvent()).toEqual(event);
|
||||
expect(room!.currentState.getStateEvents("org.example.foo", "bar")?.getEffectiveEvent()).toEqual(event);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ let event: MatrixEvent;
|
||||
let threadEvent: MatrixEvent;
|
||||
|
||||
const ROOM_ID = "!roomId:example.org";
|
||||
let THREAD_ID;
|
||||
let THREAD_ID: string;
|
||||
|
||||
function mkPushAction(notify, highlight): IActionsObject {
|
||||
return {
|
||||
@@ -76,7 +76,7 @@ describe("fixNotificationCountOnDecryption", () => {
|
||||
event: true,
|
||||
}, mockClient);
|
||||
|
||||
THREAD_ID = event.getId();
|
||||
THREAD_ID = event.getId()!;
|
||||
threadEvent = mkEvent({
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
@@ -108,6 +108,16 @@ describe("fixNotificationCountOnDecryption", () => {
|
||||
expect(room.getUnreadNotificationCount(NotificationCountType.Highlight)).toBe(1);
|
||||
});
|
||||
|
||||
it("does not change the room count when there's no unread count", () => {
|
||||
room.setUnreadNotificationCount(NotificationCountType.Total, 0);
|
||||
room.setUnreadNotificationCount(NotificationCountType.Highlight, 0);
|
||||
|
||||
fixNotificationCountOnDecryption(mockClient, event);
|
||||
|
||||
expect(room.getRoomUnreadNotificationCount(NotificationCountType.Total)).toBe(1);
|
||||
expect(room.getRoomUnreadNotificationCount(NotificationCountType.Highlight)).toBe(1);
|
||||
});
|
||||
|
||||
it("changes the thread count to highlight on decryption", () => {
|
||||
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total)).toBe(1);
|
||||
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight)).toBe(0);
|
||||
@@ -118,6 +128,16 @@ describe("fixNotificationCountOnDecryption", () => {
|
||||
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight)).toBe(1);
|
||||
});
|
||||
|
||||
it("does not change the room count when there's no unread count", () => {
|
||||
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total, 0);
|
||||
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 0);
|
||||
|
||||
fixNotificationCountOnDecryption(mockClient, event);
|
||||
|
||||
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total)).toBe(0);
|
||||
expect(room.getThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight)).toBe(0);
|
||||
});
|
||||
|
||||
it("emits events", () => {
|
||||
const cb = jest.fn();
|
||||
room.on(RoomEvent.UnreadNotifications, cb);
|
||||
|
||||
@@ -16,11 +16,11 @@ limitations under the License.
|
||||
|
||||
import MockHttpBackend from 'matrix-mock-request';
|
||||
|
||||
import { ReceiptType } from '../../src/@types/read_receipts';
|
||||
import { MAIN_ROOM_TIMELINE, ReceiptType } from '../../src/@types/read_receipts';
|
||||
import { MatrixClient } from "../../src/client";
|
||||
import { Feature, ServerSupport } from '../../src/feature';
|
||||
import { EventType } from '../../src/matrix';
|
||||
import { MAIN_ROOM_TIMELINE } from '../../src/models/read-receipt';
|
||||
import { synthesizeReceipt } from '../../src/models/read-receipt';
|
||||
import { encodeUri } from '../../src/utils';
|
||||
import * as utils from "../test-utils/test-utils";
|
||||
|
||||
@@ -176,4 +176,20 @@ describe("Read receipt", () => {
|
||||
await flushPromises();
|
||||
});
|
||||
});
|
||||
|
||||
describe("synthesizeReceipt", () => {
|
||||
it.each([
|
||||
{ event: roomEvent, destinationId: MAIN_ROOM_TIMELINE },
|
||||
{ event: threadEvent, destinationId: threadEvent.threadRootId! },
|
||||
])("adds the receipt to $destinationId", ({ event, destinationId }) => {
|
||||
const userId = "@bob:example.org";
|
||||
const receiptType = ReceiptType.Read;
|
||||
|
||||
const fakeReadReceipt = synthesizeReceipt(userId, event, receiptType);
|
||||
|
||||
const content = fakeReadReceipt.getContent()[event.getId()!][receiptType][userId];
|
||||
|
||||
expect(content.thread_id).toEqual(destinationId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -152,7 +152,7 @@ describe("RoomState", function() {
|
||||
it("should return a single MatrixEvent if a state_key was specified",
|
||||
function() {
|
||||
const event = state.getStateEvents("m.room.member", userA);
|
||||
expect(event.getContent()).toMatchObject({
|
||||
expect(event?.getContent()).toMatchObject({
|
||||
membership: "join",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,9 +38,8 @@ 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 { ReceiptType, WrappedReceipt } from "../../src/@types/read_receipts";
|
||||
import { FeatureSupport, Thread, ThreadEvent, THREAD_RELATION_TYPE } from "../../src/models/thread";
|
||||
import { WrappedReceipt } from "../../src/models/read-receipt";
|
||||
import { Crypto } from "../../src/crypto";
|
||||
|
||||
describe("Room", function() {
|
||||
|
||||
@@ -364,6 +364,63 @@ describe("SyncAccumulator", function() {
|
||||
});
|
||||
});
|
||||
|
||||
it("should accumulate threaded read receipts", () => {
|
||||
const receipt1 = {
|
||||
type: "m.receipt",
|
||||
room_id: "!foo:bar",
|
||||
content: {
|
||||
"$event1:localhost": {
|
||||
[ReceiptType.Read]: {
|
||||
"@alice:localhost": { ts: 1, thread_id: "main" },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const receipt2 = {
|
||||
type: "m.receipt",
|
||||
room_id: "!foo:bar",
|
||||
content: {
|
||||
"$event2:localhost": {
|
||||
[ReceiptType.Read]: {
|
||||
"@alice:localhost": { ts: 2, thread_id: "$123" }, // does not clobbers event1 receipt
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
sa.accumulate(syncSkeleton({
|
||||
ephemeral: {
|
||||
events: [receipt1],
|
||||
},
|
||||
}));
|
||||
sa.accumulate(syncSkeleton({
|
||||
ephemeral: {
|
||||
events: [receipt2],
|
||||
},
|
||||
}));
|
||||
|
||||
expect(
|
||||
sa.getJSON().roomsData.join["!foo:bar"].ephemeral.events.length,
|
||||
).toEqual(1);
|
||||
expect(
|
||||
sa.getJSON().roomsData.join["!foo:bar"].ephemeral.events[0],
|
||||
).toEqual({
|
||||
type: "m.receipt",
|
||||
room_id: "!foo:bar",
|
||||
content: {
|
||||
"$event1:localhost": {
|
||||
[ReceiptType.Read]: {
|
||||
"@alice:localhost": { ts: 1, thread_id: "main" },
|
||||
},
|
||||
},
|
||||
"$event2:localhost": {
|
||||
[ReceiptType.Read]: {
|
||||
"@alice:localhost": { ts: 2, thread_id: "$123" },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("summary field", function() {
|
||||
function createSyncResponseWithSummary(summary) {
|
||||
return {
|
||||
|
||||
@@ -81,10 +81,13 @@ const fakeIncomingCall = async (client: TestClient, call: MatrixCall, version: s
|
||||
call.getFeeds().push(new CallFeed({
|
||||
client: client.client,
|
||||
userId: "remote_user_id",
|
||||
// @ts-ignore Mock
|
||||
stream: new MockMediaStream("remote_stream_id", [new MockMediaStreamTrack("remote_tack_id")]),
|
||||
id: "remote_feed_id",
|
||||
deviceId: undefined,
|
||||
stream: new MockMediaStream(
|
||||
"remote_stream_id", [new MockMediaStreamTrack("remote_tack_id", "audio")],
|
||||
) as unknown as MediaStream,
|
||||
purpose: SDPStreamMetadataPurpose.Usermedia,
|
||||
audioMuted: false,
|
||||
videoMuted: false,
|
||||
}));
|
||||
await callPromise;
|
||||
};
|
||||
@@ -447,7 +450,7 @@ describe('Call', function() {
|
||||
|
||||
client.client.getRoom = () => {
|
||||
return {
|
||||
getMember: (userId) => {
|
||||
getMember: (userId: string) => {
|
||||
if (userId === opponentMember.userId) {
|
||||
return opponentMember;
|
||||
}
|
||||
@@ -521,10 +524,12 @@ describe('Call', function() {
|
||||
it("should correctly generate local SDPStreamMetadata", async () => {
|
||||
const callPromise = call.placeCallWithCallFeeds([new CallFeed({
|
||||
client: client.client,
|
||||
// @ts-ignore Mock
|
||||
stream: new MockMediaStream("local_stream1", [new MockMediaStreamTrack("track_id", "audio")]),
|
||||
stream: new MockMediaStream(
|
||||
"local_stream1", [new MockMediaStreamTrack("track_id", "audio")],
|
||||
) as unknown as MediaStream,
|
||||
roomId: call.roomId,
|
||||
userId: client.getUserId(),
|
||||
deviceId: undefined,
|
||||
purpose: SDPStreamMetadataPurpose.Usermedia,
|
||||
audioMuted: false,
|
||||
videoMuted: false,
|
||||
@@ -534,8 +539,10 @@ describe('Call', function() {
|
||||
call.getOpponentMember = jest.fn().mockReturnValue({ userId: "@bob:bar.uk" });
|
||||
|
||||
(call as any).pushNewLocalFeed(
|
||||
new MockMediaStream("local_stream2", [new MockMediaStreamTrack("track_id", "video")]),
|
||||
SDPStreamMetadataPurpose.Screenshare, "feed_id2",
|
||||
new MockMediaStream(
|
||||
"local_stream2", [new MockMediaStreamTrack("track_id", "video")],
|
||||
) as unknown as MediaStream,
|
||||
SDPStreamMetadataPurpose.Screenshare,
|
||||
);
|
||||
await call.setMicrophoneMuted(true);
|
||||
|
||||
@@ -563,20 +570,18 @@ describe('Call', function() {
|
||||
new CallFeed({
|
||||
client: client.client,
|
||||
userId: client.getUserId(),
|
||||
// @ts-ignore Mock
|
||||
stream: localUsermediaStream,
|
||||
deviceId: undefined,
|
||||
stream: localUsermediaStream as unknown as MediaStream,
|
||||
purpose: SDPStreamMetadataPurpose.Usermedia,
|
||||
id: "local_usermedia_feed_id",
|
||||
audioMuted: false,
|
||||
videoMuted: false,
|
||||
}),
|
||||
new CallFeed({
|
||||
client: client.client,
|
||||
userId: client.getUserId(),
|
||||
// @ts-ignore Mock
|
||||
stream: localScreensharingStream,
|
||||
deviceId: undefined,
|
||||
stream: localScreensharingStream as unknown as MediaStream,
|
||||
purpose: SDPStreamMetadataPurpose.Screenshare,
|
||||
id: "local_screensharing_feed_id",
|
||||
audioMuted: false,
|
||||
videoMuted: false,
|
||||
}),
|
||||
|
||||
+303
-111
@@ -23,8 +23,9 @@ import {
|
||||
Room,
|
||||
RoomMember,
|
||||
} from '../../../src';
|
||||
import { RoomStateEvent } from "../../../src/models/room-state";
|
||||
import { GroupCall, GroupCallEvent, GroupCallState } from "../../../src/webrtc/groupCall";
|
||||
import { MatrixClient } from "../../../src/client";
|
||||
import { IMyDevice, MatrixClient } from "../../../src/client";
|
||||
import {
|
||||
installWebRTCMocks,
|
||||
MockCallFeed,
|
||||
@@ -53,40 +54,57 @@ const FAKE_USER_ID_3 = "@charlie:test.dummy";
|
||||
const FAKE_STATE_EVENTS = [
|
||||
{
|
||||
getContent: () => ({
|
||||
["m.expires_ts"]: Date.now() + ONE_HOUR,
|
||||
"m.calls": [],
|
||||
}),
|
||||
getStateKey: () => FAKE_USER_ID_1,
|
||||
getRoomId: () => FAKE_ROOM_ID,
|
||||
getTs: () => 0,
|
||||
},
|
||||
{
|
||||
getContent: () => ({
|
||||
["m.expires_ts"]: Date.now() + ONE_HOUR,
|
||||
["m.calls"]: [{
|
||||
["m.call_id"]: FAKE_CONF_ID,
|
||||
["m.devices"]: [{
|
||||
"m.calls": [{
|
||||
"m.call_id": FAKE_CONF_ID,
|
||||
"m.devices": [{
|
||||
device_id: FAKE_DEVICE_ID_2,
|
||||
session_id: FAKE_SESSION_ID_2,
|
||||
expires_ts: Date.now() + ONE_HOUR,
|
||||
feeds: [],
|
||||
}],
|
||||
}],
|
||||
}),
|
||||
getStateKey: () => FAKE_USER_ID_2,
|
||||
getRoomId: () => FAKE_ROOM_ID,
|
||||
getTs: () => 0,
|
||||
}, {
|
||||
getContent: () => ({
|
||||
["m.expires_ts"]: Date.now() + ONE_HOUR,
|
||||
["m.calls"]: [{
|
||||
["m.call_id"]: FAKE_CONF_ID,
|
||||
["m.devices"]: [{
|
||||
"m.expires_ts": Date.now() + ONE_HOUR,
|
||||
"m.calls": [{
|
||||
"m.call_id": FAKE_CONF_ID,
|
||||
"m.devices": [{
|
||||
device_id: "user3_device",
|
||||
session_id: "user3_session",
|
||||
expires_ts: Date.now() + ONE_HOUR,
|
||||
feeds: [],
|
||||
}],
|
||||
}],
|
||||
}),
|
||||
getStateKey: () => "user3",
|
||||
getRoomId: () => FAKE_ROOM_ID,
|
||||
getTs: () => 0,
|
||||
},
|
||||
];
|
||||
|
||||
const mockGetStateEvents = (type: EventType, userId?: string): MatrixEvent[] | MatrixEvent | null => {
|
||||
if (type === EventType.GroupCallMemberPrefix) {
|
||||
return userId === undefined
|
||||
? FAKE_STATE_EVENTS as MatrixEvent[]
|
||||
: FAKE_STATE_EVENTS.find(e => e.getStateKey() === userId) as MatrixEvent;
|
||||
} else {
|
||||
const fakeEvent = { getContent: () => ({}), getTs: () => 0 } as MatrixEvent;
|
||||
return userId === undefined ? [fakeEvent] : fakeEvent;
|
||||
}
|
||||
};
|
||||
|
||||
const ONE_HOUR = 1000 * 60 * 60;
|
||||
|
||||
const createAndEnterGroupCall = async (cli: MatrixClient, room: Room): Promise<GroupCall> => {
|
||||
@@ -111,6 +129,8 @@ class MockCall {
|
||||
|
||||
public state = CallState.Ringing;
|
||||
public opponentUserId = FAKE_USER_ID_1;
|
||||
public opponentDeviceId = FAKE_DEVICE_ID_1;
|
||||
public opponentMember = { userId: this.opponentUserId };
|
||||
public callId = "1";
|
||||
public localUsermediaFeed = {
|
||||
setAudioVideoMuted: jest.fn<void, [boolean, boolean]>(),
|
||||
@@ -129,9 +149,11 @@ class MockCall {
|
||||
public removeListener = jest.fn();
|
||||
|
||||
public getOpponentMember(): Partial<RoomMember> {
|
||||
return {
|
||||
userId: this.opponentUserId,
|
||||
};
|
||||
return this.opponentMember;
|
||||
}
|
||||
|
||||
public getOpponentDeviceId(): string {
|
||||
return this.opponentDeviceId;
|
||||
}
|
||||
|
||||
public typed(): MatrixCall { return this as unknown as MatrixCall; }
|
||||
@@ -158,13 +180,13 @@ describe('Group Call', function() {
|
||||
|
||||
room = new Room(FAKE_ROOM_ID, mockClient, FAKE_USER_ID_1);
|
||||
groupCall = new GroupCall(mockClient, room, GroupCallType.Video, false, GroupCallIntent.Prompt);
|
||||
room.currentState.members[FAKE_USER_ID_1] = {
|
||||
userId: FAKE_USER_ID_1,
|
||||
membership: "join",
|
||||
} as unknown as RoomMember;
|
||||
});
|
||||
|
||||
it("does not initialize local call feed, if it already is", async () => {
|
||||
room.currentState.members[FAKE_USER_ID_1] = {
|
||||
userId: FAKE_USER_ID_1,
|
||||
} as unknown as RoomMember;
|
||||
|
||||
await groupCall.initLocalCallFeed();
|
||||
jest.spyOn(groupCall, "initLocalCallFeed");
|
||||
await groupCall.enter();
|
||||
@@ -194,10 +216,6 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
it("sends member state event to room on enter", async () => {
|
||||
room.currentState.members[FAKE_USER_ID_1] = {
|
||||
userId: FAKE_USER_ID_1,
|
||||
} as unknown as RoomMember;
|
||||
|
||||
await groupCall.create();
|
||||
|
||||
try {
|
||||
@@ -227,10 +245,6 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
it("sends member state event to room on leave", async () => {
|
||||
room.currentState.members[FAKE_USER_ID_1] = {
|
||||
userId: FAKE_USER_ID_1,
|
||||
} as unknown as RoomMember;
|
||||
|
||||
await groupCall.create();
|
||||
await groupCall.enter();
|
||||
mockSendState.mockClear();
|
||||
@@ -245,6 +259,18 @@ describe('Group Call', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it("includes local device in participants when entered via another session", async () => {
|
||||
const hasLocalParticipant = () => groupCall.participants.get(
|
||||
room.getMember(mockClient.getUserId()!)!,
|
||||
)?.has(mockClient.getDeviceId()!) ?? false;
|
||||
|
||||
expect(groupCall.enteredViaAnotherSession).toBe(false);
|
||||
expect(hasLocalParticipant()).toBe(false);
|
||||
|
||||
groupCall.enteredViaAnotherSession = true;
|
||||
expect(hasLocalParticipant()).toBe(true);
|
||||
});
|
||||
|
||||
it("starts with mic unmuted in regular calls", async () => {
|
||||
try {
|
||||
await groupCall.create();
|
||||
@@ -326,8 +352,8 @@ describe('Group Call', function() {
|
||||
|
||||
describe("call feeds changing", () => {
|
||||
let call: MockCall;
|
||||
const currentFeed = new MockCallFeed(FAKE_USER_ID_1, new MockMediaStream("current"));
|
||||
const newFeed = new MockCallFeed(FAKE_USER_ID_1, new MockMediaStream("new"));
|
||||
const currentFeed = new MockCallFeed(FAKE_USER_ID_1, FAKE_DEVICE_ID_1, new MockMediaStream("current"));
|
||||
const newFeed = new MockCallFeed(FAKE_USER_ID_1, FAKE_DEVICE_ID_1, new MockMediaStream("new"));
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(currentFeed, "dispose");
|
||||
@@ -358,7 +384,7 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
it("replaces usermedia feed", async () => {
|
||||
groupCall.userMediaFeeds = [currentFeed.typed()];
|
||||
groupCall.userMediaFeeds.push(currentFeed.typed());
|
||||
|
||||
call.remoteUsermediaFeed = newFeed.typed();
|
||||
// @ts-ignore Mock
|
||||
@@ -368,7 +394,7 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
it("removes usermedia feed", async () => {
|
||||
groupCall.userMediaFeeds = [currentFeed.typed()];
|
||||
groupCall.userMediaFeeds.push(currentFeed.typed());
|
||||
|
||||
// @ts-ignore Mock
|
||||
groupCall.onCallFeedsChanged(call);
|
||||
@@ -387,7 +413,7 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
it("replaces screenshare feed", async () => {
|
||||
groupCall.screenshareFeeds = [currentFeed.typed()];
|
||||
groupCall.screenshareFeeds.push(currentFeed.typed());
|
||||
|
||||
call.remoteScreensharingFeed = newFeed.typed();
|
||||
// @ts-ignore Mock
|
||||
@@ -397,7 +423,7 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
it("removes screenshare feed", async () => {
|
||||
groupCall.screenshareFeeds = [currentFeed.typed()];
|
||||
groupCall.screenshareFeeds.push(currentFeed.typed());
|
||||
|
||||
// @ts-ignore Mock
|
||||
groupCall.onCallFeedsChanged(call);
|
||||
@@ -408,7 +434,7 @@ describe('Group Call', function() {
|
||||
|
||||
describe("feed replacing", () => {
|
||||
it("replaces usermedia feed", async () => {
|
||||
groupCall.userMediaFeeds = [currentFeed.typed()];
|
||||
groupCall.userMediaFeeds.push(currentFeed.typed());
|
||||
|
||||
// @ts-ignore Mock
|
||||
groupCall.replaceUserMediaFeed(currentFeed, newFeed);
|
||||
@@ -422,7 +448,7 @@ describe('Group Call', function() {
|
||||
});
|
||||
|
||||
it("replaces screenshare feed", async () => {
|
||||
groupCall.screenshareFeeds = [currentFeed.typed()];
|
||||
groupCall.screenshareFeeds.push(currentFeed.typed());
|
||||
|
||||
// @ts-ignore Mock
|
||||
groupCall.replaceScreenshareFeed(currentFeed, newFeed);
|
||||
@@ -489,7 +515,10 @@ describe('Group Call', function() {
|
||||
|
||||
it("sends metadata updates before unmuting in PTT mode", async () => {
|
||||
const mockCall = new MockCall(FAKE_ROOM_ID, groupCall.groupCallId);
|
||||
groupCall.calls.push(mockCall as unknown as MatrixCall);
|
||||
groupCall.calls.set(
|
||||
mockCall.getOpponentMember() as RoomMember,
|
||||
new Map([[mockCall.getOpponentDeviceId(), mockCall.typed()]]),
|
||||
);
|
||||
|
||||
let metadataUpdateResolve: () => void;
|
||||
const metadataUpdatePromise = new Promise<void>(resolve => {
|
||||
@@ -511,7 +540,10 @@ describe('Group Call', function() {
|
||||
|
||||
it("sends metadata updates after muting in PTT mode", async () => {
|
||||
const mockCall = new MockCall(FAKE_ROOM_ID, groupCall.groupCallId);
|
||||
groupCall.calls.push(mockCall as unknown as MatrixCall);
|
||||
groupCall.calls.set(
|
||||
mockCall.getOpponentMember() as RoomMember,
|
||||
new Map([[mockCall.getOpponentDeviceId(), mockCall.typed()]]),
|
||||
);
|
||||
|
||||
// the call starts muted, so unmute to get in the right state to test
|
||||
await groupCall.setMicrophoneMuted(false);
|
||||
@@ -560,7 +592,7 @@ describe('Group Call', function() {
|
||||
if (eventType === EventType.GroupCallMemberPrefix) {
|
||||
const fakeEvent = {
|
||||
getContent: () => content,
|
||||
getRoomId: () => FAKE_ROOM_ID,
|
||||
getRoomId: () => roomId,
|
||||
getStateKey: () => statekey,
|
||||
} as unknown as MatrixEvent;
|
||||
|
||||
@@ -574,8 +606,8 @@ describe('Group Call', function() {
|
||||
// just add it once.
|
||||
subMap.set(statekey, fakeEvent);
|
||||
|
||||
groupCall1.onMemberStateChanged(fakeEvent);
|
||||
groupCall2.onMemberStateChanged(fakeEvent);
|
||||
client1Room.currentState.emit(RoomStateEvent.Update, client1Room.currentState);
|
||||
client2Room.currentState.emit(RoomStateEvent.Update, client2Room.currentState);
|
||||
}
|
||||
return Promise.resolve({ "event_id": "foo" });
|
||||
};
|
||||
@@ -584,9 +616,17 @@ describe('Group Call', function() {
|
||||
client2.sendStateEvent.mockImplementation(fakeSendStateEvents);
|
||||
|
||||
const client1Room = new Room(FAKE_ROOM_ID, client1.typed(), FAKE_USER_ID_1);
|
||||
|
||||
const client2Room = new Room(FAKE_ROOM_ID, client2.typed(), FAKE_USER_ID_2);
|
||||
|
||||
client1Room.currentState.members[FAKE_USER_ID_1] = client2Room.currentState.members[FAKE_USER_ID_1] = {
|
||||
userId: FAKE_USER_ID_1,
|
||||
membership: "join",
|
||||
} as unknown as RoomMember;
|
||||
client1Room.currentState.members[FAKE_USER_ID_2] = client2Room.currentState.members[FAKE_USER_ID_2] = {
|
||||
userId: FAKE_USER_ID_2,
|
||||
membership: "join",
|
||||
} as unknown as RoomMember;
|
||||
|
||||
groupCall1 = new GroupCall(
|
||||
client1.typed(), client1Room, GroupCallType.Video, false, GroupCallIntent.Prompt, FAKE_CONF_ID,
|
||||
);
|
||||
@@ -594,20 +634,6 @@ describe('Group Call', function() {
|
||||
groupCall2 = new GroupCall(
|
||||
client2.typed(), client2Room, GroupCallType.Video, false, GroupCallIntent.Prompt, FAKE_CONF_ID,
|
||||
);
|
||||
|
||||
client1Room.currentState.members[FAKE_USER_ID_1] = {
|
||||
userId: FAKE_USER_ID_1,
|
||||
} as unknown as RoomMember;
|
||||
client1Room.currentState.members[FAKE_USER_ID_2] = {
|
||||
userId: FAKE_USER_ID_2,
|
||||
} as unknown as RoomMember;
|
||||
|
||||
client2Room.currentState.members[FAKE_USER_ID_1] = {
|
||||
userId: FAKE_USER_ID_1,
|
||||
} as unknown as RoomMember;
|
||||
client2Room.currentState.members[FAKE_USER_ID_2] = {
|
||||
userId: FAKE_USER_ID_2,
|
||||
} as unknown as RoomMember;
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
@@ -672,8 +698,10 @@ describe('Group Call', function() {
|
||||
|
||||
expect(client1.sendToDevice).toHaveBeenCalled();
|
||||
|
||||
const oldCall = groupCall1.getCallByUserId(client2.userId);
|
||||
oldCall!.emit(CallEvent.Hangup, oldCall!);
|
||||
const oldCall = groupCall1.calls.get(
|
||||
groupCall1.room.getMember(client2.userId)!,
|
||||
)!.get(client2.deviceId)!;
|
||||
oldCall.emit(CallEvent.Hangup, oldCall!);
|
||||
|
||||
client1.sendToDevice.mockClear();
|
||||
|
||||
@@ -691,9 +719,11 @@ describe('Group Call', function() {
|
||||
// to even be created...
|
||||
let newCall: MatrixCall | undefined;
|
||||
while (
|
||||
(newCall = groupCall1.getCallByUserId(client2.userId)) === undefined ||
|
||||
newCall.peerConn === undefined ||
|
||||
newCall.callId == oldCall!.callId
|
||||
(newCall = groupCall1.calls.get(
|
||||
groupCall1.room.getMember(client2.userId)!,
|
||||
)?.get(client2.deviceId)) === undefined
|
||||
|| newCall.peerConn === undefined
|
||||
|| newCall.callId == oldCall.callId
|
||||
) {
|
||||
await flushPromises();
|
||||
}
|
||||
@@ -733,7 +763,9 @@ describe('Group Call', function() {
|
||||
groupCall1.setMicrophoneMuted(false);
|
||||
groupCall1.setLocalVideoMuted(false);
|
||||
|
||||
const call = groupCall1.getCallByUserId(client2.userId)!;
|
||||
const call = groupCall1.calls.get(
|
||||
groupCall1.room.getMember(client2.userId)!,
|
||||
)!.get(client2.deviceId)!;
|
||||
call.isMicrophoneMuted = jest.fn().mockReturnValue(true);
|
||||
call.setMicrophoneMuted = jest.fn();
|
||||
call.isLocalVideoMuted = jest.fn().mockReturnValue(true);
|
||||
@@ -760,12 +792,15 @@ describe('Group Call', function() {
|
||||
mockClient = typedMockClient as unknown as MatrixClient;
|
||||
|
||||
room = new Room(FAKE_ROOM_ID, mockClient, FAKE_USER_ID_1);
|
||||
room.currentState.getStateEvents = jest.fn().mockImplementation((type: EventType, userId: string) => {
|
||||
return type === EventType.GroupCallMemberPrefix
|
||||
? FAKE_STATE_EVENTS.find(e => e.getStateKey() === userId) || FAKE_STATE_EVENTS
|
||||
: { getContent: () => ([]) };
|
||||
});
|
||||
room.getMember = jest.fn().mockImplementation((userId) => ({ userId }));
|
||||
room.currentState.getStateEvents = jest.fn().mockImplementation(mockGetStateEvents);
|
||||
room.currentState.members[FAKE_USER_ID_1] = {
|
||||
userId: FAKE_USER_ID_1,
|
||||
membership: "join",
|
||||
} as unknown as RoomMember;
|
||||
room.currentState.members[FAKE_USER_ID_2] = {
|
||||
userId: FAKE_USER_ID_2,
|
||||
membership: "join",
|
||||
} as unknown as RoomMember;
|
||||
});
|
||||
|
||||
describe("local muting", () => {
|
||||
@@ -773,17 +808,13 @@ describe('Group Call', function() {
|
||||
const groupCall = await createAndEnterGroupCall(mockClient, room);
|
||||
|
||||
groupCall.localCallFeed!.setAudioVideoMuted = jest.fn();
|
||||
const setAVMutedArray = groupCall.calls.map(call => {
|
||||
call.localUsermediaFeed!.setAudioVideoMuted = jest.fn();
|
||||
return call.localUsermediaFeed!.setAudioVideoMuted;
|
||||
});
|
||||
const tracksArray = groupCall.calls.reduce((acc: MediaStreamTrack[], call: MatrixCall) => {
|
||||
acc.push(...call.localUsermediaStream!.getAudioTracks());
|
||||
return acc;
|
||||
}, []);
|
||||
const sendMetadataUpdateArray = groupCall.calls.map(call => {
|
||||
call.sendMetadataUpdate = jest.fn();
|
||||
return call.sendMetadataUpdate;
|
||||
const setAVMutedArray: ((audioMuted: boolean | null, videoMuted: boolean | null) => void)[] = [];
|
||||
const tracksArray: MediaStreamTrack[] = [];
|
||||
const sendMetadataUpdateArray: (() => Promise<void>)[] = [];
|
||||
groupCall.forEachCall(call => {
|
||||
setAVMutedArray.push(call.localUsermediaFeed!.setAudioVideoMuted = jest.fn());
|
||||
tracksArray.push(...call.localUsermediaStream!.getAudioTracks());
|
||||
sendMetadataUpdateArray.push(call.sendMetadataUpdate = jest.fn());
|
||||
});
|
||||
|
||||
await groupCall.setMicrophoneMuted(true);
|
||||
@@ -801,18 +832,14 @@ describe('Group Call', function() {
|
||||
const groupCall = await createAndEnterGroupCall(mockClient, room);
|
||||
|
||||
groupCall.localCallFeed!.setAudioVideoMuted = jest.fn();
|
||||
const setAVMutedArray = groupCall.calls.map(call => {
|
||||
call.localUsermediaFeed!.setAudioVideoMuted = jest.fn();
|
||||
const setAVMutedArray: ((audioMuted: boolean | null, videoMuted: boolean | null) => void)[] = [];
|
||||
const tracksArray: MediaStreamTrack[] = [];
|
||||
const sendMetadataUpdateArray: (() => Promise<void>)[] = [];
|
||||
groupCall.forEachCall(call => {
|
||||
call.localUsermediaFeed!.isVideoMuted = jest.fn().mockReturnValue(true);
|
||||
return call.localUsermediaFeed!.setAudioVideoMuted;
|
||||
});
|
||||
const tracksArray = groupCall.calls.reduce((acc: MediaStreamTrack[], call: MatrixCall) => {
|
||||
acc.push(...call.localUsermediaStream!.getVideoTracks());
|
||||
return acc;
|
||||
}, []);
|
||||
const sendMetadataUpdateArray = groupCall.calls.map(call => {
|
||||
call.sendMetadataUpdate = jest.fn();
|
||||
return call.sendMetadataUpdate;
|
||||
setAVMutedArray.push(call.localUsermediaFeed!.setAudioVideoMuted = jest.fn());
|
||||
tracksArray.push(...call.localUsermediaStream!.getVideoTracks());
|
||||
sendMetadataUpdateArray.push(call.sendMetadataUpdate = jest.fn());
|
||||
});
|
||||
|
||||
await groupCall.setLocalVideoMuted(true);
|
||||
@@ -847,7 +874,7 @@ describe('Group Call', function() {
|
||||
// It takes a bit of time for the calls to get created
|
||||
await sleep(10);
|
||||
|
||||
const call = groupCall.calls[0];
|
||||
const call = groupCall.calls.get(groupCall.room.getMember(FAKE_USER_ID_2)!)!.get(FAKE_DEVICE_ID_2)!;
|
||||
call.getOpponentMember = () => ({ userId: call.invitee }) as RoomMember;
|
||||
// @ts-ignore Mock
|
||||
call.pushRemoteFeed(new MockMediaStream("stream", [
|
||||
@@ -856,7 +883,7 @@ describe('Group Call', function() {
|
||||
]));
|
||||
call.onSDPStreamMetadataChangedReceived(metadataEvent);
|
||||
|
||||
const feed = groupCall.getUserMediaFeedByUserId(call.invitee!);
|
||||
const feed = groupCall.getUserMediaFeed(call.invitee!, call.getOpponentDeviceId()!);
|
||||
expect(feed!.isAudioMuted()).toBe(true);
|
||||
expect(feed!.isVideoMuted()).toBe(false);
|
||||
|
||||
@@ -870,7 +897,7 @@ describe('Group Call', function() {
|
||||
// It takes a bit of time for the calls to get created
|
||||
await sleep(10);
|
||||
|
||||
const call = groupCall.calls[0];
|
||||
const call = groupCall.calls.get(groupCall.room.getMember(FAKE_USER_ID_2)!)!.get(FAKE_DEVICE_ID_2)!;
|
||||
call.getOpponentMember = () => ({ userId: call.invitee }) as RoomMember;
|
||||
// @ts-ignore Mock
|
||||
call.pushRemoteFeed(new MockMediaStream("stream", [
|
||||
@@ -879,7 +906,7 @@ describe('Group Call', function() {
|
||||
]));
|
||||
call.onSDPStreamMetadataChangedReceived(metadataEvent);
|
||||
|
||||
const feed = groupCall.getUserMediaFeedByUserId(call.invitee!);
|
||||
const feed = groupCall.getUserMediaFeed(call.invitee!, call.getOpponentDeviceId()!);
|
||||
expect(feed!.isAudioMuted()).toBe(false);
|
||||
expect(feed!.isVideoMuted()).toBe(true);
|
||||
|
||||
@@ -945,12 +972,16 @@ describe('Group Call', function() {
|
||||
|
||||
expect(mockCall.reject).not.toHaveBeenCalled();
|
||||
expect(mockCall.answerWithCallFeeds).toHaveBeenCalled();
|
||||
expect(groupCall.calls).toEqual([mockCall]);
|
||||
expect(groupCall.calls).toEqual(new Map([[
|
||||
groupCall.room.getMember(FAKE_USER_ID_1)!,
|
||||
new Map([[FAKE_DEVICE_ID_1, mockCall]]),
|
||||
]]));
|
||||
});
|
||||
|
||||
it("replaces calls if it already has one with the same user", async () => {
|
||||
const oldMockCall = new MockCall(room.roomId, groupCall.groupCallId);
|
||||
const newMockCall = new MockCall(room.roomId, groupCall.groupCallId);
|
||||
newMockCall.opponentMember = oldMockCall.opponentMember; // Ensure referential equality
|
||||
newMockCall.callId = "not " + oldMockCall.callId;
|
||||
|
||||
mockClient.emit(CallEventHandlerEvent.Incoming, oldMockCall as unknown as MatrixCall);
|
||||
@@ -958,7 +989,10 @@ describe('Group Call', function() {
|
||||
|
||||
expect(oldMockCall.hangup).toHaveBeenCalled();
|
||||
expect(newMockCall.answerWithCallFeeds).toHaveBeenCalled();
|
||||
expect(groupCall.calls).toEqual([newMockCall]);
|
||||
expect(groupCall.calls).toEqual(new Map([[
|
||||
groupCall.room.getMember(FAKE_USER_ID_1)!,
|
||||
new Map([[FAKE_DEVICE_ID_1, newMockCall]]),
|
||||
]]));
|
||||
});
|
||||
|
||||
it("starts to process incoming calls when we've entered", async () => {
|
||||
@@ -988,32 +1022,34 @@ describe('Group Call', function() {
|
||||
mockClient = typedMockClient.typed();
|
||||
|
||||
room = new Room(FAKE_ROOM_ID, mockClient, FAKE_USER_ID_1);
|
||||
room.getMember = jest.fn().mockImplementation((userId) => ({ userId }));
|
||||
room.currentState.getStateEvents = jest.fn().mockImplementation((type: EventType, userId: string) => {
|
||||
return type === EventType.GroupCallMemberPrefix
|
||||
? FAKE_STATE_EVENTS.find(e => e.getStateKey() === userId) || FAKE_STATE_EVENTS
|
||||
: { getContent: () => ([]) };
|
||||
});
|
||||
room.currentState.members[FAKE_USER_ID_1] = {
|
||||
userId: FAKE_USER_ID_1,
|
||||
membership: "join",
|
||||
} as unknown as RoomMember;
|
||||
room.currentState.members[FAKE_USER_ID_2] = {
|
||||
userId: FAKE_USER_ID_2,
|
||||
membership: "join",
|
||||
} as unknown as RoomMember;
|
||||
room.currentState.getStateEvents = jest.fn().mockImplementation(mockGetStateEvents);
|
||||
|
||||
groupCall = await createAndEnterGroupCall(mockClient, room);
|
||||
});
|
||||
|
||||
it("sending screensharing stream", async () => {
|
||||
const onNegotiationNeededArray = groupCall.calls.map(call => {
|
||||
const onNegotiationNeededArray: (() => Promise<void>)[] = [];
|
||||
groupCall.forEachCall(call => {
|
||||
// @ts-ignore Mock
|
||||
call.gotLocalOffer = jest.fn();
|
||||
// @ts-ignore Mock
|
||||
return call.gotLocalOffer;
|
||||
onNegotiationNeededArray.push(call.gotLocalOffer = jest.fn());
|
||||
});
|
||||
|
||||
let enabledResult;
|
||||
let enabledResult: boolean;
|
||||
enabledResult = await groupCall.setScreensharingEnabled(true);
|
||||
expect(enabledResult).toEqual(true);
|
||||
expect(typedMockClient.mediaHandler.getScreensharingStream).toHaveBeenCalled();
|
||||
MockRTCPeerConnection.triggerAllNegotiations();
|
||||
|
||||
expect(groupCall.screenshareFeeds).toHaveLength(1);
|
||||
groupCall.calls.forEach(c => {
|
||||
groupCall.forEachCall(c => {
|
||||
expect(c.getLocalFeeds().find(f => f.purpose === SDPStreamMetadataPurpose.Screenshare)).toBeDefined();
|
||||
});
|
||||
onNegotiationNeededArray.forEach(f => expect(f).toHaveBeenCalled());
|
||||
@@ -1036,7 +1072,7 @@ describe('Group Call', function() {
|
||||
// It takes a bit of time for the calls to get created
|
||||
await sleep(10);
|
||||
|
||||
const call = groupCall.calls[0];
|
||||
const call = groupCall.calls.get(groupCall.room.getMember(FAKE_USER_ID_2)!)!.get(FAKE_DEVICE_ID_2)!;
|
||||
call.getOpponentMember = () => ({ userId: call.invitee }) as RoomMember;
|
||||
call.onNegotiateReceived({
|
||||
getContent: () => ({
|
||||
@@ -1057,7 +1093,7 @@ describe('Group Call', function() {
|
||||
]));
|
||||
|
||||
expect(groupCall.screenshareFeeds).toHaveLength(1);
|
||||
expect(groupCall.getScreenshareFeedByUserId(call.invitee!)).toBeDefined();
|
||||
expect(groupCall.getScreenshareFeed(call.invitee!, call.getOpponentDeviceId()!)).toBeDefined();
|
||||
|
||||
groupCall.terminate();
|
||||
});
|
||||
@@ -1097,12 +1133,16 @@ describe('Group Call', function() {
|
||||
);
|
||||
|
||||
room = new Room(FAKE_ROOM_ID, mockClient.typed(), FAKE_USER_ID_1);
|
||||
room.currentState.members[FAKE_USER_ID_1] = {
|
||||
userId: FAKE_USER_ID_1,
|
||||
} as unknown as RoomMember;
|
||||
groupCall = await createAndEnterGroupCall(mockClient.typed(), room);
|
||||
|
||||
mediaFeed1 = new CallFeed({
|
||||
client: mockClient.typed(),
|
||||
roomId: FAKE_ROOM_ID,
|
||||
userId: FAKE_USER_ID_2,
|
||||
deviceId: FAKE_DEVICE_ID_1,
|
||||
stream: (new MockMediaStream("foo", [])).typed(),
|
||||
purpose: SDPStreamMetadataPurpose.Usermedia,
|
||||
audioMuted: false,
|
||||
@@ -1114,6 +1154,7 @@ describe('Group Call', function() {
|
||||
client: mockClient.typed(),
|
||||
roomId: FAKE_ROOM_ID,
|
||||
userId: FAKE_USER_ID_3,
|
||||
deviceId: FAKE_DEVICE_ID_1,
|
||||
stream: (new MockMediaStream("foo", [])).typed(),
|
||||
purpose: SDPStreamMetadataPurpose.Usermedia,
|
||||
audioMuted: false,
|
||||
@@ -1136,15 +1177,15 @@ describe('Group Call', function() {
|
||||
mediaFeed2.speakingVolumeSamples = [0, 0];
|
||||
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(groupCall.activeSpeaker).toEqual(FAKE_USER_ID_2);
|
||||
expect(onActiveSpeakerEvent).toHaveBeenCalledWith(FAKE_USER_ID_2);
|
||||
expect(groupCall.activeSpeaker).toEqual(mediaFeed1);
|
||||
expect(onActiveSpeakerEvent).toHaveBeenCalledWith(mediaFeed1);
|
||||
|
||||
mediaFeed1.speakingVolumeSamples = [0, 0];
|
||||
mediaFeed2.speakingVolumeSamples = [100, 100];
|
||||
|
||||
jest.runOnlyPendingTimers();
|
||||
expect(groupCall.activeSpeaker).toEqual(FAKE_USER_ID_3);
|
||||
expect(onActiveSpeakerEvent).toHaveBeenCalledWith(FAKE_USER_ID_3);
|
||||
expect(groupCall.activeSpeaker).toEqual(mediaFeed2);
|
||||
expect(onActiveSpeakerEvent).toHaveBeenCalledWith(mediaFeed2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1233,4 +1274,155 @@ describe('Group Call', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("cleaning member state", () => {
|
||||
const bobWeb: IMyDevice = {
|
||||
device_id: "bobweb",
|
||||
last_seen_ts: 0,
|
||||
};
|
||||
const bobDesktop: IMyDevice = {
|
||||
device_id: "bobdesktop",
|
||||
last_seen_ts: 0,
|
||||
};
|
||||
const bobDesktopOffline: IMyDevice = {
|
||||
device_id: "bobdesktopoffline",
|
||||
last_seen_ts: 1000 * 60 * 60 * -2, // 2 hours ago
|
||||
};
|
||||
const bobDesktopNeverOnline: IMyDevice = {
|
||||
device_id: "bobdesktopneveronline",
|
||||
};
|
||||
|
||||
const mkContent = (devices: IMyDevice[]) => ({
|
||||
"m.calls": [{
|
||||
"m.call_id": groupCall.groupCallId,
|
||||
"m.devices": devices.map(d => ({
|
||||
device_id: d.device_id, session_id: "1", feeds: [], expires_ts: 1000 * 60 * 10,
|
||||
})),
|
||||
}],
|
||||
});
|
||||
|
||||
const expectDevices = (devices: IMyDevice[]) => expect(
|
||||
room.currentState.getStateEvents(EventType.GroupCallMemberPrefix, FAKE_USER_ID_2)?.getContent(),
|
||||
).toEqual({
|
||||
"m.calls": [{
|
||||
"m.call_id": groupCall.groupCallId,
|
||||
"m.devices": devices.map(d => ({
|
||||
device_id: d.device_id, session_id: "1", feeds: [], expires_ts: expect.any(Number),
|
||||
})),
|
||||
}],
|
||||
});
|
||||
|
||||
let mockClient: MatrixClient;
|
||||
let room: Room;
|
||||
let groupCall: GroupCall;
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(0);
|
||||
});
|
||||
|
||||
afterAll(() => jest.useRealTimers());
|
||||
|
||||
beforeEach(async () => {
|
||||
const typedMockClient = new MockCallMatrixClient(
|
||||
FAKE_USER_ID_2, bobWeb.device_id, FAKE_SESSION_ID_2,
|
||||
);
|
||||
jest.spyOn(typedMockClient, "sendStateEvent").mockImplementation(
|
||||
async (roomId, eventType, content, stateKey) => {
|
||||
const eventId = `$${Math.random()}`;
|
||||
if (roomId === room.roomId) {
|
||||
room.addLiveEvents([new MatrixEvent({
|
||||
event_id: eventId,
|
||||
type: eventType,
|
||||
room_id: roomId,
|
||||
sender: FAKE_USER_ID_2,
|
||||
content,
|
||||
state_key: stateKey,
|
||||
})]);
|
||||
}
|
||||
return { event_id: eventId };
|
||||
},
|
||||
);
|
||||
mockClient = typedMockClient as unknown as MatrixClient;
|
||||
|
||||
room = new Room(FAKE_ROOM_ID, mockClient, FAKE_USER_ID_2);
|
||||
room.getMember = jest.fn().mockImplementation((userId) => ({ userId }));
|
||||
|
||||
groupCall = new GroupCall(
|
||||
mockClient,
|
||||
room,
|
||||
GroupCallType.Video,
|
||||
false,
|
||||
GroupCallIntent.Prompt,
|
||||
FAKE_CONF_ID,
|
||||
);
|
||||
await groupCall.create();
|
||||
|
||||
mockClient.getDevices = async () => ({
|
||||
devices: [
|
||||
bobWeb,
|
||||
bobDesktop,
|
||||
bobDesktopOffline,
|
||||
bobDesktopNeverOnline,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => groupCall.leave());
|
||||
|
||||
it("doesn't clean up valid devices", async () => {
|
||||
await groupCall.enter();
|
||||
await mockClient.sendStateEvent(
|
||||
room.roomId,
|
||||
EventType.GroupCallMemberPrefix,
|
||||
mkContent([bobWeb, bobDesktop]),
|
||||
FAKE_USER_ID_2,
|
||||
);
|
||||
|
||||
await groupCall.cleanMemberState();
|
||||
expectDevices([bobWeb, bobDesktop]);
|
||||
});
|
||||
|
||||
it("cleans up our own device if we're disconnected", async () => {
|
||||
await mockClient.sendStateEvent(
|
||||
room.roomId,
|
||||
EventType.GroupCallMemberPrefix,
|
||||
mkContent([bobWeb, bobDesktop]),
|
||||
FAKE_USER_ID_2,
|
||||
);
|
||||
|
||||
await groupCall.cleanMemberState();
|
||||
expectDevices([bobDesktop]);
|
||||
});
|
||||
|
||||
it("doesn't clean up the local device if entered via another session", async () => {
|
||||
groupCall.enteredViaAnotherSession = true;
|
||||
await mockClient.sendStateEvent(
|
||||
room.roomId,
|
||||
EventType.GroupCallMemberPrefix,
|
||||
mkContent([bobWeb]),
|
||||
FAKE_USER_ID_2,
|
||||
);
|
||||
|
||||
await groupCall.cleanMemberState();
|
||||
expectDevices([bobWeb]);
|
||||
});
|
||||
|
||||
it("cleans up devices that have never been online", async () => {
|
||||
await mockClient.sendStateEvent(
|
||||
room.roomId,
|
||||
EventType.GroupCallMemberPrefix,
|
||||
mkContent([bobDesktop, bobDesktopNeverOnline]),
|
||||
FAKE_USER_ID_2,
|
||||
);
|
||||
|
||||
await groupCall.cleanMemberState();
|
||||
expectDevices([bobDesktop]);
|
||||
});
|
||||
|
||||
it("no-ops if there are no state events", async () => {
|
||||
await groupCall.cleanMemberState();
|
||||
expect(room.currentState.getStateEvents(EventType.GroupCallMemberPrefix, FAKE_USER_ID_2)).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,26 +16,21 @@ limitations under the License.
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import { ClientEvent } from "../../../src/client";
|
||||
import { RoomMember } from "../../../src/models/room-member";
|
||||
import { SyncState } from "../../../src/sync";
|
||||
import {
|
||||
ClientEvent,
|
||||
GroupCall,
|
||||
GroupCallIntent,
|
||||
GroupCallState,
|
||||
GroupCallType,
|
||||
IContent,
|
||||
MatrixEvent,
|
||||
Room,
|
||||
RoomState,
|
||||
} from "../../../src";
|
||||
import { SyncState } from "../../../src/sync";
|
||||
import { GroupCallTerminationReason } from "../../../src/webrtc/groupCall";
|
||||
GroupCallTerminationReason,
|
||||
} from "../../../src/webrtc/groupCall";
|
||||
import { IContent, MatrixEvent } from "../../../src/models/event";
|
||||
import { Room } from "../../../src/models/room";
|
||||
import { RoomState } from "../../../src/models/room-state";
|
||||
import { GroupCallEventHandler, GroupCallEventHandlerEvent } from "../../../src/webrtc/groupCallEventHandler";
|
||||
import { flushPromises } from "../../test-utils/flushPromises";
|
||||
import {
|
||||
makeMockGroupCallMemberStateEvent,
|
||||
makeMockGroupCallStateEvent,
|
||||
MockCallMatrixClient,
|
||||
} from "../../test-utils/webrtc";
|
||||
import { makeMockGroupCallStateEvent, MockCallMatrixClient } from "../../test-utils/webrtc";
|
||||
|
||||
const FAKE_USER_ID = "@alice:test.dummy";
|
||||
const FAKE_DEVICE_ID = "AAAAAAA";
|
||||
@@ -47,6 +42,7 @@ describe('Group Call Event Handler', function() {
|
||||
let groupCallEventHandler: GroupCallEventHandler;
|
||||
let mockClient: MockCallMatrixClient;
|
||||
let mockRoom: Room;
|
||||
let mockMember: RoomMember;
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient = new MockCallMatrixClient(
|
||||
@@ -54,13 +50,27 @@ describe('Group Call Event Handler', function() {
|
||||
);
|
||||
groupCallEventHandler = new GroupCallEventHandler(mockClient.typed());
|
||||
|
||||
mockMember = {
|
||||
userId: FAKE_USER_ID,
|
||||
membership: "join",
|
||||
} as unknown as RoomMember;
|
||||
|
||||
const mockEvent = makeMockGroupCallStateEvent(FAKE_ROOM_ID, FAKE_GROUP_CALL_ID);
|
||||
|
||||
mockRoom = {
|
||||
on: () => {},
|
||||
off: () => {},
|
||||
roomId: FAKE_ROOM_ID,
|
||||
currentState: {
|
||||
getStateEvents: jest.fn().mockReturnValue([makeMockGroupCallStateEvent(
|
||||
FAKE_ROOM_ID, FAKE_GROUP_CALL_ID,
|
||||
)]),
|
||||
getStateEvents: jest.fn((type, key) => {
|
||||
if (type === mockEvent.getType()) {
|
||||
return key === undefined ? [mockEvent] : mockEvent;
|
||||
} else {
|
||||
return key === undefined ? [] : null;
|
||||
}
|
||||
}),
|
||||
},
|
||||
getMember: (userId: string) => userId === FAKE_USER_ID ? mockMember : null,
|
||||
} as unknown as Room;
|
||||
|
||||
(mockClient as any).getRoom = jest.fn().mockReturnValue(mockRoom);
|
||||
@@ -211,27 +221,6 @@ describe('Group Call Event Handler', function() {
|
||||
);
|
||||
});
|
||||
|
||||
it("sends member events to group calls", async () => {
|
||||
await groupCallEventHandler.start();
|
||||
|
||||
const mockGroupCall = {
|
||||
onMemberStateChanged: jest.fn(),
|
||||
};
|
||||
|
||||
groupCallEventHandler.groupCalls.set(FAKE_ROOM_ID, mockGroupCall as unknown as GroupCall);
|
||||
|
||||
const mockStateEvent = makeMockGroupCallMemberStateEvent(FAKE_ROOM_ID, FAKE_GROUP_CALL_ID);
|
||||
|
||||
mockClient.emitRoomState(
|
||||
mockStateEvent,
|
||||
{
|
||||
roomId: FAKE_ROOM_ID,
|
||||
} as unknown as RoomState,
|
||||
);
|
||||
|
||||
expect(mockGroupCall.onMemberStateChanged).toHaveBeenCalledWith(mockStateEvent);
|
||||
});
|
||||
|
||||
describe("ignoring invalid group call state events", () => {
|
||||
let mockClientEmit: jest.Func;
|
||||
|
||||
|
||||
@@ -19,3 +19,38 @@ export enum ReceiptType {
|
||||
FullyRead = "m.fully_read",
|
||||
ReadPrivate = "m.read.private",
|
||||
}
|
||||
|
||||
export const MAIN_ROOM_TIMELINE = "main";
|
||||
|
||||
export interface Receipt {
|
||||
ts: number;
|
||||
thread_id?: string;
|
||||
}
|
||||
|
||||
export interface WrappedReceipt {
|
||||
eventId: string;
|
||||
data: Receipt;
|
||||
}
|
||||
|
||||
export interface CachedReceipt {
|
||||
type: ReceiptType;
|
||||
userId: string;
|
||||
data: Receipt;
|
||||
}
|
||||
|
||||
export type ReceiptCache = {[eventId: string]: CachedReceipt[]};
|
||||
|
||||
export interface ReceiptContent {
|
||||
[eventId: string]: {
|
||||
[key in ReceiptType]: {
|
||||
[userId: string]: Receipt;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// We will only hold a synthetic receipt if we do not have a real receipt or the synthetic is newer.
|
||||
export type Receipts = {
|
||||
[receiptType: string]: {
|
||||
[userId: string]: [WrappedReceipt | null, WrappedReceipt | null]; // Pair<real receipt, synthetic receipt> (both nullable)
|
||||
};
|
||||
};
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@ import { EventEmitter } from "events";
|
||||
import { ListenerMap, TypedEventEmitter } from "./models/typed-event-emitter";
|
||||
|
||||
export class ReEmitter {
|
||||
constructor(private readonly target: EventEmitter) {}
|
||||
public constructor(private readonly target: EventEmitter) {}
|
||||
|
||||
// Map from emitter to event name to re-emitter
|
||||
private reEmitters = new Map<EventEmitter, Map<string, (...args: any[]) => void>>();
|
||||
@@ -38,7 +38,7 @@ export class ReEmitter {
|
||||
// We include the source as the last argument for event handlers which may need it,
|
||||
// such as read receipt listeners on the client class which won't have the context
|
||||
// of the room.
|
||||
const forSource = (...args: any[]) => {
|
||||
const forSource = (...args: any[]): void => {
|
||||
// EventEmitter special cases 'error' to make the emit function throw if no
|
||||
// handler is attached, which sort of makes sense for making sure that something
|
||||
// handles an error, but for re-emitting, there could be a listener on the original
|
||||
@@ -74,7 +74,7 @@ export class TypedReEmitter<
|
||||
Events extends string,
|
||||
Arguments extends ListenerMap<Events>,
|
||||
> extends ReEmitter {
|
||||
constructor(target: TypedEventEmitter<Events, Arguments>) {
|
||||
public constructor(target: TypedEventEmitter<Events, Arguments>) {
|
||||
super(target);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export class ToDeviceMessageQueue {
|
||||
private retryTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
private retryAttempts = 0;
|
||||
|
||||
constructor(private client: MatrixClient) {
|
||||
public constructor(private client: MatrixClient) {
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
|
||||
+46
-45
@@ -195,7 +195,7 @@ import { MediaHandler } from "./webrtc/mediaHandler";
|
||||
import { GroupCallEventHandler } from "./webrtc/groupCallEventHandler";
|
||||
import { LoginTokenPostResponse, ILoginFlowsResponse, IRefreshTokenResponse, SSOAction } from "./@types/auth";
|
||||
import { TypedEventEmitter } from "./models/typed-event-emitter";
|
||||
import { ReceiptType } from "./@types/read_receipts";
|
||||
import { MAIN_ROOM_TIMELINE, ReceiptType } from "./@types/read_receipts";
|
||||
import { MSC3575SlidingSyncRequest, MSC3575SlidingSyncResponse, SlidingSync } from "./sliding-sync";
|
||||
import { SlidingSyncSdk } from "./sliding-sync-sdk";
|
||||
import {
|
||||
@@ -210,7 +210,6 @@ import { MBeaconInfoEventContent, M_BEACON_INFO } from "./@types/beacon";
|
||||
import { UnstableValue } from "./NamespacedValue";
|
||||
import { ToDeviceMessageQueue } from "./ToDeviceMessageQueue";
|
||||
import { ToDeviceBatch } from "./models/ToDeviceMessage";
|
||||
import { MAIN_ROOM_TIMELINE } from "./models/read-receipt";
|
||||
import { IgnoredInvites } from "./models/invites-ignorer";
|
||||
import { UIARequest, UIAResponse } from "./@types/uia";
|
||||
import { LocalNotificationSettings } from "./@types/local_notifications";
|
||||
@@ -897,6 +896,7 @@ export type EmittedEvents = ClientEvent
|
||||
| CallEvent // re-emitted by call.ts using Object.values
|
||||
| CallEventHandlerEvent.Incoming
|
||||
| GroupCallEventHandlerEvent.Incoming
|
||||
| GroupCallEventHandlerEvent.Outgoing
|
||||
| GroupCallEventHandlerEvent.Ended
|
||||
| GroupCallEventHandlerEvent.Participants
|
||||
| HttpApiEvent.SessionLoggedOut
|
||||
@@ -1011,7 +1011,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
// A manager for determining which invites should be ignored.
|
||||
public readonly ignoredInvites: IgnoredInvites;
|
||||
|
||||
constructor(opts: IMatrixClientCreateOpts) {
|
||||
public constructor(opts: IMatrixClientCreateOpts) {
|
||||
super();
|
||||
|
||||
opts.baseUrl = utils.ensureNoTrailingSlash(opts.baseUrl);
|
||||
@@ -1233,7 +1233,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
// shallow-copy the opts dict before modifying and storing it
|
||||
this.clientOpts = Object.assign({}, opts) as IStoredClientOpts;
|
||||
this.clientOpts.crypto = this.crypto;
|
||||
this.clientOpts.canResetEntireTimeline = (roomId) => {
|
||||
this.clientOpts.canResetEntireTimeline = (roomId): boolean => {
|
||||
if (!this.canResetTimelineCallback) {
|
||||
return false;
|
||||
}
|
||||
@@ -1260,7 +1260,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* High level helper method to stop the client from polling and allow a
|
||||
* clean shutdown.
|
||||
*/
|
||||
public stopClient() {
|
||||
public stopClient(): void {
|
||||
this.crypto?.stop(); // crypto might have been initialised even if the client wasn't fully started
|
||||
|
||||
if (!this.clientRunning) return; // already stopped
|
||||
@@ -1537,7 +1537,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* when creating the client.
|
||||
* @param {boolean} force True to force use of TURN servers
|
||||
*/
|
||||
public setForceTURN(force: boolean) {
|
||||
public setForceTURN(force: boolean): void {
|
||||
this.forceTURN = force;
|
||||
}
|
||||
|
||||
@@ -1545,7 +1545,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* Set whether to advertise transfer support to other parties on Matrix calls.
|
||||
* @param {boolean} support True to advertise the 'm.call.transferee' capability
|
||||
*/
|
||||
public setSupportsCallTransfer(support: boolean) {
|
||||
public setSupportsCallTransfer(support: boolean): void {
|
||||
this.supportsCallTransfer = support;
|
||||
}
|
||||
|
||||
@@ -1679,7 +1679,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* and may change without warning.</b>
|
||||
* @param {boolean} guest True if this is a guest account.
|
||||
*/
|
||||
public setGuest(guest: boolean) {
|
||||
public setGuest(guest: boolean): void {
|
||||
// EXPERIMENTAL:
|
||||
// If the token is a macaroon, it should be encoded in it that it is a 'guest'
|
||||
// access token, which means that the SDK can determine this entirely without
|
||||
@@ -1722,7 +1722,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @param {EventTimelineSet} set
|
||||
*/
|
||||
public setNotifTimelineSet(set: EventTimelineSet) {
|
||||
public setNotifTimelineSet(set: EventTimelineSet): void {
|
||||
this.notifTimelineSet = set;
|
||||
}
|
||||
|
||||
@@ -2280,11 +2280,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* send, in order to speed up sending of the message.
|
||||
* @param {module:models/room} room the room the event is in
|
||||
*/
|
||||
public prepareToEncrypt(room: Room) {
|
||||
public prepareToEncrypt(room: Room): void {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
return this.crypto.prepareToEncrypt(room);
|
||||
this.crypto.prepareToEncrypt(room);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2325,7 +2325,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* auth data as an object. Can be called multiple times, first with an empty
|
||||
* authDict, to obtain the flows.
|
||||
*/
|
||||
public bootstrapCrossSigning(opts: IBootstrapCrossSigningOpts) {
|
||||
public bootstrapCrossSigning(opts: IBootstrapCrossSigningOpts): Promise<void> {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
@@ -2353,11 +2353,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @param {boolean} val True to trust cross-signed devices
|
||||
*/
|
||||
public setCryptoTrustCrossSignedDevices(val: boolean) {
|
||||
public setCryptoTrustCrossSignedDevices(val: boolean): void {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
return this.crypto.setCryptoTrustCrossSignedDevices(val);
|
||||
this.crypto.setCryptoTrustCrossSignedDevices(val);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2498,7 +2498,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param {Array} keys The IDs of the keys to use to encrypt the secret or null/undefined
|
||||
* to use the default (will throw if no default key is set).
|
||||
*/
|
||||
public storeSecret(name: string, secret: string, keys?: string[]) {
|
||||
public storeSecret(name: string, secret: string, keys?: string[]): Promise<void> {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
@@ -2576,7 +2576,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @param {string} keyId The new default key ID
|
||||
*/
|
||||
public setDefaultSecretStorageKeyId(keyId: string) {
|
||||
public setDefaultSecretStorageKeyId(keyId: string): Promise<void> {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
@@ -2717,7 +2717,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* This should not normally be necessary.
|
||||
*/
|
||||
public forceDiscardSession(roomId: string) {
|
||||
public forceDiscardSession(roomId: string): void {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-End encryption disabled");
|
||||
}
|
||||
@@ -2841,7 +2841,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
/**
|
||||
* Disable backing up of keys.
|
||||
*/
|
||||
public disableKeyBackup() {
|
||||
public disableKeyBackup(): void {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
@@ -3045,7 +3045,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* Marks all group sessions as needing to be backed up and schedules them to
|
||||
* upload in the background as soon as possible.
|
||||
*/
|
||||
public async scheduleAllGroupSessionsForBackup() {
|
||||
public async scheduleAllGroupSessionsForBackup(): Promise<void> {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
@@ -3402,7 +3402,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param {array} userIds a list of users to share with. The keys will be sent to
|
||||
* all of the user's current devices.
|
||||
*/
|
||||
public async sendSharedHistoryKeys(roomId: string, userIds: string[]) {
|
||||
public async sendSharedHistoryKeys(roomId: string, userIds: string[]): Promise<void> {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
@@ -3630,10 +3630,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
let signPromise: Promise<IThirdPartySigned | void> = Promise.resolve();
|
||||
|
||||
if (opts.inviteSignUrl) {
|
||||
signPromise = this.http.requestOtherUrl<IThirdPartySigned>(
|
||||
Method.Post,
|
||||
new URL(opts.inviteSignUrl), { mxid: this.credentials.userId },
|
||||
);
|
||||
const url = new URL(opts.inviteSignUrl);
|
||||
url.searchParams.set("mxid", this.credentials.userId!);
|
||||
signPromise = this.http.requestOtherUrl<IThirdPartySigned>(Method.Post, url);
|
||||
}
|
||||
|
||||
const queryString: Record<string, string | string[]> = {};
|
||||
@@ -3686,7 +3685,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param {MatrixEvent} event Event to cancel
|
||||
* @throws Error if the event is not in QUEUED, NOT_SENT or ENCRYPTING state
|
||||
*/
|
||||
public cancelPendingEvent(event: MatrixEvent) {
|
||||
public cancelPendingEvent(event: MatrixEvent): void {
|
||||
if (![EventStatus.QUEUED, EventStatus.NOT_SENT, EventStatus.ENCRYPTING].includes(event.status!)) {
|
||||
throw new Error("cannot cancel an event with status " + event.status);
|
||||
}
|
||||
@@ -3835,7 +3834,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
public async unstable_createLiveBeacon(
|
||||
roomId: Room["roomId"],
|
||||
beaconInfoContent: MBeaconInfoEventContent,
|
||||
) {
|
||||
): Promise<ISendEventResponse> {
|
||||
return this.unstable_setLiveBeacon(roomId, beaconInfoContent);
|
||||
}
|
||||
|
||||
@@ -3850,7 +3849,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
public async unstable_setLiveBeacon(
|
||||
roomId: string,
|
||||
beaconInfoContent: MBeaconInfoEventContent,
|
||||
) {
|
||||
): Promise<ISendEventResponse> {
|
||||
return this.sendStateEvent(roomId, M_BEACON_INFO.name, beaconInfoContent, this.getUserId()!);
|
||||
}
|
||||
|
||||
@@ -4122,7 +4121,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
return this.isRoomEncrypted(roomId) ? EventType.RoomMessageEncrypted : eventType;
|
||||
}
|
||||
|
||||
protected updatePendingEventStatus(room: Room | null, event: MatrixEvent, newStatus: EventStatus) {
|
||||
protected updatePendingEventStatus(room: Room | null, event: MatrixEvent, newStatus: EventStatus): void {
|
||||
if (room) {
|
||||
room.updatePendingEvent(event, newStatus);
|
||||
} else {
|
||||
@@ -4945,12 +4944,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
const populationResults: { [roomId: string]: Error } = {};
|
||||
const promises: Promise<any>[] = [];
|
||||
|
||||
const doLeave = (roomId: string) => {
|
||||
const doLeave = (roomId: string): Promise<void> => {
|
||||
return this.leave(roomId).then(() => {
|
||||
delete populationResults[roomId];
|
||||
}).catch((err) => {
|
||||
// suppress error
|
||||
populationResults[roomId] = err;
|
||||
return null; // suppress error
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5831,8 +5830,14 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
).then(async (res) => {
|
||||
const mapper = this.getEventMapper();
|
||||
const matrixEvents = res.chunk.map(mapper);
|
||||
for (const event of matrixEvents) {
|
||||
await eventTimeline.getTimelineSet()?.thread?.processEvent(event);
|
||||
|
||||
// Process latest events first
|
||||
for (const event of matrixEvents.slice().reverse()) {
|
||||
await thread?.processEvent(event);
|
||||
const sender = event.getSender()!;
|
||||
if (!backwards || thread?.getEventReadUpTo(sender) === null) {
|
||||
room.addLocalEchoReceipt(sender, event, ReceiptType.Read);
|
||||
}
|
||||
}
|
||||
|
||||
const newToken = res.next_batch;
|
||||
@@ -5905,7 +5910,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* Reset the notifTimelineSet entirely, paginating in some historical notifs as
|
||||
* a starting point for subsequent pagination.
|
||||
*/
|
||||
public resetNotifTimelineSet() {
|
||||
public resetNotifTimelineSet(): void {
|
||||
if (!this.notifTimelineSet) {
|
||||
return;
|
||||
}
|
||||
@@ -5950,7 +5955,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
/**
|
||||
* Stop any ongoing room peeking.
|
||||
*/
|
||||
public stopPeeking() {
|
||||
public stopPeeking(): void {
|
||||
if (this.peekSync) {
|
||||
this.peekSync.stopPeeking();
|
||||
this.peekSync = null;
|
||||
@@ -6684,7 +6689,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @param {boolean} allow
|
||||
*/
|
||||
public setFallbackICEServerAllowed(allow: boolean) {
|
||||
public setFallbackICEServerAllowed(allow: boolean): void {
|
||||
this.fallbackICEServerAllowed = allow;
|
||||
}
|
||||
|
||||
@@ -7025,7 +7030,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* Default: returns false.
|
||||
* @param {Function} cb The callback which will be invoked.
|
||||
*/
|
||||
public setCanResetTimelineCallback(cb: ResetTimelineCallback) {
|
||||
public setCanResetTimelineCallback(cb: ResetTimelineCallback): void {
|
||||
this.canResetTimelineCallback = cb;
|
||||
}
|
||||
|
||||
@@ -7165,7 +7170,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* Set the identity server URL of this client
|
||||
* @param {string} url New identity server URL
|
||||
*/
|
||||
public setIdentityServerUrl(url: string) {
|
||||
public setIdentityServerUrl(url: string): void {
|
||||
this.idBaseUrl = utils.ensureNoTrailingSlash(url);
|
||||
this.http.setIdBaseUrl(this.idBaseUrl);
|
||||
}
|
||||
@@ -7182,7 +7187,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* Set the access token associated with this account.
|
||||
* @param {string} token The new access token.
|
||||
*/
|
||||
public setAccessToken(token: string) {
|
||||
public setAccessToken(token: string): void {
|
||||
this.http.opts.accessToken = token;
|
||||
}
|
||||
|
||||
@@ -9339,12 +9344,8 @@ export function fixNotificationCountOnDecryption(cli: MatrixClient, event: Matri
|
||||
if (!room || !cli.getUserId()) return;
|
||||
|
||||
const isThreadEvent = !!event.threadRootId && !event.isThreadRoot;
|
||||
const currentCount = (isThreadEvent
|
||||
? room.getThreadUnreadNotificationCount(
|
||||
event.threadRootId,
|
||||
NotificationCountType.Highlight,
|
||||
)
|
||||
: room.getUnreadNotificationCount(NotificationCountType.Highlight)) ?? 0;
|
||||
|
||||
const currentCount = room.getUnreadCountForEventContext(NotificationCountType.Highlight, event);
|
||||
|
||||
// Ensure the unread counts are kept up to date if the event is encrypted
|
||||
// We also want to make sure that the notification count goes up if we already
|
||||
@@ -9376,7 +9377,7 @@ export function fixNotificationCountOnDecryption(cli: MatrixClient, event: Matri
|
||||
// Fix 'Mentions Only' rooms from not having the right badge count
|
||||
const totalCount = (isThreadEvent
|
||||
? room.getThreadUnreadNotificationCount(event.threadRootId, NotificationCountType.Total)
|
||||
: room.getUnreadNotificationCount(NotificationCountType.Total)) ?? 0;
|
||||
: room.getRoomUnreadNotificationCount(NotificationCountType.Total)) ?? 0;
|
||||
|
||||
if (totalCount < newCount) {
|
||||
if (isThreadEvent) {
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
LegacyLocationEventContent,
|
||||
} from "./@types/location";
|
||||
import { MRoomTopicEventContent, MTopicContent, M_TOPIC } from "./@types/topic";
|
||||
import { IContent } from "./models/event";
|
||||
|
||||
/**
|
||||
* Generates the content for a HTML Message event
|
||||
@@ -40,7 +41,7 @@ import { MRoomTopicEventContent, MTopicContent, M_TOPIC } from "./@types/topic";
|
||||
* @param {string} htmlBody the HTML representation of the message
|
||||
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
|
||||
*/
|
||||
export function makeHtmlMessage(body: string, htmlBody: string) {
|
||||
export function makeHtmlMessage(body: string, htmlBody: string): IContent {
|
||||
return {
|
||||
msgtype: MsgType.Text,
|
||||
format: "org.matrix.custom.html",
|
||||
@@ -55,7 +56,7 @@ export function makeHtmlMessage(body: string, htmlBody: string) {
|
||||
* @param {string} htmlBody the HTML representation of the notice
|
||||
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
|
||||
*/
|
||||
export function makeHtmlNotice(body: string, htmlBody: string) {
|
||||
export function makeHtmlNotice(body: string, htmlBody: string): IContent {
|
||||
return {
|
||||
msgtype: MsgType.Notice,
|
||||
format: "org.matrix.custom.html",
|
||||
@@ -70,7 +71,7 @@ export function makeHtmlNotice(body: string, htmlBody: string) {
|
||||
* @param {string} htmlBody the HTML representation of the emote
|
||||
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
|
||||
*/
|
||||
export function makeHtmlEmote(body: string, htmlBody: string) {
|
||||
export function makeHtmlEmote(body: string, htmlBody: string): IContent {
|
||||
return {
|
||||
msgtype: MsgType.Emote,
|
||||
format: "org.matrix.custom.html",
|
||||
@@ -84,7 +85,7 @@ export function makeHtmlEmote(body: string, htmlBody: string) {
|
||||
* @param {string} body the plaintext body of the emote
|
||||
* @returns {{msgtype: string, body: string}}
|
||||
*/
|
||||
export function makeTextMessage(body: string) {
|
||||
export function makeTextMessage(body: string): IContent {
|
||||
return {
|
||||
msgtype: MsgType.Text,
|
||||
body: body,
|
||||
@@ -96,7 +97,7 @@ export function makeTextMessage(body: string) {
|
||||
* @param {string} body the plaintext body of the notice
|
||||
* @returns {{msgtype: string, body: string}}
|
||||
*/
|
||||
export function makeNotice(body: string) {
|
||||
export function makeNotice(body: string): IContent {
|
||||
return {
|
||||
msgtype: MsgType.Notice,
|
||||
body: body,
|
||||
@@ -108,7 +109,7 @@ export function makeNotice(body: string) {
|
||||
* @param {string} body the plaintext body of the emote
|
||||
* @returns {{msgtype: string, body: string}}
|
||||
*/
|
||||
export function makeEmoteMessage(body: string) {
|
||||
export function makeEmoteMessage(body: string): IContent {
|
||||
return {
|
||||
msgtype: MsgType.Emote,
|
||||
body: body,
|
||||
|
||||
@@ -21,7 +21,7 @@ limitations under the License.
|
||||
|
||||
import { PkSigning } from "@matrix-org/olm";
|
||||
|
||||
import { decodeBase64, encodeBase64, pkSign, pkVerify } from './olmlib';
|
||||
import { decodeBase64, encodeBase64, IObject, pkSign, pkVerify } from './olmlib';
|
||||
import { logger } from '../logger';
|
||||
import { IndexedDBCryptoStore } from '../crypto/store/indexeddb-crypto-store';
|
||||
import { decryptAES, encryptAES } from './aes';
|
||||
@@ -74,7 +74,7 @@ export class CrossSigningInfo {
|
||||
* Requires getCrossSigningKey and saveCrossSigningKeys
|
||||
* @param {object} cacheCallbacks Callbacks used to interact with the cache
|
||||
*/
|
||||
constructor(
|
||||
public constructor(
|
||||
public readonly userId: string,
|
||||
private callbacks: ICryptoCallbacks = {},
|
||||
private cacheCallbacks: ICacheCallbacks = {},
|
||||
@@ -175,7 +175,7 @@ export class CrossSigningInfo {
|
||||
// check what SSSS keys have encrypted the master key (if any)
|
||||
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>) {
|
||||
function intersect(s: Record<string, ISecretStorageKeyInfo>): void {
|
||||
for (const k of Object.keys(stored)) {
|
||||
if (!s[k]) {
|
||||
delete stored[k];
|
||||
@@ -586,7 +586,14 @@ export class CrossSigningInfo {
|
||||
}
|
||||
}
|
||||
|
||||
function deviceToObject(device: DeviceInfo, userId: string) {
|
||||
interface DeviceObject extends IObject {
|
||||
algorithms: string[];
|
||||
keys: Record<string, string>;
|
||||
device_id: string;
|
||||
user_id: string;
|
||||
}
|
||||
|
||||
function deviceToObject(device: DeviceInfo, userId: string): DeviceObject {
|
||||
return {
|
||||
algorithms: device.algorithms,
|
||||
keys: device.keys,
|
||||
@@ -606,7 +613,7 @@ export enum CrossSigningLevel {
|
||||
* Represents the ways in which we trust a user
|
||||
*/
|
||||
export class UserTrustLevel {
|
||||
constructor(
|
||||
public constructor(
|
||||
private readonly crossSigningVerified: boolean,
|
||||
private readonly crossSigningVerifiedBefore: boolean,
|
||||
private readonly tofu: boolean,
|
||||
@@ -646,7 +653,7 @@ export class UserTrustLevel {
|
||||
* Represents the ways in which we trust a device
|
||||
*/
|
||||
export class DeviceTrustLevel {
|
||||
constructor(
|
||||
public constructor(
|
||||
public readonly crossSigningVerified: boolean,
|
||||
public readonly tofu: boolean,
|
||||
private readonly localVerified: boolean,
|
||||
@@ -775,7 +782,7 @@ export async function requestKeysDuringVerification(
|
||||
// CrossSigningInfo.getCrossSigningKey() to validate/cache
|
||||
const crossSigning = new CrossSigningInfo(
|
||||
original.userId,
|
||||
{ getCrossSigningKey: async (type) => {
|
||||
{ getCrossSigningKey: async (type): Promise<Uint8Array> => {
|
||||
logger.debug("Cross-signing: requesting secret", type, deviceId);
|
||||
const { promise } = client.requestSecret(
|
||||
`m.cross_signing.${type}`, [deviceId],
|
||||
@@ -801,7 +808,7 @@ export async function requestKeysDuringVerification(
|
||||
});
|
||||
|
||||
// also request and cache the key backup key
|
||||
const backupKeyPromise = (async () => {
|
||||
const backupKeyPromise = (async (): Promise<void> => {
|
||||
const cachedKey = await client.crypto!.getSessionBackupPrivateKey();
|
||||
if (!cachedKey) {
|
||||
logger.info("No cached backup key found. Requesting...");
|
||||
|
||||
@@ -102,7 +102,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
|
||||
private readonly serialiser: DeviceListUpdateSerialiser;
|
||||
|
||||
constructor(
|
||||
public constructor(
|
||||
baseApis: MatrixClient,
|
||||
private readonly cryptoStore: CryptoStore,
|
||||
olmDevice: OlmDevice,
|
||||
@@ -117,7 +117,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
/**
|
||||
* Load the device tracking state from storage
|
||||
*/
|
||||
public async load() {
|
||||
public async load(): Promise<void> {
|
||||
await this.cryptoStore.doTxn(
|
||||
'readonly', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
|
||||
this.cryptoStore.getEndToEndDeviceData(txn, (deviceData) => {
|
||||
@@ -150,7 +150,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
}
|
||||
}
|
||||
|
||||
public stop() {
|
||||
public stop(): void {
|
||||
if (this.saveTimer !== null) {
|
||||
clearTimeout(this.saveTimer);
|
||||
}
|
||||
@@ -693,7 +693,7 @@ class DeviceListUpdateSerialiser {
|
||||
* @param {object} olmDevice The Olm Device
|
||||
* @param {object} deviceList The device list object, the device list to be updated
|
||||
*/
|
||||
constructor(
|
||||
public constructor(
|
||||
private readonly baseApis: MatrixClient,
|
||||
private readonly olmDevice: OlmDevice,
|
||||
private readonly deviceList: DeviceList,
|
||||
|
||||
@@ -61,7 +61,7 @@ export class EncryptionSetupBuilder {
|
||||
* @param {Object.<String, MatrixEvent>} accountData pre-existing account data, will only be read, not written.
|
||||
* @param {CryptoCallbacks} delegateCryptoCallbacks crypto callbacks to delegate to if the key isn't in cache yet
|
||||
*/
|
||||
constructor(accountData: Record<string, MatrixEvent>, delegateCryptoCallbacks?: ICryptoCallbacks) {
|
||||
public constructor(accountData: Record<string, MatrixEvent>, delegateCryptoCallbacks?: ICryptoCallbacks) {
|
||||
this.accountDataClientAdapter = new AccountDataClientAdapter(accountData);
|
||||
this.crossSigningCallbacks = new CrossSigningCallbacks();
|
||||
this.ssssCryptoCallbacks = new SSSSCryptoCallbacks(delegateCryptoCallbacks);
|
||||
@@ -192,7 +192,7 @@ export class EncryptionSetupOperation {
|
||||
* @param {Object} keyBackupInfo
|
||||
* @param {Object} keySignatures
|
||||
*/
|
||||
constructor(
|
||||
public constructor(
|
||||
private readonly accountData: Map<string, object>,
|
||||
private readonly crossSigningKeys?: ICrossSigningKeys,
|
||||
private readonly keyBackupInfo?: IKeyBackupInfo,
|
||||
@@ -272,7 +272,7 @@ class AccountDataClientAdapter
|
||||
/**
|
||||
* @param {Object.<String, MatrixEvent>} existingValues existing account data
|
||||
*/
|
||||
constructor(private readonly existingValues: Record<string, MatrixEvent>) {
|
||||
public constructor(private readonly existingValues: Record<string, MatrixEvent>) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@ class CrossSigningCallbacks implements ICryptoCallbacks, ICacheCallbacks {
|
||||
return Promise.resolve(this.privateKeys.get(type) ?? null);
|
||||
}
|
||||
|
||||
public saveCrossSigningKeys(privateKeys: Record<string, Uint8Array>) {
|
||||
public saveCrossSigningKeys(privateKeys: Record<string, Uint8Array>): void {
|
||||
for (const [type, privateKey] of Object.entries(privateKeys)) {
|
||||
this.privateKeys.set(type, privateKey);
|
||||
}
|
||||
@@ -356,7 +356,7 @@ class CrossSigningCallbacks implements ICryptoCallbacks, ICacheCallbacks {
|
||||
class SSSSCryptoCallbacks {
|
||||
private readonly privateKeys = new Map<string, Uint8Array>();
|
||||
|
||||
constructor(private readonly delegateCryptoCallbacks?: ICryptoCallbacks) {}
|
||||
public constructor(private readonly delegateCryptoCallbacks?: ICryptoCallbacks) {}
|
||||
|
||||
public async getSecretStorageKey(
|
||||
{ keys }: { keys: Record<string, ISecretStorageKeyInfo> },
|
||||
|
||||
+12
-12
@@ -177,13 +177,13 @@ export class OlmDevice {
|
||||
// Used by olm to serialise prekey message decryptions
|
||||
public olmPrekeyPromise: Promise<any> = Promise.resolve(); // set by consumers
|
||||
|
||||
constructor(private readonly cryptoStore: CryptoStore) {
|
||||
public constructor(private readonly cryptoStore: CryptoStore) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {array} The version of Olm.
|
||||
*/
|
||||
static getOlmVersion(): [number, number, number] {
|
||||
public static getOlmVersion(): [number, number, number] {
|
||||
return global.Olm.get_library_version();
|
||||
}
|
||||
|
||||
@@ -916,6 +916,7 @@ export class OlmDevice {
|
||||
}
|
||||
|
||||
public async recordSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void> {
|
||||
logger.info(`Recording problem on olm session with ${deviceKey} of type ${type}. Recreating: ${fixed}`);
|
||||
await this.cryptoStore.storeEndToEndSessionProblem(deviceKey, type, fixed);
|
||||
}
|
||||
|
||||
@@ -1139,17 +1140,14 @@ export class OlmDevice {
|
||||
}
|
||||
|
||||
if (existingSession) {
|
||||
logger.log(
|
||||
"Update for megolm session "
|
||||
+ senderKey + "/" + sessionId,
|
||||
);
|
||||
logger.log(`Update for megolm session ${senderKey}|${sessionId}`);
|
||||
if (existingSession.first_known_index() <= session.first_known_index()) {
|
||||
if (!existingSessionData!.untrusted || extraSessionData.untrusted) {
|
||||
// existing session has less-than-or-equal index
|
||||
// (i.e. can decrypt at least as much), and the
|
||||
// new session's trust does not win over the old
|
||||
// session's trust, so keep it
|
||||
logger.log(`Keeping existing megolm session ${sessionId}`);
|
||||
logger.log(`Keeping existing megolm session ${senderKey}|${sessionId}`);
|
||||
return;
|
||||
}
|
||||
if (existingSession.first_known_index() < session.first_known_index()) {
|
||||
@@ -1164,7 +1162,7 @@ export class OlmDevice {
|
||||
) {
|
||||
logger.info(
|
||||
"Upgrading trust of existing megolm session " +
|
||||
sessionId + " based on newly-received trusted session",
|
||||
`${senderKey}|${sessionId} based on newly-received trusted session`,
|
||||
);
|
||||
existingSessionData!.untrusted = false;
|
||||
this.cryptoStore.storeEndToEndInboundGroupSession(
|
||||
@@ -1172,7 +1170,7 @@ export class OlmDevice {
|
||||
);
|
||||
} else {
|
||||
logger.warn(
|
||||
"Newly-received megolm session " + sessionId +
|
||||
`Newly-received megolm session ${senderKey}|$sessionId}` +
|
||||
" does not match existing session! Keeping existing session",
|
||||
);
|
||||
}
|
||||
@@ -1183,8 +1181,8 @@ export class OlmDevice {
|
||||
}
|
||||
|
||||
logger.info(
|
||||
"Storing megolm session " + senderKey + "/" + sessionId +
|
||||
" with first index " + session.first_known_index(),
|
||||
`Storing megolm session ${senderKey}|${sessionId} with first index `+
|
||||
session.first_known_index(),
|
||||
);
|
||||
|
||||
const sessionData = Object.assign({}, extraSessionData, {
|
||||
@@ -1517,7 +1515,9 @@ export class OlmDevice {
|
||||
});
|
||||
}
|
||||
|
||||
async getSharedHistoryInboundGroupSessions(roomId: string): Promise<[senderKey: string, sessionId: string][]> {
|
||||
public async getSharedHistoryInboundGroupSessions(
|
||||
roomId: string,
|
||||
): Promise<[senderKey: string, sessionId: string][]> {
|
||||
let result: Promise<[senderKey: string, sessionId: string][]>;
|
||||
await this.cryptoStore.doTxn(
|
||||
'readonly', [
|
||||
|
||||
@@ -102,7 +102,7 @@ export class OutgoingRoomKeyRequestManager {
|
||||
|
||||
private clientRunning = true;
|
||||
|
||||
constructor(
|
||||
public constructor(
|
||||
private readonly baseApis: MatrixClient,
|
||||
private readonly deviceId: string,
|
||||
private readonly cryptoStore: CryptoStore,
|
||||
@@ -352,7 +352,7 @@ export class OutgoingRoomKeyRequestManager {
|
||||
return;
|
||||
}
|
||||
|
||||
const startSendingOutgoingRoomKeyRequests = () => {
|
||||
const startSendingOutgoingRoomKeyRequests = (): void => {
|
||||
if (this.sendOutgoingRoomKeyRequestsRunning) {
|
||||
throw new Error("RoomKeyRequestSend already in progress!");
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export class RoomList {
|
||||
// Object of roomId -> room e2e info object (body of the m.room.encryption event)
|
||||
private roomEncryption: Record<string, IRoomEncryption> = {};
|
||||
|
||||
constructor(private readonly cryptoStore?: CryptoStore) {}
|
||||
public constructor(private readonly cryptoStore?: CryptoStore) {}
|
||||
|
||||
public async init(): Promise<void> {
|
||||
await this.cryptoStore!.doTxn(
|
||||
|
||||
@@ -78,7 +78,7 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
|
||||
// as you don't request any secrets.
|
||||
// A better solution would probably be to split this class up into secret storage and
|
||||
// secret sharing which are really two separate things, even though they share an MSC.
|
||||
constructor(
|
||||
public constructor(
|
||||
private readonly accountDataAdapter: IAccountDataClient,
|
||||
private readonly cryptoCallbacks: ICryptoCallbacks,
|
||||
private readonly baseApis: B,
|
||||
@@ -381,7 +381,7 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
|
||||
const deferred = defer<string>();
|
||||
this.requests.set(requestId, { name, devices, deferred });
|
||||
|
||||
const cancel = (reason: string) => {
|
||||
const cancel = (reason: string): void => {
|
||||
// send cancellation event
|
||||
const cancelData = {
|
||||
action: "request_cancellation",
|
||||
|
||||
@@ -78,7 +78,7 @@ export abstract class EncryptionAlgorithm {
|
||||
protected readonly baseApis: MatrixClient;
|
||||
protected readonly roomId?: string;
|
||||
|
||||
constructor(params: IParams) {
|
||||
public constructor(params: IParams) {
|
||||
this.userId = params.userId;
|
||||
this.deviceId = params.deviceId;
|
||||
this.crypto = params.crypto;
|
||||
@@ -150,7 +150,7 @@ export abstract class DecryptionAlgorithm {
|
||||
protected readonly baseApis: MatrixClient;
|
||||
protected readonly roomId?: string;
|
||||
|
||||
constructor(params: DecryptionClassParams) {
|
||||
public constructor(params: DecryptionClassParams) {
|
||||
this.userId = params.userId;
|
||||
this.crypto = params.crypto;
|
||||
this.olmDevice = params.olmDevice;
|
||||
@@ -242,7 +242,7 @@ export abstract class DecryptionAlgorithm {
|
||||
export class DecryptionError extends Error {
|
||||
public readonly detailedString: string;
|
||||
|
||||
constructor(public readonly code: string, msg: string, details?: Record<string, string | Error>) {
|
||||
public constructor(public readonly code: string, msg: string, details?: Record<string, string | Error>) {
|
||||
super(msg);
|
||||
this.code = code;
|
||||
this.name = 'DecryptionError';
|
||||
@@ -272,7 +272,7 @@ function detailedStringForDecryptionError(err: DecryptionError, details?: Record
|
||||
* @extends Error
|
||||
*/
|
||||
export class UnknownDeviceError extends Error {
|
||||
constructor(
|
||||
public constructor(
|
||||
msg: string,
|
||||
public readonly devices: Record<string, Record<string, object>>,
|
||||
public event?: MatrixEvent,
|
||||
|
||||
@@ -136,7 +136,7 @@ class OutboundSessionInfo {
|
||||
public sharedWithDevices: Record<string, Record<string, SharedWithData>> = {};
|
||||
public blockedDevicesNotified: Record<string, Record<string, boolean>> = {};
|
||||
|
||||
constructor(public readonly sessionId: string, public readonly sharedHistory = false) {
|
||||
public constructor(public readonly sessionId: string, public readonly sharedHistory = false) {
|
||||
this.creationTime = new Date().getTime();
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
|
||||
protected readonly roomId: string;
|
||||
|
||||
constructor(params: IParams & Required<Pick<IParams, "roomId">>) {
|
||||
public constructor(params: IParams & Required<Pick<IParams, "roomId">>) {
|
||||
super(params);
|
||||
this.roomId = params.roomId;
|
||||
|
||||
@@ -347,7 +347,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
singleOlmCreationPhase: boolean,
|
||||
blocked: IBlockedMap,
|
||||
session: OutboundSessionInfo,
|
||||
) {
|
||||
): Promise<void> {
|
||||
// now check if we need to share with any devices
|
||||
const shareMap: Record<string, DeviceInfo[]> = {};
|
||||
|
||||
@@ -386,13 +386,13 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
(async () => {
|
||||
(async (): Promise<void> => {
|
||||
// share keys with devices that we already have a session for
|
||||
logger.debug(`Sharing keys with existing Olm sessions in ${this.roomId}`, olmSessions);
|
||||
await this.shareKeyWithOlmSessions(session, key, payload, olmSessions);
|
||||
logger.debug(`Shared keys with existing Olm sessions in ${this.roomId}`);
|
||||
})(),
|
||||
(async () => {
|
||||
(async (): Promise<void> => {
|
||||
logger.debug(
|
||||
`Sharing keys (start phase 1) with new Olm sessions in ${this.roomId}`,
|
||||
devicesWithoutSession,
|
||||
@@ -415,7 +415,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
if (!singleOlmCreationPhase && (Date.now() - start < 10000)) {
|
||||
// perform the second phase of olm session creation if requested,
|
||||
// and if the first phase didn't take too long
|
||||
(async () => {
|
||||
(async (): Promise<void> => {
|
||||
// Retry sending keys to devices that we were unable to establish
|
||||
// an olm session for. This time, we use a longer timeout, but we
|
||||
// do this in the background and don't block anything else while we
|
||||
@@ -452,7 +452,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
}
|
||||
logger.debug(`Shared keys (all phases done) with new Olm sessions in ${this.roomId}`);
|
||||
})(),
|
||||
(async () => {
|
||||
(async (): Promise<void> => {
|
||||
logger.debug(`There are ${Object.entries(blocked).length} blocked devices in ${this.roomId}`,
|
||||
Object.entries(blocked));
|
||||
|
||||
@@ -696,28 +696,27 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
): Promise<void> {
|
||||
const obSessionInfo = this.outboundSessions[sessionId];
|
||||
if (!obSessionInfo) {
|
||||
logger.debug(`megolm session ${sessionId} not found: not re-sharing keys`);
|
||||
logger.debug(`megolm session ${senderKey}|${sessionId} not found: not re-sharing keys`);
|
||||
return;
|
||||
}
|
||||
|
||||
// The chain index of the key we previously sent this device
|
||||
if (obSessionInfo.sharedWithDevices[userId] === undefined) {
|
||||
logger.debug(`megolm session ${sessionId} never shared with user ${userId}`);
|
||||
logger.debug(`megolm session ${senderKey}|${sessionId} never shared with user ${userId}`);
|
||||
return;
|
||||
}
|
||||
const sessionSharedData = obSessionInfo.sharedWithDevices[userId][device.deviceId];
|
||||
if (sessionSharedData === undefined) {
|
||||
logger.debug(
|
||||
"megolm session ID " + sessionId + " never shared with device " +
|
||||
userId + ":" + device.deviceId,
|
||||
`megolm session ${senderKey}|${sessionId} never shared with device ${userId}:${device.deviceId}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sessionSharedData.deviceKey !== device.getIdentityKey()) {
|
||||
logger.warn(
|
||||
`Session has been shared with device ${device.deviceId} but with identity ` +
|
||||
`key ${sessionSharedData.deviceKey}. Key is now ${device.getIdentityKey()}!`,
|
||||
`Megolm session ${senderKey}|${sessionId} has been shared with device ${device.deviceId} but ` +
|
||||
`with identity key ${sessionSharedData.deviceKey}. Key is now ${device.getIdentityKey()}!`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -730,7 +729,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
|
||||
if (!key) {
|
||||
logger.warn(
|
||||
`No inbound session key found for megolm ${sessionId}: not re-sharing keys`,
|
||||
`No inbound session key found for megolm session ${senderKey}|${sessionId}: not re-sharing keys`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -776,7 +775,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
[device.deviceId]: encryptedContent,
|
||||
},
|
||||
});
|
||||
logger.debug(`Re-shared key for megolm session ${sessionId} with ${userId}:${device.deviceId}`);
|
||||
logger.debug(`Re-shared key for megolm session ${senderKey}|${sessionId} with ${userId}:${device.deviceId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -810,7 +809,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
errorDevices: IOlmDevice[],
|
||||
otkTimeout: number,
|
||||
failedServers?: string[],
|
||||
) {
|
||||
): Promise<void> {
|
||||
logger.debug(`Ensuring Olm sessions for devices in ${this.roomId}`);
|
||||
const devicemap = await olmlib.ensureOlmSessionsForDevices(
|
||||
this.olmDevice, this.baseApis, devicesByUser, false, otkTimeout, failedServers,
|
||||
@@ -970,7 +969,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
|
||||
this.encryptionPreparation = {
|
||||
startTime: Date.now(),
|
||||
promise: (async () => {
|
||||
promise: (async (): Promise<void> => {
|
||||
try {
|
||||
logger.debug(`Getting devices in ${this.roomId}`);
|
||||
const [devicesInRoom, blocked] = await this.getDevicesInRoom(room);
|
||||
@@ -1232,7 +1231,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
|
||||
protected readonly roomId: string;
|
||||
|
||||
constructor(params: DecryptionClassParams<IParams & Required<Pick<IParams, "roomId">>>) {
|
||||
public constructor(params: DecryptionClassParams<IParams & Required<Pick<IParams, "roomId">>>) {
|
||||
super(params);
|
||||
this.roomId = params.roomId;
|
||||
}
|
||||
@@ -1312,6 +1311,10 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
content.sender_key, event.getTs() - 120000,
|
||||
);
|
||||
if (problem) {
|
||||
logger.info(
|
||||
`When handling UISI from ${event.getSender()} (sender key ${content.sender_key}): ` +
|
||||
`recent session problem with that sender: ${problem}`,
|
||||
);
|
||||
let problemDescription = PROBLEM_DESCRIPTIONS[problem.type as "no_olm"] || PROBLEM_DESCRIPTIONS.unknown;
|
||||
if (problem.fixed) {
|
||||
problemDescription +=
|
||||
@@ -1902,10 +1905,11 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
public async sendSharedHistoryInboundSessions(devicesByUser: Record<string, DeviceInfo[]>): Promise<void> {
|
||||
await olmlib.ensureOlmSessionsForDevices(this.olmDevice, this.baseApis, devicesByUser);
|
||||
|
||||
logger.log("sendSharedHistoryInboundSessions to users", Object.keys(devicesByUser));
|
||||
|
||||
const sharedHistorySessions = await this.olmDevice.getSharedHistoryInboundGroupSessions(this.roomId);
|
||||
logger.log("shared-history sessions", sharedHistorySessions);
|
||||
logger.log(
|
||||
`Sharing history in ${this.roomId} with users ${Object.keys(devicesByUser)}`,
|
||||
sharedHistorySessions.map(([senderKey, sessionId]) => `${senderKey}|${sessionId}`),
|
||||
);
|
||||
for (const [senderKey, sessionId] of sharedHistorySessions) {
|
||||
const payload = await this.buildKeyForwardingMessage(this.roomId, senderKey, sessionId);
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ export class BackupManager {
|
||||
private sendingBackups: boolean; // Are we currently sending backups?
|
||||
private sessionLastCheckAttemptedTime: Record<string, number> = {}; // When did we last try to check the server for a given session id?
|
||||
|
||||
constructor(private readonly baseApis: MatrixClient, public readonly getKey: GetKey) {
|
||||
public constructor(private readonly baseApis: MatrixClient, public readonly getKey: GetKey) {
|
||||
this.checkedForBackup = false;
|
||||
this.sendingBackups = false;
|
||||
}
|
||||
@@ -609,7 +609,7 @@ export class BackupManager {
|
||||
export class Curve25519 implements BackupAlgorithm {
|
||||
public static algorithmName = "m.megolm_backup.v1.curve25519-aes-sha2";
|
||||
|
||||
constructor(
|
||||
public constructor(
|
||||
public authData: ICurve25519AuthData,
|
||||
private publicKey: any, // FIXME: PkEncryption
|
||||
private getKey: () => Promise<Uint8Array>,
|
||||
@@ -661,7 +661,7 @@ export class Curve25519 implements BackupAlgorithm {
|
||||
}
|
||||
}
|
||||
|
||||
public get untrusted() { return true; }
|
||||
public get untrusted(): boolean { return true; }
|
||||
|
||||
public async encryptSession(data: Record<string, any>): Promise<any> {
|
||||
const plainText: Record<string, any> = Object.assign({}, data);
|
||||
@@ -735,7 +735,7 @@ const UNSTABLE_MSC3270_NAME = new UnstableValue(
|
||||
export class Aes256 implements BackupAlgorithm {
|
||||
public static algorithmName = UNSTABLE_MSC3270_NAME.name;
|
||||
|
||||
constructor(
|
||||
public constructor(
|
||||
public readonly authData: IAes256AuthData,
|
||||
private readonly key: Uint8Array,
|
||||
) {}
|
||||
@@ -786,7 +786,7 @@ export class Aes256 implements BackupAlgorithm {
|
||||
}
|
||||
}
|
||||
|
||||
public get untrusted() { return false; }
|
||||
public get untrusted(): boolean { return false; }
|
||||
|
||||
public encryptSession(data: Record<string, any>): Promise<any> {
|
||||
const plainText: Record<string, any> = Object.assign({}, data);
|
||||
|
||||
@@ -62,7 +62,7 @@ export class DehydrationManager {
|
||||
private keyInfo?: {[props: string]: any};
|
||||
private deviceDisplayName?: string;
|
||||
|
||||
constructor(private readonly crypto: Crypto) {
|
||||
public constructor(private readonly crypto: Crypto) {
|
||||
this.getDehydrationKeyFromCache();
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ export class DehydrationManager {
|
||||
}
|
||||
}
|
||||
|
||||
public stop() {
|
||||
public stop(): void {
|
||||
if (this.timeoutId) {
|
||||
global.clearTimeout(this.timeoutId);
|
||||
this.timeoutId = undefined;
|
||||
|
||||
@@ -94,7 +94,7 @@ export class DeviceInfo {
|
||||
public unsigned: Record<string, any> = {};
|
||||
public signatures: ISignatures = {};
|
||||
|
||||
constructor(public readonly deviceId: string) {}
|
||||
public constructor(public readonly deviceId: string) {}
|
||||
|
||||
/**
|
||||
* Prepare a DeviceInfo for JSON serialisation in the session store
|
||||
|
||||
+30
-30
@@ -278,7 +278,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
/**
|
||||
* @return {string} The version of Olm.
|
||||
*/
|
||||
static getOlmVersion(): [number, number, number] {
|
||||
public static getOlmVersion(): [number, number, number] {
|
||||
return OlmDevice.getOlmVersion();
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
* Each element can either be a string from MatrixClient.verificationMethods
|
||||
* or a class that implements a verification method.
|
||||
*/
|
||||
constructor(
|
||||
public constructor(
|
||||
public readonly baseApis: MatrixClient,
|
||||
public readonly userId: string,
|
||||
private readonly deviceId: string,
|
||||
@@ -465,7 +465,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
|
||||
// Assuming no app-supplied callback, default to getting from SSSS.
|
||||
if (!cryptoCallbacks.getCrossSigningKey && cryptoCallbacks.getSecretStorageKey) {
|
||||
cryptoCallbacks.getCrossSigningKey = async (type) => {
|
||||
cryptoCallbacks.getCrossSigningKey = async (type): Promise<Uint8Array | null> => {
|
||||
return CrossSigningInfo.getFromSecretStorage(type, this.secretStorage);
|
||||
};
|
||||
}
|
||||
@@ -709,7 +709,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
);
|
||||
|
||||
// Reset the cross-signing keys
|
||||
const resetCrossSigning = async () => {
|
||||
const resetCrossSigning = async (): Promise<void> => {
|
||||
crossSigningInfo.resetKeys();
|
||||
// Sign master key with device key
|
||||
await this.signObject(crossSigningInfo.keys.master);
|
||||
@@ -846,12 +846,12 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
*/
|
||||
// TODO this does not resolve with what it says it does
|
||||
public async bootstrapSecretStorage({
|
||||
createSecretStorageKey = async () => ({} as IRecoveryKey),
|
||||
createSecretStorageKey = async (): Promise<IRecoveryKey> => ({} as IRecoveryKey),
|
||||
keyBackupInfo,
|
||||
setupNewKeyBackup,
|
||||
setupNewSecretStorage,
|
||||
getKeyBackupPassphrase,
|
||||
}: ICreateSecretStorageOpts = {}) {
|
||||
}: ICreateSecretStorageOpts = {}): Promise<void> {
|
||||
logger.log("Bootstrapping Secure Secret Storage");
|
||||
const delegateCryptoCallbacks = this.baseApis.cryptoCallbacks;
|
||||
const builder = new EncryptionSetupBuilder(
|
||||
@@ -868,7 +868,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
let newKeyId: string | null = null;
|
||||
|
||||
// create a new SSSS key and set it as default
|
||||
const createSSSS = async (opts: IAddSecretStorageKeyOpts, privateKey?: Uint8Array) => {
|
||||
const createSSSS = async (opts: IAddSecretStorageKeyOpts, privateKey?: Uint8Array): Promise<string> => {
|
||||
if (privateKey) {
|
||||
opts.key = privateKey;
|
||||
}
|
||||
@@ -884,7 +884,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
return keyId;
|
||||
};
|
||||
|
||||
const ensureCanCheckPassphrase = async (keyId: string, keyInfo: ISecretStorageKeyInfo) => {
|
||||
const ensureCanCheckPassphrase = async (keyId: string, keyInfo: ISecretStorageKeyInfo): Promise<void> => {
|
||||
if (!keyInfo.mac) {
|
||||
const key = await this.baseApis.cryptoCallbacks.getSecretStorageKey?.(
|
||||
{ keys: { [keyId]: keyInfo } }, "",
|
||||
@@ -903,7 +903,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
}
|
||||
};
|
||||
|
||||
const signKeyBackupWithCrossSigning = async (keyBackupAuthData: IKeyBackupInfo["auth_data"]) => {
|
||||
const signKeyBackupWithCrossSigning = async (keyBackupAuthData: IKeyBackupInfo["auth_data"]): Promise<void> => {
|
||||
if (
|
||||
this.crossSigningInfo.getId() &&
|
||||
await this.crossSigningInfo.isStoredInKeyCache("master")
|
||||
@@ -1241,7 +1241,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
const signedDevice = await this.crossSigningInfo.signDevice(this.userId, device);
|
||||
logger.info(`Starting background key sig upload for ${this.deviceId}`);
|
||||
|
||||
const upload = ({ shouldEmit = false }) => {
|
||||
const upload = ({ shouldEmit = false }): Promise<void> => {
|
||||
return this.baseApis.uploadKeySignatures({
|
||||
[this.userId]: {
|
||||
[this.deviceId]: signedDevice!,
|
||||
@@ -1475,7 +1475,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
/*
|
||||
* Event handler for DeviceList's userNewDevices event
|
||||
*/
|
||||
private onDeviceListUserCrossSigningUpdated = async (userId: string) => {
|
||||
private onDeviceListUserCrossSigningUpdated = async (userId: string): Promise<void> => {
|
||||
if (userId === this.userId) {
|
||||
// An update to our own cross-signing key.
|
||||
// Get the new key first:
|
||||
@@ -1657,7 +1657,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
|
||||
const keysToUpload = Object.keys(keySignatures);
|
||||
if (keysToUpload.length) {
|
||||
const upload = ({ shouldEmit = false }) => {
|
||||
const upload = ({ shouldEmit = false }): Promise<void> => {
|
||||
logger.info(`Starting background key sig upload for ${keysToUpload}`);
|
||||
return this.baseApis.uploadKeySignatures({ [this.userId]: keySignatures })
|
||||
.then((response) => {
|
||||
@@ -1864,7 +1864,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
}
|
||||
}
|
||||
|
||||
public setNeedsNewFallback(needsNewFallback: boolean) {
|
||||
public setNeedsNewFallback(needsNewFallback: boolean): void {
|
||||
this.needsNewFallback = needsNewFallback;
|
||||
}
|
||||
|
||||
@@ -1873,7 +1873,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
}
|
||||
|
||||
// check if it's time to upload one-time keys, and do so if so.
|
||||
private maybeUploadOneTimeKeys() {
|
||||
private maybeUploadOneTimeKeys(): void {
|
||||
// frequency with which to check & upload one-time keys
|
||||
const uploadPeriod = 1000 * 60; // one minute
|
||||
|
||||
@@ -1919,7 +1919,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
// out stale private keys that won't receive a message.
|
||||
const keyLimit = Math.floor(maxOneTimeKeys / 2);
|
||||
|
||||
const uploadLoop = async (keyCount: number) => {
|
||||
const uploadLoop = async (keyCount: number): Promise<void> => {
|
||||
while (keyLimit > keyCount || this.getNeedsNewFallback()) {
|
||||
// Ask olm to generate new one time keys, then upload them to synapse.
|
||||
if (keyLimit > keyCount) {
|
||||
@@ -2155,7 +2155,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
);
|
||||
const device = await this.crossSigningInfo.signUser(xsk);
|
||||
if (device) {
|
||||
const upload = async ({ shouldEmit = false }) => {
|
||||
const upload = async ({ shouldEmit = false }): Promise<void> => {
|
||||
logger.info("Uploading signature for " + userId + "...");
|
||||
const response = await this.baseApis.uploadKeySignatures({
|
||||
[userId]: {
|
||||
@@ -2246,7 +2246,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
}
|
||||
|
||||
if (device) {
|
||||
const upload = async ({ shouldEmit = false }) => {
|
||||
const upload = async ({ shouldEmit = false }): Promise<void> => {
|
||||
logger.info("Uploading signature for " + deviceId);
|
||||
const response = await this.baseApis.uploadKeySignatures({
|
||||
[userId]: {
|
||||
@@ -2645,7 +2645,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
* @returns {Promise} when all devices for the room have been fetched and marked to track
|
||||
*/
|
||||
public trackRoomDevices(roomId: string): Promise<void> {
|
||||
const trackMembers = async () => {
|
||||
const trackMembers = async (): Promise<void> => {
|
||||
// not an encrypted room
|
||||
if (!this.roomEncryptors.has(roomId)) {
|
||||
return;
|
||||
@@ -2747,7 +2747,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
let failures = 0;
|
||||
const total = keys.length;
|
||||
|
||||
function updateProgress() {
|
||||
function updateProgress(): void {
|
||||
opts.progressCallback?.({
|
||||
stage: "load_keys",
|
||||
successes,
|
||||
@@ -3187,7 +3187,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
}
|
||||
}
|
||||
|
||||
private onMembership = (event: MatrixEvent, member: RoomMember, oldMembership?: string) => {
|
||||
private onMembership = (event: MatrixEvent, member: RoomMember, oldMembership?: string): void => {
|
||||
try {
|
||||
this.onRoomMembership(event, member, oldMembership);
|
||||
} catch (e) {
|
||||
@@ -3269,9 +3269,9 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Got room key withheld event from ${event.getSender()} (${content.sender_key}) `
|
||||
+ `for ${content.algorithm}/${content.room_id}/${content.session_id} `
|
||||
+ `with reason ${content.code} (${content.reason})`,
|
||||
`Got room key withheld event from ${event.getSender()} `
|
||||
+ `for ${content.algorithm} session ${content.sender_key}|${content.session_id} `
|
||||
+ `in room ${content.room_id} with code ${content.code} (${content.reason})`,
|
||||
);
|
||||
|
||||
const alg = this.getRoomDecryptor(content.room_id, content.algorithm);
|
||||
@@ -3340,7 +3340,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
if (!InRoomChannel.validateEvent(event, this.baseApis)) {
|
||||
return;
|
||||
}
|
||||
const createRequest = (event: MatrixEvent) => {
|
||||
const createRequest = (event: MatrixEvent): VerificationRequest => {
|
||||
const channel = new InRoomChannel(this.baseApis, event.getRoomId()!);
|
||||
return new VerificationRequest(
|
||||
channel, this.verificationMethods, this.baseApis);
|
||||
@@ -3361,7 +3361,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
eventIdListener = resolve;
|
||||
statusListener = () => {
|
||||
statusListener = (): void => {
|
||||
if (event.status == EventStatus.CANCELLED) {
|
||||
reject(new Error("Event status set to CANCELLED."));
|
||||
}
|
||||
@@ -3420,7 +3420,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
// retry decryption for all events sent by the sender_key. This will
|
||||
// update the events to show a message indicating that the olm session was
|
||||
// wedged.
|
||||
const retryDecryption = () => {
|
||||
const retryDecryption = (): void => {
|
||||
const roomDecryptors = this.getRoomDecryptors(olmlib.MEGOLM_ALGORITHM);
|
||||
for (const decryptor of roomDecryptors) {
|
||||
decryptor.retryDecryptionFromSender(deviceKey);
|
||||
@@ -3692,7 +3692,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
return;
|
||||
}
|
||||
|
||||
req.share = () => {
|
||||
req.share = (): void => {
|
||||
decryptor.shareKeysWithDevice(req);
|
||||
};
|
||||
|
||||
@@ -3863,14 +3863,14 @@ export class IncomingRoomKeyRequest {
|
||||
public readonly requestBody: IRoomKeyRequestBody;
|
||||
public share: () => void;
|
||||
|
||||
constructor(event: MatrixEvent) {
|
||||
public constructor(event: MatrixEvent) {
|
||||
const content = event.getContent();
|
||||
|
||||
this.userId = event.getSender()!;
|
||||
this.deviceId = content.requesting_device_id;
|
||||
this.requestId = content.request_id;
|
||||
this.requestBody = content.body || {};
|
||||
this.share = () => {
|
||||
this.share = (): void => {
|
||||
throw new Error("don't know how to share keys for this request yet");
|
||||
};
|
||||
}
|
||||
@@ -3888,7 +3888,7 @@ class IncomingRoomKeyRequestCancellation {
|
||||
public readonly deviceId: string;
|
||||
public readonly requestId: string;
|
||||
|
||||
constructor(event: MatrixEvent) {
|
||||
public constructor(event: MatrixEvent) {
|
||||
const content = event.getContent();
|
||||
|
||||
this.userId = event.getSender()!;
|
||||
|
||||
@@ -82,7 +82,7 @@ export async function encryptMessageForDevice(
|
||||
recipientUserId: string,
|
||||
recipientDevice: DeviceInfo,
|
||||
payloadFields: Record<string, any>,
|
||||
) {
|
||||
): Promise<void> {
|
||||
const deviceKey = recipientDevice.getIdentityKey();
|
||||
const sessionId = await olmDevice.getSessionIdForDevice(deviceKey);
|
||||
if (sessionId === null) {
|
||||
@@ -173,7 +173,7 @@ export async function getExistingOlmSessions(
|
||||
for (const deviceInfo of devices) {
|
||||
const deviceId = deviceInfo.deviceId;
|
||||
const key = deviceInfo.getIdentityKey();
|
||||
promises.push((async () => {
|
||||
promises.push((async (): Promise<void> => {
|
||||
const sessionId = await olmDevice.getSessionIdForDevice(
|
||||
key, true,
|
||||
);
|
||||
@@ -256,7 +256,7 @@ export async function ensureOlmSessionsForDevices(
|
||||
// conditions. If we find that we already have a session, then
|
||||
// we'll resolve
|
||||
olmDevice.sessionsInProgress[key] = new Promise(resolve => {
|
||||
resolveSession[key] = (v: any) => {
|
||||
resolveSession[key] = (v: any): void => {
|
||||
delete olmDevice.sessionsInProgress[key];
|
||||
resolve(v);
|
||||
};
|
||||
@@ -463,7 +463,7 @@ export async function verifySignature(
|
||||
signingUserId: string,
|
||||
signingDeviceId: string,
|
||||
signingKey: string,
|
||||
) {
|
||||
): Promise<void> {
|
||||
const signKeyId = "ed25519:" + signingDeviceId;
|
||||
const signatures = obj.signatures || {};
|
||||
const userSigs = signatures[signingUserId] || {};
|
||||
@@ -527,7 +527,7 @@ export function pkSign(obj: IObject, key: PkSigning, userId: string, pubKey: str
|
||||
* @param {string} pubKey The public key to use to verify
|
||||
* @param {string} userId The user ID who signed the object
|
||||
*/
|
||||
export function pkVerify(obj: IObject, pubKey: string, userId: string) {
|
||||
export function pkVerify(obj: IObject, pubKey: string, userId: string): void {
|
||||
const keyId = "ed25519:" + pubKey;
|
||||
if (!(obj.signatures && obj.signatures[userId] && obj.signatures[userId][keyId])) {
|
||||
throw new Error("No signature");
|
||||
|
||||
@@ -48,11 +48,11 @@ export class Backend implements CryptoStore {
|
||||
/**
|
||||
* @param {IDBDatabase} db
|
||||
*/
|
||||
constructor(private db: IDBDatabase) {
|
||||
public constructor(private db: IDBDatabase) {
|
||||
// make sure we close the db on `onversionchange` - otherwise
|
||||
// attempts to delete the database will block (and subsequent
|
||||
// attempts to re-create it will also block).
|
||||
db.onversionchange = () => {
|
||||
db.onversionchange = (): void => {
|
||||
logger.log(`versionchange for indexeddb ${this.db.name}: closing`);
|
||||
db.close();
|
||||
};
|
||||
@@ -103,7 +103,7 @@ export class Backend implements CryptoStore {
|
||||
`enqueueing key request for ${requestBody.room_id} / ` +
|
||||
requestBody.session_id,
|
||||
);
|
||||
txn.oncomplete = () => {resolve(request);};
|
||||
txn.oncomplete = (): void => {resolve(request);};
|
||||
const store = txn.objectStore("outgoingRoomKeyRequests");
|
||||
store.add(request);
|
||||
});
|
||||
@@ -157,7 +157,7 @@ export class Backend implements CryptoStore {
|
||||
requestBody.session_id,
|
||||
]);
|
||||
|
||||
cursorReq.onsuccess = () => {
|
||||
cursorReq.onsuccess = (): void => {
|
||||
const cursor = cursorReq.result;
|
||||
if (!cursor) {
|
||||
// no match found
|
||||
@@ -201,7 +201,7 @@ export class Backend implements CryptoStore {
|
||||
let stateIndex = 0;
|
||||
let result: OutgoingRoomKeyRequest;
|
||||
|
||||
function onsuccess(this: IDBRequest<IDBCursorWithValue | null>) {
|
||||
function onsuccess(this: IDBRequest<IDBCursorWithValue | null>): void {
|
||||
const cursor = this.result;
|
||||
if (cursor) {
|
||||
// got a match
|
||||
@@ -243,8 +243,8 @@ export class Backend implements CryptoStore {
|
||||
const index = store.index("state");
|
||||
const request = index.getAll(wantedState);
|
||||
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onsuccess = (): void => resolve(request.result);
|
||||
request.onerror = (): void => reject(request.error);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@ export class Backend implements CryptoStore {
|
||||
let stateIndex = 0;
|
||||
const results: OutgoingRoomKeyRequest[] = [];
|
||||
|
||||
function onsuccess(this: IDBRequest<IDBCursorWithValue | null>) {
|
||||
function onsuccess(this: IDBRequest<IDBCursorWithValue | null>): void {
|
||||
const cursor = this.result;
|
||||
if (cursor) {
|
||||
const keyReq = cursor.value;
|
||||
@@ -309,7 +309,7 @@ export class Backend implements CryptoStore {
|
||||
): Promise<OutgoingRoomKeyRequest | null> {
|
||||
let result: OutgoingRoomKeyRequest | null = null;
|
||||
|
||||
function onsuccess(this: IDBRequest<IDBCursorWithValue | null>) {
|
||||
function onsuccess(this: IDBRequest<IDBCursorWithValue | null>): void {
|
||||
const cursor = this.result;
|
||||
if (!cursor) {
|
||||
return;
|
||||
@@ -348,7 +348,7 @@ export class Backend implements CryptoStore {
|
||||
): Promise<OutgoingRoomKeyRequest | null> {
|
||||
const txn = this.db.transaction("outgoingRoomKeyRequests", "readwrite");
|
||||
const cursorReq = txn.objectStore("outgoingRoomKeyRequests").openCursor(requestId);
|
||||
cursorReq.onsuccess = () => {
|
||||
cursorReq.onsuccess = (): void => {
|
||||
const cursor = cursorReq.result;
|
||||
if (!cursor) {
|
||||
return;
|
||||
@@ -371,7 +371,7 @@ export class Backend implements CryptoStore {
|
||||
public getAccount(txn: IDBTransaction, func: (accountPickle: string | null) => void): void {
|
||||
const objectStore = txn.objectStore("account");
|
||||
const getReq = objectStore.get("-");
|
||||
getReq.onsuccess = function() {
|
||||
getReq.onsuccess = function(): void {
|
||||
try {
|
||||
func(getReq.result || null);
|
||||
} catch (e) {
|
||||
@@ -391,7 +391,7 @@ export class Backend implements CryptoStore {
|
||||
): void {
|
||||
const objectStore = txn.objectStore("account");
|
||||
const getReq = objectStore.get("crossSigningKeys");
|
||||
getReq.onsuccess = function() {
|
||||
getReq.onsuccess = function(): void {
|
||||
try {
|
||||
func(getReq.result || null);
|
||||
} catch (e) {
|
||||
@@ -407,7 +407,7 @@ export class Backend implements CryptoStore {
|
||||
): void {
|
||||
const objectStore = txn.objectStore("account");
|
||||
const getReq = objectStore.get(`ssss_cache:${type}`);
|
||||
getReq.onsuccess = function() {
|
||||
getReq.onsuccess = function(): void {
|
||||
try {
|
||||
func(getReq.result || null);
|
||||
} catch (e) {
|
||||
@@ -435,7 +435,7 @@ export class Backend implements CryptoStore {
|
||||
public countEndToEndSessions(txn: IDBTransaction, func: (count: number) => void): void {
|
||||
const objectStore = txn.objectStore("sessions");
|
||||
const countReq = objectStore.count();
|
||||
countReq.onsuccess = function() {
|
||||
countReq.onsuccess = function(): void {
|
||||
try {
|
||||
func(countReq.result);
|
||||
} catch (e) {
|
||||
@@ -453,7 +453,7 @@ export class Backend implements CryptoStore {
|
||||
const idx = objectStore.index("deviceKey");
|
||||
const getReq = idx.openCursor(deviceKey);
|
||||
const results: Parameters<Parameters<Backend["getEndToEndSessions"]>[2]>[0] = {};
|
||||
getReq.onsuccess = function() {
|
||||
getReq.onsuccess = function(): void {
|
||||
const cursor = getReq.result;
|
||||
if (cursor) {
|
||||
results[cursor.value.sessionId] = {
|
||||
@@ -479,7 +479,7 @@ export class Backend implements CryptoStore {
|
||||
): void {
|
||||
const objectStore = txn.objectStore("sessions");
|
||||
const getReq = objectStore.get([deviceKey, sessionId]);
|
||||
getReq.onsuccess = function() {
|
||||
getReq.onsuccess = function(): void {
|
||||
try {
|
||||
if (getReq.result) {
|
||||
func({
|
||||
@@ -498,7 +498,7 @@ export class Backend implements CryptoStore {
|
||||
public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo | null) => void): void {
|
||||
const objectStore = txn.objectStore("sessions");
|
||||
const getReq = objectStore.openCursor();
|
||||
getReq.onsuccess = function() {
|
||||
getReq.onsuccess = function(): void {
|
||||
try {
|
||||
const cursor = getReq.result;
|
||||
if (cursor) {
|
||||
@@ -546,7 +546,7 @@ export class Backend implements CryptoStore {
|
||||
const objectStore = txn.objectStore("session_problems");
|
||||
const index = objectStore.index("deviceKey");
|
||||
const req = index.getAll(deviceKey);
|
||||
req.onsuccess = () => {
|
||||
req.onsuccess = (): void => {
|
||||
const problems = req.result;
|
||||
if (!problems.length) {
|
||||
result = null;
|
||||
@@ -583,7 +583,7 @@ export class Backend implements CryptoStore {
|
||||
return new Promise<void>((resolve) => {
|
||||
const { userId, deviceInfo } = device;
|
||||
const getReq = objectStore.get([userId, deviceInfo.deviceId]);
|
||||
getReq.onsuccess = function() {
|
||||
getReq.onsuccess = function(): void {
|
||||
if (!getReq.result) {
|
||||
objectStore.put({ userId, deviceId: deviceInfo.deviceId });
|
||||
ret.push(device);
|
||||
@@ -608,7 +608,7 @@ export class Backend implements CryptoStore {
|
||||
let withheld: IWithheld | null | boolean = false;
|
||||
const objectStore = txn.objectStore("inbound_group_sessions");
|
||||
const getReq = objectStore.get([senderCurve25519Key, sessionId]);
|
||||
getReq.onsuccess = function() {
|
||||
getReq.onsuccess = function(): void {
|
||||
try {
|
||||
if (getReq.result) {
|
||||
session = getReq.result.session;
|
||||
@@ -625,7 +625,7 @@ export class Backend implements CryptoStore {
|
||||
|
||||
const withheldObjectStore = txn.objectStore("inbound_group_sessions_withheld");
|
||||
const withheldGetReq = withheldObjectStore.get([senderCurve25519Key, sessionId]);
|
||||
withheldGetReq.onsuccess = function() {
|
||||
withheldGetReq.onsuccess = function(): void {
|
||||
try {
|
||||
if (withheldGetReq.result) {
|
||||
withheld = withheldGetReq.result.session;
|
||||
@@ -644,7 +644,7 @@ export class Backend implements CryptoStore {
|
||||
public getAllEndToEndInboundGroupSessions(txn: IDBTransaction, func: (session: ISession | null) => void): void {
|
||||
const objectStore = txn.objectStore("inbound_group_sessions");
|
||||
const getReq = objectStore.openCursor();
|
||||
getReq.onsuccess = function() {
|
||||
getReq.onsuccess = function(): void {
|
||||
const cursor = getReq.result;
|
||||
if (cursor) {
|
||||
try {
|
||||
@@ -677,7 +677,7 @@ export class Backend implements CryptoStore {
|
||||
const addReq = objectStore.add({
|
||||
senderCurve25519Key, sessionId, session: sessionData,
|
||||
});
|
||||
addReq.onerror = (ev) => {
|
||||
addReq.onerror = (ev): void => {
|
||||
if (addReq.error?.name === 'ConstraintError') {
|
||||
// This stops the error from triggering the txn's onerror
|
||||
ev.stopPropagation();
|
||||
@@ -722,7 +722,7 @@ export class Backend implements CryptoStore {
|
||||
public getEndToEndDeviceData(txn: IDBTransaction, func: (deviceData: IDeviceData | null) => void): void {
|
||||
const objectStore = txn.objectStore("device_data");
|
||||
const getReq = objectStore.get("-");
|
||||
getReq.onsuccess = function() {
|
||||
getReq.onsuccess = function(): void {
|
||||
try {
|
||||
func(getReq.result || null);
|
||||
} catch (e) {
|
||||
@@ -745,7 +745,7 @@ export class Backend implements CryptoStore {
|
||||
const rooms: Parameters<Parameters<Backend["getEndToEndRooms"]>[1]>[0] = {};
|
||||
const objectStore = txn.objectStore("rooms");
|
||||
const getReq = objectStore.openCursor();
|
||||
getReq.onsuccess = function() {
|
||||
getReq.onsuccess = function(): void {
|
||||
const cursor = getReq.result;
|
||||
if (cursor) {
|
||||
rooms[cursor.key as string] = cursor.value;
|
||||
@@ -771,17 +771,17 @@ export class Backend implements CryptoStore {
|
||||
"readonly",
|
||||
);
|
||||
txn.onerror = reject;
|
||||
txn.oncomplete = function() {
|
||||
txn.oncomplete = function(): void {
|
||||
resolve(sessions);
|
||||
};
|
||||
const objectStore = txn.objectStore("sessions_needing_backup");
|
||||
const sessionStore = txn.objectStore("inbound_group_sessions");
|
||||
const getReq = objectStore.openCursor();
|
||||
getReq.onsuccess = function() {
|
||||
getReq.onsuccess = function(): void {
|
||||
const cursor = getReq.result;
|
||||
if (cursor) {
|
||||
const sessionGetReq = sessionStore.get(cursor.key);
|
||||
sessionGetReq.onsuccess = function() {
|
||||
sessionGetReq.onsuccess = function(): void {
|
||||
sessions.push({
|
||||
senderKey: sessionGetReq.result.senderCurve25519Key,
|
||||
sessionId: sessionGetReq.result.sessionId,
|
||||
@@ -804,7 +804,7 @@ export class Backend implements CryptoStore {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = objectStore.count();
|
||||
req.onerror = reject;
|
||||
req.onsuccess = () => resolve(req.result);
|
||||
req.onsuccess = (): void => resolve(req.result);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -852,7 +852,7 @@ export class Backend implements CryptoStore {
|
||||
}
|
||||
const objectStore = txn.objectStore("shared_history_inbound_group_sessions");
|
||||
const req = objectStore.get([roomId]);
|
||||
req.onsuccess = () => {
|
||||
req.onsuccess = (): void => {
|
||||
const { sessions } = req.result || { sessions: [] };
|
||||
sessions.push([senderKey, sessionId]);
|
||||
objectStore.put({ roomId, sessions });
|
||||
@@ -871,7 +871,7 @@ export class Backend implements CryptoStore {
|
||||
const objectStore = txn.objectStore("shared_history_inbound_group_sessions");
|
||||
const req = objectStore.get([roomId]);
|
||||
return new Promise((resolve, reject) => {
|
||||
req.onsuccess = () => {
|
||||
req.onsuccess = (): void => {
|
||||
const { sessions } = req.result || { sessions: [] };
|
||||
resolve(sessions);
|
||||
};
|
||||
@@ -891,7 +891,7 @@ export class Backend implements CryptoStore {
|
||||
}
|
||||
const objectStore = txn.objectStore("parked_shared_history");
|
||||
const req = objectStore.get([roomId]);
|
||||
req.onsuccess = () => {
|
||||
req.onsuccess = (): void => {
|
||||
const { parked } = req.result || { parked: [] };
|
||||
parked.push(parkedData);
|
||||
objectStore.put({ roomId, parked });
|
||||
@@ -909,7 +909,7 @@ export class Backend implements CryptoStore {
|
||||
}
|
||||
const cursorReq = txn.objectStore("parked_shared_history").openCursor(roomId);
|
||||
return new Promise((resolve, reject) => {
|
||||
cursorReq.onsuccess = () => {
|
||||
cursorReq.onsuccess = (): void => {
|
||||
const cursor = cursorReq.result;
|
||||
if (!cursor) {
|
||||
resolve([]);
|
||||
@@ -957,32 +957,32 @@ export class Backend implements CryptoStore {
|
||||
|
||||
type DbMigration = (db: IDBDatabase) => void;
|
||||
const DB_MIGRATIONS: DbMigration[] = [
|
||||
(db) => { createDatabase(db); },
|
||||
(db) => { db.createObjectStore("account"); },
|
||||
(db) => {
|
||||
(db): void => { createDatabase(db); },
|
||||
(db): void => { db.createObjectStore("account"); },
|
||||
(db): void => {
|
||||
const sessionsStore = db.createObjectStore("sessions", {
|
||||
keyPath: ["deviceKey", "sessionId"],
|
||||
});
|
||||
sessionsStore.createIndex("deviceKey", "deviceKey");
|
||||
},
|
||||
(db) => {
|
||||
(db): void => {
|
||||
db.createObjectStore("inbound_group_sessions", {
|
||||
keyPath: ["senderCurve25519Key", "sessionId"],
|
||||
});
|
||||
},
|
||||
(db) => { db.createObjectStore("device_data"); },
|
||||
(db) => { db.createObjectStore("rooms"); },
|
||||
(db) => {
|
||||
(db): void => { db.createObjectStore("device_data"); },
|
||||
(db): void => { db.createObjectStore("rooms"); },
|
||||
(db): void => {
|
||||
db.createObjectStore("sessions_needing_backup", {
|
||||
keyPath: ["senderCurve25519Key", "sessionId"],
|
||||
});
|
||||
},
|
||||
(db) => {
|
||||
(db): void => {
|
||||
db.createObjectStore("inbound_group_sessions_withheld", {
|
||||
keyPath: ["senderCurve25519Key", "sessionId"],
|
||||
});
|
||||
},
|
||||
(db) => {
|
||||
(db): void => {
|
||||
const problemsStore = db.createObjectStore("session_problems", {
|
||||
keyPath: ["deviceKey", "time"],
|
||||
});
|
||||
@@ -992,12 +992,12 @@ const DB_MIGRATIONS: DbMigration[] = [
|
||||
keyPath: ["userId", "deviceId"],
|
||||
});
|
||||
},
|
||||
(db) => {
|
||||
(db): void => {
|
||||
db.createObjectStore("shared_history_inbound_group_sessions", {
|
||||
keyPath: ["roomId"],
|
||||
});
|
||||
},
|
||||
(db) => {
|
||||
(db): void => {
|
||||
db.createObjectStore("parked_shared_history", {
|
||||
keyPath: ["roomId"],
|
||||
});
|
||||
@@ -1037,7 +1037,7 @@ interface IWrappedIDBTransaction extends IDBTransaction {
|
||||
* Aborts a transaction with a given exception
|
||||
* The transaction promise will be rejected with this exception.
|
||||
*/
|
||||
function abortWithException(txn: IDBTransaction, e: Error) {
|
||||
function abortWithException(txn: IDBTransaction, e: Error): void {
|
||||
// We cheekily stick our exception onto the transaction object here
|
||||
// We could alternatively make the thing we pass back to the app
|
||||
// an object containing the transaction and exception.
|
||||
@@ -1052,13 +1052,13 @@ function abortWithException(txn: IDBTransaction, e: Error) {
|
||||
|
||||
function promiseifyTxn<T>(txn: IDBTransaction): Promise<T | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
txn.oncomplete = () => {
|
||||
txn.oncomplete = (): void => {
|
||||
if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) {
|
||||
reject((txn as IWrappedIDBTransaction)._mx_abortexception);
|
||||
}
|
||||
resolve(null);
|
||||
};
|
||||
txn.onerror = (event) => {
|
||||
txn.onerror = (event): void => {
|
||||
if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) {
|
||||
reject((txn as IWrappedIDBTransaction)._mx_abortexception);
|
||||
} else {
|
||||
@@ -1066,7 +1066,7 @@ function promiseifyTxn<T>(txn: IDBTransaction): Promise<T | null> {
|
||||
reject(txn.error);
|
||||
}
|
||||
};
|
||||
txn.onabort = (event) => {
|
||||
txn.onabort = (event): void => {
|
||||
if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) {
|
||||
reject((txn as IWrappedIDBTransaction)._mx_abortexception);
|
||||
} else {
|
||||
|
||||
@@ -73,7 +73,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* @param {IDBFactory} indexedDB global indexedDB instance
|
||||
* @param {string} dbName name of db to connect to
|
||||
*/
|
||||
constructor(private readonly indexedDB: IDBFactory, private readonly dbName: string) {}
|
||||
public constructor(private readonly indexedDB: IDBFactory, private readonly dbName: string) {}
|
||||
|
||||
/**
|
||||
* Ensure the database exists and is up-to-date, or fall back to
|
||||
@@ -99,24 +99,24 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
|
||||
const req = this.indexedDB.open(this.dbName, IndexedDBCryptoStoreBackend.VERSION);
|
||||
|
||||
req.onupgradeneeded = (ev) => {
|
||||
req.onupgradeneeded = (ev): void => {
|
||||
const db = req.result;
|
||||
const oldVersion = ev.oldVersion;
|
||||
IndexedDBCryptoStoreBackend.upgradeDatabase(db, oldVersion);
|
||||
};
|
||||
|
||||
req.onblocked = () => {
|
||||
req.onblocked = (): void => {
|
||||
logger.log(
|
||||
`can't yet open IndexedDBCryptoStore because it is open elsewhere`,
|
||||
);
|
||||
};
|
||||
|
||||
req.onerror = (ev) => {
|
||||
req.onerror = (ev): void => {
|
||||
logger.log("Error connecting to indexeddb", ev);
|
||||
reject(req.error);
|
||||
};
|
||||
|
||||
req.onsuccess = () => {
|
||||
req.onsuccess = (): void => {
|
||||
const db = req.result;
|
||||
|
||||
logger.log(`connected to indexeddb ${this.dbName}`);
|
||||
@@ -179,18 +179,18 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
logger.log(`Removing indexeddb instance: ${this.dbName}`);
|
||||
const req = this.indexedDB.deleteDatabase(this.dbName);
|
||||
|
||||
req.onblocked = () => {
|
||||
req.onblocked = (): void => {
|
||||
logger.log(
|
||||
`can't yet delete IndexedDBCryptoStore because it is open elsewhere`,
|
||||
);
|
||||
};
|
||||
|
||||
req.onerror = (ev) => {
|
||||
req.onerror = (ev): void => {
|
||||
logger.log("Error deleting data from indexeddb", ev);
|
||||
reject(req.error);
|
||||
};
|
||||
|
||||
req.onsuccess = () => {
|
||||
req.onsuccess = (): void => {
|
||||
logger.log(`Removed indexeddb instance: ${this.dbName}`);
|
||||
resolve();
|
||||
};
|
||||
@@ -322,7 +322,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* @param {*} txn An active transaction. See doTxn().
|
||||
* @param {function(string)} func Called with the account pickle
|
||||
*/
|
||||
public getAccount(txn: IDBTransaction, func: (accountPickle: string | null) => void) {
|
||||
public getAccount(txn: IDBTransaction, func: (accountPickle: string | null) => void): void {
|
||||
this.backend!.getAccount(txn, func);
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
return false;
|
||||
}
|
||||
|
||||
constructor(private readonly store: Storage) {
|
||||
public constructor(private readonly store: Storage) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
setJsonItem(this.store, key, problems);
|
||||
}
|
||||
|
||||
async getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null> {
|
||||
public async getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null> {
|
||||
const key = keyEndToEndSessionProblems(deviceKey);
|
||||
const problems = getJsonItem<IProblem[]>(this.store, key) || [];
|
||||
if (!problems.length) {
|
||||
@@ -408,7 +408,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
setJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`, key);
|
||||
}
|
||||
|
||||
doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn: unknown) => T): Promise<T> {
|
||||
public doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn: unknown) => T): Promise<T> {
|
||||
return Promise.resolve(func(null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ export class MemoryCryptoStore implements CryptoStore {
|
||||
|
||||
// Olm Account
|
||||
|
||||
public getAccount(txn: unknown, func: (accountPickle: string | null) => void) {
|
||||
public getAccount(txn: unknown, func: (accountPickle: string | null) => void): void {
|
||||
func(this.account);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ import { ListenerMap, TypedEventEmitter } from "../../models/typed-event-emitter
|
||||
const timeoutException = new Error("Verification timed out");
|
||||
|
||||
export class SwitchStartEventError extends Error {
|
||||
constructor(public readonly startEvent: MatrixEvent | null) {
|
||||
public constructor(public readonly startEvent: MatrixEvent | null) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ export class VerificationBase<
|
||||
* @param {object} [request] the key verification request object related to
|
||||
* this verification, if any
|
||||
*/
|
||||
constructor(
|
||||
public constructor(
|
||||
public readonly channel: IVerificationChannel,
|
||||
public readonly baseApis: MatrixClient,
|
||||
public readonly userId: string,
|
||||
@@ -286,12 +286,12 @@ export class VerificationBase<
|
||||
if (this.promise) return this.promise;
|
||||
|
||||
this.promise = new Promise((resolve, reject) => {
|
||||
this.resolve = (...args) => {
|
||||
this.resolve = (...args): void => {
|
||||
this._done = true;
|
||||
this.endTimer();
|
||||
resolve(...args);
|
||||
};
|
||||
this.reject = (e: Error | MatrixEvent) => {
|
||||
this.reject = (e: Error | MatrixEvent): void => {
|
||||
this._done = true;
|
||||
this.endTimer();
|
||||
reject(e);
|
||||
|
||||
@@ -154,7 +154,7 @@ interface IQrData {
|
||||
}
|
||||
|
||||
export class QRCodeData {
|
||||
constructor(
|
||||
public constructor(
|
||||
public readonly mode: Mode,
|
||||
private readonly sharedSecret: string,
|
||||
// only set when mode is MODE_VERIFY_OTHER_USER, master key of other party at time of generating QR code
|
||||
@@ -283,21 +283,21 @@ export class QRCodeData {
|
||||
private static generateBuffer(qrData: IQrData): Buffer {
|
||||
let buf = Buffer.alloc(0); // we'll concat our way through life
|
||||
|
||||
const appendByte = (b) => {
|
||||
const appendByte = (b): void => {
|
||||
const tmpBuf = Buffer.from([b]);
|
||||
buf = Buffer.concat([buf, tmpBuf]);
|
||||
};
|
||||
const appendInt = (i) => {
|
||||
const appendInt = (i): void => {
|
||||
const tmpBuf = Buffer.alloc(2);
|
||||
tmpBuf.writeInt16BE(i, 0);
|
||||
buf = Buffer.concat([buf, tmpBuf]);
|
||||
};
|
||||
const appendStr = (s, enc, withLengthPrefix = true) => {
|
||||
const appendStr = (s, enc, withLengthPrefix = true): void => {
|
||||
const tmpBuf = Buffer.from(s, enc);
|
||||
if (withLengthPrefix) appendInt(tmpBuf.byteLength);
|
||||
buf = Buffer.concat([buf, tmpBuf]);
|
||||
};
|
||||
const appendEncBase64 = (b64) => {
|
||||
const appendEncBase64 = (b64): void => {
|
||||
const b = decodeBase64(b64);
|
||||
const tmpBuf = Buffer.from(b);
|
||||
buf = Buffer.concat([buf, tmpBuf]);
|
||||
|
||||
@@ -170,10 +170,12 @@ const macMethods = {
|
||||
"hmac-sha256": "calculate_mac_long_kdf",
|
||||
};
|
||||
|
||||
function calculateMAC(olmSAS: OlmSAS, method: string) {
|
||||
return function(...args) {
|
||||
type Method = keyof typeof macMethods;
|
||||
|
||||
function calculateMAC(olmSAS: OlmSAS, method: Method) {
|
||||
return function(...args): string {
|
||||
const macFunction = olmSAS[macMethods[method]];
|
||||
const mac = macFunction.apply(olmSAS, args);
|
||||
const mac: string = macFunction.apply(olmSAS, args);
|
||||
logger.log("SAS calculateMAC:", method, args, mac);
|
||||
return mac;
|
||||
};
|
||||
@@ -208,7 +210,7 @@ const calculateKeyAgreement = {
|
||||
*/
|
||||
const KEY_AGREEMENT_LIST = ["curve25519-hkdf-sha256", "curve25519"];
|
||||
const HASHES_LIST = ["sha256"];
|
||||
const MAC_LIST = ["org.matrix.msc3783.hkdf-hmac-sha256", "hkdf-hmac-sha256", "hmac-sha256"];
|
||||
const MAC_LIST: Method[] = ["org.matrix.msc3783.hkdf-hmac-sha256", "hkdf-hmac-sha256", "hmac-sha256"];
|
||||
const SAS_LIST = Object.keys(sasGenerators);
|
||||
|
||||
const KEY_AGREEMENT_SET = new Set(KEY_AGREEMENT_LIST);
|
||||
@@ -300,13 +302,13 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
|
||||
keyAgreement: string,
|
||||
sasMethods: string[],
|
||||
olmSAS: OlmSAS,
|
||||
macMethod: string,
|
||||
macMethod: Method,
|
||||
): Promise<void> {
|
||||
const sasBytes = calculateKeyAgreement[keyAgreement](this, olmSAS, 6);
|
||||
const verifySAS = new Promise<void>((resolve, reject) => {
|
||||
this.sasEvent = {
|
||||
sas: generateSas(sasBytes, sasMethods),
|
||||
confirm: async () => {
|
||||
confirm: async (): Promise<void> => {
|
||||
try {
|
||||
await this.sendMAC(olmSAS, macMethod);
|
||||
resolve();
|
||||
@@ -443,7 +445,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
|
||||
}
|
||||
}
|
||||
|
||||
private sendMAC(olmSAS: OlmSAS, method: string): Promise<void> {
|
||||
private sendMAC(olmSAS: OlmSAS, method: Method): Promise<void> {
|
||||
const mac = {};
|
||||
const keyList: string[] = [];
|
||||
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
|
||||
@@ -475,7 +477,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
|
||||
return this.send(EventType.KeyVerificationMac, { mac, keys });
|
||||
}
|
||||
|
||||
private async checkMAC(olmSAS: OlmSAS, content: IContent, method: string): Promise<void> {
|
||||
private async checkMAC(olmSAS: OlmSAS, content: IContent, method: Method): Promise<void> {
|
||||
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
|
||||
+ this.userId + this.deviceId
|
||||
+ this.baseApis.getUserId() + this.baseApis.deviceId
|
||||
|
||||
@@ -44,7 +44,7 @@ export class InRoomChannel implements IVerificationChannel {
|
||||
* @param {string} roomId id of the room where verification events should be posted in, should be a DM with the given user.
|
||||
* @param {string} userId id of user that the verification request is directed at, should be present in the room.
|
||||
*/
|
||||
constructor(
|
||||
public constructor(
|
||||
private readonly client: MatrixClient,
|
||||
public readonly roomId: string,
|
||||
public userId?: string,
|
||||
|
||||
@@ -42,7 +42,7 @@ export class ToDeviceChannel implements IVerificationChannel {
|
||||
public request?: VerificationRequest;
|
||||
|
||||
// userId and devices of user we're about to verify
|
||||
constructor(
|
||||
public constructor(
|
||||
private readonly client: MatrixClient,
|
||||
public readonly userId: string,
|
||||
private readonly devices: string[],
|
||||
|
||||
@@ -116,7 +116,7 @@ export class VerificationRequest<
|
||||
public _cancellingUserId?: string; // Used in tests only
|
||||
private _verifier?: VerificationBase<any, any>;
|
||||
|
||||
constructor(
|
||||
public constructor(
|
||||
public readonly channel: C,
|
||||
private readonly verificationMethods: Map<VerificationMethod, typeof VerificationBase>,
|
||||
private readonly client: MatrixClient,
|
||||
@@ -498,7 +498,7 @@ export class VerificationRequest<
|
||||
*/
|
||||
public waitFor(fn: (request: VerificationRequest) => boolean): Promise<VerificationRequest> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const check = () => {
|
||||
const check = (): boolean => {
|
||||
let handled = false;
|
||||
if (fn(this)) {
|
||||
resolve(this);
|
||||
@@ -539,7 +539,7 @@ export class VerificationRequest<
|
||||
|
||||
private calculatePhaseTransitions(): ITransition[] {
|
||||
const transitions: ITransition[] = [{ phase: PHASE_UNSENT }];
|
||||
const phase = () => transitions[transitions.length - 1].phase;
|
||||
const phase = (): Phase => transitions[transitions.length - 1].phase;
|
||||
|
||||
// always pass by .request first to be sure channel.userId has been set
|
||||
const hasRequestByThem = this.eventsByThem.has(REQUEST_TYPE);
|
||||
@@ -816,7 +816,7 @@ export class VerificationRequest<
|
||||
}
|
||||
}
|
||||
|
||||
private cancelOnTimeout = async () => {
|
||||
private cancelOnTimeout = async (): Promise<void> => {
|
||||
try {
|
||||
if (this.initiatedByMe) {
|
||||
await this.cancel({
|
||||
|
||||
+9
-7
@@ -101,7 +101,7 @@ export class RoomWidgetClient extends MatrixClient {
|
||||
private lifecycle?: AbortController;
|
||||
private syncState: SyncState | null = null;
|
||||
|
||||
constructor(
|
||||
public constructor(
|
||||
private readonly widgetApi: WidgetApi,
|
||||
private readonly capabilities: ICapabilities,
|
||||
private readonly roomId: string,
|
||||
@@ -211,7 +211,7 @@ export class RoomWidgetClient extends MatrixClient {
|
||||
if (this.capabilities.turnServers) this.watchTurnServers();
|
||||
}
|
||||
|
||||
public stopClient() {
|
||||
public stopClient(): void {
|
||||
this.widgetApi.off(`action:${WidgetApiToWidgetAction.SendEvent}`, this.onEvent);
|
||||
this.widgetApi.off(`action:${WidgetApiToWidgetAction.SendToDevice}`, this.onToDevice);
|
||||
|
||||
@@ -288,7 +288,7 @@ export class RoomWidgetClient extends MatrixClient {
|
||||
return this.syncState;
|
||||
}
|
||||
|
||||
private setSyncState(state: SyncState) {
|
||||
private setSyncState(state: SyncState): void {
|
||||
const oldState = this.syncState;
|
||||
this.syncState = state;
|
||||
this.emit(ClientEvent.Sync, state, oldState);
|
||||
@@ -298,7 +298,7 @@ export class RoomWidgetClient extends MatrixClient {
|
||||
await this.widgetApi.transport.reply<IWidgetApiAcknowledgeResponseData>(ev.detail, {});
|
||||
}
|
||||
|
||||
private onEvent = async (ev: CustomEvent<ISendEventToWidgetActionRequest>) => {
|
||||
private onEvent = async (ev: CustomEvent<ISendEventToWidgetActionRequest>): Promise<void> => {
|
||||
ev.preventDefault();
|
||||
|
||||
// Verify the room ID matches, since it's possible for the client to
|
||||
@@ -317,7 +317,7 @@ export class RoomWidgetClient extends MatrixClient {
|
||||
await this.ack(ev);
|
||||
};
|
||||
|
||||
private onToDevice = async (ev: CustomEvent<ISendToDeviceToWidgetActionRequest>) => {
|
||||
private onToDevice = async (ev: CustomEvent<ISendToDeviceToWidgetActionRequest>): Promise<void> => {
|
||||
ev.preventDefault();
|
||||
|
||||
const event = new MatrixEvent({
|
||||
@@ -333,9 +333,11 @@ export class RoomWidgetClient extends MatrixClient {
|
||||
await this.ack(ev);
|
||||
};
|
||||
|
||||
private async watchTurnServers() {
|
||||
private async watchTurnServers(): Promise<void> {
|
||||
const servers = this.widgetApi.getTurnServers();
|
||||
const onClientStopped = () => servers.return(undefined);
|
||||
const onClientStopped = (): void => {
|
||||
servers.return(undefined);
|
||||
};
|
||||
this.lifecycle!.signal.addEventListener("abort", onClientStopped);
|
||||
|
||||
try {
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ export function eventMapperFor(client: MatrixClient, options: MapperOpts): Event
|
||||
let preventReEmit = Boolean(options.preventReEmit);
|
||||
const decrypt = options.decrypt !== false;
|
||||
|
||||
function mapper(plainOldJsObject: Partial<IEvent>) {
|
||||
function mapper(plainOldJsObject: Partial<IEvent>): MatrixEvent {
|
||||
if (options.toDevice) {
|
||||
delete plainOldJsObject.room_id;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ export interface IFilterComponent {
|
||||
* @param {Object} filterJson the definition of this filter JSON, e.g. { 'contains_url': true }
|
||||
*/
|
||||
export class FilterComponent {
|
||||
constructor(private filterJson: IFilterComponent, public readonly userId?: string | undefined | null) {}
|
||||
public constructor(private filterJson: IFilterComponent, public readonly userId?: string | undefined | null) {}
|
||||
|
||||
/**
|
||||
* Checks with the filter component matches the given event
|
||||
|
||||
+6
-6
@@ -31,7 +31,7 @@ import { MatrixEvent } from "./models/event";
|
||||
* @param {string} keyNesting
|
||||
* @param {*} val
|
||||
*/
|
||||
function setProp(obj: object, keyNesting: string, val: any) {
|
||||
function setProp(obj: object, keyNesting: string, val: any): void {
|
||||
const nestedKeys = keyNesting.split(".");
|
||||
let currentObj = obj;
|
||||
for (let i = 0; i < (nestedKeys.length - 1); i++) {
|
||||
@@ -88,7 +88,7 @@ interface IRoomFilter {
|
||||
* @prop {?string} filterId The filter ID
|
||||
*/
|
||||
export class Filter {
|
||||
static LAZY_LOADING_MESSAGES_FILTER = {
|
||||
public static LAZY_LOADING_MESSAGES_FILTER = {
|
||||
lazy_load_members: true,
|
||||
};
|
||||
|
||||
@@ -110,7 +110,7 @@ export class Filter {
|
||||
private roomFilter?: FilterComponent;
|
||||
private roomTimelineFilter?: FilterComponent;
|
||||
|
||||
constructor(public readonly userId: string | undefined | null, public filterId?: string) {}
|
||||
public constructor(public readonly userId: string | undefined | null, public filterId?: string) {}
|
||||
|
||||
/**
|
||||
* Get the ID of this filter on your homeserver (if known)
|
||||
@@ -132,7 +132,7 @@ export class Filter {
|
||||
* Set the JSON body of the filter
|
||||
* @param {Object} definition The filter definition
|
||||
*/
|
||||
public setDefinition(definition: IFilterDefinition) {
|
||||
public setDefinition(definition: IFilterDefinition): void {
|
||||
this.definition = definition;
|
||||
|
||||
// This is all ported from synapse's FilterCollection()
|
||||
@@ -225,7 +225,7 @@ export class Filter {
|
||||
* Set the max number of events to return for each room's timeline.
|
||||
* @param {Number} limit The max number of events to return for each room.
|
||||
*/
|
||||
public setTimelineLimit(limit: number) {
|
||||
public setTimelineLimit(limit: number): void {
|
||||
setProp(this.definition, "room.timeline.limit", limit);
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ export class Filter {
|
||||
* @param {boolean} includeLeave True to make rooms the user has left appear
|
||||
* in responses.
|
||||
*/
|
||||
public setIncludeLeaveRooms(includeLeave: boolean) {
|
||||
public setIncludeLeaveRooms(includeLeave: boolean): void {
|
||||
setProp(this.definition, "room.include_leave", includeLeave);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ interface IErrorJson extends Partial<IUsageLimit> {
|
||||
* @param {number} httpStatus The HTTP response status code.
|
||||
*/
|
||||
export class HTTPError extends Error {
|
||||
constructor(msg: string, public readonly httpStatus?: number) {
|
||||
public constructor(msg: string, public readonly httpStatus?: number) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ export class MatrixError extends HTTPError {
|
||||
public readonly errcode?: string;
|
||||
public data: IErrorJson;
|
||||
|
||||
constructor(
|
||||
public constructor(
|
||||
errorJson: IErrorJson = {},
|
||||
public readonly httpStatus?: number,
|
||||
public url?: string,
|
||||
@@ -79,11 +79,11 @@ export class MatrixError extends HTTPError {
|
||||
* @constructor
|
||||
*/
|
||||
export class ConnectionError extends Error {
|
||||
constructor(message: string, cause?: Error) {
|
||||
public constructor(message: string, cause?: Error) {
|
||||
super(message + (cause ? `: ${cause.message}` : ""));
|
||||
}
|
||||
|
||||
get name() {
|
||||
public get name(): string {
|
||||
return "ConnectionError";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export type ResponseType<T, O extends IHttpOpts> =
|
||||
export class FetchHttpApi<O extends IHttpOpts> {
|
||||
private abortController = new AbortController();
|
||||
|
||||
constructor(
|
||||
public constructor(
|
||||
private eventEmitter: TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>,
|
||||
public readonly opts: O,
|
||||
) {
|
||||
|
||||
@@ -77,7 +77,7 @@ export class MatrixHttpApi<O extends IHttpOpts> extends FetchHttpApi<O> {
|
||||
if (global.XMLHttpRequest) {
|
||||
const xhr = new global.XMLHttpRequest();
|
||||
|
||||
const timeoutFn = function() {
|
||||
const timeoutFn = function(): void {
|
||||
xhr.abort();
|
||||
defer.reject(new Error("Timeout"));
|
||||
};
|
||||
@@ -85,7 +85,7 @@ export class MatrixHttpApi<O extends IHttpOpts> extends FetchHttpApi<O> {
|
||||
// set an initial timeout of 30s; we'll advance it each time we get a progress notification
|
||||
let timeoutTimer = callbacks.setTimeout(timeoutFn, 30000);
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
xhr.onreadystatechange = function(): void {
|
||||
switch (xhr.readyState) {
|
||||
case global.XMLHttpRequest.DONE:
|
||||
callbacks.clearTimeout(timeoutTimer);
|
||||
@@ -113,7 +113,7 @@ export class MatrixHttpApi<O extends IHttpOpts> extends FetchHttpApi<O> {
|
||||
}
|
||||
};
|
||||
|
||||
xhr.upload.onprogress = (ev: ProgressEvent) => {
|
||||
xhr.upload.onprogress = (ev: ProgressEvent): void => {
|
||||
callbacks.clearTimeout(timeoutTimer);
|
||||
upload.loaded = ev.loaded;
|
||||
upload.total = ev.total;
|
||||
|
||||
@@ -36,13 +36,13 @@ export function anySignal(signals: AbortSignal[]): {
|
||||
} {
|
||||
const controller = new AbortController();
|
||||
|
||||
function cleanup() {
|
||||
function cleanup(): void {
|
||||
for (const signal of signals) {
|
||||
signal.removeEventListener("abort", onAbort);
|
||||
}
|
||||
}
|
||||
|
||||
function onAbort() {
|
||||
function onAbort(): void {
|
||||
controller.abort();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@ export function exists(indexedDB: IDBFactory, dbName: string): Promise<boolean>
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
let exists = true;
|
||||
const req = indexedDB.open(dbName);
|
||||
req.onupgradeneeded = () => {
|
||||
req.onupgradeneeded = (): void => {
|
||||
// Since we did not provide an explicit version when opening, this event
|
||||
// should only fire if the DB did not exist before at any version.
|
||||
exists = false;
|
||||
};
|
||||
req.onblocked = () => reject(req.error);
|
||||
req.onsuccess = () => {
|
||||
req.onblocked = (): void => reject(req.error);
|
||||
req.onsuccess = (): void => {
|
||||
const db = req.result;
|
||||
db.close();
|
||||
if (!exists) {
|
||||
@@ -45,6 +45,6 @@ export function exists(indexedDB: IDBFactory, dbName: string): Promise<boolean>
|
||||
}
|
||||
resolve(exists);
|
||||
};
|
||||
req.onerror = ev => reject(req.error);
|
||||
req.onerror = (): void => reject(req.error);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ class NoAuthFlowFoundError extends Error {
|
||||
public name = "NoAuthFlowFoundError";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention, camelcase
|
||||
constructor(m: string, public readonly required_stages: string[], public readonly flows: IFlow[]) {
|
||||
public constructor(m: string, public readonly required_stages: string[], public readonly flows: IFlow[]) {
|
||||
super(m);
|
||||
}
|
||||
}
|
||||
@@ -218,7 +218,7 @@ export class InteractiveAuth {
|
||||
// the promise the will resolve/reject when it completes
|
||||
private submitPromise: Promise<void> | null = null;
|
||||
|
||||
constructor(opts: IOpts) {
|
||||
public constructor(opts: IOpts) {
|
||||
this.matrixClient = opts.matrixClient;
|
||||
this.data = opts.authData || {};
|
||||
this.requestCallback = opts.doRequest;
|
||||
@@ -419,7 +419,7 @@ export class InteractiveAuth {
|
||||
/**
|
||||
* Requests a new email token and sets the email sid for the validation session
|
||||
*/
|
||||
public requestEmailToken = async () => {
|
||||
public requestEmailToken = async (): Promise<void> => {
|
||||
if (!this.requestingEmailToken) {
|
||||
logger.trace("Requesting email token. Attempt: " + this.emailAttempt);
|
||||
// If we've picked a flow with email auth, we send the email
|
||||
|
||||
+2
-2
@@ -35,7 +35,7 @@ const DEFAULT_NAMESPACE = "matrix";
|
||||
// console methods at initialization time by a factory that looks up the console methods
|
||||
// when logging so we always get the current value of console methods.
|
||||
log.methodFactory = function(methodName, logLevel, loggerName) {
|
||||
return function(this: PrefixedLogger, ...args) {
|
||||
return function(this: PrefixedLogger, ...args): void {
|
||||
/* eslint-disable @typescript-eslint/no-invalid-this */
|
||||
if (this.prefix) {
|
||||
args.unshift(this.prefix);
|
||||
@@ -67,7 +67,7 @@ export interface PrefixedLogger extends Logger {
|
||||
prefix: string;
|
||||
}
|
||||
|
||||
function extendLogger(logger: Logger) {
|
||||
function extendLogger(logger: Logger): void {
|
||||
(<PrefixedLogger>logger).withPrefix = function(prefix: string): PrefixedLogger {
|
||||
const existingPrefix = this.prefix || "";
|
||||
return getPrefixedLogger(existingPrefix + prefix);
|
||||
|
||||
+3
-2
@@ -21,6 +21,7 @@ import { MemoryStore } from "./store/memory";
|
||||
import { MatrixScheduler } from "./scheduler";
|
||||
import { MatrixClient, ICreateClientOpts } from "./client";
|
||||
import { RoomWidgetClient, ICapabilities } from "./embedded";
|
||||
import { CryptoStore } from "./crypto/store/base";
|
||||
|
||||
export * from "./client";
|
||||
export * from "./embedded";
|
||||
@@ -64,7 +65,7 @@ export {
|
||||
} from "./webrtc/groupCall";
|
||||
export type { GroupCall } from "./webrtc/groupCall";
|
||||
|
||||
let cryptoStoreFactory = () => new MemoryCryptoStore;
|
||||
let cryptoStoreFactory = (): CryptoStore => new MemoryCryptoStore;
|
||||
|
||||
/**
|
||||
* Configure a different factory to be used for creating crypto stores
|
||||
@@ -72,7 +73,7 @@ let cryptoStoreFactory = () => new MemoryCryptoStore;
|
||||
* @param {Function} fac a function which will return a new
|
||||
* {@link module:crypto.store.base~CryptoStore}.
|
||||
*/
|
||||
export function setCryptoStoreFactory(fac) {
|
||||
export function setCryptoStoreFactory(fac: () => CryptoStore): void {
|
||||
cryptoStoreFactory = fac;
|
||||
}
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ export class MSC3089TreeSpace {
|
||||
const currentPls = this.room.currentState.getStateEvents(EventType.RoomPowerLevels, "");
|
||||
if (Array.isArray(currentPls)) throw new Error("Unexpected return type for power levels");
|
||||
|
||||
const pls = currentPls.getContent() || {};
|
||||
const pls = currentPls?.getContent() || {};
|
||||
const viewLevel = pls['users_default'] || 0;
|
||||
const editLevel = pls['events_default'] || 50;
|
||||
const adminLevel = pls['events']?.[EventType.RoomPowerLevels] || 100;
|
||||
@@ -207,7 +207,7 @@ export class MSC3089TreeSpace {
|
||||
const currentPls = this.room.currentState.getStateEvents(EventType.RoomPowerLevels, "");
|
||||
if (Array.isArray(currentPls)) throw new Error("Unexpected return type for power levels");
|
||||
|
||||
const pls = currentPls.getContent() || {};
|
||||
const pls = currentPls?.getContent() || {};
|
||||
const viewLevel = pls['users_default'] || 0;
|
||||
const editLevel = pls['events_default'] || 50;
|
||||
const adminLevel = pls['events']?.[EventType.RoomPowerLevels] || 100;
|
||||
|
||||
@@ -56,7 +56,7 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
|
||||
private livenessWatchTimeout?: ReturnType<typeof setTimeout>;
|
||||
private _latestLocationEvent?: MatrixEvent;
|
||||
|
||||
constructor(
|
||||
public constructor(
|
||||
private rootEvent: MatrixEvent,
|
||||
) {
|
||||
super();
|
||||
@@ -180,7 +180,7 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
|
||||
}
|
||||
}
|
||||
|
||||
private clearLatestLocation = () => {
|
||||
private clearLatestLocation = (): void => {
|
||||
this._latestLocationEvent = undefined;
|
||||
this.emit(BeaconEvent.LocationUpdate, this.latestLocationState!);
|
||||
};
|
||||
|
||||
@@ -42,7 +42,7 @@ export class EventContext {
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
constructor(public readonly ourEvent: MatrixEvent) {
|
||||
public constructor(public readonly ourEvent: MatrixEvent) {
|
||||
this.timeline = [ourEvent];
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ if (DEBUG) {
|
||||
// using bind means that we get to keep useful line numbers in the console
|
||||
debuglog = logger.log.bind(logger);
|
||||
} else {
|
||||
debuglog = function() {};
|
||||
debuglog = function(): void {};
|
||||
}
|
||||
|
||||
interface IOpts {
|
||||
@@ -135,7 +135,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
||||
* @param {boolean} isThreadTimeline Whether this timeline set relates to a thread list timeline
|
||||
* (e.g., All threads or My threads)
|
||||
*/
|
||||
constructor(
|
||||
public constructor(
|
||||
public readonly room: Room | undefined,
|
||||
opts: IOpts = {},
|
||||
client?: MatrixClient,
|
||||
@@ -702,7 +702,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
||||
}
|
||||
|
||||
if (timeline.getTimelineSet() !== this) {
|
||||
throw new Error(`EventTimelineSet.addEventToTimeline: Timeline=${timeline.toString()} does not belong " +
|
||||
throw new Error(`EventTimelineSet.addEventToTimeline: Timeline=${timeline.toString()} does not belong " +
|
||||
"in timelineSet(threadId=${this.thread?.id})`);
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ export class EventTimeline {
|
||||
* @param {RoomState} stateContext the room state to be queried
|
||||
* @param {boolean} toStartOfTimeline if true the event's forwardLooking flag is set false
|
||||
*/
|
||||
static setEventMetadata(event: MatrixEvent, stateContext: RoomState, toStartOfTimeline: boolean): void {
|
||||
public static setEventMetadata(event: MatrixEvent, stateContext: RoomState, toStartOfTimeline: boolean): void {
|
||||
// When we try to generate a sentinel member before we have that member
|
||||
// in the members object, we still generate a sentinel but it doesn't
|
||||
// have a membership event, so test to see if events.member is set. We
|
||||
@@ -130,7 +130,7 @@ export class EventTimeline {
|
||||
* @param {EventTimelineSet} eventTimelineSet the set of timelines this is part of
|
||||
* @constructor
|
||||
*/
|
||||
constructor(private readonly eventTimelineSet: EventTimelineSet) {
|
||||
public constructor(private readonly eventTimelineSet: EventTimelineSet) {
|
||||
this.roomId = eventTimelineSet.room?.roomId ?? null;
|
||||
if (this.roomId) {
|
||||
this.startState = new RoomState(this.roomId);
|
||||
|
||||
+16
-6
@@ -309,7 +309,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
||||
* that getDirectionalContent() will return event.content and not event.prev_content.
|
||||
* Default: true. <strong>This property is experimental and may change.</strong>
|
||||
*/
|
||||
constructor(public event: Partial<IEvent> = {}) {
|
||||
public constructor(public event: Partial<IEvent> = {}) {
|
||||
super();
|
||||
|
||||
// intern the values of matrix events to force share strings and reduce the
|
||||
@@ -352,7 +352,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
||||
return this._cachedExtEv;
|
||||
}
|
||||
|
||||
private invalidateExtensibleEvent() {
|
||||
private invalidateExtensibleEvent(): void {
|
||||
// just reset the flag - that'll trick the getter into parsing a new event
|
||||
this._hasCachedExtEv = false;
|
||||
}
|
||||
@@ -679,7 +679,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
||||
return this.clearEvent?.content?.msgtype === "m.bad.encrypted";
|
||||
}
|
||||
|
||||
public shouldAttemptDecryption() {
|
||||
public shouldAttemptDecryption(): boolean {
|
||||
if (this.isRedacted()) return false;
|
||||
if (this.isBeingDecrypted()) return false;
|
||||
if (this.clearEvent) return false;
|
||||
@@ -1443,11 +1443,21 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
||||
* Checks if this event is associated with another event. See `getAssociatedId`.
|
||||
*
|
||||
* @return {boolean}
|
||||
* @deprecated use hasAssociation instead.
|
||||
*/
|
||||
public hasAssocation(): boolean {
|
||||
return !!this.getAssociatedId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this event is associated with another event. See `getAssociatedId`.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
public hasAssociation(): boolean {
|
||||
return !!this.getAssociatedId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the related id with a new one.
|
||||
*
|
||||
@@ -1480,7 +1490,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
||||
* more information.
|
||||
* @returns {boolean} True if the event is cancelled, false otherwise.
|
||||
*/
|
||||
isCancelled(): boolean {
|
||||
public isCancelled(): boolean {
|
||||
return this._isCancelled;
|
||||
}
|
||||
|
||||
@@ -1498,7 +1508,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
||||
* features (such as sender) surrounding the event.
|
||||
* @returns {MatrixEvent} A snapshot of this event.
|
||||
*/
|
||||
toSnapshot(): MatrixEvent {
|
||||
public toSnapshot(): MatrixEvent {
|
||||
const ev = new MatrixEvent(JSON.parse(JSON.stringify(this.event)));
|
||||
for (const [p, v] of Object.entries(this)) {
|
||||
if (p !== "event") { // exclude the thing we just cloned
|
||||
@@ -1515,7 +1525,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
||||
* @param {MatrixEvent} otherEvent The other event to check against.
|
||||
* @returns {boolean} True if the events are the same, false otherwise.
|
||||
*/
|
||||
isEquivalentTo(otherEvent: MatrixEvent): boolean {
|
||||
public isEquivalentTo(otherEvent: MatrixEvent): boolean {
|
||||
if (!otherEvent) return false;
|
||||
if (otherEvent === this) return true;
|
||||
const myProps = deepSortedObjectEntries(this.event);
|
||||
|
||||
@@ -73,7 +73,7 @@ export enum PolicyScope {
|
||||
* our data structures.
|
||||
*/
|
||||
export class IgnoredInvites {
|
||||
constructor(
|
||||
public constructor(
|
||||
private readonly client: MatrixClient,
|
||||
) {
|
||||
}
|
||||
@@ -99,7 +99,7 @@ export class IgnoredInvites {
|
||||
/**
|
||||
* Remove a rule.
|
||||
*/
|
||||
public async removeRule(event: MatrixEvent) {
|
||||
public async removeRule(event: MatrixEvent): Promise<void> {
|
||||
await this.client.redactEvent(event.getRoomId()!, event.getId()!);
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ export class IgnoredInvites {
|
||||
/**
|
||||
* Modify in place the `IGNORE_INVITES_POLICIES` object from account data.
|
||||
*/
|
||||
private async withIgnoreInvitesPolicies(cb: (ignoreInvitesPolicies: {[key: string]: any}) => void) {
|
||||
private async withIgnoreInvitesPolicies(cb: (ignoreInvitesPolicies: {[key: string]: any}) => void): Promise<void> {
|
||||
const { policies, ignoreInvitesPolicies } = this.getPoliciesAndIgnoreInvitesPolicies();
|
||||
cb(ignoreInvitesPolicies);
|
||||
policies[IGNORE_INVITES_ACCOUNT_EVENT_KEY.name] = ignoreInvitesPolicies;
|
||||
|
||||
+10
-36
@@ -11,15 +11,21 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ReceiptType } from "../@types/read_receipts";
|
||||
import {
|
||||
CachedReceipt,
|
||||
MAIN_ROOM_TIMELINE,
|
||||
Receipt,
|
||||
ReceiptCache,
|
||||
Receipts,
|
||||
ReceiptType,
|
||||
WrappedReceipt,
|
||||
} from "../@types/read_receipts";
|
||||
import { ListenerMap, TypedEventEmitter } from "./typed-event-emitter";
|
||||
import * as utils from "../utils";
|
||||
import { MatrixEvent } from "./event";
|
||||
import { EventType } from "../@types/event";
|
||||
import { EventTimelineSet } from "./event-timeline-set";
|
||||
|
||||
export const MAIN_ROOM_TIMELINE = "main";
|
||||
|
||||
export function synthesizeReceipt(userId: string, event: MatrixEvent, receiptType: ReceiptType): MatrixEvent {
|
||||
return new MatrixEvent({
|
||||
content: {
|
||||
@@ -27,7 +33,7 @@ export function synthesizeReceipt(userId: string, event: MatrixEvent, receiptTyp
|
||||
[receiptType]: {
|
||||
[userId]: {
|
||||
ts: event.getTs(),
|
||||
threadId: event.threadRootId ?? MAIN_ROOM_TIMELINE,
|
||||
thread_id: event.threadRootId ?? MAIN_ROOM_TIMELINE,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -37,40 +43,8 @@ export function synthesizeReceipt(userId: string, event: MatrixEvent, receiptTyp
|
||||
});
|
||||
}
|
||||
|
||||
export interface Receipt {
|
||||
ts: number;
|
||||
thread_id?: string;
|
||||
}
|
||||
|
||||
export interface WrappedReceipt {
|
||||
eventId: string;
|
||||
data: Receipt;
|
||||
}
|
||||
|
||||
interface CachedReceipt {
|
||||
type: ReceiptType;
|
||||
userId: string;
|
||||
data: Receipt;
|
||||
}
|
||||
|
||||
type ReceiptCache = {[eventId: string]: CachedReceipt[]};
|
||||
|
||||
export interface ReceiptContent {
|
||||
[eventId: string]: {
|
||||
[key in ReceiptType]: {
|
||||
[userId: string]: Receipt;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const ReceiptPairRealIndex = 0;
|
||||
const ReceiptPairSyntheticIndex = 1;
|
||||
// We will only hold a synthetic receipt if we do not have a real receipt or the synthetic is newer.
|
||||
type Receipts = {
|
||||
[receiptType: string]: {
|
||||
[userId: string]: [WrappedReceipt | null, WrappedReceipt | null]; // Pair<real receipt, synthetic receipt> (both nullable)
|
||||
};
|
||||
};
|
||||
|
||||
export abstract class ReadReceipt<
|
||||
Events extends string,
|
||||
|
||||
@@ -29,11 +29,11 @@ export class RelatedRelations {
|
||||
return this.relations.reduce<MatrixEvent[]>((c, p) => [...c, ...p.getRelations()], []);
|
||||
}
|
||||
|
||||
public on<T extends RelationsEvent>(ev: T, fn: Listener<RelationsEvent, EventHandlerMap, T>) {
|
||||
public on<T extends RelationsEvent>(ev: T, fn: Listener<RelationsEvent, EventHandlerMap, T>): void {
|
||||
this.relations.forEach(r => r.on(ev, fn));
|
||||
}
|
||||
|
||||
public off<T extends RelationsEvent>(ev: T, fn: Listener<RelationsEvent, EventHandlerMap, T>) {
|
||||
public off<T extends RelationsEvent>(ev: T, fn: Listener<RelationsEvent, EventHandlerMap, T>): void {
|
||||
this.relations.forEach(r => r.off(ev, fn));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export class RelationsContainer {
|
||||
// this.relations.get(parentEventId).get(relationType).get(relationEventType)
|
||||
private relations = new Map<string, Map<RelationType | string, Map<EventType | string, Relations>>>();
|
||||
|
||||
constructor(private readonly client: MatrixClient, private readonly room?: Room) {
|
||||
public constructor(private readonly client: MatrixClient, private readonly room?: Room) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +98,7 @@ export class RelationsContainer {
|
||||
const relation = event.getRelation();
|
||||
if (!relation) return;
|
||||
|
||||
const onEventDecrypted = () => {
|
||||
const onEventDecrypted = (): void => {
|
||||
if (event.isDecryptionFailure()) {
|
||||
// This could for example happen if the encryption keys are not yet available.
|
||||
// The event may still be decrypted later. Register the listener again.
|
||||
|
||||
@@ -60,7 +60,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
||||
* @param {MatrixClient|Room} client
|
||||
* The client which created this instance. For backwards compatibility also accepts a Room.
|
||||
*/
|
||||
constructor(
|
||||
public constructor(
|
||||
public readonly relationType: RelationType | string,
|
||||
public readonly eventType: string,
|
||||
client: MatrixClient | Room,
|
||||
@@ -75,7 +75,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
||||
* @param {MatrixEvent} event
|
||||
* The new relation event to be added.
|
||||
*/
|
||||
public async addEvent(event: MatrixEvent) {
|
||||
public async addEvent(event: MatrixEvent): Promise<void> {
|
||||
if (this.relationEventIds.has(event.getId()!)) {
|
||||
return;
|
||||
}
|
||||
@@ -123,7 +123,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
||||
* @param {MatrixEvent} event
|
||||
* The relation event to remove.
|
||||
*/
|
||||
private async removeEvent(event: MatrixEvent) {
|
||||
private async removeEvent(event: MatrixEvent): Promise<void> {
|
||||
if (!this.relations.has(event)) {
|
||||
return;
|
||||
}
|
||||
@@ -160,7 +160,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
||||
* @param {MatrixEvent} event The event whose status has changed
|
||||
* @param {EventStatus} status The new status
|
||||
*/
|
||||
private onEventStatus = (event: MatrixEvent, status: EventStatus | null) => {
|
||||
private onEventStatus = (event: MatrixEvent, status: EventStatus | null): void => {
|
||||
if (!event.isSending()) {
|
||||
// Sending is done, so we don't need to listen anymore
|
||||
event.removeListener(MatrixEventEvent.Status, this.onEventStatus);
|
||||
@@ -356,7 +356,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
||||
/*
|
||||
* @param {MatrixEvent} targetEvent the event the relations are related to.
|
||||
*/
|
||||
public async setTargetEvent(event: MatrixEvent) {
|
||||
public async setTargetEvent(event: MatrixEvent): Promise<void> {
|
||||
if (this.targetEvent) {
|
||||
return;
|
||||
}
|
||||
@@ -374,7 +374,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
||||
this.maybeEmitCreated();
|
||||
}
|
||||
|
||||
private maybeEmitCreated() {
|
||||
private maybeEmitCreated(): void {
|
||||
if (this.creationEmitted) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
|
||||
* @prop {MatrixEvent} events.member The m.room.member event for this RoomMember.
|
||||
* @prop {boolean} disambiguate True if the member's name is disambiguated.
|
||||
*/
|
||||
constructor(public readonly roomId: string, public readonly userId: string) {
|
||||
public constructor(public readonly roomId: string, public readonly userId: string) {
|
||||
super();
|
||||
|
||||
this.name = userId;
|
||||
@@ -232,7 +232,7 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
|
||||
/**
|
||||
* Update the last modified time to the current time.
|
||||
*/
|
||||
private updateModifiedTime() {
|
||||
private updateModifiedTime(): void {
|
||||
this.modified = Date.now();
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
||||
* events dictionary, keyed on the event type and then the state_key value.
|
||||
* @prop {string} paginationToken The pagination token for this state.
|
||||
*/
|
||||
constructor(public readonly roomId: string, private oobMemberFlags = { status: OobStatus.NotStarted }) {
|
||||
public constructor(public readonly roomId: string, private oobMemberFlags = { status: OobStatus.NotStarted }) {
|
||||
super();
|
||||
this.updateModifiedTime();
|
||||
}
|
||||
@@ -250,8 +250,8 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
||||
* <code>undefined</code>, else a single event (or null if no match found).
|
||||
*/
|
||||
public getStateEvents(eventType: EventType | string): MatrixEvent[];
|
||||
public getStateEvents(eventType: EventType | string, stateKey: string): MatrixEvent;
|
||||
public getStateEvents(eventType: EventType | string, stateKey?: string) {
|
||||
public getStateEvents(eventType: EventType | string, stateKey: string): MatrixEvent | null;
|
||||
public getStateEvents(eventType: EventType | string, stateKey?: string): MatrixEvent[] | MatrixEvent | null {
|
||||
if (!this.events.has(eventType)) {
|
||||
// no match
|
||||
return stateKey === undefined ? [] : null;
|
||||
@@ -342,7 +342,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
||||
* @fires module:client~MatrixClient#event:"RoomState.events"
|
||||
* @fires module:client~MatrixClient#event:"RoomStateEvent.Marker"
|
||||
*/
|
||||
public setStateEvents(stateEvents: MatrixEvent[], markerFoundOptions?: IMarkerFoundOptions) {
|
||||
public setStateEvents(stateEvents: MatrixEvent[], markerFoundOptions?: IMarkerFoundOptions): void {
|
||||
this.updateModifiedTime();
|
||||
|
||||
// update the core event dict
|
||||
|
||||
@@ -46,6 +46,6 @@ interface IInfo {
|
||||
* @param {Number} info.timestamp The timestamp for this room.
|
||||
*/
|
||||
export class RoomSummary {
|
||||
constructor(public readonly roomId: string, info?: IInfo) {}
|
||||
public constructor(public readonly roomId: string, info?: IInfo) {}
|
||||
}
|
||||
|
||||
|
||||
+34
-19
@@ -54,14 +54,11 @@ import {
|
||||
FILTER_RELATED_BY_SENDERS,
|
||||
ThreadFilterType,
|
||||
} from "./thread";
|
||||
import { ReceiptType } from "../@types/read_receipts";
|
||||
import { MAIN_ROOM_TIMELINE, Receipt, ReceiptContent, ReceiptType } from "../@types/read_receipts";
|
||||
import { IStateEventWithRoomId } from "../@types/search";
|
||||
import { RelationsContainer } from "./relations-container";
|
||||
import {
|
||||
MAIN_ROOM_TIMELINE,
|
||||
ReadReceipt,
|
||||
Receipt,
|
||||
ReceiptContent,
|
||||
synthesizeReceipt,
|
||||
} from "./read-receipt";
|
||||
import { Feature, ServerSupport } from "../feature";
|
||||
@@ -320,7 +317,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
* @param {boolean} [opts.timelineSupport = false] Set to true to enable improved
|
||||
* timeline support.
|
||||
*/
|
||||
constructor(
|
||||
public constructor(
|
||||
public readonly roomId: string,
|
||||
public readonly client: MatrixClient,
|
||||
public readonly myUserId: string,
|
||||
@@ -1186,7 +1183,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
* for this type.
|
||||
*/
|
||||
public getUnreadNotificationCount(type = NotificationCountType.Total): number {
|
||||
let count = this.notificationCounts[type] ?? 0;
|
||||
let count = this.getRoomUnreadNotificationCount(type);
|
||||
if (this.client.canSupport.get(Feature.ThreadUnreadNotifications) !== ServerSupport.Unsupported) {
|
||||
for (const threadNotification of this.threadNotifications.values()) {
|
||||
count += threadNotification[type] ?? 0;
|
||||
@@ -1195,6 +1192,27 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification for the event context (room or thread timeline)
|
||||
*/
|
||||
public getUnreadCountForEventContext(type = NotificationCountType.Total, event: MatrixEvent): number {
|
||||
const isThreadEvent = !!event.threadRootId && !event.isThreadRoot;
|
||||
|
||||
return (isThreadEvent
|
||||
? this.getThreadUnreadNotificationCount(event.threadRootId, type)
|
||||
: this.getRoomUnreadNotificationCount(type)) ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get one of the notification counts for this room
|
||||
* @param {String} type The type of notification count to get. default: 'total'
|
||||
* @return {Number} The notification count, or undefined if there is no count
|
||||
* for this type.
|
||||
*/
|
||||
public getRoomUnreadNotificationCount(type = NotificationCountType.Total): number {
|
||||
return this.notificationCounts[type] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
* Get one of the notification counts for a thread
|
||||
@@ -1678,7 +1696,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
Array.from(this.threads)
|
||||
.forEach(([, thread]) => {
|
||||
if (thread.length === 0) return;
|
||||
const currentUserParticipated = thread.events.some(event => {
|
||||
const currentUserParticipated = thread.timeline.some(event => {
|
||||
return event.getSender() === this.client.getUserId();
|
||||
});
|
||||
if (filterType !== ThreadFilterType.My || currentUserParticipated) {
|
||||
@@ -1761,20 +1779,17 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
let latestMyThreadsRootEvent: MatrixEvent | undefined;
|
||||
const roomState = this.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
||||
for (const rootEvent of threadRoots) {
|
||||
this.threadsTimelineSets[0]?.addLiveEvent(rootEvent, {
|
||||
const opts = {
|
||||
duplicateStrategy: DuplicateStrategy.Ignore,
|
||||
fromCache: false,
|
||||
roomState,
|
||||
});
|
||||
};
|
||||
this.threadsTimelineSets[0]?.addLiveEvent(rootEvent, opts);
|
||||
|
||||
const threadRelationship = rootEvent
|
||||
.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
|
||||
if (threadRelationship?.current_user_participated) {
|
||||
this.threadsTimelineSets[1]?.addLiveEvent(rootEvent, {
|
||||
duplicateStrategy: DuplicateStrategy.Ignore,
|
||||
fromCache: false,
|
||||
roomState,
|
||||
});
|
||||
this.threadsTimelineSets[1]?.addLiveEvent(rootEvent, opts);
|
||||
latestMyThreadsRootEvent = rootEvent;
|
||||
}
|
||||
}
|
||||
@@ -1953,7 +1968,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
));
|
||||
}
|
||||
|
||||
private updateThreadRootEvents = (thread: Thread, toStartOfTimeline: boolean) => {
|
||||
private updateThreadRootEvents = (thread: Thread, toStartOfTimeline: boolean): void => {
|
||||
if (thread.length) {
|
||||
this.updateThreadRootEvent(this.threadsTimelineSets?.[0], thread, toStartOfTimeline);
|
||||
if (thread.hasCurrentUserParticipated) {
|
||||
@@ -1966,7 +1981,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
timelineSet: Optional<EventTimelineSet>,
|
||||
thread: Thread,
|
||||
toStartOfTimeline: boolean,
|
||||
) => {
|
||||
): void => {
|
||||
if (timelineSet && thread.rootEvent) {
|
||||
if (Thread.hasServerSideSupport) {
|
||||
timelineSet.addLiveEvent(thread.rootEvent, {
|
||||
@@ -2053,7 +2068,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
redactedEvent.getType(),
|
||||
redactedEvent.getStateKey()!,
|
||||
);
|
||||
if (currentStateEvent.getId() === redactedEvent.getId()) {
|
||||
if (currentStateEvent?.getId() === redactedEvent.getId()) {
|
||||
this.currentState.setStateEvents([redactedEvent]);
|
||||
}
|
||||
}
|
||||
@@ -2916,7 +2931,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
let excludedUserIds: string[] = [];
|
||||
const mFunctionalMembers = this.currentState.getStateEvents(UNSTABLE_ELEMENT_FUNCTIONAL_USERS.name, "");
|
||||
if (Array.isArray(mFunctionalMembers?.getContent().service_members)) {
|
||||
excludedUserIds = mFunctionalMembers.getContent().service_members;
|
||||
excludedUserIds = mFunctionalMembers!.getContent().service_members;
|
||||
}
|
||||
|
||||
// get members that are NOT ourselves and are actually in the room.
|
||||
@@ -3080,7 +3095,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
originalEvent.applyVisibilityEvent(visibilityChange);
|
||||
}
|
||||
|
||||
private redactVisibilityChangeEvent(event: MatrixEvent) {
|
||||
private redactVisibilityChangeEvent(event: MatrixEvent): void {
|
||||
// Sanity checks.
|
||||
if (!event.isVisibilityEvent) {
|
||||
throw new Error("expected a visibility change event");
|
||||
|
||||
@@ -60,5 +60,5 @@ export class SearchResult {
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
constructor(public readonly rank: number, public readonly context: EventContext) {}
|
||||
public constructor(public readonly rank: number, public readonly context: EventContext) {}
|
||||
}
|
||||
|
||||
+25
-14
@@ -81,6 +81,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
* A reference to all the events ID at the bottom of the threads
|
||||
*/
|
||||
public readonly timelineSet: EventTimelineSet;
|
||||
public timeline: MatrixEvent[] = [];
|
||||
|
||||
private _currentUserParticipated = false;
|
||||
|
||||
@@ -94,7 +95,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
|
||||
public initialEventsFetched = !Thread.hasServerSideSupport;
|
||||
|
||||
constructor(
|
||||
public constructor(
|
||||
public readonly id: string,
|
||||
public rootEvent: MatrixEvent | undefined,
|
||||
opts: IThreadOpts,
|
||||
@@ -123,7 +124,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
this.room.on(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
|
||||
this.room.on(RoomEvent.Redaction, this.onRedaction);
|
||||
this.room.on(RoomEvent.LocalEchoUpdated, this.onEcho);
|
||||
this.timelineSet.on(RoomEvent.Timeline, this.onEcho);
|
||||
this.timelineSet.on(RoomEvent.Timeline, this.onTimelineEvent);
|
||||
|
||||
// even if this thread is thought to be originating from this client, we initialise it as we may be in a
|
||||
// gappy sync and a thread around this event may already exist.
|
||||
@@ -167,7 +168,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
Thread.hasServerSideFwdPaginationSupport = status;
|
||||
}
|
||||
|
||||
private onBeforeRedaction = (event: MatrixEvent, redaction: MatrixEvent) => {
|
||||
private onBeforeRedaction = (event: MatrixEvent, redaction: MatrixEvent): void => {
|
||||
if (event?.isRelation(THREAD_RELATION_TYPE.name) &&
|
||||
this.room.eventShouldLiveIn(event).threadId === this.id &&
|
||||
event.getId() !== this.id && // the root event isn't counted in the length so ignore this redaction
|
||||
@@ -178,10 +179,10 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
}
|
||||
};
|
||||
|
||||
private onRedaction = async (event: MatrixEvent) => {
|
||||
private onRedaction = async (event: MatrixEvent): Promise<void> => {
|
||||
if (event.threadRootId !== this.id) return; // ignore redactions for other timelines
|
||||
if (this.replyCount <= 0) {
|
||||
for (const threadEvent of this.events) {
|
||||
for (const threadEvent of this.timeline) {
|
||||
this.clearEventMetadata(threadEvent);
|
||||
}
|
||||
this.lastEvent = this.rootEvent;
|
||||
@@ -192,7 +193,19 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
}
|
||||
};
|
||||
|
||||
private onEcho = async (event: MatrixEvent) => {
|
||||
private onTimelineEvent = (
|
||||
event: MatrixEvent,
|
||||
room: Room | undefined,
|
||||
toStartOfTimeline: boolean | undefined,
|
||||
): void => {
|
||||
// Add a synthesized receipt when paginating forward in the timeline
|
||||
if (!toStartOfTimeline) {
|
||||
room!.addLocalEchoReceipt(event.getSender()!, event, ReceiptType.Read);
|
||||
}
|
||||
this.onEcho(event);
|
||||
};
|
||||
|
||||
private onEcho = async (event: MatrixEvent): Promise<void> => {
|
||||
if (event.threadRootId !== this.id) return; // ignore echoes for other timelines
|
||||
if (this.lastEvent === event) return;
|
||||
if (!event.isRelation(THREAD_RELATION_TYPE.name)) return;
|
||||
@@ -216,6 +229,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
roomState: this.roomState,
|
||||
},
|
||||
);
|
||||
this.timeline = this.events;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,6 +289,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
this.setEventMetadata(event);
|
||||
await this.fetchEditsWhereNeeded(event);
|
||||
}
|
||||
this.timeline = this.events;
|
||||
}
|
||||
|
||||
private getRootEventBundledRelationship(rootEvent = this.rootEvent): IThreadBundledRelationship | undefined {
|
||||
@@ -349,7 +364,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
/**
|
||||
* Finds an event by ID in the current thread
|
||||
*/
|
||||
public findEventById(eventId: string) {
|
||||
public findEventById(eventId: string): MatrixEvent | undefined {
|
||||
// Check the lastEvent as it may have been created based on a bundled relationship and not in a timeline
|
||||
if (this.lastEvent?.getId() === eventId) {
|
||||
return this.lastEvent;
|
||||
@@ -361,9 +376,9 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
/**
|
||||
* Return last reply to the thread, if known.
|
||||
*/
|
||||
public lastReply(matches: (ev: MatrixEvent) => boolean = () => true): MatrixEvent | null {
|
||||
for (let i = this.events.length - 1; i >= 0; i--) {
|
||||
const event = this.events[i];
|
||||
public lastReply(matches: (ev: MatrixEvent) => boolean = (): boolean => true): MatrixEvent | null {
|
||||
for (let i = this.timeline.length - 1; i >= 0; i--) {
|
||||
const event = this.timeline[i];
|
||||
if (matches(event)) {
|
||||
return event;
|
||||
}
|
||||
@@ -411,10 +426,6 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
return this.timelineSet;
|
||||
}
|
||||
|
||||
public get timeline(): MatrixEvent[] {
|
||||
return this.events;
|
||||
}
|
||||
|
||||
public addReceipt(event: MatrixEvent, synthetic: boolean): void {
|
||||
throw new Error("Unsupported function on the thread model");
|
||||
}
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
|
||||
* @prop {Object} events The events describing this user.
|
||||
* @prop {MatrixEvent} events.presence The m.presence event for this user.
|
||||
*/
|
||||
constructor(public readonly userId: string) {
|
||||
public constructor(public readonly userId: string) {
|
||||
super();
|
||||
this.displayName = userId;
|
||||
this.rawDisplayName = userId;
|
||||
|
||||
@@ -126,7 +126,7 @@ export class PushProcessor {
|
||||
* @constructor
|
||||
* @param {Object} client The Matrix client object to use
|
||||
*/
|
||||
constructor(private readonly client: MatrixClient) {}
|
||||
public constructor(private readonly client: MatrixClient) {}
|
||||
|
||||
/**
|
||||
* Convert a list of actions into a object with the actions as keys and their values
|
||||
|
||||
@@ -48,7 +48,7 @@ type Callback = {
|
||||
const callbackList: Callback[] = [];
|
||||
|
||||
// var debuglog = logger.log.bind(logger);
|
||||
const debuglog = function(...params: any[]) {};
|
||||
const debuglog = function(...params: any[]): void {};
|
||||
|
||||
/**
|
||||
* reimplementation of window.setTimeout, which will call the callback if
|
||||
|
||||
@@ -143,11 +143,11 @@ export class MSC3906Rendezvous {
|
||||
return await this.channel.receive() as MSC3906RendezvousPayload;
|
||||
}
|
||||
|
||||
private async send(payload: MSC3906RendezvousPayload) {
|
||||
private async send(payload: MSC3906RendezvousPayload): Promise<void> {
|
||||
await this.channel.send(payload);
|
||||
}
|
||||
|
||||
public async declineLoginOnExistingDevice() {
|
||||
public async declineLoginOnExistingDevice(): Promise<void> {
|
||||
logger.info('User declined sign in');
|
||||
await this.send({ type: PayloadType.Finish, outcome: Outcome.Declined });
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ limitations under the License.
|
||||
import { RendezvousFailureReason } from ".";
|
||||
|
||||
export class RendezvousError extends Error {
|
||||
constructor(message: string, public readonly code: RendezvousFailureReason) {
|
||||
public constructor(message: string, public readonly code: RendezvousFailureReason) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export class RoomHierarchy {
|
||||
* @param {boolean} suggestedOnly whether to only return rooms with suggested=true.
|
||||
* @constructor
|
||||
*/
|
||||
constructor(
|
||||
public constructor(
|
||||
public readonly root: Room,
|
||||
private readonly pageSize?: number,
|
||||
private readonly maxDepth?: number,
|
||||
|
||||
+4
-4
@@ -97,9 +97,9 @@ export class MatrixScheduler<T = ISendEventResponse> {
|
||||
* @see module:scheduler~queueAlgorithm
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
public static QUEUE_MESSAGES(event: MatrixEvent) {
|
||||
public static QUEUE_MESSAGES(event: MatrixEvent): "message" | null {
|
||||
// enqueue messages or events that associate with another event (redactions and relations)
|
||||
if (event.getType() === EventType.RoomMessage || event.hasAssocation()) {
|
||||
if (event.getType() === EventType.RoomMessage || event.hasAssociation()) {
|
||||
// put these events in the 'message' queue.
|
||||
return "message";
|
||||
}
|
||||
@@ -116,7 +116,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
|
||||
private activeQueues: string[] = [];
|
||||
private procFn: ProcessFunction<T> | null = null;
|
||||
|
||||
constructor(
|
||||
public constructor(
|
||||
public readonly retryAlgorithm = MatrixScheduler.RETRY_BACKOFF_RATELIMIT,
|
||||
public readonly queueAlgorithm = MatrixScheduler.QUEUE_MESSAGES,
|
||||
) {}
|
||||
@@ -283,7 +283,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
|
||||
}
|
||||
}
|
||||
|
||||
function debuglog(...args: any[]) {
|
||||
function debuglog(...args: any[]): void {
|
||||
if (DEBUG) {
|
||||
logger.log(...args);
|
||||
}
|
||||
|
||||
+90
-9
@@ -45,7 +45,7 @@ import { RoomMemberEvent } from "./models/room-member";
|
||||
const FAILED_SYNC_ERROR_THRESHOLD = 3;
|
||||
|
||||
class ExtensionE2EE implements Extension {
|
||||
constructor(private readonly crypto: Crypto) {}
|
||||
public constructor(private readonly crypto: Crypto) {}
|
||||
|
||||
public name(): string {
|
||||
return "e2ee";
|
||||
@@ -95,7 +95,7 @@ class ExtensionE2EE implements Extension {
|
||||
class ExtensionToDevice implements Extension {
|
||||
private nextBatch: string | null = null;
|
||||
|
||||
constructor(private readonly client: MatrixClient) {}
|
||||
public constructor(private readonly client: MatrixClient) {}
|
||||
|
||||
public name(): string {
|
||||
return "to_device";
|
||||
@@ -170,7 +170,7 @@ class ExtensionToDevice implements Extension {
|
||||
}
|
||||
|
||||
class ExtensionAccountData implements Extension {
|
||||
constructor(private readonly client: MatrixClient) {}
|
||||
public constructor(private readonly client: MatrixClient) {}
|
||||
|
||||
public name(): string {
|
||||
return "account_data";
|
||||
@@ -233,6 +233,72 @@ class ExtensionAccountData implements Extension {
|
||||
}
|
||||
}
|
||||
|
||||
class ExtensionTyping implements Extension {
|
||||
public constructor(private readonly client: MatrixClient) {}
|
||||
|
||||
public name(): string {
|
||||
return "typing";
|
||||
}
|
||||
|
||||
public when(): ExtensionState {
|
||||
return ExtensionState.PostProcess;
|
||||
}
|
||||
|
||||
public onRequest(isInitial: boolean): object | undefined {
|
||||
if (!isInitial) {
|
||||
return undefined; // don't send a JSON object for subsequent requests, we don't need to.
|
||||
}
|
||||
return {
|
||||
enabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
public onResponse(data: {rooms: Record<string, IMinimalEvent>}): void {
|
||||
if (!data || !data.rooms) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const roomId in data.rooms) {
|
||||
processEphemeralEvents(
|
||||
this.client, roomId, [data.rooms[roomId]],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExtensionReceipts implements Extension {
|
||||
public constructor(private readonly client: MatrixClient) {}
|
||||
|
||||
public name(): string {
|
||||
return "receipts";
|
||||
}
|
||||
|
||||
public when(): ExtensionState {
|
||||
return ExtensionState.PostProcess;
|
||||
}
|
||||
|
||||
public onRequest(isInitial: boolean): object | undefined {
|
||||
if (isInitial) {
|
||||
return {
|
||||
enabled: true,
|
||||
};
|
||||
}
|
||||
return undefined; // don't send a JSON object for subsequent requests, we don't need to.
|
||||
}
|
||||
|
||||
public onResponse(data: {rooms: Record<string, IMinimalEvent>}): void {
|
||||
if (!data || !data.rooms) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const roomId in data.rooms) {
|
||||
processEphemeralEvents(
|
||||
this.client, roomId, [data.rooms[roomId]],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A copy of SyncApi such that it can be used as a drop-in replacement for sync v2. For the actual
|
||||
* sliding sync API, see sliding-sync.ts or the class SlidingSync.
|
||||
@@ -244,7 +310,7 @@ export class SlidingSyncSdk {
|
||||
private failCount = 0;
|
||||
private notifEvents: MatrixEvent[] = []; // accumulator of sync events in the current sync response
|
||||
|
||||
constructor(
|
||||
public constructor(
|
||||
private readonly slidingSync: SlidingSync,
|
||||
private readonly client: MatrixClient,
|
||||
private readonly opts: Partial<IStoredClientOpts> = {},
|
||||
@@ -256,7 +322,7 @@ export class SlidingSyncSdk {
|
||||
this.opts.experimentalThreadSupport = this.opts.experimentalThreadSupport === true;
|
||||
|
||||
if (!opts.canResetEntireTimeline) {
|
||||
opts.canResetEntireTimeline = (_roomId: string) => {
|
||||
opts.canResetEntireTimeline = (_roomId: string): boolean => {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
@@ -273,6 +339,8 @@ export class SlidingSyncSdk {
|
||||
const extensions: Extension[] = [
|
||||
new ExtensionToDevice(this.client),
|
||||
new ExtensionAccountData(this.client),
|
||||
new ExtensionTyping(this.client),
|
||||
new ExtensionReceipts(this.client),
|
||||
];
|
||||
if (this.opts.crypto) {
|
||||
extensions.push(
|
||||
@@ -348,7 +416,7 @@ export class SlidingSyncSdk {
|
||||
* Sync rooms the user has left.
|
||||
* @return {Promise} Resolved when they've been added to the store.
|
||||
*/
|
||||
public async syncLeftRooms() {
|
||||
public async syncLeftRooms(): Promise<Room[]> {
|
||||
return []; // TODO
|
||||
}
|
||||
|
||||
@@ -457,7 +525,7 @@ export class SlidingSyncSdk {
|
||||
return false;
|
||||
}
|
||||
|
||||
private async processRoomData(client: MatrixClient, room: Room, roomData: MSC3575RoomData) {
|
||||
private async processRoomData(client: MatrixClient, room: Room, roomData: MSC3575RoomData): Promise<void> {
|
||||
roomData = ensureNameEvent(client, room.roomId, roomData);
|
||||
const stateEvents = mapEvents(this.client, room.roomId, roomData.required_state);
|
||||
// Prevent events from being decrypted ahead of time
|
||||
@@ -632,7 +700,7 @@ export class SlidingSyncSdk {
|
||||
// we'll purge this once we've fully processed the sync response
|
||||
this.addNotifications(timelineEvents);
|
||||
|
||||
const processRoomEvent = async (e: MatrixEvent) => {
|
||||
const processRoomEvent = async (e: MatrixEvent): Promise<void> => {
|
||||
client.emit(ClientEvent.Event, e);
|
||||
if (e.isState() && e.getType() == EventType.RoomEncryption && this.opts.crypto) {
|
||||
await this.opts.crypto.onCryptoEvent(e);
|
||||
@@ -767,7 +835,7 @@ export class SlidingSyncSdk {
|
||||
/**
|
||||
* Main entry point. Blocks until stop() is called.
|
||||
*/
|
||||
public async sync() {
|
||||
public async sync(): Promise<void> {
|
||||
logger.debug("Sliding sync init loop");
|
||||
|
||||
// 1) We need to get push rules so we can check if events should bing as we get
|
||||
@@ -888,3 +956,16 @@ function mapEvents(client: MatrixClient, roomId: string | undefined, events: obj
|
||||
return mapper(e);
|
||||
});
|
||||
}
|
||||
|
||||
function processEphemeralEvents(client: MatrixClient, roomId: string, ephEvents: IMinimalEvent[]): void {
|
||||
const ephemeralEvents = mapEvents(client, roomId, ephEvents);
|
||||
const room = client.getRoom(roomId);
|
||||
if (!room) {
|
||||
logger.warn("got ephemeral events for room but room doesn't exist on client:", roomId);
|
||||
return;
|
||||
}
|
||||
room.addEphemeralEvents(ephemeralEvents);
|
||||
ephemeralEvents.forEach((e) => {
|
||||
client.emit(ClientEvent.Event, e);
|
||||
});
|
||||
}
|
||||
|
||||
+14
-10
@@ -27,6 +27,10 @@ import { HTTPError } from "./http-api";
|
||||
// to determine the max time we're willing to wait.
|
||||
const BUFFER_PERIOD_MS = 10 * 1000;
|
||||
|
||||
export const MSC3575_WILDCARD = "*";
|
||||
export const MSC3575_STATE_KEY_ME = "$ME";
|
||||
export const MSC3575_STATE_KEY_LAZY = "$LAZY";
|
||||
|
||||
/**
|
||||
* Represents a subscription to a room or set of rooms. Controls which events are returned.
|
||||
*/
|
||||
@@ -165,7 +169,7 @@ class SlidingList {
|
||||
* Construct a new sliding list.
|
||||
* @param {MSC3575List} list The range, sort and filter values to use for this list.
|
||||
*/
|
||||
constructor(list: MSC3575List) {
|
||||
public constructor(list: MSC3575List) {
|
||||
this.replaceList(list);
|
||||
}
|
||||
|
||||
@@ -369,7 +373,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
* @param {MatrixClient} client The client to use for /sync calls.
|
||||
* @param {number} timeoutMS The number of milliseconds to wait for a response.
|
||||
*/
|
||||
constructor(
|
||||
public constructor(
|
||||
private readonly proxyBaseUrl: string,
|
||||
lists: MSC3575List[],
|
||||
private roomSubscriptionInfo: MSC3575RoomSubscription,
|
||||
@@ -387,7 +391,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
* useCustomSubscription.
|
||||
* @param sub The subscription information.
|
||||
*/
|
||||
public addCustomSubscription(name: string, sub: MSC3575RoomSubscription) {
|
||||
public addCustomSubscription(name: string, sub: MSC3575RoomSubscription): void {
|
||||
this.customSubscriptions.set(name, sub);
|
||||
}
|
||||
|
||||
@@ -398,7 +402,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
* @param name The name of the subscription. If this name is unknown, the default subscription
|
||||
* will be used.
|
||||
*/
|
||||
public useCustomSubscription(roomId: string, name: string) {
|
||||
public useCustomSubscription(roomId: string, name: string): void {
|
||||
this.roomIdToCustomSubscription.set(roomId, name);
|
||||
// unconfirm this subscription so a resend() will send it up afresh.
|
||||
this.confirmedRoomSubscriptions.delete(roomId);
|
||||
@@ -570,7 +574,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
this.emit(SlidingSyncEvent.Lifecycle, state, resp, err);
|
||||
}
|
||||
|
||||
private shiftRight(listIndex: number, hi: number, low: number) {
|
||||
private shiftRight(listIndex: number, hi: number, low: number): void {
|
||||
// l h
|
||||
// 0,1,2,3,4 <- before
|
||||
// 0,1,2,2,3 <- after, hi is deleted and low is duplicated
|
||||
@@ -584,7 +588,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
}
|
||||
}
|
||||
|
||||
private shiftLeft(listIndex: number, hi: number, low: number) {
|
||||
private shiftLeft(listIndex: number, hi: number, low: number): void {
|
||||
// l h
|
||||
// 0,1,2,3,4 <- before
|
||||
// 0,1,3,4,4 <- after, low is deleted and hi is duplicated
|
||||
@@ -598,7 +602,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
}
|
||||
}
|
||||
|
||||
private removeEntry(listIndex: number, index: number) {
|
||||
private removeEntry(listIndex: number, index: number): void {
|
||||
// work out the max index
|
||||
let max = -1;
|
||||
for (const n in this.lists[listIndex].roomIndexToRoomId) {
|
||||
@@ -614,7 +618,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
delete this.lists[listIndex].roomIndexToRoomId[max];
|
||||
}
|
||||
|
||||
private addEntry(listIndex: number, index: number) {
|
||||
private addEntry(listIndex: number, index: number): void {
|
||||
// work out the max index
|
||||
let max = -1;
|
||||
for (const n in this.lists[listIndex].roomIndexToRoomId) {
|
||||
@@ -743,7 +747,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
private resolveTransactionDefers(txnId?: string) {
|
||||
private resolveTransactionDefers(txnId?: string): void {
|
||||
if (!txnId) {
|
||||
return;
|
||||
}
|
||||
@@ -807,7 +811,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
/**
|
||||
* Start syncing with the server. Blocks until stopped.
|
||||
*/
|
||||
public async start() {
|
||||
public async start(): Promise<void> {
|
||||
this.abortController = new AbortController();
|
||||
|
||||
let currentPos: string | undefined;
|
||||
|
||||
@@ -25,7 +25,7 @@ import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDevice
|
||||
|
||||
type DbMigration = (db: IDBDatabase) => void;
|
||||
const DB_MIGRATIONS: DbMigration[] = [
|
||||
(db) => {
|
||||
(db): void => {
|
||||
// Make user store, clobber based on user ID. (userId property of User objects)
|
||||
db.createObjectStore("users", { keyPath: ["userId"] });
|
||||
|
||||
@@ -36,15 +36,15 @@ const DB_MIGRATIONS: DbMigration[] = [
|
||||
// Make /sync store (sync tokens, room data, etc), always clobber (const key).
|
||||
db.createObjectStore("sync", { keyPath: ["clobber"] });
|
||||
},
|
||||
(db) => {
|
||||
(db): void => {
|
||||
const oobMembersStore = db.createObjectStore(
|
||||
"oob_membership_events", {
|
||||
keyPath: ["room_id", "state_key"],
|
||||
});
|
||||
oobMembersStore.createIndex("room", "room_id");
|
||||
},
|
||||
(db) => { db.createObjectStore("client_options", { keyPath: ["clobber"] }); },
|
||||
(db) => { db.createObjectStore("to_device_queue", { autoIncrement: true }); },
|
||||
(db): void => { db.createObjectStore("client_options", { keyPath: ["clobber"] }); },
|
||||
(db): void => { db.createObjectStore("to_device_queue", { autoIncrement: true }); },
|
||||
// Expand as needed.
|
||||
];
|
||||
const VERSION = DB_MIGRATIONS.length;
|
||||
@@ -67,11 +67,11 @@ function selectQuery<T>(
|
||||
const query = store.openCursor(keyRange);
|
||||
return new Promise((resolve, reject) => {
|
||||
const results: T[] = [];
|
||||
query.onerror = () => {
|
||||
query.onerror = (): void => {
|
||||
reject(new Error("Query failed: " + query.error));
|
||||
};
|
||||
// collect results
|
||||
query.onsuccess = () => {
|
||||
query.onsuccess = (): void => {
|
||||
const cursor = query.result;
|
||||
if (!cursor) {
|
||||
resolve(results);
|
||||
@@ -85,10 +85,10 @@ function selectQuery<T>(
|
||||
|
||||
function txnAsPromise(txn: IDBTransaction): Promise<Event> {
|
||||
return new Promise((resolve, reject) => {
|
||||
txn.oncomplete = function(event) {
|
||||
txn.oncomplete = function(event): void {
|
||||
resolve(event);
|
||||
};
|
||||
txn.onerror = function() {
|
||||
txn.onerror = function(): void {
|
||||
reject(txn.error);
|
||||
};
|
||||
});
|
||||
@@ -96,10 +96,10 @@ function txnAsPromise(txn: IDBTransaction): Promise<Event> {
|
||||
|
||||
function reqAsEventPromise(req: IDBRequest): Promise<Event> {
|
||||
return new Promise((resolve, reject) => {
|
||||
req.onsuccess = function(event) {
|
||||
req.onsuccess = function(event): void {
|
||||
resolve(event);
|
||||
};
|
||||
req.onerror = function() {
|
||||
req.onerror = function(): void {
|
||||
reject(req.error);
|
||||
};
|
||||
});
|
||||
@@ -107,8 +107,8 @@ function reqAsEventPromise(req: IDBRequest): Promise<Event> {
|
||||
|
||||
function reqAsPromise(req: IDBRequest): Promise<IDBRequest> {
|
||||
return new Promise((resolve, reject) => {
|
||||
req.onsuccess = () => resolve(req);
|
||||
req.onerror = (err) => reject(err);
|
||||
req.onsuccess = (): void => resolve(req);
|
||||
req.onerror = (err): void => reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
||||
* @param {string=} dbName Optional database name. The same name must be used
|
||||
* to open the same database.
|
||||
*/
|
||||
constructor(private readonly indexedDB: IDBFactory, dbName = "default") {
|
||||
public constructor(private readonly indexedDB: IDBFactory, dbName = "default") {
|
||||
this.dbName = "matrix-js-sdk:" + dbName;
|
||||
this.syncAccumulator = new SyncAccumulator();
|
||||
}
|
||||
@@ -161,7 +161,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
||||
|
||||
logger.log(`LocalIndexedDBStoreBackend.connect: connecting...`);
|
||||
const req = this.indexedDB.open(this.dbName, VERSION);
|
||||
req.onupgradeneeded = (ev) => {
|
||||
req.onupgradeneeded = (ev): void => {
|
||||
const db = req.result;
|
||||
const oldVersion = ev.oldVersion;
|
||||
logger.log(
|
||||
@@ -176,22 +176,22 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
||||
});
|
||||
};
|
||||
|
||||
req.onblocked = () => {
|
||||
req.onblocked = (): void => {
|
||||
logger.log(`can't yet open LocalIndexedDBStoreBackend because it is open elsewhere`);
|
||||
};
|
||||
|
||||
logger.log(`LocalIndexedDBStoreBackend.connect: awaiting connection...`);
|
||||
return reqAsEventPromise(req).then(() => {
|
||||
return reqAsEventPromise(req).then(async () => {
|
||||
logger.log(`LocalIndexedDBStoreBackend.connect: connected`);
|
||||
this.db = req.result;
|
||||
|
||||
// add a poorly-named listener for when deleteDatabase is called
|
||||
// so we can close our db connections.
|
||||
this.db.onversionchange = () => {
|
||||
this.db.onversionchange = (): void => {
|
||||
this.db?.close();
|
||||
};
|
||||
|
||||
return this.init();
|
||||
await this.init();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
||||
* Having connected, load initial data from the database and prepare for use
|
||||
* @return {Promise} Resolves on success
|
||||
*/
|
||||
private init() {
|
||||
private init(): Promise<unknown> {
|
||||
return Promise.all([
|
||||
this.loadAccountData(),
|
||||
this.loadSyncData(),
|
||||
@@ -243,7 +243,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
||||
// were all known already
|
||||
let oobWritten = false;
|
||||
|
||||
request.onsuccess = () => {
|
||||
request.onsuccess = (): void => {
|
||||
const cursor = request.result;
|
||||
if (!cursor) {
|
||||
// Unknown room
|
||||
@@ -260,7 +260,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
||||
}
|
||||
cursor.continue();
|
||||
};
|
||||
request.onerror = (err) => {
|
||||
request.onerror = (err): void => {
|
||||
reject(err);
|
||||
};
|
||||
}).then((events) => {
|
||||
@@ -346,11 +346,11 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
||||
logger.log(`Removing indexeddb instance: ${this.dbName}`);
|
||||
const req = this.indexedDB.deleteDatabase(this.dbName);
|
||||
|
||||
req.onblocked = () => {
|
||||
req.onblocked = (): void => {
|
||||
logger.log(`can't yet delete indexeddb ${this.dbName} because it is open elsewhere`);
|
||||
};
|
||||
|
||||
req.onerror = () => {
|
||||
req.onerror = (): void => {
|
||||
// in firefox, with indexedDB disabled, this fails with a
|
||||
// DOMError. We treat this as non-fatal, so that we can still
|
||||
// use the app.
|
||||
@@ -358,7 +358,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
||||
resolve();
|
||||
};
|
||||
|
||||
req.onsuccess = () => {
|
||||
req.onsuccess = (): void => {
|
||||
logger.log(`Removed indexeddb instance: ${this.dbName}`);
|
||||
resolve();
|
||||
};
|
||||
|
||||
@@ -42,7 +42,7 @@ export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend {
|
||||
* @param {string=} dbName Optional database name. The same name must be used
|
||||
* to open the same database.
|
||||
*/
|
||||
constructor(
|
||||
public constructor(
|
||||
private readonly workerFactory: () => Worker,
|
||||
private readonly dbName?: string,
|
||||
) {}
|
||||
|
||||
@@ -45,7 +45,7 @@ export class IndexedDBStoreWorker {
|
||||
* @param {function} postMessage The web worker postMessage function that
|
||||
* should be used to communicate back to the main script.
|
||||
*/
|
||||
constructor(private readonly postMessage: InstanceType<typeof Worker>["postMessage"]) {}
|
||||
public constructor(private readonly postMessage: InstanceType<typeof Worker>["postMessage"]) {}
|
||||
|
||||
/**
|
||||
* Passes a message event from the main script into the class. This method
|
||||
|
||||
@@ -53,7 +53,7 @@ type EventHandlerMap = {
|
||||
};
|
||||
|
||||
export class IndexedDBStore extends MemoryStore {
|
||||
static exists(indexedDB: IDBFactory, dbName: string): Promise<boolean> {
|
||||
public static exists(indexedDB: IDBFactory, dbName: string): Promise<boolean> {
|
||||
return LocalIndexedDBStoreBackend.exists(indexedDB, dbName);
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ export class IndexedDBStore extends MemoryStore {
|
||||
* this API if you need to perform specific indexeddb actions like deleting the
|
||||
* database.
|
||||
*/
|
||||
constructor(opts: IOpts) {
|
||||
public constructor(opts: IOpts) {
|
||||
super(opts);
|
||||
|
||||
if (!opts.indexedDB) {
|
||||
|
||||
+6
-6
@@ -69,7 +69,7 @@ export class MemoryStore implements IStore {
|
||||
private pendingToDeviceBatches: IndexedToDeviceBatch[] = [];
|
||||
private nextToDeviceBatchId = 0;
|
||||
|
||||
constructor(opts: IOpts = {}) {
|
||||
public constructor(opts: IOpts = {}) {
|
||||
this.localStorage = opts.localStorage;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ export class MemoryStore implements IStore {
|
||||
* Set the token to stream from.
|
||||
* @param {string} token The token to stream from.
|
||||
*/
|
||||
public setSyncToken(token: string) {
|
||||
public setSyncToken(token: string): void {
|
||||
this.syncToken = token;
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ export class MemoryStore implements IStore {
|
||||
* Store the given room.
|
||||
* @param {Room} room The room to be stored. All properties must be stored.
|
||||
*/
|
||||
public storeRoom(room: Room) {
|
||||
public storeRoom(room: Room): void {
|
||||
this.rooms[room.roomId] = room;
|
||||
// add listeners for room member changes so we can keep the room member
|
||||
// map up-to-date.
|
||||
@@ -116,7 +116,7 @@ export class MemoryStore implements IStore {
|
||||
* @param {RoomState} state
|
||||
* @param {RoomMember} member
|
||||
*/
|
||||
private onRoomMember = (event: MatrixEvent | null, state: RoomState, member: RoomMember) => {
|
||||
private onRoomMember = (event: MatrixEvent | null, state: RoomState, member: RoomMember): void => {
|
||||
if (member.membership === "invite") {
|
||||
// We do NOT add invited members because people love to typo user IDs
|
||||
// which would then show up in these lists (!)
|
||||
@@ -217,7 +217,7 @@ export class MemoryStore implements IStore {
|
||||
* @param {string} token The token associated with these events.
|
||||
* @param {boolean} toStart True if these are paginated results.
|
||||
*/
|
||||
public storeEvents(room: Room, events: MatrixEvent[], token: string, toStart: boolean) {
|
||||
public storeEvents(room: Room, events: MatrixEvent[], token: string, toStart: boolean): void {
|
||||
// no-op because they've already been added to the room instance.
|
||||
}
|
||||
|
||||
@@ -275,7 +275,7 @@ export class MemoryStore implements IStore {
|
||||
* @param {string} filterName
|
||||
* @param {string} filterId
|
||||
*/
|
||||
public setFilterIdByName(filterName: string, filterId?: string) {
|
||||
public setFilterIdByName(filterName: string, filterId?: string): void {
|
||||
if (!this.localStorage) {
|
||||
return;
|
||||
}
|
||||
|
||||
+9
-9
@@ -56,7 +56,7 @@ export class StubStore implements IStore {
|
||||
* Set the sync token.
|
||||
* @param {string} token
|
||||
*/
|
||||
public setSyncToken(token: string) {
|
||||
public setSyncToken(token: string): void {
|
||||
this.fromToken = token;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ export class StubStore implements IStore {
|
||||
* No-op.
|
||||
* @param {Room} room
|
||||
*/
|
||||
public storeRoom(room: Room) {}
|
||||
public storeRoom(room: Room): void {}
|
||||
|
||||
/**
|
||||
* No-op.
|
||||
@@ -87,7 +87,7 @@ export class StubStore implements IStore {
|
||||
* Permanently delete a room.
|
||||
* @param {string} roomId
|
||||
*/
|
||||
public removeRoom(roomId: string) {
|
||||
public removeRoom(roomId: string): void {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ export class StubStore implements IStore {
|
||||
* No-op.
|
||||
* @param {User} user
|
||||
*/
|
||||
public storeUser(user: User) {}
|
||||
public storeUser(user: User): void {}
|
||||
|
||||
/**
|
||||
* No-op.
|
||||
@@ -139,13 +139,13 @@ export class StubStore implements IStore {
|
||||
* @param {string} token The token associated with these events.
|
||||
* @param {boolean} toStart True if these are paginated results.
|
||||
*/
|
||||
public storeEvents(room: Room, events: MatrixEvent[], token: string, toStart: boolean) {}
|
||||
public storeEvents(room: Room, events: MatrixEvent[], token: string, toStart: boolean): void {}
|
||||
|
||||
/**
|
||||
* Store a filter.
|
||||
* @param {Filter} filter
|
||||
*/
|
||||
public storeFilter(filter: Filter) {}
|
||||
public storeFilter(filter: Filter): void {}
|
||||
|
||||
/**
|
||||
* Retrieve a filter.
|
||||
@@ -171,13 +171,13 @@ export class StubStore implements IStore {
|
||||
* @param {string} filterName
|
||||
* @param {string} filterId
|
||||
*/
|
||||
public setFilterIdByName(filterName: string, filterId?: string) {}
|
||||
public setFilterIdByName(filterName: string, filterId?: string): void {}
|
||||
|
||||
/**
|
||||
* Store user-scoped account data events
|
||||
* @param {Array<MatrixEvent>} events The events to store.
|
||||
*/
|
||||
public storeAccountDataEvents(events: MatrixEvent[]) {}
|
||||
public storeAccountDataEvents(events: MatrixEvent[]): void {}
|
||||
|
||||
/**
|
||||
* Get account data event by event type
|
||||
@@ -209,7 +209,7 @@ export class StubStore implements IStore {
|
||||
/**
|
||||
* Save does nothing as there is no backing data store.
|
||||
*/
|
||||
public save() {}
|
||||
public save(): void {}
|
||||
|
||||
/**
|
||||
* Startup does nothing.
|
||||
|
||||
+47
-16
@@ -24,7 +24,7 @@ import { deepCopy, isSupportedReceiptType } 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";
|
||||
import { MAIN_ROOM_TIMELINE, ReceiptContent, ReceiptType } from "./@types/read_receipts";
|
||||
import { UNREAD_THREAD_NOTIFICATIONS } from './@types/sync';
|
||||
|
||||
interface IOpts {
|
||||
@@ -165,6 +165,15 @@ interface IRoom {
|
||||
eventId: string;
|
||||
};
|
||||
};
|
||||
_threadReadReceipts: {
|
||||
[threadId: string]: {
|
||||
[userId: string]: {
|
||||
data: IMinimalEvent;
|
||||
type: ReceiptType;
|
||||
eventId: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface ISyncData {
|
||||
@@ -202,7 +211,7 @@ export class SyncAccumulator {
|
||||
* never be more. This cannot be 0 or else it makes it impossible to scroll
|
||||
* back in a room. Default: 50.
|
||||
*/
|
||||
constructor(private readonly opts: IOpts = {}) {
|
||||
public constructor(private readonly opts: IOpts = {}) {
|
||||
this.opts.maxTimelineEntries = this.opts.maxTimelineEntries || 50;
|
||||
}
|
||||
|
||||
@@ -369,6 +378,7 @@ export class SyncAccumulator {
|
||||
_unreadThreadNotifications: {},
|
||||
_summary: {},
|
||||
_readReceipts: {},
|
||||
_threadReadReceipts: {},
|
||||
};
|
||||
}
|
||||
const currentData = this.joinRooms[roomId];
|
||||
@@ -425,23 +435,30 @@ 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) => {
|
||||
Object.entries<{
|
||||
[eventId: string]: {
|
||||
[receiptType: string]: {
|
||||
[userId: string]: IMinimalEvent;
|
||||
};
|
||||
};
|
||||
}>(e.content[eventId]).forEach(([key, value]) => {
|
||||
Object.entries<ReceiptContent>(e.content[eventId]).forEach(([key, value]) => {
|
||||
if (!isSupportedReceiptType(key)) return;
|
||||
|
||||
Object.keys(value).forEach((userId) => {
|
||||
// clobber on user ID
|
||||
currentData._readReceipts[userId] = {
|
||||
for (const userId of Object.keys(value)) {
|
||||
const data = e.content[eventId][key][userId];
|
||||
|
||||
const receipt = {
|
||||
data: e.content[eventId][key][userId],
|
||||
type: key as ReceiptType,
|
||||
eventId: eventId,
|
||||
};
|
||||
});
|
||||
|
||||
if (!data.thread_id || data.thread_id === MAIN_ROOM_TIMELINE) {
|
||||
currentData._readReceipts[userId] = receipt;
|
||||
} else {
|
||||
currentData._threadReadReceipts = {
|
||||
...currentData._threadReadReceipts,
|
||||
[data.thread_id]: {
|
||||
...(currentData._threadReadReceipts[data.thread_id] ?? {}),
|
||||
[userId]: receipt,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -566,8 +583,8 @@ export class SyncAccumulator {
|
||||
// $event_id: { "m.read": { $user_id: $json } }
|
||||
},
|
||||
};
|
||||
Object.keys(roomData._readReceipts).forEach((userId) => {
|
||||
const receiptData = roomData._readReceipts[userId];
|
||||
|
||||
for (const [userId, receiptData] of Object.entries(roomData._readReceipts)) {
|
||||
if (!receiptEvent.content[receiptData.eventId]) {
|
||||
receiptEvent.content[receiptData.eventId] = {};
|
||||
}
|
||||
@@ -577,7 +594,21 @@ export class SyncAccumulator {
|
||||
receiptEvent.content[receiptData.eventId][receiptData.type][userId] = (
|
||||
receiptData.data
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
for (const threadReceipts of Object.values(roomData._threadReadReceipts)) {
|
||||
for (const [userId, receiptData] of Object.entries(threadReceipts)) {
|
||||
if (!receiptEvent.content[receiptData.eventId]) {
|
||||
receiptEvent.content[receiptData.eventId] = {};
|
||||
}
|
||||
if (!receiptEvent.content[receiptData.eventId][receiptData.type]) {
|
||||
receiptEvent.content[receiptData.eventId][receiptData.type] = {};
|
||||
}
|
||||
receiptEvent.content[receiptData.eventId][receiptData.type][userId] = (
|
||||
receiptData.data
|
||||
);
|
||||
}
|
||||
}
|
||||
// add only if we have some receipt data
|
||||
if (Object.keys(receiptEvent.content).length > 0) {
|
||||
roomJson.ephemeral.events.push(receiptEvent as IMinimalEvent);
|
||||
|
||||
+8
-8
@@ -106,7 +106,7 @@ function getFilterName(userId: string, suffix?: string): string {
|
||||
return `FILTER_SYNC_${userId}` + (suffix ? "_" + suffix : "");
|
||||
}
|
||||
|
||||
function debuglog(...params) {
|
||||
function debuglog(...params): void {
|
||||
if (!DEBUG) return;
|
||||
logger.log(...params);
|
||||
}
|
||||
@@ -175,7 +175,7 @@ export class SyncApi {
|
||||
private failedSyncCount = 0; // Number of consecutive failed /sync requests
|
||||
private storeIsInvalid = false; // flag set if the store needs to be cleared before we can start
|
||||
|
||||
constructor(private readonly client: MatrixClient, private readonly opts: Partial<IStoredClientOpts> = {}) {
|
||||
public constructor(private readonly client: MatrixClient, private readonly opts: Partial<IStoredClientOpts> = {}) {
|
||||
this.opts.initialSyncLimit = this.opts.initialSyncLimit ?? 8;
|
||||
this.opts.resolveInvitesToProfiles = this.opts.resolveInvitesToProfiles || false;
|
||||
this.opts.pollTimeout = this.opts.pollTimeout || (30 * 1000);
|
||||
@@ -183,7 +183,7 @@ export class SyncApi {
|
||||
this.opts.experimentalThreadSupport = this.opts.experimentalThreadSupport === true;
|
||||
|
||||
if (!opts.canResetEntireTimeline) {
|
||||
opts.canResetEntireTimeline = (roomId: string) => {
|
||||
opts.canResetEntireTimeline = (roomId: string): boolean => {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
@@ -554,7 +554,7 @@ export class SyncApi {
|
||||
return false;
|
||||
}
|
||||
|
||||
private getPushRules = async () => {
|
||||
private getPushRules = async (): Promise<void> => {
|
||||
try {
|
||||
debuglog("Getting push rules...");
|
||||
const result = await this.client.getPushRules();
|
||||
@@ -572,7 +572,7 @@ export class SyncApi {
|
||||
}
|
||||
};
|
||||
|
||||
private buildDefaultFilter = () => {
|
||||
private buildDefaultFilter = (): Filter => {
|
||||
const filter = new Filter(this.client.credentials.userId);
|
||||
if (this.client.canSupport.get(Feature.ThreadUnreadNotifications) !== ServerSupport.Unsupported) {
|
||||
filter.setUnreadThreadNotifications(true);
|
||||
@@ -580,7 +580,7 @@ export class SyncApi {
|
||||
return filter;
|
||||
};
|
||||
|
||||
private checkLazyLoadStatus = async () => {
|
||||
private checkLazyLoadStatus = async (): Promise<void> => {
|
||||
debuglog("Checking lazy load status...");
|
||||
if (this.opts.lazyLoadMembers && this.client.isGuest()) {
|
||||
this.opts.lazyLoadMembers = false;
|
||||
@@ -1389,7 +1389,7 @@ export class SyncApi {
|
||||
|
||||
this.processEventsForNotifs(room, events);
|
||||
|
||||
const processRoomEvent = async (e) => {
|
||||
const processRoomEvent = async (e): Promise<void> => {
|
||||
client.emit(ClientEvent.Event, e);
|
||||
if (e.isState() && e.getType() == "m.room.encryption" && this.opts.crypto) {
|
||||
await this.opts.crypto.onCryptoEvent(e);
|
||||
@@ -1521,7 +1521,7 @@ export class SyncApi {
|
||||
* @param {boolean} connDidFail True if a connectivity failure has been detected. Optional.
|
||||
*/
|
||||
private pokeKeepAlive(connDidFail = false): void {
|
||||
const success = () => {
|
||||
const success = (): void => {
|
||||
clearTimeout(this.keepAliveTimer);
|
||||
if (this.connectionReturnedDefer) {
|
||||
this.connectionReturnedDefer.resolve(connDidFail);
|
||||
|
||||
@@ -32,7 +32,7 @@ const DEBUG = false;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
const debuglog = DEBUG ? logger.log.bind(logger) : function() {};
|
||||
const debuglog = DEBUG ? logger.log.bind(logger) : function(): void {};
|
||||
|
||||
/**
|
||||
* the number of times we ask the server for more events before giving up
|
||||
@@ -84,7 +84,7 @@ export class TimelineWindow {
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
constructor(
|
||||
public constructor(
|
||||
private readonly client: MatrixClient,
|
||||
private readonly timelineSet: EventTimelineSet,
|
||||
opts: IOpts = {},
|
||||
@@ -104,7 +104,7 @@ export class TimelineWindow {
|
||||
public load(initialEventId?: string, initialWindowSize = 20): Promise<void> {
|
||||
// given an EventTimeline, find the event we were looking for, and initialise our
|
||||
// fields so that the event in question is in the middle of the window.
|
||||
const initFields = (timeline: Optional<EventTimeline>) => {
|
||||
const initFields = (timeline: Optional<EventTimeline>): void => {
|
||||
if (!timeline) {
|
||||
throw new Error("No timeline given to initFields");
|
||||
}
|
||||
@@ -430,7 +430,7 @@ export class TimelineIndex {
|
||||
public pendingPaginate?: Promise<boolean>;
|
||||
|
||||
// index: the indexes are relative to BaseIndex, so could well be negative.
|
||||
constructor(public timeline: EventTimeline, public index: number) {}
|
||||
public constructor(public timeline: EventTimeline, public index: number) {}
|
||||
|
||||
/**
|
||||
* @return {number} the minimum possible value for the index in the current
|
||||
|
||||
+17
-4
@@ -178,7 +178,7 @@ export function removeElement<T>(
|
||||
* @param {*} value The thing to check.
|
||||
* @return {boolean} True if it is a function.
|
||||
*/
|
||||
export function isFunction(value: any) {
|
||||
export function isFunction(value: any): boolean {
|
||||
return Object.prototype.toString.call(value) === "[object Function]";
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ export function isFunction(value: any) {
|
||||
* @throws If the object is missing keys.
|
||||
*/
|
||||
// note using 'keys' here would shadow the 'keys' function defined above
|
||||
export function checkObjectHasKeys(obj: object, keys: string[]) {
|
||||
export function checkObjectHasKeys(obj: object, keys: string[]): void {
|
||||
for (const key of keys) {
|
||||
if (!obj.hasOwnProperty(key)) {
|
||||
throw new Error("Missing required key: " + key);
|
||||
@@ -383,7 +383,7 @@ export function globToRegexp(glob: string, extended = false): string {
|
||||
if (!extended) {
|
||||
replacements.push([
|
||||
/\\\[(!|)(.*)\\]/g,
|
||||
(_match: string, neg: string, pat: string) => [
|
||||
(_match: string, neg: string, pat: string): string => [
|
||||
'[',
|
||||
neg ? '^' : '',
|
||||
pat.replace(/\\-/, '-'),
|
||||
@@ -490,7 +490,7 @@ export function simpleRetryOperation<T>(promiseFn: (attempt: number) => Promise<
|
||||
* The default alphabet used by string averaging in this SDK. This matches
|
||||
* all usefully printable ASCII characters (0x20-0x7E, inclusive).
|
||||
*/
|
||||
export const DEFAULT_ALPHABET = (() => {
|
||||
export const DEFAULT_ALPHABET = ((): string => {
|
||||
let str = "";
|
||||
for (let c = 0x20; c <= 0x7E; c++) {
|
||||
str += String.fromCharCode(c);
|
||||
@@ -694,3 +694,16 @@ export function sortEventsByLatestContentTimestamp(left: MatrixEvent, right: Mat
|
||||
export function isSupportedReceiptType(receiptType: string): boolean {
|
||||
return [ReceiptType.Read, ReceiptType.ReadPrivate].includes(receiptType as ReceiptType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether two maps are equal.
|
||||
* @param eq The equivalence relation to compare values by. Defaults to strict equality.
|
||||
*/
|
||||
export function mapsEqual<K, V>(x: Map<K, V>, y: Map<K, V>, eq = (v1: V, v2: V): boolean => v1 === v2): boolean {
|
||||
if (x.size !== y.size) return false;
|
||||
for (const [k, v1] of x) {
|
||||
const v2 = y.get(k);
|
||||
if (v2 === undefined || !eq(v1, v2)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export const acquireContext = (): AudioContext => {
|
||||
* released, allowing the context and associated hardware resources to be
|
||||
* cleaned up if nothing else is using it.
|
||||
*/
|
||||
export const releaseContext = () => {
|
||||
export const releaseContext = (): void => {
|
||||
refCount--;
|
||||
if (refCount === 0) {
|
||||
audioContext?.close();
|
||||
|
||||
+16
-7
@@ -268,7 +268,7 @@ const CALL_TIMEOUT_MS = 60000;
|
||||
export class CallError extends Error {
|
||||
public readonly code: string;
|
||||
|
||||
constructor(code: CallErrorCode, msg: string, err: Error) {
|
||||
public constructor(code: CallErrorCode, msg: string, err: Error) {
|
||||
// Still don't think there's any way to have proper nested errors
|
||||
super(msg + ": " + err);
|
||||
|
||||
@@ -404,7 +404,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
private opponentSessionId?: string;
|
||||
public groupCallId?: string;
|
||||
|
||||
constructor(opts: CallOpts) {
|
||||
public constructor(opts: CallOpts) {
|
||||
super();
|
||||
|
||||
this.roomId = opts.roomId;
|
||||
@@ -452,7 +452,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
* @param label A human readable label for this datachannel
|
||||
* @param options An object providing configuration options for the data channel.
|
||||
*/
|
||||
public createDataChannel(label: string, options: RTCDataChannelInit | undefined) {
|
||||
public createDataChannel(label: string, options: RTCDataChannelInit | undefined): RTCDataChannel {
|
||||
const dataChannel = this.peerConn!.createDataChannel(label, options);
|
||||
this.emit(CallEvent.DataChannel, dataChannel);
|
||||
return dataChannel;
|
||||
@@ -462,6 +462,10 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
return this.opponentMember;
|
||||
}
|
||||
|
||||
public getOpponentDeviceId(): string | undefined {
|
||||
return this.opponentDeviceId;
|
||||
}
|
||||
|
||||
public getOpponentSessionId(): string | undefined {
|
||||
return this.opponentSessionId;
|
||||
}
|
||||
@@ -564,7 +568,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
return this.feeds.filter((feed) => !feed.isLocal());
|
||||
}
|
||||
|
||||
private async initOpponentCrypto() {
|
||||
private async initOpponentCrypto(): Promise<void> {
|
||||
if (!this.opponentDeviceId) return;
|
||||
if (!this.client.getUseE2eForGroupCall()) return;
|
||||
// It's possible to want E2EE and yet not have the means to manage E2EE
|
||||
@@ -644,6 +648,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
client: this.client,
|
||||
roomId: this.roomId,
|
||||
userId,
|
||||
deviceId: this.getOpponentDeviceId(),
|
||||
stream,
|
||||
purpose,
|
||||
audioMuted,
|
||||
@@ -688,6 +693,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
audioMuted: false,
|
||||
videoMuted: false,
|
||||
userId,
|
||||
deviceId: this.getOpponentDeviceId(),
|
||||
stream,
|
||||
purpose,
|
||||
}));
|
||||
@@ -718,6 +724,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
audioMuted: false,
|
||||
videoMuted: false,
|
||||
userId,
|
||||
deviceId: this.getOpponentDeviceId(),
|
||||
stream,
|
||||
purpose,
|
||||
}),
|
||||
@@ -938,7 +945,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
}, invite.lifetime - event.getLocalAge());
|
||||
|
||||
const onState = (state: CallState) => {
|
||||
const onState = (state: CallState): void => {
|
||||
if (state !== CallState.Ringing) {
|
||||
clearTimeout(ringingTimer);
|
||||
this.off(CallEvent.State, onState);
|
||||
@@ -1009,6 +1016,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
client: this.client,
|
||||
roomId: this.roomId,
|
||||
userId: this.client.getUserId()!,
|
||||
deviceId: this.client.getDeviceId() ?? undefined,
|
||||
stream,
|
||||
purpose: SDPStreamMetadataPurpose.Usermedia,
|
||||
audioMuted: false,
|
||||
@@ -1079,7 +1087,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
logger.debug(`Ending call ${this.callId} with reason ${reason}`);
|
||||
this.terminate(CallParty.Local, reason, !suppressEvent);
|
||||
// We don't want to send hangup here if we didn't even get to sending an invite
|
||||
if (this.state === CallState.WaitLocalMedia) return;
|
||||
if ([CallState.Fledgling, CallState.WaitLocalMedia].includes(this.state)) return;
|
||||
const content = {};
|
||||
// Don't send UserHangup reason to older clients
|
||||
if ((this.opponentVersion && this.opponentVersion !== 0) || reason !== CallErrorCode.UserHangup) {
|
||||
@@ -2132,7 +2140,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.pushRemoteFeed(stream);
|
||||
|
||||
if (!this.removeTrackListeners.has(stream)) {
|
||||
const onRemoveTrack = () => {
|
||||
const onRemoveTrack = (): void => {
|
||||
if (stream.getTracks().length === 0) {
|
||||
logger.info(`Call ${this.callId} removing track streamId: ${stream.id}`);
|
||||
this.deleteFeedByStream(stream);
|
||||
@@ -2584,6 +2592,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
client: this.client,
|
||||
roomId: this.roomId,
|
||||
userId: this.client.getUserId()!,
|
||||
deviceId: this.client.getDeviceId() ?? undefined,
|
||||
stream,
|
||||
purpose: SDPStreamMetadataPurpose.Usermedia,
|
||||
audioMuted: false,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user