1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-31 15:24:23 +03:00

Apply more strict typescript around the codebase (#2778)

* Apply more strict typescript around the codebase

* Fix tests

* Revert strict mode commit

* Iterate strict

* Iterate

* Iterate strict

* Iterate

* Fix tests

* Iterate

* Iterate strict

* Add tests

* Iterate

* Iterate

* Fix tests

* Fix tests

* Strict types be strict

* Fix types

* detectOpenHandles

* Strict

* Fix client not stopping

* Add sync peeking tests

* Make test happier

* More strict

* Iterate

* Stabilise

* Moar strictness

* Improve coverage

* Fix types

* Fix types

* Improve types further

* Fix types

* Improve typing of NamespacedValue

* Fix types
This commit is contained in:
Michael Telatynski
2022-10-21 11:44:40 +01:00
committed by GitHub
parent fdbbd9bca4
commit 867a0ca7ee
94 changed files with 1980 additions and 1735 deletions

View File

@ -38,8 +38,8 @@ import { IKeysUploadResponse, IUploadKeysRequest } from '../src/client';
export class TestClient {
public readonly httpBackend: MockHttpBackend;
public readonly client: MatrixClient;
public deviceKeys: IDeviceKeys;
public oneTimeKeys: Record<string, IOneTimeKey>;
public deviceKeys?: IDeviceKeys | null;
public oneTimeKeys?: Record<string, IOneTimeKey>;
constructor(
public readonly userId?: string,
@ -123,7 +123,7 @@ export class TestClient {
logger.log(this + ': received device keys');
// we expect this to happen before any one-time keys are uploaded.
expect(Object.keys(this.oneTimeKeys).length).toEqual(0);
expect(Object.keys(this.oneTimeKeys!).length).toEqual(0);
this.deviceKeys = content.device_keys;
return { one_time_key_counts: { signed_curve25519: 0 } };
@ -138,9 +138,9 @@ export class TestClient {
* @returns {Promise} for the one-time keys
*/
public awaitOneTimeKeyUpload(): Promise<Record<string, IOneTimeKey>> {
if (Object.keys(this.oneTimeKeys).length != 0) {
if (Object.keys(this.oneTimeKeys!).length != 0) {
// already got one-time keys
return Promise.resolve(this.oneTimeKeys);
return Promise.resolve(this.oneTimeKeys!);
}
this.httpBackend.when("POST", "/keys/upload")
@ -148,7 +148,7 @@ export class TestClient {
expect(content.device_keys).toBe(undefined);
expect(content.one_time_keys).toBe(undefined);
return { one_time_key_counts: {
signed_curve25519: Object.keys(this.oneTimeKeys).length,
signed_curve25519: Object.keys(this.oneTimeKeys!).length,
} };
});
@ -158,17 +158,17 @@ export class TestClient {
expect(content.one_time_keys).toBeTruthy();
expect(content.one_time_keys).not.toEqual({});
logger.log('%s: received %i one-time keys', this,
Object.keys(content.one_time_keys).length);
Object.keys(content.one_time_keys!).length);
this.oneTimeKeys = content.one_time_keys;
return { one_time_key_counts: {
signed_curve25519: Object.keys(this.oneTimeKeys).length,
signed_curve25519: Object.keys(this.oneTimeKeys!).length,
} };
});
// this can take ages
return this.httpBackend.flush('/keys/upload', 2, 1000).then((flushed) => {
expect(flushed).toEqual(2);
return this.oneTimeKeys;
return this.oneTimeKeys!;
});
}
@ -183,7 +183,7 @@ export class TestClient {
this.httpBackend.when('POST', '/keys/query').respond<IDownloadKeyResult>(
200, (_path, content) => {
Object.keys(response.device_keys).forEach((userId) => {
expect(content.device_keys[userId]).toEqual([]);
expect(content.device_keys![userId]).toEqual([]);
});
return response;
});
@ -206,7 +206,7 @@ export class TestClient {
*/
public getDeviceKey(): string {
const keyId = 'curve25519:' + this.deviceId;
return this.deviceKeys.keys[keyId];
return this.deviceKeys!.keys[keyId];
}
/**
@ -216,7 +216,7 @@ export class TestClient {
*/
public getSigningKey(): string {
const keyId = 'ed25519:' + this.deviceId;
return this.deviceKeys.keys[keyId];
return this.deviceKeys!.keys[keyId];
}
/**
@ -237,6 +237,6 @@ export class TestClient {
}
public getUserId(): string {
return this.userId;
return this.userId!;
}
}

View File

@ -59,7 +59,7 @@ async function bobUploadsDeviceKeys(): Promise<void> {
bobTestClient.client.uploadKeys(),
bobTestClient.httpBackend.flushAllExpected(),
]);
expect(Object.keys(bobTestClient.deviceKeys).length).not.toEqual(0);
expect(Object.keys(bobTestClient.deviceKeys!).length).not.toEqual(0);
}
/**
@ -99,7 +99,7 @@ async function expectAliClaimKeys(): Promise<void> {
expect(claimType).toEqual("signed_curve25519");
let keyId = '';
for (keyId in keys) {
if (bobTestClient.oneTimeKeys.hasOwnProperty(keyId)) {
if (bobTestClient.oneTimeKeys!.hasOwnProperty(keyId)) {
if (keyId.indexOf(claimType + ":") === 0) {
break;
}
@ -137,7 +137,7 @@ async function aliDownloadsKeys(): Promise<void> {
// @ts-ignore - protected
aliTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
const devices = data!.devices[bobUserId]!;
expect(devices[bobDeviceId].keys).toEqual(bobTestClient.deviceKeys.keys);
expect(devices[bobDeviceId].keys).toEqual(bobTestClient.deviceKeys!.keys);
expect(devices[bobDeviceId].verified).
toBe(DeviceInfo.DeviceVerification.UNVERIFIED);
});
@ -223,7 +223,7 @@ async function expectBobSendMessageRequest(): Promise<OlmPayload> {
const content = await expectSendMessageRequest(bobTestClient.httpBackend);
bobMessages.push(content);
const aliKeyId = "curve25519:" + aliDeviceId;
const aliDeviceCurve25519Key = aliTestClient.deviceKeys.keys[aliKeyId];
const aliDeviceCurve25519Key = aliTestClient.deviceKeys!.keys[aliKeyId];
expect(Object.keys(content.ciphertext)).toEqual([aliDeviceCurve25519Key]);
const ciphertext = content.ciphertext[aliDeviceCurve25519Key];
expect(ciphertext).toBeTruthy();
@ -393,7 +393,7 @@ describe("MatrixClient crypto", () => {
it("Ali gets keys with an invalid signature", async () => {
await bobUploadsDeviceKeys();
// tamper bob's keys
const bobDeviceKeys = bobTestClient.deviceKeys;
const bobDeviceKeys = bobTestClient.deviceKeys!;
expect(bobDeviceKeys.keys["curve25519:" + bobDeviceId]).toBeTruthy();
bobDeviceKeys.keys["curve25519:" + bobDeviceId] += "abc";
await Promise.all([
@ -479,7 +479,7 @@ describe("MatrixClient crypto", () => {
await bobTestClient.start();
const keys = await bobTestClient.awaitOneTimeKeyUpload();
expect(Object.keys(keys).length).toEqual(5);
expect(Object.keys(bobTestClient.deviceKeys).length).not.toEqual(0);
expect(Object.keys(bobTestClient.deviceKeys!).length).not.toEqual(0);
});
it("Ali sends a message", async () => {

View File

@ -1047,7 +1047,7 @@ describe("MatrixClient event timelines", function() {
response = {
chunk: [THREAD_ROOT],
state: [],
next_batch: RANDOM_TOKEN,
next_batch: RANDOM_TOKEN as string | null,
},
): ExpectedHttpRequest {
const request = httpBackend.when("GET", encodeUri("/_matrix/client/r0/rooms/$roomId/threads", {

View File

@ -139,7 +139,7 @@ describe("MatrixClient", function() {
const r = client!.cancelUpload(prom);
expect(r).toBe(true);
await expect(prom).rejects.toThrow("Aborted");
expect(client.getCurrentUploads()).toHaveLength(0);
expect(client!.getCurrentUploads()).toHaveLength(0);
});
});
@ -178,7 +178,7 @@ describe("MatrixClient", function() {
expect(request.data.third_party_signed).toEqual(signature);
}).respond(200, { room_id: roomId });
const prom = client.joinRoom(roomId, {
const prom = client!.joinRoom(roomId, {
inviteSignUrl,
viaServers,
});
@ -1164,18 +1164,18 @@ describe("MatrixClient", function() {
describe("logout", () => {
it("should abort pending requests when called with stopClient=true", async () => {
httpBackend.when("POST", "/logout").respond(200, {});
httpBackend!.when("POST", "/logout").respond(200, {});
const fn = jest.fn();
client.http.request(Method.Get, "/test").catch(fn);
client.logout(true);
await httpBackend.flush(undefined);
client!.http.request(Method.Get, "/test").catch(fn);
client!.logout(true);
await httpBackend!.flush(undefined);
expect(fn).toHaveBeenCalled();
});
});
describe("sendHtmlEmote", () => {
it("should send valid html emote", async () => {
httpBackend.when("PUT", "/send").check(req => {
httpBackend!.when("PUT", "/send").check(req => {
expect(req.data).toStrictEqual({
"msgtype": "m.emote",
"body": "Body",
@ -1184,15 +1184,15 @@ describe("MatrixClient", function() {
"org.matrix.msc1767.message": expect.anything(),
});
}).respond(200, { event_id: "$foobar" });
const prom = client.sendHtmlEmote("!room:server", "Body", "<h1>Body</h1>");
await httpBackend.flush(undefined);
const prom = client!.sendHtmlEmote("!room:server", "Body", "<h1>Body</h1>");
await httpBackend!.flush(undefined);
await expect(prom).resolves.toStrictEqual({ event_id: "$foobar" });
});
});
describe("sendHtmlMessage", () => {
it("should send valid html message", async () => {
httpBackend.when("PUT", "/send").check(req => {
httpBackend!.when("PUT", "/send").check(req => {
expect(req.data).toStrictEqual({
"msgtype": "m.text",
"body": "Body",
@ -1201,24 +1201,24 @@ describe("MatrixClient", function() {
"org.matrix.msc1767.message": expect.anything(),
});
}).respond(200, { event_id: "$foobar" });
const prom = client.sendHtmlMessage("!room:server", "Body", "<h1>Body</h1>");
await httpBackend.flush(undefined);
const prom = client!.sendHtmlMessage("!room:server", "Body", "<h1>Body</h1>");
await httpBackend!.flush(undefined);
await expect(prom).resolves.toStrictEqual({ event_id: "$foobar" });
});
});
describe("forget", () => {
it("should remove from store by default", async () => {
const room = new Room("!roomId:server", client, userId);
client.store.storeRoom(room);
expect(client.store.getRooms()).toContain(room);
const room = new Room("!roomId:server", client!, userId);
client!.store.storeRoom(room);
expect(client!.store.getRooms()).toContain(room);
httpBackend.when("POST", "/forget").respond(200, {});
httpBackend!.when("POST", "/forget").respond(200, {});
await Promise.all([
client.forget(room.roomId),
httpBackend.flushAllExpected(),
client!.forget(room.roomId),
httpBackend!.flushAllExpected(),
]);
expect(client.store.getRooms()).not.toContain(room);
expect(client!.store.getRooms()).not.toContain(room);
});
});
@ -1306,8 +1306,8 @@ describe("MatrixClient", function() {
const resp = await prom;
expect(resp.access_token).toBe(token);
expect(resp.user_id).toBe(userId);
expect(client.getUserId()).toBe(userId);
expect(client.http.opts.accessToken).toBe(token);
expect(client!.getUserId()).toBe(userId);
expect(client!.http.opts.accessToken).toBe(token);
});
});

View File

@ -1541,6 +1541,67 @@ describe("MatrixClient syncing", () => {
});
});
describe("peek", () => {
beforeEach(() => {
httpBackend!.expectedRequests = [];
});
it("should return a room based on the room initialSync API", async () => {
httpBackend!.when("GET", `/rooms/${encodeURIComponent(roomOne)}/initialSync`).respond(200, {
room_id: roomOne,
membership: "leave",
messages: {
start: "start",
end: "end",
chunk: [{
content: { body: "Message 1" },
type: "m.room.message",
event_id: "$eventId1",
sender: userA,
origin_server_ts: 12313525,
room_id: roomOne,
}, {
content: { body: "Message 2" },
type: "m.room.message",
event_id: "$eventId2",
sender: userB,
origin_server_ts: 12315625,
room_id: roomOne,
}],
},
state: [{
content: { name: "Room Name" },
type: "m.room.name",
event_id: "$eventId",
sender: userA,
origin_server_ts: 12314525,
state_key: "",
room_id: roomOne,
}],
presence: [{
content: {},
type: "m.presence",
sender: userA,
}],
});
httpBackend!.when("GET", "/events").respond(200, { chunk: [] });
const prom = client!.peekInRoom(roomOne);
await httpBackend!.flushAllExpected();
const room = await prom;
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.timeline[0].getContent().body).toBe("Message 1");
expect(room.timeline[1].getContent().body).toBe("Message 2");
client?.stopPeeking();
httpBackend!.when("GET", "/events").respond(200, { chunk: [] });
await httpBackend!.flushAllExpected();
});
});
/**
* waits for the MatrixClient to emit one or more 'sync' events.
*

View File

@ -1160,11 +1160,11 @@ describe("megolm", () => {
"algorithm": 'm.megolm.v1.aes-sha2',
"room_id": ROOM_ID,
"sender_key": content.sender_key,
"sender_claimed_ed25519_key": groupSessionKey.sender_claimed_ed25519_key,
"sender_claimed_ed25519_key": groupSessionKey!.sender_claimed_ed25519_key,
"session_id": content.session_id,
"session_key": groupSessionKey.key,
"chain_index": groupSessionKey.chain_index,
"forwarding_curve25519_key_chain": groupSessionKey.forwarding_curve25519_key_chain,
"session_key": groupSessionKey!.key,
"chain_index": groupSessionKey!.chain_index,
"forwarding_curve25519_key_chain": groupSessionKey!.forwarding_curve25519_key_chain,
"org.matrix.msc3061.shared_history": true,
},
plaintype: 'm.forwarded_room_key',
@ -1298,11 +1298,11 @@ describe("megolm", () => {
"algorithm": 'm.megolm.v1.aes-sha2',
"room_id": ROOM_ID,
"sender_key": content.sender_key,
"sender_claimed_ed25519_key": groupSessionKey.sender_claimed_ed25519_key,
"sender_claimed_ed25519_key": groupSessionKey!.sender_claimed_ed25519_key,
"session_id": content.session_id,
"session_key": groupSessionKey.key,
"chain_index": groupSessionKey.chain_index,
"forwarding_curve25519_key_chain": groupSessionKey.forwarding_curve25519_key_chain,
"session_key": groupSessionKey!.key,
"chain_index": groupSessionKey!.chain_index,
"forwarding_curve25519_key_chain": groupSessionKey!.forwarding_curve25519_key_chain,
"org.matrix.msc3061.shared_history": true,
},
plaintype: 'm.forwarded_room_key',

View File

@ -468,7 +468,7 @@ describe("SlidingSyncSdk", () => {
it("emits SyncState.Reconnecting when < FAILED_SYNC_ERROR_THRESHOLD & SyncState.Error when over", async () => {
mockSlidingSync!.emit(
SlidingSyncEvent.Lifecycle, SlidingSyncState.Complete,
{ pos: "h", lists: [], rooms: {}, extensions: {} }, null,
{ pos: "h", lists: [], rooms: {}, extensions: {} },
);
expect(sdk!.getSyncState()).toEqual(SyncState.Syncing);
@ -490,7 +490,6 @@ describe("SlidingSyncSdk", () => {
SlidingSyncEvent.Lifecycle,
SlidingSyncState.Complete,
{ pos: "i", lists: [], rooms: {}, extensions: {} },
null,
);
expect(sdk!.getSyncState()).toEqual(SyncState.Syncing);
});

View File

@ -82,7 +82,7 @@ describe("SlidingSync", () => {
it("should reset the connection on HTTP 400 and send everything again", async () => {
// seed the connection with some lists, extensions and subscriptions to verify they are sent again
slidingSync = new SlidingSync(proxyBaseUrl, [], {}, client, 1);
slidingSync = new SlidingSync(proxyBaseUrl, [], {}, client!, 1);
const roomId = "!sub:localhost";
const subInfo = {
timeline_limit: 42,
@ -108,7 +108,7 @@ describe("SlidingSync", () => {
// expect everything to be sent
let txnId;
httpBackend.when("POST", syncUrl).check(function(req) {
httpBackend!.when("POST", syncUrl).check(function(req) {
const body = req.data;
logger.debug("got ", body);
expect(body.room_subscriptions).toEqual({
@ -117,7 +117,7 @@ describe("SlidingSync", () => {
expect(body.lists[0]).toEqual(listInfo);
expect(body.extensions).toBeTruthy();
expect(body.extensions["custom_extension"]).toEqual({ initial: true });
expect(req.queryParams["pos"]).toBeUndefined();
expect(req.queryParams!["pos"]).toBeUndefined();
txnId = body.txn_id;
}).respond(200, function() {
return {
@ -127,10 +127,10 @@ describe("SlidingSync", () => {
txn_id: txnId,
};
});
await httpBackend.flushAllExpected();
await httpBackend!.flushAllExpected();
// expect nothing but ranges and non-initial extensions to be sent
httpBackend.when("POST", syncUrl).check(function(req) {
httpBackend!.when("POST", syncUrl).check(function(req) {
const body = req.data;
logger.debug("got ", body);
expect(body.room_subscriptions).toBeFalsy();
@ -139,7 +139,7 @@ describe("SlidingSync", () => {
});
expect(body.extensions).toBeTruthy();
expect(body.extensions["custom_extension"]).toEqual({ initial: false });
expect(req.queryParams["pos"]).toEqual("11");
expect(req.queryParams!["pos"]).toEqual("11");
}).respond(200, function() {
return {
pos: "12",
@ -147,19 +147,19 @@ describe("SlidingSync", () => {
extensions: {},
};
});
await httpBackend.flushAllExpected();
await httpBackend!.flushAllExpected();
// now we expire the session
httpBackend.when("POST", syncUrl).respond(400, function() {
httpBackend!.when("POST", syncUrl).respond(400, function() {
logger.debug("sending session expired 400");
return {
error: "HTTP 400 : session expired",
};
});
await httpBackend.flushAllExpected();
await httpBackend!.flushAllExpected();
// ...and everything should be sent again
httpBackend.when("POST", syncUrl).check(function(req) {
httpBackend!.when("POST", syncUrl).check(function(req) {
const body = req.data;
logger.debug("got ", body);
expect(body.room_subscriptions).toEqual({
@ -168,7 +168,7 @@ describe("SlidingSync", () => {
expect(body.lists[0]).toEqual(listInfo);
expect(body.extensions).toBeTruthy();
expect(body.extensions["custom_extension"]).toEqual({ initial: true });
expect(req.queryParams["pos"]).toBeUndefined();
expect(req.queryParams!["pos"]).toBeUndefined();
}).respond(200, function() {
return {
pos: "1",
@ -176,7 +176,7 @@ describe("SlidingSync", () => {
extensions: {},
};
});
await httpBackend.flushAllExpected();
await httpBackend!.flushAllExpected();
slidingSync.stop();
});
});
@ -415,7 +415,7 @@ describe("SlidingSync", () => {
expect(slidingSync.getList(0)).toBeDefined();
expect(slidingSync.getList(5)).toBeNull();
expect(slidingSync.getListData(5)).toBeNull();
const syncData = slidingSync.getListData(0);
const syncData = slidingSync.getListData(0)!;
expect(syncData.joinedCount).toEqual(500); // from previous test
expect(syncData.roomIndexToRoomId).toEqual({
0: roomA,
@ -665,7 +665,7 @@ describe("SlidingSync", () => {
0: roomB,
1: roomC,
};
expect(slidingSync.getListData(0).roomIndexToRoomId).toEqual(indexToRoomId);
expect(slidingSync.getListData(0)!.roomIndexToRoomId).toEqual(indexToRoomId);
httpBackend!.when("POST", syncUrl).respond(200, {
pos: "f",
// currently the list is [B,C] so we will insert D then immediately delete it
@ -703,7 +703,7 @@ describe("SlidingSync", () => {
});
it("should handle deletions correctly", async () => {
expect(slidingSync.getListData(0).roomIndexToRoomId).toEqual({
expect(slidingSync.getListData(0)!.roomIndexToRoomId).toEqual({
0: roomB,
1: roomC,
});
@ -739,7 +739,7 @@ describe("SlidingSync", () => {
});
it("should handle insertions correctly", async () => {
expect(slidingSync.getListData(0).roomIndexToRoomId).toEqual({
expect(slidingSync.getListData(0)!.roomIndexToRoomId).toEqual({
0: roomC,
});
httpBackend!.when("POST", syncUrl).respond(200, {

View File

@ -135,7 +135,7 @@ export class MockMediaDeviceInfo {
export class MockMediaHandler {
getUserMediaStream(audio: boolean, video: boolean) {
const tracks = [];
const tracks: MockMediaStreamTrack[] = [];
if (audio) tracks.push(new MockMediaStreamTrack("audio_track", "audio"));
if (video) tracks.push(new MockMediaStreamTrack("video_track", "video"));

View File

@ -32,7 +32,7 @@ describe("NamespacedValue", () => {
});
it("should have a falsey unstable if needed", () => {
const ns = new NamespacedValue("stable", null);
const ns = new NamespacedValue("stable");
expect(ns.name).toBe(ns.stable);
expect(ns.altName).toBeFalsy();
expect(ns.names).toEqual([ns.stable]);
@ -41,17 +41,17 @@ describe("NamespacedValue", () => {
it("should match against either stable or unstable", () => {
const ns = new NamespacedValue("stable", "unstable");
expect(ns.matches("no")).toBe(false);
expect(ns.matches(ns.stable)).toBe(true);
expect(ns.matches(ns.unstable)).toBe(true);
expect(ns.matches(ns.stable!)).toBe(true);
expect(ns.matches(ns.unstable!)).toBe(true);
});
it("should not permit falsey values for both parts", () => {
try {
new UnstableValue(null, null);
new UnstableValue(null!, null!);
// noinspection ExceptionCaughtLocallyJS
throw new Error("Failed to fail");
} catch (e) {
expect(e.message).toBe("One of stable or unstable values must be supplied");
expect((<Error>e).message).toBe("One of stable or unstable values must be supplied");
}
});
});
@ -65,7 +65,7 @@ describe("UnstableValue", () => {
});
it("should return unstable if there is no stable", () => {
const ns = new UnstableValue(null, "unstable");
const ns = new UnstableValue(null!, "unstable");
expect(ns.name).toBe(ns.unstable);
expect(ns.altName).toBeFalsy();
expect(ns.names).toEqual([ns.unstable]);
@ -73,11 +73,11 @@ describe("UnstableValue", () => {
it("should not permit falsey unstable values", () => {
try {
new UnstableValue("stable", null);
new UnstableValue("stable", null!);
// noinspection ExceptionCaughtLocallyJS
throw new Error("Failed to fail");
} catch (e) {
expect(e.message).toBe("Unstable value must be supplied");
expect((<Error>e).message).toBe("Unstable value must be supplied");
}
});
});

View File

@ -678,7 +678,7 @@ describe("AutoDiscovery", function() {
it("should return FAIL_PROMPT for connection errors", () => {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "/.well-known/matrix/client").fail(0, undefined);
httpBackend.when("GET", "/.well-known/matrix/client").fail(0, undefined!);
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then((conf) => {

View File

@ -1,3 +1,19 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { getHttpUriForMxc } from "../../src/content-repo";
describe("ContentRepo", function() {

View File

@ -2,6 +2,7 @@ import '../olm-loader';
// eslint-disable-next-line no-restricted-imports
import { EventEmitter } from "events";
import type { PkDecryption, PkSigning } from "@matrix-org/olm";
import { MatrixClient } from "../../src/client";
import { Crypto } from "../../src/crypto";
import { MemoryCryptoStore } from "../../src/crypto/store/memory-crypto-store";
@ -32,7 +33,7 @@ function awaitEvent(emitter, event) {
async function keyshareEventForEvent(client, event, index): Promise<MatrixEvent> {
const roomId = event.getRoomId();
const eventContent = event.getWireContent();
const key = await client.crypto.olmDevice.getInboundGroupSessionKey(
const key = await client.crypto!.olmDevice.getInboundGroupSessionKey(
roomId,
eventContent.sender_key,
eventContent.session_id,
@ -68,10 +69,10 @@ async function keyshareEventForEvent(client, event, index): Promise<MatrixEvent>
function roomKeyEventForEvent(client: MatrixClient, event: MatrixEvent): MatrixEvent {
const roomId = event.getRoomId();
const eventContent = event.getWireContent();
const key = client.crypto.olmDevice.getOutboundGroupSessionKey(eventContent.session_id);
const key = client.crypto!.olmDevice.getOutboundGroupSessionKey(eventContent.session_id);
const ksEvent = new MatrixEvent({
type: "m.room_key",
sender: client.getUserId(),
sender: client.getUserId()!,
content: {
"algorithm": olmlib.MEGOLM_ALGORITHM,
"room_id": roomId,
@ -146,7 +147,7 @@ describe("Crypto", function() {
'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI';
device.keys["ed25519:FLIBBLE"] =
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
client.crypto.deviceList.getDeviceByIdentityKey = () => device;
client.crypto!.deviceList.getDeviceByIdentityKey = () => device;
encryptionInfo = client.getEventEncryptionInfo(event);
expect(encryptionInfo.encrypted).toBeTruthy();
@ -334,7 +335,7 @@ describe("Crypto", function() {
await Promise.all(events.map(async (event) => {
// alice encrypts each event, and then bob tries to decrypt
// them without any keys, so that they'll be in pending
await aliceClient.crypto.encryptEvent(event, aliceRoom);
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
// remove keys from the event
// @ts-ignore private properties
event.clearEvent = undefined;
@ -343,17 +344,17 @@ describe("Crypto", function() {
// @ts-ignore private properties
event.claimedEd25519Key = null;
try {
await bobClient.crypto.decryptEvent(event);
await bobClient.crypto!.decryptEvent(event);
} catch (e) {
// we expect this to fail because we don't have the
// decryption keys yet
}
}));
const device = new DeviceInfo(aliceClient.deviceId);
bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
const device = new DeviceInfo(aliceClient.deviceId!);
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
const bobDecryptor = bobClient.crypto.getRoomDecryptor(
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
@ -365,14 +366,14 @@ describe("Crypto", function() {
// the first message can't be decrypted yet, but the second one
// can
let ksEvent = await keyshareEventForEvent(aliceClient, events[1], 1);
bobClient.crypto.deviceList.downloadKeys = () => Promise.resolve({});
bobClient.crypto.deviceList.getUserByIdentityKey = () => "@alice:example.com";
bobClient.crypto!.deviceList.downloadKeys = () => Promise.resolve({});
bobClient.crypto!.deviceList.getUserByIdentityKey = () => "@alice:example.com";
await bobDecryptor.onRoomKeyEvent(ksEvent);
await decryptEventsPromise;
expect(events[0].getContent().msgtype).toBe("m.bad.encrypted");
expect(events[1].getContent().msgtype).not.toBe("m.bad.encrypted");
const cryptoStore = bobClient.crypto.cryptoStore;
const cryptoStore = bobClient.crypto!.cryptoStore;
const eventContent = events[0].getWireContent();
const senderKey = eventContent.sender_key;
const sessionId = eventContent.session_id;
@ -437,7 +438,7 @@ describe("Crypto", function() {
});
// alice encrypts each event, and then bob tries to decrypt
// them without any keys, so that they'll be in pending
await aliceClient.crypto.encryptEvent(event, aliceRoom);
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
// remove keys from the event
// @ts-ignore private property
event.clearEvent = undefined;
@ -446,24 +447,24 @@ describe("Crypto", function() {
// @ts-ignore private property
event.claimedEd25519Key = null;
try {
await bobClient.crypto.decryptEvent(event);
await bobClient.crypto!.decryptEvent(event);
} catch (e) {
// we expect this to fail because we don't have the
// decryption keys yet
}
const device = new DeviceInfo(aliceClient.deviceId);
bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
const device = new DeviceInfo(aliceClient.deviceId!);
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
const bobDecryptor = bobClient.crypto.getRoomDecryptor(
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
const ksEvent = await keyshareEventForEvent(aliceClient, event, 1);
ksEvent.getContent().sender_key = undefined; // test
bobClient.crypto.olmDevice.addInboundGroupSession = jest.fn();
bobClient.crypto!.olmDevice.addInboundGroupSession = jest.fn();
await bobDecryptor.onRoomKeyEvent(ksEvent);
expect(bobClient.crypto.olmDevice.addInboundGroupSession).not.toHaveBeenCalled();
expect(bobClient.crypto!.olmDevice.addInboundGroupSession).not.toHaveBeenCalled();
});
it("creates a new keyshare request if we request a keyshare", async function() {
@ -479,7 +480,7 @@ describe("Crypto", function() {
},
});
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
const cryptoStore = aliceClient.crypto.cryptoStore;
const cryptoStore = aliceClient.crypto!.cryptoStore;
const roomKeyRequestBody = {
algorithm: olmlib.MEGOLM_ALGORITHM,
room_id: "!someroom",
@ -514,7 +515,7 @@ describe("Crypto", function() {
// let the client set up enough for that to happen, so gut-wrench a bit
// to force it to send now.
// @ts-ignore
aliceClient.crypto.outgoingRoomKeyRequestManager.sendQueuedRequests();
aliceClient.crypto!.outgoingRoomKeyRequestManager.sendQueuedRequests();
jest.runAllTimers();
await Promise.resolve();
expect(aliceSendToDevice).toBeCalledTimes(1);
@ -571,7 +572,7 @@ describe("Crypto", function() {
await Promise.all(events.map(async (event) => {
// alice encrypts each event, and then bob tries to decrypt
// them without any keys, so that they'll be in pending
await aliceClient.crypto.encryptEvent(event, aliceRoom);
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
// remove keys from the event
// @ts-ignore private properties
event.clearEvent = undefined;
@ -580,18 +581,18 @@ describe("Crypto", function() {
// @ts-ignore private properties
event.claimedEd25519Key = null;
try {
await bobClient.crypto.decryptEvent(event);
await bobClient.crypto!.decryptEvent(event);
} catch (e) {
// we expect this to fail because we don't have the
// decryption keys yet
}
}));
const device = new DeviceInfo(aliceClient.deviceId);
bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
bobClient.crypto.deviceList.getUserByIdentityKey = () => "@alice:example.com";
const device = new DeviceInfo(aliceClient.deviceId!);
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
bobClient.crypto!.deviceList.getUserByIdentityKey = () => "@alice:example.com";
const cryptoStore = bobClient.crypto.cryptoStore;
const cryptoStore = bobClient.crypto!.cryptoStore;
const eventContent = events[0].getWireContent();
const senderKey = eventContent.sender_key;
const sessionId = eventContent.session_id;
@ -604,11 +605,11 @@ describe("Crypto", function() {
const outgoingReq = await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody);
expect(outgoingReq).toBeDefined();
await cryptoStore.updateOutgoingRoomKeyRequest(
outgoingReq.requestId, RoomKeyRequestState.Unsent,
outgoingReq!.requestId, RoomKeyRequestState.Unsent,
{ state: RoomKeyRequestState.Sent },
);
const bobDecryptor = bobClient.crypto.getRoomDecryptor(
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
@ -617,7 +618,7 @@ describe("Crypto", function() {
}));
const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
await bobDecryptor.onRoomKeyEvent(ksEvent);
const key = await bobClient.crypto.olmDevice.getInboundGroupSessionKey(
const key = await bobClient.crypto!.olmDevice.getInboundGroupSessionKey(
roomId,
events[0].getWireContent().sender_key,
events[0].getWireContent().session_id,
@ -675,7 +676,7 @@ describe("Crypto", function() {
await Promise.all(events.map(async (event) => {
// alice encrypts each event, and then bob tries to decrypt
// them without any keys, so that they'll be in pending
await aliceClient.crypto.encryptEvent(event, aliceRoom);
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
// remove keys from the event
// @ts-ignore private properties
event.clearEvent = undefined;
@ -684,18 +685,18 @@ describe("Crypto", function() {
// @ts-ignore private properties
event.claimedEd25519Key = null;
try {
await bobClient.crypto.decryptEvent(event);
await bobClient.crypto!.decryptEvent(event);
} catch (e) {
// we expect this to fail because we don't have the
// decryption keys yet
}
}));
const device = new DeviceInfo(claraClient.deviceId);
bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
bobClient.crypto.deviceList.getUserByIdentityKey = () => "@clara:example.com";
const device = new DeviceInfo(claraClient.deviceId!);
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
bobClient.crypto!.deviceList.getUserByIdentityKey = () => "@clara:example.com";
const bobDecryptor = bobClient.crypto.getRoomDecryptor(
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
@ -703,10 +704,10 @@ describe("Crypto", function() {
return awaitEvent(ev, "Event.decrypted");
}));
const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
ksEvent.event.sender = claraClient.getUserId(),
ksEvent.sender = new RoomMember(roomId, claraClient.getUserId());
ksEvent.event.sender = claraClient.getUserId()!;
ksEvent.sender = new RoomMember(roomId, claraClient.getUserId()!);
await bobDecryptor.onRoomKeyEvent(ksEvent);
const key = await bobClient.crypto.olmDevice.getInboundGroupSessionKey(
const key = await bobClient.crypto!.olmDevice.getInboundGroupSessionKey(
roomId,
events[0].getWireContent().sender_key,
events[0].getWireContent().session_id,
@ -753,7 +754,7 @@ describe("Crypto", function() {
await Promise.all(events.map(async (event) => {
// alice encrypts each event, and then bob tries to decrypt
// them without any keys, so that they'll be in pending
await aliceClient.crypto.encryptEvent(event, aliceRoom);
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
// remove keys from the event
// @ts-ignore private properties
event.clearEvent = undefined;
@ -762,19 +763,19 @@ describe("Crypto", function() {
// @ts-ignore private properties
event.claimedEd25519Key = null;
try {
await bobClient.crypto.decryptEvent(event);
await bobClient.crypto!.decryptEvent(event);
} catch (e) {
// we expect this to fail because we don't have the
// decryption keys yet
}
}));
const device = new DeviceInfo(claraClient.deviceId);
const device = new DeviceInfo(claraClient.deviceId!);
device.verified = DeviceInfo.DeviceVerification.VERIFIED;
bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
bobClient.crypto.deviceList.getUserByIdentityKey = () => "@bob:example.com";
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
bobClient.crypto!.deviceList.getUserByIdentityKey = () => "@bob:example.com";
const bobDecryptor = bobClient.crypto.getRoomDecryptor(
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
@ -782,10 +783,10 @@ describe("Crypto", function() {
return awaitEvent(ev, "Event.decrypted");
}));
const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
ksEvent.event.sender = bobClient.getUserId(),
ksEvent.sender = new RoomMember(roomId, bobClient.getUserId());
ksEvent.event.sender = bobClient.getUserId()!;
ksEvent.sender = new RoomMember(roomId, bobClient.getUserId()!);
await bobDecryptor.onRoomKeyEvent(ksEvent);
const key = await bobClient.crypto.olmDevice.getInboundGroupSessionKey(
const key = await bobClient.crypto!.olmDevice.getInboundGroupSessionKey(
roomId,
events[0].getWireContent().sender_key,
events[0].getWireContent().session_id,
@ -835,7 +836,7 @@ describe("Crypto", function() {
await Promise.all(events.map(async (event) => {
// alice encrypts each event, and then bob tries to decrypt
// them without any keys, so that they'll be in pending
await aliceClient.crypto.encryptEvent(event, aliceRoom);
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
// remove keys from the event
// @ts-ignore private properties
event.clearEvent = undefined;
@ -844,26 +845,26 @@ describe("Crypto", function() {
// @ts-ignore private properties
event.claimedEd25519Key = null;
try {
await bobClient.crypto.decryptEvent(event);
await bobClient.crypto!.decryptEvent(event);
} catch (e) {
// we expect this to fail because we don't have the
// decryption keys yet
}
}));
const device = new DeviceInfo(claraClient.deviceId);
bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
bobClient.crypto.deviceList.getUserByIdentityKey = () => "@alice:example.com";
const device = new DeviceInfo(claraClient.deviceId!);
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
bobClient.crypto!.deviceList.getUserByIdentityKey = () => "@alice:example.com";
const bobDecryptor = bobClient.crypto.getRoomDecryptor(
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
ksEvent.event.sender = claraClient.getUserId(),
ksEvent.sender = new RoomMember(roomId, claraClient.getUserId());
ksEvent.event.sender = claraClient.getUserId()!;
ksEvent.sender = new RoomMember(roomId, claraClient.getUserId()!);
await bobDecryptor.onRoomKeyEvent(ksEvent);
const key = await bobClient.crypto.olmDevice.getInboundGroupSessionKey(
const key = await bobClient.crypto!.olmDevice.getInboundGroupSessionKey(
roomId,
events[0].getWireContent().sender_key,
events[0].getWireContent().session_id,
@ -904,7 +905,7 @@ describe("Crypto", function() {
await Promise.all(events.map(async (event) => {
// alice encrypts each event, and then bob tries to decrypt
// them without any keys, so that they'll be in pending
await aliceClient.crypto.encryptEvent(event, aliceRoom);
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
// remove keys from the event
// @ts-ignore private properties
event.clearEvent = undefined;
@ -914,11 +915,11 @@ describe("Crypto", function() {
event.claimedEd25519Key = null;
}));
const device = new DeviceInfo(aliceClient.deviceId);
bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
bobClient.crypto.deviceList.getUserByIdentityKey = () => "@alice:example.com";
const device = new DeviceInfo(aliceClient.deviceId!);
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
bobClient.crypto!.deviceList.getUserByIdentityKey = () => "@alice:example.com";
const bobDecryptor = bobClient.crypto.getRoomDecryptor(
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
@ -926,25 +927,25 @@ describe("Crypto", function() {
const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
await bobDecryptor.onRoomKeyEvent(ksEvent);
const bobKey = await bobClient.crypto.olmDevice.getInboundGroupSessionKey(
const bobKey = await bobClient.crypto!.olmDevice.getInboundGroupSessionKey(
roomId,
content.sender_key,
content.session_id,
);
expect(bobKey).toBeNull();
const aliceKey = await aliceClient.crypto.olmDevice.getInboundGroupSessionKey(
const aliceKey = await aliceClient.crypto!.olmDevice.getInboundGroupSessionKey(
roomId,
content.sender_key,
content.session_id,
);
const parked = await bobClient.crypto.cryptoStore.takeParkedSharedHistory(roomId);
const parked = await bobClient.crypto!.cryptoStore.takeParkedSharedHistory(roomId);
expect(parked).toEqual([{
senderId: aliceClient.getUserId(),
senderKey: content.sender_key,
sessionId: content.session_id,
sessionKey: aliceKey.key,
keysClaimed: { ed25519: aliceKey.sender_claimed_ed25519_key },
sessionKey: aliceKey!.key,
keysClaimed: { ed25519: aliceKey!.sender_claimed_ed25519_key },
forwardingCurve25519KeyChain: ["akey"],
}]);
});
@ -956,19 +957,19 @@ describe("Crypto", function() {
jest.setTimeout(10000);
const client = (new TestClient("@a:example.com", "dev")).client;
await client.initCrypto();
client.crypto.getSecretStorageKey = jest.fn().mockResolvedValue(null);
client.crypto.isCrossSigningReady = async () => false;
client.crypto.baseApis.uploadDeviceSigningKeys = jest.fn().mockResolvedValue(null);
client.crypto.baseApis.setAccountData = jest.fn().mockResolvedValue(null);
client.crypto.baseApis.uploadKeySignatures = jest.fn();
client.crypto.baseApis.http.authedRequest = jest.fn();
client.crypto!.getSecretStorageKey = jest.fn().mockResolvedValue(null);
client.crypto!.isCrossSigningReady = async () => false;
client.crypto!.baseApis.uploadDeviceSigningKeys = jest.fn().mockResolvedValue(null);
client.crypto!.baseApis.setAccountData = jest.fn().mockResolvedValue(null);
client.crypto!.baseApis.uploadKeySignatures = jest.fn();
client.crypto!.baseApis.http.authedRequest = jest.fn();
const createSecretStorageKey = async () => {
return {
keyInfo: undefined, // Returning undefined here used to cause a crash
privateKey: Uint8Array.of(32, 33),
};
};
await client.crypto.bootstrapSecretStorage({
await client.crypto!.bootstrapSecretStorage({
createSecretStorageKey,
});
client.stopClient();
@ -995,7 +996,7 @@ describe("Crypto", function() {
encryptedPayload = {
algorithm: "m.olm.v1.curve25519-aes-sha2",
sender_key: client.client.crypto.olmDevice.deviceCurve25519Key,
sender_key: client.client.crypto!.olmDevice.deviceCurve25519Key,
ciphertext: { plaintext: JSON.stringify(payload) },
};
});
@ -1075,4 +1076,50 @@ describe("Crypto", function() {
client.httpBackend.verifyNoOutstandingRequests();
});
});
describe("checkSecretStoragePrivateKey", () => {
let client: TestClient;
beforeEach(async () => {
client = new TestClient("@alice:example.org", "aliceweb");
await client.client.initCrypto();
});
afterEach(async () => {
await client.stop();
});
it("should free PkDecryption", () => {
const free = jest.fn();
jest.spyOn(Olm, "PkDecryption").mockImplementation(() => ({
init_with_private_key: jest.fn(),
free,
}) as unknown as PkDecryption);
client.client.checkSecretStoragePrivateKey(new Uint8Array(), "");
expect(free).toHaveBeenCalled();
});
});
describe("checkCrossSigningPrivateKey", () => {
let client: TestClient;
beforeEach(async () => {
client = new TestClient("@alice:example.org", "aliceweb");
await client.client.initCrypto();
});
afterEach(async () => {
await client.stop();
});
it("should free PkSigning", () => {
const free = jest.fn();
jest.spyOn(Olm, "PkSigning").mockImplementation(() => ({
init_with_seed: jest.fn(),
free,
}) as unknown as PkSigning);
client.client.checkCrossSigningPrivateKey(new Uint8Array(), "");
expect(free).toHaveBeenCalled();
});
});
});

View File

@ -247,14 +247,14 @@ describe.each([
const olmDevice = new OlmDevice(store);
const { getCrossSigningKeyCache, storeCrossSigningKeyCache } =
createCryptoStoreCacheCallbacks(store, olmDevice);
await storeCrossSigningKeyCache("self_signing", testKey);
await storeCrossSigningKeyCache!("self_signing", testKey);
// If we've not saved anything, don't expect anything
// Definitely don't accidentally return the wrong key for the type
const nokey = await getCrossSigningKeyCache("self", "");
const nokey = await getCrossSigningKeyCache!("self", "");
expect(nokey).toBeNull();
const key = await getCrossSigningKeyCache("self_signing", "");
const key = await getCrossSigningKeyCache!("self_signing", "");
expect(new Uint8Array(key)).toEqual(testKey);
});
});

View File

@ -90,7 +90,7 @@ const signedDeviceList2: IDownloadKeyResult = {
describe('DeviceList', function() {
let downloadSpy;
let cryptoStore;
let deviceLists = [];
let deviceLists: DeviceList[] = [];
beforeEach(function() {
deviceLists = [];

View File

@ -32,8 +32,8 @@ import { ClientEvent, MatrixClient, RoomMember } from '../../../../src';
import { DeviceInfo, IDevice } from '../../../../src/crypto/deviceinfo';
import { DeviceTrustLevel } from '../../../../src/crypto/CrossSigning';
const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get('m.megolm.v1.aes-sha2');
const MegolmEncryption = algorithms.ENCRYPTION_CLASSES.get('m.megolm.v1.aes-sha2');
const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get('m.megolm.v1.aes-sha2')!;
const MegolmEncryption = algorithms.ENCRYPTION_CLASSES.get('m.megolm.v1.aes-sha2')!;
const ROOM_ID = '!ROOM:ID';
@ -331,7 +331,7 @@ describe("MegolmDecryption", function() {
},
},
});
mockBaseApis.sendToDevice.mockResolvedValue(undefined);
mockBaseApis.sendToDevice.mockResolvedValue({});
mockBaseApis.queueToDevice.mockResolvedValue(undefined);
aliceDeviceInfo = {
@ -515,8 +515,8 @@ describe("MegolmDecryption", function() {
bobdevice1: {
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Dynabook": bobDevice1.deviceEd25519Key,
"curve25519:Dynabook": bobDevice1.deviceCurve25519Key,
"ed25519:Dynabook": bobDevice1.deviceEd25519Key!,
"curve25519:Dynabook": bobDevice1.deviceCurve25519Key!,
},
verified: 0,
known: false,
@ -524,8 +524,8 @@ describe("MegolmDecryption", function() {
bobdevice2: {
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Dynabook": bobDevice2.deviceEd25519Key,
"curve25519:Dynabook": bobDevice2.deviceCurve25519Key,
"ed25519:Dynabook": bobDevice2.deviceEd25519Key!,
"curve25519:Dynabook": bobDevice2.deviceCurve25519Key!,
},
verified: -1,
known: false,
@ -614,8 +614,8 @@ describe("MegolmDecryption", function() {
bobdevice: {
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:bobdevice": bobDevice.deviceEd25519Key,
"curve25519:bobdevice": bobDevice.deviceCurve25519Key,
"ed25519:bobdevice": bobDevice.deviceEd25519Key!,
"curve25519:bobdevice": bobDevice.deviceCurve25519Key!,
},
verified: 0,
known: true,
@ -718,8 +718,8 @@ describe("MegolmDecryption", function() {
device_id: "bobdevice",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:bobdevice": bobDevice.deviceEd25519Key,
"curve25519:bobdevice": bobDevice.deviceCurve25519Key,
"ed25519:bobdevice": bobDevice.deviceEd25519Key!,
"curve25519:bobdevice": bobDevice.deviceCurve25519Key!,
},
known: true,
verified: 1,

View File

@ -67,13 +67,13 @@ describe("OlmDevice", function() {
const sid = await setupSession(aliceOlmDevice, bobOlmDevice);
const ciphertext = await aliceOlmDevice.encryptMessage(
bobOlmDevice.deviceCurve25519Key,
bobOlmDevice.deviceCurve25519Key!,
sid,
"The olm or proteus is an aquatic salamander in the family Proteidae",
) as any; // OlmDevice.encryptMessage has incorrect return type
const result = await bobOlmDevice.createInboundSession(
aliceOlmDevice.deviceCurve25519Key,
aliceOlmDevice.deviceCurve25519Key!,
ciphertext.type,
ciphertext.body,
);
@ -94,7 +94,7 @@ describe("OlmDevice", function() {
+ " in the family Proteidae"
);
const ciphertext = await aliceOlmDevice.encryptMessage(
bobOlmDevice.deviceCurve25519Key,
bobOlmDevice.deviceCurve25519Key!,
sessionId,
MESSAGE,
) as any; // OlmDevice.encryptMessage has incorrect return type
@ -103,7 +103,7 @@ describe("OlmDevice", function() {
bobRecreatedOlmDevice.init({ fromExportedDevice: exported });
const decrypted = await bobRecreatedOlmDevice.createInboundSession(
aliceOlmDevice.deviceCurve25519Key,
aliceOlmDevice.deviceCurve25519Key!,
ciphertext.type,
ciphertext.body,
);
@ -118,7 +118,7 @@ describe("OlmDevice", function() {
+ " the olm is entirely aquatic"
);
const ciphertext2 = await aliceOlmDevice.encryptMessage(
bobOlmDevice.deviceCurve25519Key,
bobOlmDevice.deviceCurve25519Key!,
sessionId,
MESSAGE_2,
) as any; // OlmDevice.encryptMessage has incorrect return type
@ -128,7 +128,7 @@ describe("OlmDevice", function() {
// Note: "decrypted_2" does not have the same structure as "decrypted"
const decrypted2 = await bobRecreatedAgainOlmDevice.decryptMessage(
aliceOlmDevice.deviceCurve25519Key,
aliceOlmDevice.deviceCurve25519Key!,
decrypted.session_id,
ciphertext2.type,
ciphertext2.body,

View File

@ -34,7 +34,7 @@ import { MatrixScheduler } from '../../../src';
const Olm = global.Olm;
const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get('m.megolm.v1.aes-sha2');
const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get('m.megolm.v1.aes-sha2')!;
const ROOM_ID = '!ROOM:ID';
@ -197,7 +197,7 @@ describe("MegolmBackup", function() {
// to tick the clock between the first try and the retry.
const realSetTimeout = global.setTimeout;
jest.spyOn(global, 'setTimeout').mockImplementation(function(f, n) {
return realSetTimeout(f!, n/100);
return realSetTimeout(f!, n!/100);
});
});
@ -318,7 +318,7 @@ describe("MegolmBackup", function() {
resolve();
return Promise.resolve({} as T);
};
client.crypto.backupManager.backupGroupSession(
client.crypto!.backupManager.backupGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
groupSession.session_id(),
);
@ -349,7 +349,7 @@ describe("MegolmBackup", function() {
return client.initCrypto()
.then(() => {
return client.crypto.storeSessionBackupPrivateKey(new Uint8Array(32));
return client.crypto!.storeSessionBackupPrivateKey(new Uint8Array(32));
})
.then(() => {
return cryptoStore.doTxn(
@ -401,7 +401,7 @@ describe("MegolmBackup", function() {
resolve();
return Promise.resolve({} as T);
};
client.crypto.backupManager.backupGroupSession(
client.crypto!.backupManager.backupGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
groupSession.session_id(),
);
@ -449,7 +449,7 @@ describe("MegolmBackup", function() {
try {
// make sure auth_data is signed by the master key
olmlib.pkVerify(
(data as Record<string, any>).auth_data, client.getCrossSigningId(), "@alice:bar",
(data as Record<string, any>).auth_data, client.getCrossSigningId()!, "@alice:bar",
);
} catch (e) {
reject(e);
@ -568,7 +568,7 @@ describe("MegolmBackup", function() {
);
}
};
return client.crypto.backupManager.backupGroupSession(
return client.crypto!.backupManager.backupGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
groupSession.session_id(),
);
@ -699,4 +699,30 @@ describe("MegolmBackup", function() {
)).rejects.toThrow();
});
});
describe("flagAllGroupSessionsForBackup", () => {
it("should return number of sesions needing backup", async () => {
const scheduler = [
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
"setProcessFunction",
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {}) as MockedObject<MatrixScheduler>;
const store = new StubStore();
const client = new MatrixClient({
baseUrl: "https://my.home.server",
idBaseUrl: "https://identity.server",
accessToken: "my.access.token",
fetchFn: jest.fn(), // NOP
store,
scheduler,
userId: "@alice:bar",
deviceId: "device",
cryptoStore,
});
await client.initCrypto();
cryptoStore.countSessionsNeedingBackup = jest.fn().mockReturnValue(6);
await expect(client.flagAllGroupSessionsForBackup()).resolves.toBe(6);
client.stopClient();
});
});
});

View File

@ -93,8 +93,8 @@ describe("Cross Signing", function() {
);
alice.uploadDeviceSigningKeys = jest.fn().mockImplementation(async (auth, keys) => {
await olmlib.verifySignature(
alice.crypto.olmDevice, keys.master_key, "@alice:example.com",
"Osborne2", alice.crypto.olmDevice.deviceEd25519Key,
alice.crypto!.olmDevice, keys.master_key, "@alice:example.com",
"Osborne2", alice.crypto!.olmDevice.deviceEd25519Key!,
);
});
alice.uploadKeySignatures = async () => ({ failures: {} });
@ -152,7 +152,7 @@ describe("Cross Signing", function() {
authUploadDeviceSigningKeys,
});
} catch (e) {
if (e.errcode === "M_FORBIDDEN") {
if ((<MatrixError>e).errcode === "M_FORBIDDEN") {
bootstrapDidThrow = true;
}
}
@ -169,7 +169,7 @@ describe("Cross Signing", function() {
// set Alice's cross-signing key
await resetCrossSigningKeys(alice);
// Alice downloads Bob's device key
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
alice.crypto!.deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
@ -238,12 +238,12 @@ describe("Cross Signing", function() {
alice.uploadKeySignatures = jest.fn().mockImplementation(async (content) => {
try {
await olmlib.verifySignature(
alice.crypto.olmDevice,
alice.crypto!.olmDevice,
content["@alice:example.com"][
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
],
"@alice:example.com",
"Osborne2", alice.crypto.olmDevice.deviceEd25519Key,
"Osborne2", alice.crypto!.olmDevice.deviceEd25519Key!,
);
olmlib.pkVerify(
content["@alice:example.com"]["Osborne2"],
@ -258,7 +258,7 @@ describe("Cross Signing", function() {
});
// @ts-ignore private property
const deviceInfo = alice.crypto.deviceList.devices["@alice:example.com"]
const deviceInfo = alice.crypto!.deviceList.devices["@alice:example.com"]
.Osborne2;
const aliceDevice = {
user_id: "@alice:example.com",
@ -266,7 +266,7 @@ describe("Cross Signing", function() {
keys: deviceInfo.keys,
algorithms: deviceInfo.algorithms,
};
await alice.crypto.signObject(aliceDevice);
await alice.crypto!.signObject(aliceDevice);
olmlib.pkSign(
aliceDevice as ISignedKey,
selfSigningKey as unknown as PkSigning,
@ -401,7 +401,7 @@ describe("Cross Signing", function() {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
alice.crypto!.deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
@ -435,7 +435,7 @@ describe("Cross Signing", function() {
verified: 0,
known: false,
};
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
alice.crypto!.deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Bob's device key should be TOFU
@ -467,11 +467,11 @@ describe("Cross Signing", function() {
const aliceKeys: Record<string, PkSigning> = {};
const { client: alice, httpBackend } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
null,
undefined,
aliceKeys,
);
alice.crypto.deviceList.startTrackingDeviceList("@bob:example.com");
alice.crypto.deviceList.stopTrackingAllDeviceLists = () => {};
alice.crypto!.deviceList.startTrackingDeviceList("@bob:example.com");
alice.crypto!.deviceList.stopTrackingAllDeviceLists = () => {};
alice.uploadDeviceSigningKeys = async () => ({});
alice.uploadKeySignatures = async () => ({ failures: {} });
@ -486,7 +486,7 @@ describe("Cross Signing", function() {
]);
const keyChangePromise = new Promise<void>((resolve, reject) => {
alice.crypto.deviceList.once(CryptoEvent.UserCrossSigningUpdated, (userId) => {
alice.crypto!.deviceList.once(CryptoEvent.UserCrossSigningUpdated, (userId) => {
if (userId === "@bob:example.com") {
resolve();
}
@ -494,7 +494,7 @@ describe("Cross Signing", function() {
});
// @ts-ignore private property
const deviceInfo = alice.crypto.deviceList.devices["@alice:example.com"]
const deviceInfo = alice.crypto!.deviceList.devices["@alice:example.com"]
.Osborne2;
const aliceDevice = {
user_id: "@alice:example.com",
@ -502,7 +502,7 @@ describe("Cross Signing", function() {
keys: deviceInfo.keys,
algorithms: deviceInfo.algorithms,
};
await alice.crypto.signObject(aliceDevice);
await alice.crypto!.signObject(aliceDevice);
const bobOlmAccount = new global.Olm.Account();
bobOlmAccount.create();
@ -667,7 +667,7 @@ describe("Cross Signing", function() {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
alice.crypto!.deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
@ -690,7 +690,7 @@ describe("Cross Signing", function() {
"ed25519:Dynabook": "someOtherPubkey",
},
};
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
alice.crypto!.deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice as unknown as IDevice,
});
// Bob's device key should be untrusted
@ -735,7 +735,7 @@ describe("Cross Signing", function() {
["ed25519:" + bobMasterPubkey]: sskSig,
},
};
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
alice.crypto!.deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
@ -770,7 +770,7 @@ describe("Cross Signing", function() {
},
},
};
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
alice.crypto!.deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
// Alice verifies Bob's SSK
@ -802,7 +802,7 @@ describe("Cross Signing", function() {
["ed25519:" + bobMasterPubkey2]: sskSig2,
},
};
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
alice.crypto!.deviceList.storeCrossSigningForUser("@bob:example.com", {
keys: {
master: {
user_id: "@bob:example.com",
@ -838,8 +838,8 @@ describe("Cross Signing", function() {
// Alice gets new signature for device
const sig2 = bobSigning2.sign(bobDeviceString);
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey2] = sig2;
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
bobDevice.signatures!["@bob:example.com"]["ed25519:" + bobPubkey2] = sig2;
alice.crypto!.deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: bobDevice,
});
@ -876,20 +876,20 @@ describe("Cross Signing", function() {
bob.uploadKeySignatures = async () => ({ failures: {} });
// set Bob's cross-signing key
await resetCrossSigningKeys(bob);
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
alice.crypto!.deviceList.storeDevicesForUser("@bob:example.com", {
Dynabook: {
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
keys: {
"curve25519:Dynabook": bob.crypto.olmDevice.deviceCurve25519Key,
"ed25519:Dynabook": bob.crypto.olmDevice.deviceEd25519Key,
"curve25519:Dynabook": bob.crypto!.olmDevice.deviceCurve25519Key!,
"ed25519:Dynabook": bob.crypto!.olmDevice.deviceEd25519Key!,
},
verified: 1,
known: true,
},
});
alice.crypto.deviceList.storeCrossSigningForUser(
alice.crypto!.deviceList.storeCrossSigningForUser(
"@bob:example.com",
bob.crypto.crossSigningInfo.toStorage(),
bob.crypto!.crossSigningInfo.toStorage(),
);
alice.uploadDeviceSigningKeys = async () => ({});
@ -909,8 +909,8 @@ describe("Cross Signing", function() {
expect(bobTrust.isTofu()).toBeTruthy();
// "forget" that Bob is trusted
delete alice.crypto.deviceList.crossSigningInfo["@bob:example.com"]
.keys.master.signatures["@alice:example.com"];
delete alice.crypto!.deviceList.crossSigningInfo["@bob:example.com"]
.keys.master.signatures!["@alice:example.com"];
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
expect(bobTrust2.isCrossSigningVerified()).toBeFalsy();
@ -919,9 +919,9 @@ describe("Cross Signing", function() {
upgradePromise = new Promise((resolve) => {
upgradeResolveFunc = resolve;
});
alice.crypto.deviceList.emit(CryptoEvent.UserCrossSigningUpdated, "@bob:example.com");
alice.crypto!.deviceList.emit(CryptoEvent.UserCrossSigningUpdated, "@bob:example.com");
await new Promise((resolve) => {
alice.crypto.on(CryptoEvent.UserTrustStatusChanged, resolve);
alice.crypto!.on(CryptoEvent.UserTrustStatusChanged, resolve);
});
await upgradePromise;
@ -963,7 +963,7 @@ describe("Cross Signing", function() {
};
// Alice's device downloads the keys, but doesn't trust them yet
alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", {
alice.crypto!.deviceList.storeCrossSigningForUser("@alice:example.com", {
keys: {
master: {
user_id: "@alice:example.com",
@ -999,7 +999,7 @@ describe("Cross Signing", function() {
["ed25519:" + alicePubkey]: sig,
},
} };
alice.crypto.deviceList.storeDevicesForUser("@alice:example.com", {
alice.crypto!.deviceList.storeDevicesForUser("@alice:example.com", {
[aliceDeviceId]: aliceCrossSignedDevice,
});
@ -1042,7 +1042,7 @@ describe("Cross Signing", function() {
};
// Alice's device downloads the keys
alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", {
alice.crypto!.deviceList.storeCrossSigningForUser("@alice:example.com", {
keys: {
master: {
user_id: "@alice:example.com",
@ -1067,11 +1067,65 @@ describe("Cross Signing", function() {
"ed25519:Dynabook": "someOtherPubkey",
},
};
alice.crypto.deviceList.storeDevicesForUser("@alice:example.com", {
alice.crypto!.deviceList.storeDevicesForUser("@alice:example.com", {
[deviceId]: aliceNotCrossSignedDevice,
});
expect(alice.checkIfOwnDeviceCrossSigned(deviceId)).toBeFalsy();
alice.stopClient();
});
it("checkIfOwnDeviceCrossSigned should sanely handle unknown devices", async () => {
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
alice.uploadDeviceSigningKeys = async () => ({});
alice.uploadKeySignatures = async () => ({ failures: {} });
// Generate Alice's SSK etc
const aliceMasterSigning = new global.Olm.PkSigning();
const aliceMasterPrivkey = aliceMasterSigning.generate_seed();
const aliceMasterPubkey = aliceMasterSigning.init_with_seed(aliceMasterPrivkey);
const aliceSigning = new global.Olm.PkSigning();
const alicePrivkey = aliceSigning.generate_seed();
const alicePubkey = aliceSigning.init_with_seed(alicePrivkey);
const aliceSSK: ICrossSigningKey = {
user_id: "@alice:example.com",
usage: ["self_signing"],
keys: {
["ed25519:" + alicePubkey]: alicePubkey,
},
};
const sskSig = aliceMasterSigning.sign(anotherjson.stringify(aliceSSK));
aliceSSK.signatures = {
"@alice:example.com": {
["ed25519:" + aliceMasterPubkey]: sskSig,
},
};
// Alice's device downloads the keys
alice.crypto!.deviceList.storeCrossSigningForUser("@alice:example.com", {
keys: {
master: {
user_id: "@alice:example.com",
usage: ["master"],
keys: {
["ed25519:" + aliceMasterPubkey]: aliceMasterPubkey,
},
},
self_signing: aliceSSK,
},
firstUse: true,
crossSigningVerifiedBefore: false,
});
expect(alice.checkIfOwnDeviceCrossSigned("notadevice")).toBeFalsy();
alice.stopClient();
});
it("checkIfOwnDeviceCrossSigned should sanely handle unknown users", async () => {
const { client: alice } = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" });
expect(alice.checkIfOwnDeviceCrossSigned("notadevice")).toBeFalsy();
alice.stopClient();
});
});

View File

@ -39,7 +39,7 @@ export async function createSecretStorageKey(): Promise<IRecoveryKey> {
decryption.free();
return {
// `pubkey` not used anymore with symmetric 4S
keyInfo: { pubkey: storagePublicKey, key: undefined },
keyInfo: { pubkey: storagePublicKey, key: undefined! },
privateKey: storagePrivateKey,
};
}

View File

@ -93,7 +93,7 @@ describe.each([
await store.getOutgoingRoomKeyRequestByState([RoomKeyRequestState.Sent]);
expect(r).not.toBeNull();
expect(r).not.toBeUndefined();
expect(r.state).toEqual(RoomKeyRequestState.Sent);
expect(r!.state).toEqual(RoomKeyRequestState.Sent);
expect(requests).toContainEqual(r);
});
});

View File

@ -21,9 +21,9 @@ import { MatrixEvent } from "../../../src/models/event";
import { TestClient } from '../../TestClient';
import { makeTestClients } from './verification/util';
import { encryptAES } from "../../../src/crypto/aes";
import { resetCrossSigningKeys, createSecretStorageKey } from "./crypto-utils";
import { createSecretStorageKey, resetCrossSigningKeys } from "./crypto-utils";
import { logger } from '../../../src/logger';
import { ICreateClientOpts } from '../../../src/client';
import { ClientEvent, ICreateClientOpts } from '../../../src/client';
import { ISecretStorageKeyInfo } from '../../../src/crypto/api';
import { DeviceInfo } from '../../../src/crypto/deviceinfo';
@ -41,7 +41,7 @@ async function makeTestClient(userInfo: { userId: string, deviceId: string}, opt
await client.initCrypto();
// No need to download keys for these tests
jest.spyOn(client.crypto, 'downloadKeys').mockResolvedValue({});
jest.spyOn(client.crypto!, 'downloadKeys').mockResolvedValue({});
return client;
}
@ -93,11 +93,11 @@ describe("Secrets", function() {
},
},
);
alice.crypto.crossSigningInfo.setKeys({
alice.crypto!.crossSigningInfo.setKeys({
master: signingkeyInfo,
});
const secretStorage = alice.crypto.secretStorage;
const secretStorage = alice.crypto!.secretStorage;
jest.spyOn(alice, 'setAccountData').mockImplementation(
async function(eventType, contents) {
@ -113,7 +113,7 @@ describe("Secrets", function() {
const keyAccountData = {
algorithm: SECRET_STORAGE_ALGORITHM_V1_AES,
};
await alice.crypto.crossSigningInfo.signObject(keyAccountData, 'master');
await alice.crypto!.crossSigningInfo.signObject(keyAccountData, 'master');
alice.store.storeAccountDataEvents([
new MatrixEvent({
@ -200,7 +200,7 @@ describe("Secrets", function() {
await alice.storeSecret("foo", "bar");
const accountData = alice.getAccountData('foo');
expect(accountData.getContent().encrypted).toBeTruthy();
expect(accountData!.getContent().encrypted).toBeTruthy();
alice.stopClient();
});
@ -233,29 +233,29 @@ describe("Secrets", function() {
},
);
const vaxDevice = vax.client.crypto.olmDevice;
const osborne2Device = osborne2.client.crypto.olmDevice;
const secretStorage = osborne2.client.crypto.secretStorage;
const vaxDevice = vax.client.crypto!.olmDevice;
const osborne2Device = osborne2.client.crypto!.olmDevice;
const secretStorage = osborne2.client.crypto!.secretStorage;
osborne2.client.crypto.deviceList.storeDevicesForUser("@alice:example.com", {
osborne2.client.crypto!.deviceList.storeDevicesForUser("@alice:example.com", {
"VAX": {
known: false,
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:VAX": vaxDevice.deviceEd25519Key,
"curve25519:VAX": vaxDevice.deviceCurve25519Key,
"ed25519:VAX": vaxDevice.deviceEd25519Key!,
"curve25519:VAX": vaxDevice.deviceCurve25519Key!,
},
verified: DeviceInfo.DeviceVerification.VERIFIED,
},
});
vax.client.crypto.deviceList.storeDevicesForUser("@alice:example.com", {
vax.client.crypto!.deviceList.storeDevicesForUser("@alice:example.com", {
"Osborne2": {
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
verified: 0,
known: false,
keys: {
"ed25519:Osborne2": osborne2Device.deviceEd25519Key,
"curve25519:Osborne2": osborne2Device.deviceCurve25519Key,
"ed25519:Osborne2": osborne2Device.deviceEd25519Key!,
"curve25519:Osborne2": osborne2Device.deviceCurve25519Key!,
},
},
});
@ -264,13 +264,13 @@ describe("Secrets", function() {
const otks = (await osborne2Device.getOneTimeKeys()).curve25519;
await osborne2Device.markKeysAsPublished();
await vax.client.crypto.olmDevice.createOutboundSession(
osborne2Device.deviceCurve25519Key,
await vax.client.crypto!.olmDevice.createOutboundSession(
osborne2Device.deviceCurve25519Key!,
Object.values(otks)[0],
);
osborne2.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
osborne2.client.crypto.deviceList.getUserByIdentityKey = () => "@alice:example.com";
osborne2.client.crypto!.deviceList.downloadKeys = () => Promise.resolve({});
osborne2.client.crypto!.deviceList.getUserByIdentityKey = () => "@alice:example.com";
const request = await secretStorage.request("foo", ["VAX"]);
await request.promise; // return value not used
@ -328,7 +328,7 @@ describe("Secrets", function() {
this.store.storeAccountDataEvents([
event,
]);
this.emit("accountData", event);
this.emit(ClientEvent.AccountData, event);
return {};
};
@ -339,8 +339,8 @@ describe("Secrets", function() {
createSecretStorageKey,
});
const crossSigning = bob.crypto.crossSigningInfo;
const secretStorage = bob.crypto.secretStorage;
const crossSigning = bob.crypto!.crossSigningInfo;
const secretStorage = bob.crypto!.secretStorage;
expect(crossSigning.getId()).toBeTruthy();
expect(await crossSigning.isStoredInSecretStorage(secretStorage))
@ -486,7 +486,7 @@ describe("Secrets", function() {
},
}),
]);
alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", {
alice.crypto!.deviceList.storeCrossSigningForUser("@alice:example.com", {
firstUse: false,
crossSigningVerifiedBefore: false,
keys: {
@ -528,16 +528,15 @@ describe("Secrets", function() {
content: data,
});
alice.store.storeAccountDataEvents([event]);
this.emit("accountData", event);
this.emit(ClientEvent.AccountData, event);
return {};
};
await alice.bootstrapSecretStorage({});
expect(alice.getAccountData("m.secret_storage.default_key").getContent())
expect(alice.getAccountData("m.secret_storage.default_key")!.getContent())
.toEqual({ key: "key_id" });
const keyInfo = alice.getAccountData("m.secret_storage.key.key_id")
.getContent() as ISecretStorageKeyInfo;
const keyInfo = alice.getAccountData("m.secret_storage.key.key_id")!.getContent<ISecretStorageKeyInfo>();
expect(keyInfo.algorithm)
.toEqual("m.secret_storage.v1.aes-hmac-sha2");
expect(keyInfo.passphrase).toEqual({
@ -630,7 +629,7 @@ describe("Secrets", function() {
},
}),
]);
alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", {
alice.crypto!.deviceList.storeCrossSigningForUser("@alice:example.com", {
firstUse: false,
crossSigningVerifiedBefore: false,
keys: {
@ -672,14 +671,13 @@ describe("Secrets", function() {
content: data,
});
alice.store.storeAccountDataEvents([event]);
this.emit("accountData", event);
this.emit(ClientEvent.AccountData, event);
return {};
};
await alice.bootstrapSecretStorage({});
const backupKey = alice.getAccountData("m.megolm_backup.v1")
.getContent();
const backupKey = alice.getAccountData("m.megolm_backup.v1")!.getContent();
expect(backupKey.encrypted).toHaveProperty("key_id");
expect(await alice.getSecret("m.megolm_backup.v1"))
.toEqual("ey0GB1kB6jhOWgwiBUMIWg==");

View File

@ -49,7 +49,7 @@ describe("verification request integration tests with crypto layer", function()
verificationMethods: [verificationMethods.SAS],
},
);
alice.client.crypto.deviceList.getRawStoredDevicesForUser = function() {
alice.client.crypto!.deviceList.getRawStoredDevicesForUser = function() {
return {
Dynabook: {
algorithms: [],

View File

@ -17,16 +17,17 @@ limitations under the License.
import "../../../olm-loader";
import { makeTestClients, setupWebcrypto, teardownWebcrypto } from './util';
import { MatrixEvent } from "../../../../src/models/event";
import { SAS } from "../../../../src/crypto/verification/SAS";
import { ISasEvent, SAS, SasEvent } from "../../../../src/crypto/verification/SAS";
import { DeviceInfo } from "../../../../src/crypto/deviceinfo";
import { CryptoEvent, verificationMethods } from "../../../../src/crypto";
import * as olmlib from "../../../../src/crypto/olmlib";
import { logger } from "../../../../src/logger";
import { resetCrossSigningKeys } from "../crypto-utils";
import { VerificationBase } from "../../../../src/crypto/verification/Base";
import { VerificationBase as Verification, VerificationBase } from "../../../../src/crypto/verification/Base";
import { IVerificationChannel } from "../../../../src/crypto/verification/request/Channel";
import { MatrixClient } from "../../../../src";
import { VerificationRequest } from "../../../../src/crypto/verification/request/VerificationRequest";
import { TestClient } from "../../../TestClient";
const Olm = global.Olm;
@ -75,13 +76,13 @@ describe("SAS verification", function() {
});
describe("verification", () => {
let alice;
let bob;
let aliceSasEvent;
let bobSasEvent;
let aliceVerifier;
let bobPromise;
let clearTestClientTimeouts;
let alice: TestClient;
let bob: TestClient;
let aliceSasEvent: ISasEvent | null;
let bobSasEvent: ISasEvent | null;
let aliceVerifier: Verification<any, any>;
let bobPromise: Promise<VerificationBase<any, any>>;
let clearTestClientTimeouts: () => void;
beforeEach(async () => {
[[alice, bob], clearTestClientTimeouts] = await makeTestClients(
@ -94,8 +95,8 @@ describe("SAS verification", function() {
},
);
const aliceDevice = alice.client.crypto.olmDevice;
const bobDevice = bob.client.crypto.olmDevice;
const aliceDevice = alice.client.crypto!.olmDevice;
const bobDevice = bob.client.crypto!.olmDevice;
ALICE_DEVICES = {
Osborne2: {
@ -121,26 +122,26 @@ describe("SAS verification", function() {
},
};
alice.client.crypto.deviceList.storeDevicesForUser(
alice.client.crypto!.deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
alice.client.downloadKeys = () => {
return Promise.resolve();
return Promise.resolve({});
};
bob.client.crypto.deviceList.storeDevicesForUser(
bob.client.crypto!.deviceList.storeDevicesForUser(
"@alice:example.com", ALICE_DEVICES,
);
bob.client.downloadKeys = () => {
return Promise.resolve();
return Promise.resolve({});
};
aliceSasEvent = null;
bobSasEvent = null;
bobPromise = new Promise((resolve, reject) => {
bob.client.on("crypto.verification.request", request => {
request.verifier.on("show_sas", (e) => {
bobPromise = new Promise<VerificationBase<any, any>>((resolve, reject) => {
bob.client.on(CryptoEvent.VerificationRequest, request => {
request.verifier!.on("show_sas", (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
e.cancel();
} else if (!aliceSasEvent) {
@ -156,14 +157,14 @@ describe("SAS verification", function() {
}
}
});
resolve(request.verifier);
resolve(request.verifier!);
});
});
aliceVerifier = alice.client.beginKeyVerification(
verificationMethods.SAS, bob.client.getUserId(), bob.deviceId,
verificationMethods.SAS, bob.client.getUserId()!, bob.deviceId!,
);
aliceVerifier.on("show_sas", (e) => {
aliceVerifier.on(SasEvent.ShowSas, (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
e.cancel();
} else if (!bobSasEvent) {
@ -195,9 +196,9 @@ describe("SAS verification", function() {
const origSendToDevice = bob.client.sendToDevice.bind(bob.client);
bob.client.sendToDevice = function(type, map) {
if (type === "m.key.verification.accept") {
macMethod = map[alice.client.getUserId()][alice.client.deviceId]
macMethod = map[alice.client.getUserId()!][alice.client.deviceId!]
.message_authentication_code;
keyAgreement = map[alice.client.getUserId()][alice.client.deviceId]
keyAgreement = map[alice.client.getUserId()!][alice.client.deviceId!]
.key_agreement_protocol;
}
return origSendToDevice(type, map);
@ -219,8 +220,8 @@ describe("SAS verification", function() {
await Promise.all([
aliceVerifier.verify(),
bobPromise.then((verifier) => verifier.verify()),
alice.httpBackend.flush(),
bob.httpBackend.flush(),
alice.httpBackend.flush(undefined),
bob.httpBackend.flush(undefined),
]);
// make sure that it uses the preferred method
@ -230,10 +231,10 @@ describe("SAS verification", function() {
// make sure Alice and Bob verified each other
const bobDevice
= await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
expect(bobDevice.isVerified()).toBeTruthy();
expect(bobDevice?.isVerified()).toBeTruthy();
const aliceDevice
= await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
expect(aliceDevice.isVerified()).toBeTruthy();
expect(aliceDevice?.isVerified()).toBeTruthy();
});
it("should be able to verify using the old base64", async () => {
@ -248,7 +249,7 @@ describe("SAS verification", function() {
// has, since it is the same object. If this does not
// happen, the verification will fail due to a hash
// commitment mismatch.
map[bob.client.getUserId()][bob.client.deviceId]
map[bob.client.getUserId()!][bob.client.deviceId!]
.message_authentication_codes = ['hkdf-hmac-sha256'];
}
return aliceOrigSendToDevice(type, map);
@ -256,7 +257,7 @@ describe("SAS verification", function() {
const bobOrigSendToDevice = bob.client.sendToDevice.bind(bob.client);
bob.client.sendToDevice = (type, map) => {
if (type === "m.key.verification.accept") {
macMethod = map[alice.client.getUserId()][alice.client.deviceId]
macMethod = map[alice.client.getUserId()!][alice.client.deviceId!]
.message_authentication_code;
}
return bobOrigSendToDevice(type, map);
@ -278,18 +279,18 @@ describe("SAS verification", function() {
await Promise.all([
aliceVerifier.verify(),
bobPromise.then((verifier) => verifier.verify()),
alice.httpBackend.flush(),
bob.httpBackend.flush(),
alice.httpBackend.flush(undefined),
bob.httpBackend.flush(undefined),
]);
expect(macMethod).toBe("hkdf-hmac-sha256");
const bobDevice
= await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
expect(bobDevice.isVerified()).toBeTruthy();
expect(bobDevice!.isVerified()).toBeTruthy();
const aliceDevice
= await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
expect(aliceDevice.isVerified()).toBeTruthy();
expect(aliceDevice!.isVerified()).toBeTruthy();
});
it("should be able to verify using the old MAC", async () => {
@ -304,7 +305,7 @@ describe("SAS verification", function() {
// has, since it is the same object. If this does not
// happen, the verification will fail due to a hash
// commitment mismatch.
map[bob.client.getUserId()][bob.client.deviceId]
map[bob.client.getUserId()!][bob.client.deviceId!]
.message_authentication_codes = ['hmac-sha256'];
}
return aliceOrigSendToDevice(type, map);
@ -312,7 +313,7 @@ describe("SAS verification", function() {
const bobOrigSendToDevice = bob.client.sendToDevice.bind(bob.client);
bob.client.sendToDevice = (type, map) => {
if (type === "m.key.verification.accept") {
macMethod = map[alice.client.getUserId()][alice.client.deviceId]
macMethod = map[alice.client.getUserId()!][alice.client.deviceId!]
.message_authentication_code;
}
return bobOrigSendToDevice(type, map);
@ -334,18 +335,18 @@ describe("SAS verification", function() {
await Promise.all([
aliceVerifier.verify(),
bobPromise.then((verifier) => verifier.verify()),
alice.httpBackend.flush(),
bob.httpBackend.flush(),
alice.httpBackend.flush(undefined),
bob.httpBackend.flush(undefined),
]);
expect(macMethod).toBe("hmac-sha256");
const bobDevice
= await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
expect(bobDevice.isVerified()).toBeTruthy();
expect(bobDevice?.isVerified()).toBeTruthy();
const aliceDevice
= await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
expect(aliceDevice.isVerified()).toBeTruthy();
expect(aliceDevice?.isVerified()).toBeTruthy();
});
it("should verify a cross-signing key", async () => {
@ -361,9 +362,11 @@ describe("SAS verification", function() {
await resetCrossSigningKeys(bob.client);
bob.client.crypto.deviceList.storeCrossSigningForUser(
bob.client.crypto!.deviceList.storeCrossSigningForUser(
"@alice:example.com", {
keys: alice.client.crypto.crossSigningInfo.keys,
keys: alice.client.crypto!.crossSigningInfo.keys,
crossSigningVerifiedBefore: false,
firstUse: true,
},
);
@ -415,10 +418,10 @@ describe("SAS verification", function() {
const bobPromise = new Promise<VerificationBase<any, any>>((resolve, reject) => {
bob.client.on(CryptoEvent.VerificationRequest, request => {
request.verifier.on("show_sas", (e) => {
request.verifier!.on("show_sas", (e) => {
e.mismatch();
});
resolve(request.verifier);
resolve(request.verifier!);
});
});
@ -464,7 +467,7 @@ describe("SAS verification", function() {
},
);
alice.client.crypto.setDeviceVerification = jest.fn();
alice.client.crypto!.setDeviceVerification = jest.fn();
alice.client.getDeviceEd25519Key = () => {
return "alice+base64+ed25519+key";
};
@ -482,7 +485,7 @@ describe("SAS verification", function() {
return Promise.resolve();
};
bob.client.crypto.setDeviceVerification = jest.fn();
bob.client.crypto!.setDeviceVerification = jest.fn();
bob.client.getStoredDevice = () => {
return DeviceInfo.fromStorage(
{
@ -565,7 +568,7 @@ describe("SAS verification", function() {
]);
// make sure Alice and Bob verified each other
expect(alice.client.crypto.setDeviceVerification)
expect(alice.client.crypto!.setDeviceVerification)
.toHaveBeenCalledWith(
bob.client.getUserId(),
bob.client.deviceId,
@ -574,7 +577,7 @@ describe("SAS verification", function() {
null,
{ "ed25519:Dynabook": "bob+base64+ed25519+key" },
);
expect(bob.client.crypto.setDeviceVerification)
expect(bob.client.crypto!.setDeviceVerification)
.toHaveBeenCalledWith(
alice.client.getUserId(),
alice.client.deviceId,

View File

@ -41,7 +41,7 @@ export async function makeTestClients(userInfos, options): Promise<[TestClient[]
});
const client = clientMap[userId][deviceId];
const decryptionPromise = event.isEncrypted() ?
event.attemptDecryption(client.crypto) :
event.attemptDecryption(client.crypto!) :
Promise.resolve();
decryptionPromise.then(

View File

@ -32,7 +32,7 @@ describe("eventMapperFor", function() {
fetchFn: function() {} as any, // NOP
store: {
getRoom(roomId: string): Room | null {
return rooms.find(r => r.roomId === roomId);
return rooms.find(r => r.roomId === roomId) ?? null;
},
} as IStore,
scheduler: {

View File

@ -50,8 +50,8 @@ describe('EventTimelineSet', () => {
EventType.RoomMessage,
);
expect(relations).toBeDefined();
expect(relations.getRelations().length).toBe(1);
expect(relations.getRelations()[0].getId()).toBe(replyEvent.getId());
expect(relations!.getRelations().length).toBe(1);
expect(relations!.getRelations()[0].getId()).toBe(replyEvent.getId());
});
};

View File

@ -21,7 +21,7 @@ describe("EventTimeline", function() {
const getTimeline = (): EventTimeline => {
const room = new Room(roomId, mockClient, userA);
const timelineSet = new EventTimelineSet(room);
jest.spyOn(timelineSet.room, 'getUnfilteredTimelineSet').mockReturnValue(timelineSet);
jest.spyOn(room, 'getUnfilteredTimelineSet').mockReturnValue(timelineSet);
return new EventTimeline(timelineSet);
};

View File

@ -1,3 +1,19 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { UNREAD_THREAD_NOTIFICATIONS } from "../../src/@types/sync";
import { Filter, IFilterDefinition } from "../../src/filter";
import { mkEvent } from "../test-utils/test-utils";

View File

@ -36,7 +36,7 @@ import { ReceiptType } from "../../src/@types/read_receipts";
import * as testUtils from "../test-utils/test-utils";
import { makeBeaconInfoContent } from "../../src/content-helpers";
import { M_BEACON_INFO } from "../../src/@types/beacon";
import { ContentHelpers, EventTimeline, Room } from "../../src";
import { ContentHelpers, EventTimeline, MatrixError, Room } from "../../src";
import { supportsMatrixCall } from "../../src/webrtc/call";
import { makeBeaconEvent } from "../test-utils/beacon";
import {
@ -88,21 +88,22 @@ describe("MatrixClient", function() {
data: SYNC_DATA,
};
let httpLookups = [
// items are objects which look like:
// {
// method: "GET",
// path: "/initialSync",
// data: {},
// error: { errcode: M_FORBIDDEN } // if present will reject promise,
// expectBody: {} // additional expects on the body
// expectQueryParams: {} // additional expects on query params
// thenCall: function(){} // function to call *AFTER* returning response.
// }
// items are popped off when processed and block if no items left.
];
// items are popped off when processed and block if no items left.
let httpLookups: {
method: string;
path: string;
data?: object;
error?: object;
expectBody?: object;
expectQueryParams?: object;
thenCall?: Function;
}[] = [];
let acceptKeepalives: boolean;
let pendingLookup = null;
let pendingLookup: {
promise: Promise<any>;
method: string;
path: string;
} | null = null;
function httpReq(method, path, qp, data, prefix) {
if (path === KEEP_ALIVE_PATH && acceptKeepalives) {
return Promise.resolve({
@ -144,7 +145,7 @@ describe("MatrixClient", function() {
}
if (next.expectQueryParams) {
Object.keys(next.expectQueryParams).forEach(function(k) {
expect(qp[k]).toEqual(next.expectQueryParams[k]);
expect(qp[k]).toEqual(next.expectQueryParams![k]);
});
}
@ -155,9 +156,9 @@ describe("MatrixClient", function() {
if (next.error) {
// eslint-disable-next-line
return Promise.reject({
errcode: next.error.errcode,
httpStatus: next.error.httpStatus,
name: next.error.errcode,
errcode: (<MatrixError>next.error).errcode,
httpStatus: (<MatrixError>next.error).httpStatus,
name: (<MatrixError>next.error).errcode,
message: "Expected testing error",
data: next.error,
});
@ -254,7 +255,7 @@ describe("MatrixClient", function() {
type: UNSTABLE_MSC3088_PURPOSE.unstable,
state_key: UNSTABLE_MSC3089_TREE_SUBTYPE.unstable,
content: {
[UNSTABLE_MSC3088_ENABLED.unstable]: true,
[UNSTABLE_MSC3088_ENABLED.unstable!]: true,
},
},
{
@ -299,7 +300,7 @@ describe("MatrixClient", function() {
expect(stateKey).toEqual(UNSTABLE_MSC3089_TREE_SUBTYPE.unstable);
return new MatrixEvent({
content: {
[UNSTABLE_MSC3088_ENABLED.unstable]: true,
[UNSTABLE_MSC3088_ENABLED.unstable!]: true,
},
});
} else {
@ -359,7 +360,7 @@ describe("MatrixClient", function() {
expect(stateKey).toEqual(UNSTABLE_MSC3089_TREE_SUBTYPE.unstable);
return new MatrixEvent({
content: {
[UNSTABLE_MSC3088_ENABLED.unstable]: true,
[UNSTABLE_MSC3088_ENABLED.unstable!]: true,
},
});
} else {
@ -393,7 +394,7 @@ describe("MatrixClient", function() {
expect(stateKey).toEqual(UNSTABLE_MSC3089_TREE_SUBTYPE.unstable);
return new MatrixEvent({
content: {
[UNSTABLE_MSC3088_ENABLED.unstable]: false,
[UNSTABLE_MSC3088_ENABLED.unstable!]: false,
},
});
} else {
@ -599,14 +600,14 @@ describe("MatrixClient", function() {
}
it("should transition null -> PREPARED after the first /sync", function(done) {
const expectedStates = [];
const expectedStates: [string, string | null][] = [];
expectedStates.push(["PREPARED", null]);
client.on("sync", syncChecker(expectedStates, done));
client.startClient();
});
it("should transition null -> ERROR after a failed /filter", function(done) {
const expectedStates = [];
const expectedStates: [string, string | null][] = [];
httpLookups = [];
httpLookups.push(PUSH_RULES_RESPONSE);
httpLookups.push({
@ -620,36 +621,35 @@ describe("MatrixClient", function() {
// Disabled because now `startClient` makes a legit call to `/versions`
// And those tests are really unhappy about it... Not possible to figure
// out what a good resolution would look like
xit("should transition ERROR -> CATCHUP after /sync if prev failed",
function(done) {
const expectedStates = [];
acceptKeepalives = false;
httpLookups = [];
httpLookups.push(PUSH_RULES_RESPONSE);
httpLookups.push(FILTER_RESPONSE);
httpLookups.push({
method: "GET", path: "/sync", error: { errcode: "NOPE_NOPE_NOPE" },
});
httpLookups.push({
method: "GET", path: KEEP_ALIVE_PATH,
error: { errcode: "KEEPALIVE_FAIL" },
});
httpLookups.push({
method: "GET", path: KEEP_ALIVE_PATH, data: {},
});
httpLookups.push({
method: "GET", path: "/sync", data: SYNC_DATA,
});
expectedStates.push(["RECONNECTING", null]);
expectedStates.push(["ERROR", "RECONNECTING"]);
expectedStates.push(["CATCHUP", "ERROR"]);
client.on("sync", syncChecker(expectedStates, done));
client.startClient();
xit("should transition ERROR -> CATCHUP after /sync if prev failed", function(done) {
const expectedStates: [string, string | null][] = [];
acceptKeepalives = false;
httpLookups = [];
httpLookups.push(PUSH_RULES_RESPONSE);
httpLookups.push(FILTER_RESPONSE);
httpLookups.push({
method: "GET", path: "/sync", error: { errcode: "NOPE_NOPE_NOPE" },
});
httpLookups.push({
method: "GET", path: KEEP_ALIVE_PATH,
error: { errcode: "KEEPALIVE_FAIL" },
});
httpLookups.push({
method: "GET", path: KEEP_ALIVE_PATH, data: {},
});
httpLookups.push({
method: "GET", path: "/sync", data: SYNC_DATA,
});
expectedStates.push(["RECONNECTING", null]);
expectedStates.push(["ERROR", "RECONNECTING"]);
expectedStates.push(["CATCHUP", "ERROR"]);
client.on("sync", syncChecker(expectedStates, done));
client.startClient();
});
it("should transition PREPARED -> SYNCING after /sync", function(done) {
const expectedStates = [];
const expectedStates: [string, string | null][] = [];
expectedStates.push(["PREPARED", null]);
expectedStates.push(["SYNCING", "PREPARED"]);
client.on("sync", syncChecker(expectedStates, done));
@ -658,7 +658,7 @@ describe("MatrixClient", function() {
xit("should transition SYNCING -> ERROR after a failed /sync", function(done) {
acceptKeepalives = false;
const expectedStates = [];
const expectedStates: [string, string | null][] = [];
httpLookups.push({
method: "GET", path: "/sync", error: { errcode: "NONONONONO" },
});
@ -675,37 +675,35 @@ describe("MatrixClient", function() {
client.startClient();
});
xit("should transition ERROR -> SYNCING after /sync if prev failed",
function(done) {
const expectedStates = [];
httpLookups.push({
method: "GET", path: "/sync", error: { errcode: "NONONONONO" },
});
httpLookups.push(SYNC_RESPONSE);
expectedStates.push(["PREPARED", null]);
expectedStates.push(["SYNCING", "PREPARED"]);
expectedStates.push(["ERROR", "SYNCING"]);
client.on("sync", syncChecker(expectedStates, done));
client.startClient();
xit("should transition ERROR -> SYNCING after /sync if prev failed", function(done) {
const expectedStates: [string, string | null][] = [];
httpLookups.push({
method: "GET", path: "/sync", error: { errcode: "NONONONONO" },
});
httpLookups.push(SYNC_RESPONSE);
it("should transition SYNCING -> SYNCING on subsequent /sync successes",
function(done) {
const expectedStates = [];
httpLookups.push(SYNC_RESPONSE);
httpLookups.push(SYNC_RESPONSE);
expectedStates.push(["PREPARED", null]);
expectedStates.push(["SYNCING", "PREPARED"]);
expectedStates.push(["ERROR", "SYNCING"]);
client.on("sync", syncChecker(expectedStates, done));
client.startClient();
});
expectedStates.push(["PREPARED", null]);
expectedStates.push(["SYNCING", "PREPARED"]);
expectedStates.push(["SYNCING", "SYNCING"]);
client.on("sync", syncChecker(expectedStates, done));
client.startClient();
});
it("should transition SYNCING -> SYNCING on subsequent /sync successes", function(done) {
const expectedStates: [string, string | null][] = [];
httpLookups.push(SYNC_RESPONSE);
httpLookups.push(SYNC_RESPONSE);
expectedStates.push(["PREPARED", null]);
expectedStates.push(["SYNCING", "PREPARED"]);
expectedStates.push(["SYNCING", "SYNCING"]);
client.on("sync", syncChecker(expectedStates, done));
client.startClient();
});
xit("should transition ERROR -> ERROR if keepalive keeps failing", function(done) {
acceptKeepalives = false;
const expectedStates = [];
const expectedStates: [string, string | null][] = [];
httpLookups.push({
method: "GET", path: "/sync", error: { errcode: "NONONONONO" },
});

View File

@ -209,32 +209,32 @@ describe('NotificationService', function() {
msgtype: "m.text",
},
});
matrixClient.pushRules = PushProcessor.rewriteDefaultRules(matrixClient.pushRules);
matrixClient.pushRules = PushProcessor.rewriteDefaultRules(matrixClient.pushRules!);
pushProcessor = new PushProcessor(matrixClient);
});
// User IDs
it('should bing on a user ID.', function() {
testEvent.event.content.body = "Hello @ali:matrix.org, how are you?";
testEvent.event.content!.body = "Hello @ali:matrix.org, how are you?";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on a partial user ID with an @.', function() {
testEvent.event.content.body = "Hello @ali, how are you?";
testEvent.event.content!.body = "Hello @ali, how are you?";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on a partial user ID without @.', function() {
testEvent.event.content.body = "Hello ali, how are you?";
testEvent.event.content!.body = "Hello ali, how are you?";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on a case-insensitive user ID.', function() {
testEvent.event.content.body = "Hello @AlI:matrix.org, how are you?";
testEvent.event.content!.body = "Hello @AlI:matrix.org, how are you?";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
@ -242,13 +242,13 @@ describe('NotificationService', function() {
// Display names
it('should bing on a display name.', function() {
testEvent.event.content.body = "Hello Alice M, how are you?";
testEvent.event.content!.body = "Hello Alice M, how are you?";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on a case-insensitive display name.', function() {
testEvent.event.content.body = "Hello ALICE M, how are you?";
testEvent.event.content!.body = "Hello ALICE M, how are you?";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
@ -256,43 +256,43 @@ describe('NotificationService', function() {
// Bing words
it('should bing on a bing word.', function() {
testEvent.event.content.body = "I really like coffee";
testEvent.event.content!.body = "I really like coffee";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on case-insensitive bing words.', function() {
testEvent.event.content.body = "Coffee is great";
testEvent.event.content!.body = "Coffee is great";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on wildcard (.*) bing words.', function() {
testEvent.event.content.body = "It was foomahbar I think.";
testEvent.event.content!.body = "It was foomahbar I think.";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on character group ([abc]) bing words.', function() {
testEvent.event.content.body = "Ping!";
testEvent.event.content!.body = "Ping!";
let actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
testEvent.event.content.body = "Pong!";
testEvent.event.content!.body = "Pong!";
actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on character range ([a-z]) bing words.', function() {
testEvent.event.content.body = "I ate 6 pies";
testEvent.event.content!.body = "I ate 6 pies";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on character negation ([!a]) bing words.', function() {
testEvent.event.content.body = "boke";
testEvent.event.content!.body = "boke";
let actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
testEvent.event.content.body = "bake";
testEvent.event.content!.body = "bake";
actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(false);
});
@ -316,7 +316,7 @@ describe('NotificationService', function() {
// invalid
it('should gracefully handle bad input.', function() {
testEvent.event.content.body = { "foo": "bar" };
testEvent.event.content!.body = { "foo": "bar" };
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(false);
});

View File

@ -18,6 +18,7 @@ import { EventTimelineSet } from "../../src/models/event-timeline-set";
import { MatrixEvent, MatrixEventEvent } from "../../src/models/event";
import { Room } from "../../src/models/room";
import { Relations } from "../../src/models/relations";
import { TestClient } from "../TestClient";
describe("Relations", function() {
it("should deduplicate annotations", function() {
@ -43,7 +44,7 @@ describe("Relations", function() {
// Add the event once and check results
{
relations.addEvent(eventA);
const annotationsByKey = relations.getSortedAnnotationsByKey();
const annotationsByKey = relations.getSortedAnnotationsByKey()!;
expect(annotationsByKey.length).toEqual(1);
const [key, events] = annotationsByKey[0];
expect(key).toEqual("👍️");
@ -53,7 +54,7 @@ describe("Relations", function() {
// Add the event again and expect the same
{
relations.addEvent(eventA);
const annotationsByKey = relations.getSortedAnnotationsByKey();
const annotationsByKey = relations.getSortedAnnotationsByKey()!;
expect(annotationsByKey.length).toEqual(1);
const [key, events] = annotationsByKey[0];
expect(key).toEqual("👍️");
@ -66,7 +67,7 @@ describe("Relations", function() {
// Add the event again and expect the same
{
relations.addEvent(eventB);
const annotationsByKey = relations.getSortedAnnotationsByKey();
const annotationsByKey = relations.getSortedAnnotationsByKey()!;
expect(annotationsByKey.length).toEqual(1);
const [key, events] = annotationsByKey[0];
expect(key).toEqual("👍️");
@ -179,4 +180,28 @@ describe("Relations", function() {
expect(badlyEditedTopic.replacingEvent()).toBe(null);
expect(badlyEditedTopic.getContent().topic).toBe("topic");
});
it("getSortedAnnotationsByKey should return null for non-annotation relations", async () => {
const userId = "@user:server";
const room = new Room("room123", new TestClient(userId).client, userId);
const relations = new Relations("m.replace", "m.room.message", room);
// Create an instance of an annotation
const eventData = {
"sender": "@bob:example.com",
"type": "m.room.message",
"event_id": "$cZ1biX33ENJqIm00ks0W_hgiO_6CHrsAc3ZQrnLeNTw",
"room_id": "!pzVjCQSoQPpXQeHpmK:example.com",
"content": {
"m.relates_to": {
"event_id": "$2s4yYpEkVQrPglSCSqB_m6E8vDhWsg0yFNyOJdVIb_o",
"rel_type": "m.replace",
},
},
};
const eventA = new MatrixEvent(eventData);
relations.addEvent(eventA);
expect(relations.getSortedAnnotationsByKey()).toBeNull();
});
});

View File

@ -601,7 +601,7 @@ describe("Room", function() {
});
const resetTimelineTests = function(timelineSupport) {
let events = null;
let events: MatrixEvent[];
beforeEach(function() {
room = new Room(roomId, new TestClient(userA).client, userA, { timelineSupport: timelineSupport });
@ -1732,7 +1732,7 @@ describe("Room", function() {
client.members.mockReturnValue({ chunk: [memberEvent] });
await room.loadMembersIfNeeded();
const memberA = room.getMember("@user_a:bar");
const memberA = room.getMember("@user_a:bar")!;
expect(memberA.name).toEqual("User A");
});
});
@ -2455,28 +2455,28 @@ describe("Room", function() {
room.addLiveEvents(events);
const thread = threadRoot.getThread();
const thread = threadRoot.getThread()!;
expect(thread.rootEvent).toBe(threadRoot);
const rootRelations = thread.timelineSet.relations.getChildEventsForEvent(
threadRoot.getId(),
RelationType.Annotation,
EventType.Reaction,
).getSortedAnnotationsByKey();
)!.getSortedAnnotationsByKey();
expect(rootRelations).toHaveLength(1);
expect(rootRelations[0][0]).toEqual(rootReaction.getRelation().key);
expect(rootRelations[0][1].size).toEqual(1);
expect(rootRelations[0][1].has(rootReaction)).toBeTruthy();
expect(rootRelations![0][0]).toEqual(rootReaction.getRelation()!.key);
expect(rootRelations![0][1].size).toEqual(1);
expect(rootRelations![0][1].has(rootReaction)).toBeTruthy();
const responseRelations = thread.timelineSet.relations.getChildEventsForEvent(
threadResponse.getId(),
RelationType.Annotation,
EventType.Reaction,
).getSortedAnnotationsByKey();
)!.getSortedAnnotationsByKey();
expect(responseRelations).toHaveLength(1);
expect(responseRelations[0][0]).toEqual(threadReaction.getRelation().key);
expect(responseRelations[0][1].size).toEqual(1);
expect(responseRelations[0][1].has(threadReaction)).toBeTruthy();
expect(responseRelations![0][0]).toEqual(threadReaction.getRelation()!.key);
expect(responseRelations![0][1].size).toEqual(1);
expect(responseRelations![0][1].has(threadReaction)).toBeTruthy();
});
});

View File

@ -37,7 +37,7 @@ const mockClient = {
function createTimeline(numEvents = 3, baseIndex = 1): EventTimeline {
const room = new Room(ROOM_ID, mockClient, USER_ID);
const timelineSet = new EventTimelineSet(room);
jest.spyOn(timelineSet.room, 'getUnfilteredTimelineSet').mockReturnValue(timelineSet);
jest.spyOn(room, 'getUnfilteredTimelineSet').mockReturnValue(timelineSet);
const timeline = new EventTimeline(timelineSet);
@ -170,7 +170,7 @@ describe("TimelineWindow", function() {
beforeEach(() => {
jest.clearAllMocks();
mockClient.getEventTimeline.mockResolvedValue(undefined);
mockClient.paginateEventTimeline.mockReturnValue(undefined);
mockClient.paginateEventTimeline.mockResolvedValue(false);
});
describe("load", function() {

View File

@ -26,17 +26,19 @@ import {
MockRTCPeerConnection,
} from "../../test-utils/webrtc";
import { CallFeed } from "../../../src/webrtc/callFeed";
import { EventType, MatrixClient } from "../../../src";
import { MediaHandler } from "../../../src/webrtc/mediaHandler";
const startVoiceCall = async (client: TestClient, call: MatrixCall): Promise<void> => {
const callPromise = call.placeVoiceCall();
await client.httpBackend.flush("");
await client.httpBackend!.flush("");
await callPromise;
call.getOpponentMember = jest.fn().mockReturnValue({ userId: "@bob:bar.uk" });
};
describe('Call', function() {
let client;
let client: TestClient;
let call;
let prevNavigator;
let prevDocument;
@ -71,10 +73,10 @@ describe('Call', function() {
client = new TestClient("@alice:foo", "somedevice", "token", undefined, {});
// We just stub out sendEvent: we're not interested in testing the client's
// event sending code here
client.client.sendEvent = () => {};
client.client.mediaHandler = new MockMediaHandler;
client.client.getMediaHandler = () => client.client.mediaHandler;
client.httpBackend.when("GET", "/voip/turnServer").respond(200, {});
client.client.sendEvent = (() => {}) as unknown as MatrixClient["sendEvent"];
client.client["mediaHandler"] = new MockMediaHandler as unknown as MediaHandler;
client.client.getMediaHandler = () => client.client["mediaHandler"]!;
client.httpBackend!.when("GET", "/voip/turnServer").respond(200, {});
call = new MatrixCall({
client: client.client,
roomId: '!foo:bar',
@ -237,7 +239,7 @@ describe('Call', function() {
expect(identChangedCallback).toHaveBeenCalled();
const ident = call.getRemoteAssertedIdentity();
const ident = call.getRemoteAssertedIdentity()!;
expect(ident.id).toEqual("@steve:example.com");
expect(ident.displayName).toEqual("Steve Gibbons");
@ -306,19 +308,19 @@ describe('Call', function() {
});
it("should fallback to answering with no video", async () => {
await client.httpBackend.flush();
await client.httpBackend!.flush(undefined);
call.shouldAnswerWithMediaType = (wantedValue: boolean) => wantedValue;
client.client.mediaHandler.getUserMediaStream = jest.fn().mockRejectedValue("reject");
client.client["mediaHandler"].getUserMediaStream = jest.fn().mockRejectedValue("reject");
await call.answer(true, true);
expect(client.client.mediaHandler.getUserMediaStream).toHaveBeenNthCalledWith(1, true, true);
expect(client.client.mediaHandler.getUserMediaStream).toHaveBeenNthCalledWith(2, true, false);
expect(client.client["mediaHandler"].getUserMediaStream).toHaveBeenNthCalledWith(1, true, true);
expect(client.client["mediaHandler"].getUserMediaStream).toHaveBeenNthCalledWith(2, true, false);
});
it("should handle mid-call device changes", async () => {
client.client.mediaHandler.getUserMediaStream = jest.fn().mockReturnValue(
client.client["mediaHandler"].getUserMediaStream = jest.fn().mockReturnValue(
new MockMediaStream(
"stream", [
new MockMediaStreamTrack("audio_track", "audio"),
@ -424,7 +426,7 @@ describe('Call', function() {
it("should choose opponent member", async () => {
const callPromise = call.placeVoiceCall();
await client.httpBackend.flush();
await client.httpBackend!.flush(undefined);
await callPromise;
const opponentMember = {
@ -480,7 +482,7 @@ describe('Call', function() {
it("should correctly generate local SDPStreamMetadata", async () => {
const callPromise = call.placeCallWithCallFeeds([new CallFeed({
client,
client: client.client,
// @ts-ignore Mock
stream: new MockMediaStream("local_stream1", [new MockMediaStreamTrack("track_id", "audio")]),
roomId: call.roomId,
@ -489,7 +491,7 @@ describe('Call', function() {
audioMuted: false,
videoMuted: false,
})]);
await client.httpBackend.flush();
await client.httpBackend!.flush(undefined);
await callPromise;
call.getOpponentMember = jest.fn().mockReturnValue({ userId: "@bob:bar.uk" });
@ -521,7 +523,7 @@ describe('Call', function() {
const callPromise = call.placeCallWithCallFeeds([
new CallFeed({
client,
client: client.client,
userId: client.getUserId(),
// @ts-ignore Mock
stream: localUsermediaStream,
@ -531,7 +533,7 @@ describe('Call', function() {
videoMuted: false,
}),
new CallFeed({
client,
client: client.client,
userId: client.getUserId(),
// @ts-ignore Mock
stream: localScreensharingStream,
@ -541,7 +543,7 @@ describe('Call', function() {
videoMuted: false,
}),
]);
await client.httpBackend.flush();
await client.httpBackend!.flush(undefined);
await callPromise;
call.getOpponentMember = jest.fn().mockReturnValue({ userId: "@bob:bar.uk" });
@ -586,14 +588,14 @@ describe('Call', function() {
getLocalAge: () => null,
});
call.feeds.push(new CallFeed({
client,
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",
purpose: SDPStreamMetadataPurpose.Usermedia,
}));
await client.httpBackend.flush();
await client.httpBackend!.flush(undefined);
await callPromise;
const callHangupCallback = jest.fn();
@ -664,10 +666,10 @@ describe('Call', function() {
});
it("should return false if window or document are undefined", () => {
global.window = undefined;
global.window = undefined!;
expect(supportsMatrixCall()).toBe(false);
global.window = prevWindow;
global.document = undefined;
global.document = undefined!;
expect(supportsMatrixCall()).toBe(false);
});
@ -685,9 +687,9 @@ describe('Call', function() {
it("should return false if RTCPeerConnection & RTCSessionDescription " +
"& RTCIceCandidate & mediaDevices are unavailable",
() => {
global.window.RTCPeerConnection = undefined;
global.window.RTCSessionDescription = undefined;
global.window.RTCIceCandidate = undefined;
global.window.RTCPeerConnection = undefined!;
global.window.RTCSessionDescription = undefined!;
global.window.RTCIceCandidate = undefined!;
// @ts-ignore - writing to a read-only property as we are simulating faulty browsers
global.navigator.mediaDevices = undefined;
expect(supportsMatrixCall()).toBe(false);
@ -752,4 +754,17 @@ describe('Call', function() {
expect(call.pushLocalFeed).toHaveBeenCalled();
});
});
describe("transferToCall", () => {
it("should send the required events", async () => {
const targetCall = new MatrixCall({ client: client.client });
const sendEvent = jest.spyOn(client.client, "sendEvent");
await call.transferToCall(targetCall);
const newCallId = (sendEvent.mock.calls[0][2] as any)!.await_call;
expect(sendEvent).toHaveBeenCalledWith(call.roomId, EventType.CallReplaces, expect.objectContaining({
create_call: newCallId,
}));
});
});
});

View File

@ -58,7 +58,7 @@ describe("callEventHandler", () => {
client.on(CallEventHandlerEvent.Incoming, incomingCallEmitted);
client.getSyncState = jest.fn().mockReturnValue(SyncState.Syncing);
client.emit(ClientEvent.Sync, SyncState.Syncing);
client.emit(ClientEvent.Sync, SyncState.Syncing, null);
expect(incomingCallEmitted).not.toHaveBeenCalled();
});

View File

@ -23,7 +23,10 @@ import { Optional } from "matrix-events-sdk/lib/types";
export class NamespacedValue<S extends string, U extends string> {
// Stable is optional, but one of the two parameters is required, hence the weird-looking types.
// Goal is to to have developers explicitly say there is no stable value (if applicable).
public constructor(public readonly stable: S | null | undefined, public readonly unstable?: U) {
public constructor(stable: S, unstable: U);
public constructor(stable: S, unstable?: U);
public constructor(stable: null | undefined, unstable: U);
public constructor(public readonly stable?: S | null, public readonly unstable?: U) {
if (!this.unstable && !this.stable) {
throw new Error("One of stable or unstable values must be supplied");
}
@ -33,10 +36,10 @@ export class NamespacedValue<S extends string, U extends string> {
if (this.stable) {
return this.stable;
}
return this.unstable;
return this.unstable!;
}
public get altName(): U | S | null {
public get altName(): U | S | null | undefined {
if (!this.stable) {
return null;
}
@ -57,7 +60,7 @@ export class NamespacedValue<S extends string, U extends string> {
// this desperately wants https://github.com/microsoft/TypeScript/pull/26349 at the top level of the class
// so we can instantiate `NamespacedValue<string, _, _>` as a default type for that namespace.
public findIn<T>(obj: any): Optional<T> {
let val: T;
let val: T | undefined = undefined;
if (this.name) {
val = obj?.[this.name];
}
@ -91,7 +94,7 @@ export class ServerControlledNamespacedValue<S extends string, U extends string>
if (this.stable && !this.preferUnstable) {
return this.stable;
}
return this.unstable;
return this.unstable!;
}
}
@ -109,10 +112,10 @@ export class UnstableValue<S extends string, U extends string> extends Namespace
}
public get name(): U {
return this.unstable;
return this.unstable!;
}
public get altName(): S {
return this.stable;
return this.stable!;
}
}

View File

@ -15,7 +15,7 @@ limitations under the License.
*/
import { logger } from "./logger";
import { MatrixClient } from "./matrix";
import { MatrixError, MatrixClient } from "./matrix";
import { IndexedToDeviceBatch, ToDeviceBatch, ToDeviceBatchWithTxnId, ToDevicePayload } from "./models/ToDeviceMessage";
import { MatrixScheduler } from "./scheduler";
@ -72,7 +72,7 @@ export class ToDeviceMessageQueue {
logger.debug("Attempting to send queued to-device messages");
this.sending = true;
let headBatch: IndexedToDeviceBatch;
let headBatch: IndexedToDeviceBatch | null;
try {
while (this.running) {
headBatch = await this.client.store.getOldestToDeviceBatch();
@ -90,11 +90,11 @@ export class ToDeviceMessageQueue {
++this.retryAttempts;
// eslint-disable-next-line @typescript-eslint/naming-convention
// eslint-disable-next-line new-cap
const retryDelay = MatrixScheduler.RETRY_BACKOFF_RATELIMIT(null, this.retryAttempts, e);
const retryDelay = MatrixScheduler.RETRY_BACKOFF_RATELIMIT(null, this.retryAttempts, <MatrixError>e);
if (retryDelay === -1) {
// the scheduler function doesn't differentiate between fatal errors and just getting
// bored and giving up for now
if (Math.floor(e.httpStatus / 100) === 4) {
if (Math.floor((<MatrixError>e).httpStatus! / 100) === 4) {
logger.error("Fatal error when sending to-device message - dropping to-device batch!", e);
await this.client.store.removeToDeviceBatch(headBatch!.id);
} else {

View File

@ -347,7 +347,7 @@ export class AutoDiscovery {
* @returns {Promise<object>} Resolves to the domain's client config. Can
* be an empty object.
*/
public static async getRawClientConfig(domain: string): Promise<IClientWellKnown> {
public static async getRawClientConfig(domain?: string): Promise<IClientWellKnown> {
if (!domain || typeof(domain) !== "string" || domain.length === 0) {
throw new Error("'domain' must be a string of non-zero length");
}

View File

@ -434,7 +434,7 @@ export interface IStartClientOpts {
}
export interface IStoredClientOpts extends IStartClientOpts {
crypto: Crypto;
crypto?: Crypto;
canResetEntireTimeline: ResetTimelineCallback;
}
@ -697,41 +697,31 @@ export interface IMyDevice {
[UNSTABLE_MSC3852_LAST_SEEN_UA.unstable]?: string;
}
export interface Keys {
keys: { [keyId: string]: string };
usage: string[];
user_id: string;
}
export interface SigningKeys extends Keys {
signatures: ISignatures;
}
export interface DeviceKeys {
[deviceId: string]: IDeviceKeys & {
unsigned?: {
device_display_name: string;
};
};
}
export interface IDownloadKeyResult {
failures: { [serverName: string]: object };
device_keys: {
[userId: string]: {
[deviceId: string]: IDeviceKeys & {
unsigned?: {
device_display_name: string;
};
};
};
};
device_keys: { [userId: string]: DeviceKeys };
// the following three fields were added in 1.1
master_keys?: {
[userId: string]: {
keys: { [keyId: string]: string };
usage: string[];
user_id: string;
};
};
self_signing_keys?: {
[userId: string]: {
keys: { [keyId: string]: string };
signatures: ISignatures;
usage: string[];
user_id: string;
};
};
user_signing_keys?: {
[userId: string]: {
keys: { [keyId: string]: string };
signatures: ISignatures;
usage: string[];
user_id: string;
};
};
master_keys?: { [userId: string]: Keys };
self_signing_keys?: { [userId: string]: SigningKeys };
user_signing_keys?: { [userId: string]: SigningKeys };
}
export interface IClaimOTKsResult {
@ -886,7 +876,7 @@ export type EmittedEvents = ClientEvent
| BeaconEvent;
export type ClientEventHandlerMap = {
[ClientEvent.Sync]: (state: SyncState, lastState?: SyncState, data?: ISyncStateData) => void;
[ClientEvent.Sync]: (state: SyncState, lastState: SyncState | null, data?: ISyncStateData) => void;
[ClientEvent.Event]: (event: MatrixEvent) => void;
[ClientEvent.ToDeviceEvent]: (event: MatrixEvent) => void;
[ClientEvent.AccountData]: (event: MatrixEvent, lastEvent?: MatrixEvent) => void;
@ -943,22 +933,22 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// We don't technically support this usage, but have reasons to do this.
protected canSupportVoip = false;
protected peekSync: SyncApi = null;
protected peekSync: SyncApi | null = null;
protected isGuestAccount = false;
protected ongoingScrollbacks: {[roomId: string]: {promise?: Promise<Room>, errorTs?: number}} = {};
protected notifTimelineSet: EventTimelineSet = null;
protected notifTimelineSet: EventTimelineSet | null = null;
protected cryptoStore: CryptoStore;
protected verificationMethods: VerificationMethod[];
protected fallbackICEServerAllowed = false;
protected roomList: RoomList;
protected syncApi: SlidingSyncSdk | SyncApi;
protected syncApi?: SlidingSyncSdk | SyncApi;
public roomNameGenerator?: ICreateClientOpts["roomNameGenerator"];
public pushRules: IPushRules;
protected syncLeftRoomsPromise: Promise<Room[]>;
public pushRules?: IPushRules;
protected syncLeftRoomsPromise?: Promise<Room[]>;
protected syncedLeftRooms = false;
protected clientOpts: IStoredClientOpts;
protected clientWellKnownIntervalID: ReturnType<typeof setInterval>;
protected canResetTimelineCallback: ResetTimelineCallback;
protected clientOpts?: IStoredClientOpts;
protected clientWellKnownIntervalID?: ReturnType<typeof setInterval>;
protected canResetTimelineCallback?: ResetTimelineCallback;
public canSupport = new Map<Feature, ServerSupport>();
@ -967,7 +957,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// Promise to a response of the server's /versions response
// TODO: This should expire: https://github.com/matrix-org/matrix-js-sdk/issues/1020
protected serverVersionsPromise: Promise<IServerVersions>;
protected serverVersionsPromise?: Promise<IServerVersions>;
public cachedCapabilities: {
capabilities: ICapabilities;
@ -1242,7 +1232,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
this.clientRunning = false;
this.syncApi?.stop();
this.syncApi = null;
this.syncApi = undefined;
this.peekSync?.stopPeeking();
@ -1268,7 +1258,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* to the new device ID if the dehydration was successful.
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
public async rehydrateDevice(): Promise<string> {
public async rehydrateDevice(): Promise<string | undefined> {
if (this.crypto) {
throw new Error("Cannot rehydrate device after crypto is initialized");
}
@ -1317,7 +1307,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
},
);
if (rehydrateResult.success === true) {
if (rehydrateResult.success) {
this.deviceId = getDeviceResult.device_id;
logger.info("using dehydrated device");
const pickleKey = this.pickleKey || "DEFAULT_KEY";
@ -1395,7 +1385,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
key: Uint8Array,
keyInfo: IDehydratedDeviceKeyInfo,
deviceDisplayName?: string,
): Promise<string> {
): Promise<string | undefined> {
if (!this.crypto) {
logger.warn('not dehydrating device if crypto is not enabled');
return;
@ -1404,7 +1394,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
return this.crypto.dehydrationManager.dehydrateDevice();
}
public async exportDevice(): Promise<IExportedDevice> {
public async exportDevice(): Promise<IExportedDevice | undefined> {
if (!this.crypto) {
logger.warn('not exporting device if crypto is not enabled');
return;
@ -1452,7 +1442,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* Get the domain for this client's MXID
* @return {?string} Domain of this MXID
*/
public getDomain(): string {
public getDomain(): string | null {
if (this.credentials && this.credentials.userId) {
return this.credentials.userId.replace(/^.*?:/, '');
}
@ -1527,11 +1517,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @return {?SyncState} the sync state, which may be null.
* @see module:client~MatrixClient#event:"sync"
*/
public getSyncState(): SyncState {
if (!this.syncApi) {
return null;
}
return this.syncApi.getSyncState();
public getSyncState(): SyncState | null {
return this.syncApi?.getSyncState() ?? null;
}
/**
@ -1600,7 +1587,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
public retryImmediately(): boolean {
// don't await for this promise: we just want to kick it off
this.toDeviceMessageQueue.sendQueue();
return this.syncApi.retryImmediately();
return this.syncApi?.retryImmediately() ?? false;
}
/**
@ -1608,7 +1595,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*
* @return {EventTimelineSet} the globl notification EventTimelineSet
*/
public getNotifTimelineSet(): EventTimelineSet {
public getNotifTimelineSet(): EventTimelineSet | null {
return this.notifTimelineSet;
}
@ -1759,9 +1746,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @return {?string} base64-encoded ed25519 key. Null if crypto is
* disabled.
*/
public getDeviceEd25519Key(): string {
if (!this.crypto) return null;
return this.crypto.getDeviceEd25519Key();
public getDeviceEd25519Key(): string | null {
return this.crypto?.getDeviceEd25519Key() ?? null;
}
/**
@ -1770,9 +1756,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @return {?string} base64-encoded curve25519 key. Null if crypto is
* disabled.
*/
public getDeviceCurve25519Key(): string {
if (!this.crypto) return null;
return this.crypto.getDeviceCurve25519Key();
public getDeviceCurve25519Key(): string | null {
return this.crypto?.getDeviceCurve25519Key() ?? null;
}
/**
@ -1900,9 +1885,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
private async setDeviceVerification(
userId: string,
deviceId: string,
verified: boolean,
blocked: boolean,
known: boolean,
verified?: boolean | null,
blocked?: boolean | null,
known?: boolean | null,
): Promise<void> {
if (!this.crypto) {
throw new Error("End-to-end encryption disabled");
@ -2058,7 +2043,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*
* @returns {string} the key ID
*/
public getCrossSigningId(type: CrossSigningKey | string = CrossSigningKey.Master): string {
public getCrossSigningId(type: CrossSigningKey | string = CrossSigningKey.Master): string | null {
if (!this.crypto) {
throw new Error("End-to-end encryption disabled");
}
@ -2074,7 +2059,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*
* @returns {CrossSigningInfo} the cross signing information for the user.
*/
public getStoredCrossSigningForUser(userId: string): CrossSigningInfo {
public getStoredCrossSigningForUser(userId: string): CrossSigningInfo | null {
if (!this.crypto) {
throw new Error("End-to-end encryption disabled");
}
@ -2408,7 +2393,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*
* @return {string} the contents of the secret
*/
public getSecret(name: string): Promise<string> {
public getSecret(name: string): Promise<string | undefined> {
if (!this.crypto) {
throw new Error("End-to-end encryption disabled");
}
@ -2656,7 +2641,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* trust information (as returned by isKeyBackupTrusted)
* in trustInfo.
*/
public checkKeyBackup(): Promise<IKeyBackupCheck> {
public checkKeyBackup(): Promise<IKeyBackupCheck | null> {
if (!this.crypto) {
throw new Error("End-to-end encryption disabled");
}
return this.crypto.backupManager.checkKeyBackup();
}
@ -2672,7 +2660,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
{ prefix: ClientPrefix.V3 },
);
} catch (e) {
if (e.errcode === 'M_NOT_FOUND') {
if ((<MatrixError>e).errcode === 'M_NOT_FOUND') {
return null;
} else {
throw e;
@ -2693,6 +2681,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* }
*/
public isKeyBackupTrusted(info: IKeyBackupInfo): Promise<TrustInfo> {
if (!this.crypto) {
throw new Error("End-to-end encryption disabled");
}
return this.crypto.backupManager.isKeyBackupTrusted(info);
}
@ -2701,7 +2692,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* the server, otherwise false. If we haven't completed a successful check
* of key backup status yet, returns null.
*/
public getKeyBackupEnabled(): boolean {
public getKeyBackupEnabled(): boolean | null {
if (!this.crypto) {
throw new Error("End-to-end encryption disabled");
}
@ -2750,7 +2741,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*/
// TODO: Verify types
public async prepareKeyBackupVersion(
password?: string,
password?: string | Uint8Array | null,
opts: IKeyBackupPrepareOpts = { secureSecretStorage: false },
): Promise<Pick<IPreparedKeyBackupVersion, "algorithm" | "auth_data" | "recovery_key">> {
if (!this.crypto) {
@ -3053,17 +3044,20 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
targetSessionId?: string,
opts?: IKeyBackupRestoreOpts,
): Promise<IKeyBackupRestoreResult> {
if (!this.crypto) {
throw new Error("End-to-end encryption disabled");
}
const storedKey = await this.getSecret("m.megolm_backup.v1");
// ensure that the key is in the right format. If not, fix the key and
// store the fixed version
const fixedKey = fixBackupKey(storedKey);
if (fixedKey) {
const [keyId] = await this.crypto.getSecretStorageKey();
await this.storeSecret("m.megolm_backup.v1", fixedKey, [keyId]);
const keys = await this.crypto.getSecretStorageKey();
await this.storeSecret("m.megolm_backup.v1", fixedKey, [keys![0]]);
}
const privKey = decodeBase64(fixedKey || storedKey);
const privKey = decodeBase64(fixedKey || storedKey!);
return this.restoreKeyBackup(
privKey, targetRoomId, targetSessionId, backupInfo, opts,
);
@ -3406,7 +3400,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*/
public setAccountData(eventType: EventType | string, content: IContent): Promise<{}> {
const path = utils.encodeUri("/user/$userId/account_data/$type", {
$userId: this.credentials.userId,
$userId: this.credentials.userId!,
$type: eventType,
});
return retryNetworkOperation(5, () => {
@ -3442,7 +3436,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
return event.getContent<T>();
}
const path = utils.encodeUri("/user/$userId/account_data/$type", {
$userId: this.credentials.userId,
$userId: this.credentials.userId!,
$type: eventType,
});
try {
@ -3506,7 +3500,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
const room = this.getRoom(roomIdOrAlias);
if (room?.hasMembershipState(this.credentials.userId, "join")) {
if (room?.hasMembershipState(this.credentials.userId!, "join")) {
return Promise.resolve(room);
}
@ -3621,7 +3615,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*/
public getRoomTags(roomId: string): Promise<ITagsResponse> {
const path = utils.encodeUri("/user/$userId/rooms/$roomId/tags", {
$userId: this.credentials.userId,
$userId: this.credentials.userId!,
$roomId: roomId,
});
return this.http.authedRequest(Method.Get, path);
@ -3636,7 +3630,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*/
public setRoomTag(roomId: string, tagName: string, metadata: ITagMetadata): Promise<{}> {
const path = utils.encodeUri("/user/$userId/rooms/$roomId/tags/$tag", {
$userId: this.credentials.userId,
$userId: this.credentials.userId!,
$roomId: roomId,
$tag: tagName,
});
@ -3651,7 +3645,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*/
public deleteRoomTag(roomId: string, tagName: string): Promise<{}> {
const path = utils.encodeUri("/user/$userId/rooms/$roomId/tags/$tag", {
$userId: this.credentials.userId,
$userId: this.credentials.userId!,
$roomId: roomId,
$tag: tagName,
});
@ -3671,7 +3665,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
content: Record<string, any>,
): Promise<{}> {
const path = utils.encodeUri("/user/$userId/rooms/$roomId/account_data/$type", {
$userId: this.credentials.userId,
$userId: this.credentials.userId!,
$roomId: roomId,
$type: eventType,
});
@ -3734,8 +3728,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
roomId: string,
beaconInfoContent: MBeaconInfoEventContent,
) {
const userId = this.getUserId();
return this.sendStateEvent(roomId, M_BEACON_INFO.name, beaconInfoContent, userId);
return this.sendStateEvent(roomId, M_BEACON_INFO.name, beaconInfoContent, this.getUserId()!);
}
/**
@ -3827,7 +3820,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}));
const room = this.getRoom(roomId);
const thread = room?.getThread(threadId);
const thread = threadId ? room?.getThread(threadId) : undefined;
if (thread) {
localEvent.setThread(thread);
}
@ -3847,8 +3840,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// this event does get sent, we have the correct event_id
const targetId = localEvent.getAssociatedId();
if (targetId?.startsWith("~")) {
const target = room.getPendingEvents().find(e => e.getId() === targetId);
target.once(MatrixEventEvent.LocalEventIdReplaced, () => {
const target = room?.getPendingEvents().find(e => e.getId() === targetId);
target?.once(MatrixEventEvent.LocalEventIdReplaced, () => {
localEvent.updateAssociatedId(target.getId());
});
}
@ -3879,13 +3872,13 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @returns {Promise} returns a promise which resolves with the result of the send request
* @private
*/
private encryptAndSendEvent(room: Room, event: MatrixEvent): Promise<ISendEventResponse> {
private encryptAndSendEvent(room: Room | null, event: MatrixEvent): Promise<ISendEventResponse> {
let cancelled = false;
// Add an extra Promise.resolve() to turn synchronous exceptions into promise rejections,
// so that we can handle synchronous and asynchronous exceptions with the
// same code path.
return Promise.resolve().then(() => {
const encryptionPromise = this.encryptEventIfNeeded(event, room);
const encryptionPromise = this.encryptEventIfNeeded(event, room ?? undefined);
if (!encryptionPromise) return null; // doesn't need encryption
this.pendingEventEncryption.set(event.getId(), encryptionPromise);
@ -3900,14 +3893,14 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
});
}).then(() => {
if (cancelled) return {} as ISendEventResponse;
let promise: Promise<ISendEventResponse>;
let promise: Promise<ISendEventResponse> | null = null;
if (this.scheduler) {
// if this returns a promise then the scheduler has control now and will
// resolve/reject when it is done. Internally, the scheduler will invoke
// processFn which is set to this._sendEventHttpRequest so the same code
// path is executed regardless.
promise = this.scheduler.queueEvent(event);
if (promise && this.scheduler.getQueueForEvent(event).length > 1) {
if (promise && this.scheduler.getQueueForEvent(event)!.length > 1) {
// event is processed FIFO so if the length is 2 or more we know
// this event is stuck behind an earlier event.
this.updatePendingEventStatus(room, event, EventStatus.QUEUED);
@ -3933,11 +3926,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// consistent at that point.
event.error = err;
this.updatePendingEventStatus(room, event, EventStatus.NOT_SENT);
// also put the event object on the error: the caller will need this
// to resend or cancel the event
err.event = event;
} catch (e) {
logger.error("Exception in error handler!", e.stack || err);
logger.error("Exception in error handler!", (<Error>e).stack || err);
}
if (err instanceof MatrixError) {
err.event = event;
}
throw err;
});
@ -4128,8 +4121,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// reasonably large pool of messages to parse.
let eventType: string = EventType.RoomMessage;
let sendContent: IContent = content as IContent;
const makeContentExtensible = (content: IContent = {}, recurse = true): IPartialEvent<object> => {
let newEvent: IPartialEvent<object> = null;
const makeContentExtensible = (content: IContent = {}, recurse = true): IPartialEvent<object> | undefined => {
let newEvent: IPartialEvent<object> | undefined;
if (content['msgtype'] === MsgType.Text) {
newEvent = MessageEvent.from(content['body'], content['formatted_body']).serialize();
@ -4818,7 +4811,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
const populationResults: { [roomId: string]: Error } = {};
const promises = [];
const promises: Promise<any>[] = [];
const doLeave = (roomId: string) => {
return this.leave(roomId).then(() => {
@ -4907,7 +4900,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
private membershipChange(
roomId: string,
userId: string,
userId: string | undefined,
membership: string,
reason?: string,
): Promise<{}> { // API returns an empty object
@ -5020,13 +5013,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*/
public async setPresence(opts: IPresenceOpts): Promise<void> {
const path = utils.encodeUri("/presence/$userId/status", {
$userId: this.credentials.userId,
$userId: this.credentials.userId!,
});
if (typeof opts === "string") {
opts = { presence: opts }; // legacy
}
const validStates = ["offline", "online", "unavailable"];
if (validStates.indexOf(opts.presence) === -1) {
throw new Error("Bad presence value: " + opts.presence);
@ -5086,7 +5075,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// reduce the required number of events appropriately
limit = limit - numAdded;
const prom = new Promise<Room>((resolve, reject) => {
const promise = new Promise<Room>((resolve, reject) => {
// wait for a time before doing this request
// (which may be 0 in order not to special case the code paths)
sleep(timeToWaitMs).then(() => {
@ -5114,7 +5103,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
room.oldState.paginationToken = null;
}
this.store.storeEvents(room, matrixEvents, res.end, true);
this.ongoingScrollbacks[room.roomId] = null;
delete this.ongoingScrollbacks[room.roomId];
resolve(room);
}).catch((err) => {
this.ongoingScrollbacks[room.roomId] = {
@ -5124,13 +5113,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
});
});
info = {
promise: prom,
errorTs: null,
};
info = { promise };
this.ongoingScrollbacks[room.roomId] = info;
return prom;
return promise;
}
/**
@ -5181,7 +5167,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
);
let params: Record<string, string | string[]> | undefined = undefined;
if (this.clientOpts.lazyLoadMembers) {
if (this.clientOpts?.lazyLoadMembers) {
params = { filter: JSON.stringify(Filter.LAZY_LOADING_MESSAGES_FILTER) };
}
@ -5343,7 +5329,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
let filter: IRoomEventFilter | null = null;
if (this.clientOpts.lazyLoadMembers) {
if (this.clientOpts?.lazyLoadMembers) {
// create a shallow copy of LAZY_LOADING_MESSAGES_FILTER,
// so the timelineFilter doesn't get written into it below
filter = Object.assign({}, Filter.LAZY_LOADING_MESSAGES_FILTER);
@ -5393,7 +5379,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
let filter: IRoomEventFilter | null = null;
if (this.clientOpts.lazyLoadMembers) {
if (this.clientOpts?.lazyLoadMembers) {
// create a shallow copy of LAZY_LOADING_MESSAGES_FILTER,
// so the timelineFilter doesn't get written into it below
filter = {
@ -5441,7 +5427,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*/
public paginateEventTimeline(eventTimeline: EventTimeline, opts: IPaginateOpts): Promise<boolean> {
const isNotifTimeline = (eventTimeline.getTimelineSet() === this.notifTimelineSet);
const room = this.getRoom(eventTimeline.getRoomId());
const room = this.getRoom(eventTimeline.getRoomId()!);
const isThreadTimeline = eventTimeline.getTimelineSet().isThreadTimeline;
// TODO: we should implement a backoff (as per scrollback()) to deal more
@ -5519,7 +5505,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
promise = this.createThreadListMessagesRequest(
eventTimeline.getRoomId(),
eventTimeline.getRoomId()!,
token,
opts.limit,
dir,
@ -5555,7 +5541,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
promise = this.createMessagesRequest(
eventTimeline.getRoomId(),
eventTimeline.getRoomId()!,
token,
opts.limit,
dir,
@ -5612,7 +5598,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// know about /notifications, so we have no choice but to start paginating
// from the current point in time. This may well overlap with historical
// notifs which are then inserted into the timeline by /sync responses.
this.notifTimelineSet.resetLiveTimeline('end', null);
this.notifTimelineSet.resetLiveTimeline('end');
// we could try to paginate a single event at this point in order to get
// a more valid pagination token, but it just ends up with an out of order
@ -5634,9 +5620,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
public peekInRoom(roomId: string): Promise<Room> {
if (this.peekSync) {
this.peekSync.stopPeeking();
}
this.peekSync?.stopPeeking();
this.peekSync = new SyncApi(this, this.clientOpts);
return this.peekSync.peek(roomId);
}
@ -5943,8 +5927,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @return {Promise} Resolves: result object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
public setRoomMutePushRule(scope: string, roomId: string, mute: boolean): Promise<void> | void {
let promise: Promise<unknown>;
public setRoomMutePushRule(scope: string, roomId: string, mute: boolean): Promise<void> | undefined {
let promise: Promise<unknown> | undefined;
let hasDontNotifyRule = false;
// Get the existing room-kind push rule if any
@ -5986,7 +5970,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
if (promise) {
return new Promise<void>((resolve, reject) => {
// Update this.pushRules when the operation completes
promise.then(() => {
promise!.then(() => {
this.getPushRules().then((result) => {
this.pushRules = result;
resolve();
@ -6173,7 +6157,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
logger.log("Marking success of sync left room request");
this.syncedLeftRooms = true; // flip the bit on success
}).finally(() => {
this.syncLeftRoomsPromise = null; // cleanup ongoing request state
this.syncLeftRoomsPromise = undefined; // cleanup ongoing request state
});
return this.syncLeftRoomsPromise;
@ -6235,13 +6219,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*/
public async getOrCreateFilter(filterName: string, filter: Filter): Promise<string> {
const filterId = this.store.getFilterIdByName(filterName);
let existingId = undefined;
let existingId: string | undefined;
if (filterId) {
// check that the existing filter matches our expectations
try {
const existingFilter =
await this.getFilter(this.credentials.userId, filterId, true);
const existingFilter = await this.getFilter(this.credentials.userId!, filterId, true);
if (existingFilter) {
const oldDef = existingFilter.getDefinition();
const newDef = filter.getDefinition();
@ -6260,7 +6243,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// name: "M_UNKNOWN",
// message: "No row found",
// }
if (error.errcode !== "M_UNKNOWN" && error.errcode !== "M_NOT_FOUND") {
if ((<MatrixError>error).errcode !== "M_UNKNOWN" && (<MatrixError>error).errcode !== "M_NOT_FOUND") {
throw error;
}
}
@ -6413,7 +6396,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
public isSynapseAdministrator(): Promise<boolean> {
const path = utils.encodeUri(
"/_synapse/admin/v1/users/$userId/admin",
{ $userId: this.getUserId() },
{ $userId: this.getUserId()! },
);
return this.http.authedRequest(
Method.Get, path, undefined, undefined, { prefix: '' },
@ -6452,7 +6435,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
private async fetchClientWellKnown(): Promise<void> {
// `getRawClientConfig` does not throw or reject on network errors, instead
// it absorbs errors and returns `{}`.
this.clientWellKnownPromise = AutoDiscovery.getRawClientConfig(this.getDomain());
this.clientWellKnownPromise = AutoDiscovery.getRawClientConfig(this.getDomain() ?? undefined);
this.clientWellKnown = await this.clientWellKnownPromise;
this.emit(ClientEvent.ClientWellKnown, this.clientWellKnown);
}
@ -6474,7 +6457,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*/
public storeClientOptions(): Promise<void> { // XXX: Intended private, used in code
const primTypes = ["boolean", "string", "number"];
const serializableOpts = Object.entries(this.clientOpts)
const serializableOpts = Object.entries(this.clientOpts!)
.filter(([key, value]) => {
return primTypes.includes(typeof value);
})
@ -6530,7 +6513,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
},
).catch((e: Error) => {
// Need to unset this if it fails, otherwise we'll never retry
this.serverVersionsPromise = null;
this.serverVersionsPromise = undefined;
// but rethrow the exception to anything that was waiting
throw e;
});
@ -6692,7 +6675,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @return {boolean} Whether or not members are lazy loaded by this client
*/
public hasLazyLoadMembersEnabled(): boolean {
return !!this.clientOpts.lazyLoadMembers;
return !!this.clientOpts?.lazyLoadMembers;
}
/**
@ -6712,7 +6695,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* Get the callback set via `setCanResetTimelineCallback`.
* @return {?Function} The callback or null
*/
public getCanResetTimelineCallback(): ResetTimelineCallback {
public getCanResetTimelineCallback(): ResetTimelineCallback | undefined {
return this.canResetTimelineCallback;
}
@ -6734,7 +6717,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
eventType?: EventType | string | null,
opts: IRelationsRequestOpts = { dir: Direction.Backward },
): Promise<{
originalEvent: MatrixEvent;
originalEvent?: MatrixEvent;
events: MatrixEvent[];
nextBatch?: string;
prevBatch?: string;
@ -6775,7 +6758,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* triggering a user interaction.
* @return {object}
*/
public getCrossSigningCacheCallbacks(): ICacheCallbacks {
public getCrossSigningCacheCallbacks(): ICacheCallbacks | undefined {
// XXX: Private member access
return this.crypto?.crossSigningInfo.getCacheCallbacks();
}
@ -8032,7 +8015,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
public getPushRules(): Promise<IPushRules> {
return this.http.authedRequest(Method.Get, "/pushrules/").then((rules: IPushRules) => {
return this.http.authedRequest<IPushRules>(Method.Get, "/pushrules/").then((rules: IPushRules) => {
return PushProcessor.rewriteDefaultRules(rules);
});
}
@ -8455,8 +8438,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*/
public getIdentityHashDetails(identityAccessToken: string): Promise<any> { // TODO: Types
return this.http.idServerRequest(
Method.Get, "/hash_details",
null, IdentityPrefix.V2, identityAccessToken,
Method.Get,
"/hash_details",
undefined,
IdentityPrefix.V2,
identityAccessToken,
);
}
@ -8530,7 +8516,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
if (!response || !response['mappings']) return []; // no results
const foundAddresses = [/* {address: "plain@example.org", mxid} */];
const foundAddresses: { address: string, mxid: string }[] = [];
for (const hashed of Object.keys(response['mappings'])) {
const mxid = response['mappings'][hashed];
const plainAddress = localMapping[hashed];
@ -8560,14 +8546,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
public async lookupThreePid(
medium: string,
address: string,
identityAccessToken?: string,
identityAccessToken: string,
): Promise<any> { // TODO: Types
// Note: we're using the V2 API by calling this function, but our
// function contract requires a V1 response. We therefore have to
// convert it manually.
const response = await this.identityHashedLookup(
[[address, medium]], identityAccessToken,
);
const response = await this.identityHashedLookup([[address, medium]], identityAccessToken);
const result = response.find(p => p.address === address);
if (!result) {
return {};
@ -8608,7 +8592,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
query.map(p => [p[1], p[0]]), identityAccessToken,
);
const v1results = [];
const v1results: [medium: string, address: string, mxid: string][] = [];
for (const mapping of response) {
const originalQuery = query.find(p => p[1] === mapping.address);
if (!originalQuery) {
@ -8800,7 +8784,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
$roomId: roomId,
});
const queryParams: Record<string, string | string[]> = {
const queryParams: QueryDict = {
suggested_only: String(suggestedOnly),
max_depth: maxDepth?.toString(),
from: fromToken,

View File

@ -114,10 +114,10 @@ export class CrossSigningInfo {
}
if (expectedPubkey === undefined) {
expectedPubkey = this.getId(type);
expectedPubkey = this.getId(type)!;
}
function validateKey(key: Uint8Array): [string, PkSigning] {
function validateKey(key: Uint8Array | null): [string, PkSigning] | undefined {
if (!key) return;
const signing = new global.Olm.PkSigning();
const gotPubkey = signing.init_with_seed(key);
@ -127,7 +127,7 @@ export class CrossSigningInfo {
signing.free();
}
let privkey;
let privkey: Uint8Array | null = null;
if (this.cacheCallbacks.getCrossSigningKeyCache && shouldCache) {
privkey = await this.cacheCallbacks.getCrossSigningKeyCache(type, expectedPubkey);
}
@ -141,7 +141,7 @@ export class CrossSigningInfo {
const result = validateKey(privkey);
if (result) {
if (this.cacheCallbacks.storeCrossSigningKeyCache && shouldCache) {
await this.cacheCallbacks.storeCrossSigningKeyCache(type, privkey);
await this.cacheCallbacks.storeCrossSigningKeyCache(type, privkey!);
}
return result;
}
@ -169,7 +169,7 @@ export class CrossSigningInfo {
* with, or null if it is not present or not encrypted with a trusted
* key
*/
public async isStoredInSecretStorage(secretStorage: SecretStorage): Promise<Record<string, object>> {
public async isStoredInSecretStorage(secretStorage: SecretStorage): Promise<Record<string, object> | null> {
// 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
@ -213,7 +213,7 @@ export class CrossSigningInfo {
* @param {SecretStorage} secretStorage The secret store using account data
* @return {Uint8Array} The private key
*/
public static async getFromSecretStorage(type: string, secretStorage: SecretStorage): Promise<Uint8Array> {
public static async getFromSecretStorage(type: string, secretStorage: SecretStorage): Promise<Uint8Array | null> {
const encodedKey = await secretStorage.get(`m.cross_signing.${type}`);
if (!encodedKey) {
return null;
@ -233,7 +233,7 @@ export class CrossSigningInfo {
if (!cacheCallbacks) return false;
const types = type ? [type] : ["master", "self_signing", "user_signing"];
for (const t of types) {
if (!await cacheCallbacks.getCrossSigningKeyCache(t)) {
if (!await cacheCallbacks.getCrossSigningKeyCache?.(t)) {
return false;
}
}
@ -250,7 +250,7 @@ export class CrossSigningInfo {
const cacheCallbacks = this.cacheCallbacks;
if (!cacheCallbacks) return keys;
for (const type of ["master", "self_signing", "user_signing"]) {
const privKey = await cacheCallbacks.getCrossSigningKeyCache(type);
const privKey = await cacheCallbacks.getCrossSigningKeyCache?.(type);
if (!privKey) {
continue;
}
@ -268,7 +268,7 @@ export class CrossSigningInfo {
*
* @return {string} the ID
*/
public getId(type = "master"): string {
public getId(type = "master"): string | null {
if (!this.keys[type]) return null;
const keyInfo = this.keys[type];
return publicKeyFromKeyInfo(keyInfo);
@ -469,7 +469,7 @@ export class CrossSigningInfo {
}
}
public async signUser(key: CrossSigningInfo): Promise<ICrossSigningKey> {
public async signUser(key: CrossSigningInfo): Promise<ICrossSigningKey | undefined> {
if (!this.keys.user_signing) {
logger.info("No user signing key: not signing user");
return;
@ -477,7 +477,7 @@ export class CrossSigningInfo {
return this.signObject(key.keys.master, "user_signing");
}
public async signDevice(userId: string, device: DeviceInfo): Promise<ISignedKey> {
public async signDevice(userId: string, device: DeviceInfo): Promise<ISignedKey | undefined> {
if (userId !== this.userId) {
throw new Error(
`Trying to sign ${userId}'s device; can only sign our own device`,
@ -521,9 +521,9 @@ export class CrossSigningInfo {
return new UserTrustLevel(false, false, userCrossSigning.firstUse);
}
let userTrusted;
let userTrusted: boolean;
const userMaster = userCrossSigning.keys.master;
const uskId = this.getId('user_signing');
const uskId = this.getId('user_signing')!;
try {
pkVerify(userMaster, uskId, this.userId);
userTrusted = true;
@ -567,7 +567,7 @@ export class CrossSigningInfo {
const deviceObj = deviceToObject(device, userCrossSigning.userId);
try {
// if we can verify the user's SSK from their master key...
pkVerify(userSSK, userCrossSigning.getId(), userCrossSigning.userId);
pkVerify(userSSK, userCrossSigning.getId()!, userCrossSigning.userId);
// ...and this device's key from their SSK...
pkVerify(deviceObj, publicKeyFromKeyInfo(userSSK), userCrossSigning.userId);
// ...then we trust this device as much as far as we trust the user
@ -752,7 +752,7 @@ export type KeysDuringVerification = [[string, PkSigning], [string, PkSigning],
* @param {string} userId The user ID being verified
* @param {string} deviceId The device ID being verified
*/
export function requestKeysDuringVerification(
export async function requestKeysDuringVerification(
baseApis: MatrixClient,
userId: string,
deviceId: string,
@ -766,7 +766,7 @@ export function requestKeysDuringVerification(
// it. We return here in order to test.
return new Promise<KeysDuringVerification | void>((resolve, reject) => {
const client = baseApis;
const original = client.crypto.crossSigningInfo;
const original = client.crypto!.crossSigningInfo;
// We already have all of the infrastructure we need to validate and
// cache cross-signing keys, so instead of replicating that, here we set
@ -801,7 +801,7 @@ export function requestKeysDuringVerification(
// also request and cache the key backup key
const backupKeyPromise = (async () => {
const cachedKey = await client.crypto.getSessionBackupPrivateKey();
const cachedKey = await client.crypto!.getSessionBackupPrivateKey();
if (!cachedKey) {
logger.info("No cached backup key found. Requesting...");
const secretReq = client.requestSecret(
@ -811,13 +811,13 @@ export function requestKeysDuringVerification(
logger.info("Got key backup key, decoding...");
const decodedKey = decodeBase64(base64Key);
logger.info("Decoded backup key, storing...");
await client.crypto.storeSessionBackupPrivateKey(
await client.crypto!.storeSessionBackupPrivateKey(
Uint8Array.from(decodedKey),
);
logger.info("Backup key stored. Starting backup restore...");
const backupInfo = await client.getKeyBackupVersion();
// no need to await for this - just let it go in the bg
client.restoreKeyBackupWithCache(undefined, undefined, backupInfo).then(() => {
client.restoreKeyBackupWithCache(undefined, undefined, backupInfo!).then(() => {
logger.info("Backup restored.");
});
}

View File

@ -26,7 +26,7 @@ import { CrossSigningInfo, ICrossSigningInfo } from './CrossSigning';
import * as olmlib from './olmlib';
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
import { chunkPromises, defer, IDeferred, sleep } from '../utils';
import { IDownloadKeyResult, MatrixClient } from "../client";
import { DeviceKeys, IDownloadKeyResult, Keys, MatrixClient, SigningKeys } from "../client";
import { OlmDevice } from "./OlmDevice";
import { CryptoStore } from "./store/base";
import { TypedEventEmitter } from "../models/typed-event-emitter";
@ -81,24 +81,24 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
// The 'next_batch' sync token at the point the data was written,
// ie. a token representing the point immediately after the
// moment represented by the snapshot in the db.
private syncToken: string = null;
private syncToken: string | null = null;
private keyDownloadsInProgressByUser: { [userId: string]: Promise<void> } = {};
private keyDownloadsInProgressByUser = new Map<string, Promise<void>>;
// Set whenever changes are made other than setting the sync token
private dirty = false;
// Promise resolved when device data is saved
private savePromise: Promise<boolean> = null;
private savePromise: Promise<boolean> | null = null;
// Function that resolves the save promise
private resolveSavePromise: (saved: boolean) => void = null;
private resolveSavePromise: ((saved: boolean) => void) | null = null;
// The time the save is scheduled for
private savePromiseTime: number = null;
private savePromiseTime: number | null = null;
// The timer used to delay the save
private saveTimer: ReturnType<typeof setTimeout> = null;
private saveTimer: ReturnType<typeof setTimeout> | null = null;
// True if we have fetched data from the server or loaded a non-empty
// set of device data from the store
private hasFetched: boolean = null;
private hasFetched: boolean | null = null;
private readonly serialiser: DeviceListUpdateSerialiser;
@ -127,7 +127,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
deviceData.crossSigningInfo || {} : {};
this.deviceTrackingStatus = deviceData ?
deviceData.trackingStatus : {};
this.syncToken = deviceData ? deviceData.syncToken : null;
this.syncToken = deviceData?.syncToken ?? null;
this.userByIdentityKey = {};
for (const user of Object.keys(this.devices)) {
const userDevices = this.devices[user];
@ -181,7 +181,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
if (this.savePromiseTime && targetTime < this.savePromiseTime) {
// There's a save scheduled but for after we would like: cancel
// it & schedule one for the time we want
clearTimeout(this.saveTimer);
clearTimeout(this.saveTimer!);
this.saveTimer = null;
this.savePromiseTime = null;
// (but keep the save promise since whatever called save before
@ -216,13 +216,13 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
devices: this.devices,
crossSigningInfo: this.crossSigningInfo,
trackingStatus: this.deviceTrackingStatus,
syncToken: this.syncToken,
syncToken: this.syncToken ?? undefined,
}, txn);
},
).then(() => {
// The device list is considered dirty until the write completes.
this.dirty = false;
resolveSavePromise(true);
resolveSavePromise?.(true);
}, err => {
logger.error('Failed to save device tracking data', this.syncToken);
logger.error(err);
@ -230,7 +230,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
}, delay);
}
return savePromise;
return savePromise!;
}
/**
@ -238,7 +238,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
*
* @return {string} The sync token
*/
public getSyncToken(): string {
public getSyncToken(): string | null {
return this.syncToken;
}
@ -272,14 +272,14 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
userIds.forEach((u) => {
const trackingStatus = this.deviceTrackingStatus[u];
if (this.keyDownloadsInProgressByUser[u]) {
if (this.keyDownloadsInProgressByUser.has(u)) {
// already a key download in progress/queued for this user; its results
// will be good enough for us.
logger.log(
`downloadKeys: already have a download in progress for ` +
`${u}: awaiting its result`,
);
promises.push(this.keyDownloadsInProgressByUser[u]);
promises.push(this.keyDownloadsInProgressByUser.get(u)!);
} else if (forceDownload || trackingStatus != TrackingStatus.UpToDate) {
usersToDownload.push(u);
}
@ -341,7 +341,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
if (!devs) {
return null;
}
const res = [];
const res: DeviceInfo[] = [];
for (const deviceId in devs) {
if (devs.hasOwnProperty(deviceId)) {
res.push(DeviceInfo.fromStorage(devs[deviceId], deviceId));
@ -362,7 +362,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
return this.devices[userId];
}
public getStoredCrossSigningForUser(userId: string): CrossSigningInfo {
public getStoredCrossSigningForUser(userId: string): CrossSigningInfo | null {
if (!this.crossSigningInfo[userId]) return null;
return CrossSigningInfo.fromStorage(this.crossSigningInfo[userId], userId);
@ -382,9 +382,9 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
* @return {module:crypto/deviceinfo?} device, or undefined
* if we don't know about this device
*/
public getStoredDevice(userId: string, deviceId: string): DeviceInfo {
public getStoredDevice(userId: string, deviceId: string): DeviceInfo | undefined {
const devs = this.devices[userId];
if (!devs || !devs[deviceId]) {
if (!devs?.[deviceId]) {
return undefined;
}
return DeviceInfo.fromStorage(devs[deviceId], deviceId);
@ -398,11 +398,8 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
*
* @return {string} user ID
*/
public getUserByIdentityKey(algorithm: string, senderKey: string): string {
if (
algorithm !== olmlib.OLM_ALGORITHM &&
algorithm !== olmlib.MEGOLM_ALGORITHM
) {
public getUserByIdentityKey(algorithm: string, senderKey: string): string | null {
if (algorithm !== olmlib.OLM_ALGORITHM && algorithm !== olmlib.MEGOLM_ALGORITHM) {
// we only deal in olm keys
return null;
}
@ -557,7 +554,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
public refreshOutdatedDeviceLists(): Promise<void> {
this.saveIfDirty();
const usersToDownload = [];
const usersToDownload: string[] = [];
for (const userId of Object.keys(this.deviceTrackingStatus)) {
const stat = this.deviceTrackingStatus[userId];
if (stat == TrackingStatus.PendingDownload) {
@ -617,7 +614,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
return Promise.resolve();
}
const prom = this.serialiser.updateDevicesForUsers(users, this.syncToken).then(() => {
const prom = this.serialiser.updateDevicesForUsers(users, this.syncToken!).then(() => {
finished(true);
}, (e) => {
logger.error(
@ -628,7 +625,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
});
users.forEach((u) => {
this.keyDownloadsInProgressByUser[u] = prom;
this.keyDownloadsInProgressByUser.set(u, prom);
const stat = this.deviceTrackingStatus[u];
if (stat == TrackingStatus.PendingDownload) {
this.deviceTrackingStatus[u] = TrackingStatus.DownloadInProgress;
@ -643,11 +640,11 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
// we may have queued up another download request for this user
// since we started this request. If that happens, we should
// ignore the completion of the first one.
if (this.keyDownloadsInProgressByUser[u] !== prom) {
if (this.keyDownloadsInProgressByUser.get(u) !== prom) {
logger.log('Another update in the queue for', u, '- not marking up-to-date');
return;
}
delete this.keyDownloadsInProgressByUser[u];
this.keyDownloadsInProgressByUser.delete(u);
const stat = this.deviceTrackingStatus[u];
if (stat == TrackingStatus.DownloadInProgress) {
if (success) {
@ -687,9 +684,9 @@ class DeviceListUpdateSerialiser {
// deferred which is resolved when the queued users are downloaded.
// non-null indicates that we have users queued for download.
private queuedQueryDeferred: IDeferred<void> = null;
private queuedQueryDeferred?: IDeferred<void>;
private syncToken: string = null; // The sync token we send with the requests
private syncToken?: string; // The sync token we send with the requests
/*
* @param {object} baseApis Base API object
@ -748,7 +745,7 @@ class DeviceListUpdateSerialiser {
const downloadUsers = Object.keys(this.keyDownloadsQueuedByUser);
this.keyDownloadsQueuedByUser = {};
const deferred = this.queuedQueryDeferred;
this.queuedQueryDeferred = null;
this.queuedQueryDeferred = undefined;
logger.log('Starting key download for', downloadUsers);
this.downloadInProgress = true;
@ -785,9 +782,9 @@ class DeviceListUpdateSerialiser {
try {
await this.processQueryResponseForUser(
userId, dk[userId], {
master: masterKeys[userId],
self_signing: ssks[userId],
user_signing: usks[userId],
master: masterKeys?.[userId],
self_signing: ssks?.[userId],
user_signing: usks?.[userId],
},
);
} catch (e) {
@ -800,7 +797,7 @@ class DeviceListUpdateSerialiser {
logger.log('Completed key download for ' + downloadUsers);
this.downloadInProgress = false;
deferred.resolve();
deferred?.resolve();
// if we have queued users, fire off another request.
if (this.queuedQueryDeferred) {
@ -809,19 +806,19 @@ class DeviceListUpdateSerialiser {
}, (e) => {
logger.warn('Error downloading keys for ' + downloadUsers + ':', e);
this.downloadInProgress = false;
deferred.reject(e);
deferred?.reject(e);
});
return deferred.promise;
return deferred!.promise;
}
private async processQueryResponseForUser(
userId: string,
dkResponse: IDownloadKeyResult["device_keys"]["user_id"],
dkResponse: DeviceKeys,
crossSigningResponse: {
master: IDownloadKeyResult["master_keys"]["user_id"];
self_signing: IDownloadKeyResult["master_keys"]["user_id"]; // eslint-disable-line camelcase
user_signing: IDownloadKeyResult["user_signing_keys"]["user_id"]; // eslint-disable-line camelcase
master?: Keys;
self_signing?: SigningKeys;
user_signing?: SigningKeys;
},
): Promise<void> {
logger.log('got device keys for ' + userId + ':', dkResponse);
@ -840,7 +837,7 @@ class DeviceListUpdateSerialiser {
await updateStoredDeviceKeysForUser(
this.olmDevice, userId, userStore, dkResponse || {},
this.baseApis.getUserId(), this.baseApis.deviceId,
this.baseApis.getUserId()!, this.baseApis.deviceId!,
);
// put the updates into the object that will be returned as our results

View File

@ -15,9 +15,8 @@ limitations under the License.
*/
import { Account, InboundGroupSession, OutboundGroupSession, Session, Utility } from "@matrix-org/olm";
import { Logger } from "loglevel";
import { logger } from '../logger';
import { logger, PrefixedLogger } from '../logger';
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
import * as algorithms from './algorithms';
import { CryptoStore, IProblem, ISessionInfo, IWithheld } from "./store/base";
@ -121,9 +120,9 @@ interface IInboundGroupSessionKey {
chain_index: number;
key: string;
forwarding_curve25519_key_chain: string[];
sender_claimed_ed25519_key: string;
sender_claimed_ed25519_key: string | null;
shared_history: boolean;
untrusted: boolean;
untrusted?: boolean;
}
/* eslint-enable camelcase */
@ -145,9 +144,9 @@ export class OlmDevice {
public pickleKey = "DEFAULT_KEY"; // set by consumers
// don't know these until we load the account from storage in init()
public deviceCurve25519Key: string = null;
public deviceEd25519Key: string = null;
private maxOneTimeKeys: number = null;
public deviceCurve25519Key: string | null = null;
public deviceEd25519Key: string | null = null;
private maxOneTimeKeys: number | null = null;
// we don't bother stashing outboundgroupsessions in the cryptoStore -
// instead we keep them here.
@ -266,8 +265,8 @@ export class OlmDevice {
lastReceivedMessageTs: session.lastReceivedMessageTs,
};
this.cryptoStore.storeEndToEndSession(
deviceKey,
sessionId,
deviceKey!,
sessionId!,
sessionInfo,
txn,
);
@ -358,7 +357,7 @@ export class OlmDevice {
// is not exactly the same thing you get in method _getSession
// see documentation of IndexedDBCryptoStore.getAllEndToEndSessions
this.cryptoStore.getAllEndToEndSessions(txn, (pickledSession) => {
result.sessions.push(pickledSession);
result.sessions!.push(pickledSession!);
});
},
);
@ -384,8 +383,8 @@ export class OlmDevice {
func: (unpickledSessionInfo: IUnpickledSessionInfo) => void,
): void {
this.cryptoStore.getEndToEndSession(
deviceKey, sessionId, txn, (sessionInfo: ISessionInfo) => {
this.unpickleSession(sessionInfo, func);
deviceKey, sessionId, txn, (sessionInfo: ISessionInfo | null) => {
this.unpickleSession(sessionInfo!, func);
},
);
}
@ -405,7 +404,7 @@ export class OlmDevice {
): void {
const session = new global.Olm.Session();
try {
session.unpickle(this.pickleKey, sessionInfo.session);
session.unpickle(this.pickleKey, sessionInfo.session!);
const unpickledSessInfo: IUnpickledSessionInfo = Object.assign({}, sessionInfo, { session });
func(unpickledSessInfo);
@ -491,7 +490,7 @@ export class OlmDevice {
* @return {number} number of keys
*/
public maxNumberOfOneTimeKeys(): number {
return this.maxOneTimeKeys;
return this.maxOneTimeKeys ?? -1;
}
/**
@ -554,7 +553,7 @@ export class OlmDevice {
});
},
);
return result;
return result!;
}
public async forgetOldFallbackKey(): Promise<void> {
@ -607,7 +606,7 @@ export class OlmDevice {
},
logger.withPrefix("[createOutboundSession]"),
);
return newSessionId;
return newSessionId!;
}
/**
@ -668,7 +667,7 @@ export class OlmDevice {
logger.withPrefix("[createInboundSession]"),
);
return result;
return result!;
}
/**
@ -703,7 +702,7 @@ export class OlmDevice {
log,
);
return sessionIds;
return sessionIds!;
}
/**
@ -714,13 +713,13 @@ export class OlmDevice {
* @param {boolean} nowait Don't wait for an in-progress session to complete.
* This should only be set to true of the calling function is the function
* that marked the session as being in-progress.
* @param {Logger} [log] A possibly customised log
* @param {PrefixedLogger} [log] A possibly customised log
* @return {Promise<?string>} session id, or null if no established session
*/
public async getSessionIdForDevice(
theirDeviceIdentityKey: string,
nowait = false,
log?: Logger,
log?: PrefixedLogger,
): Promise<string | null> {
const sessionInfos = await this.getSessionInfoForDevice(theirDeviceIdentityKey, nowait, log);
@ -780,7 +779,11 @@ export class OlmDevice {
// return an empty result
}
}
const info = [];
const info: {
lastReceivedMessageTs: number;
hasReceivedMessage: boolean;
sessionId: string;
}[] = [];
await this.cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_SESSIONS],
@ -790,9 +793,9 @@ export class OlmDevice {
for (const sessionId of sessionIds) {
this.unpickleSession(sessions[sessionId], (sessInfo: IUnpickledSessionInfo) => {
info.push({
lastReceivedMessageTs: sessInfo.lastReceivedMessageTs,
lastReceivedMessageTs: sessInfo.lastReceivedMessageTs!,
hasReceivedMessage: sessInfo.session.has_received_message(),
sessionId: sessionId,
sessionId,
});
});
}
@ -801,7 +804,7 @@ export class OlmDevice {
log,
);
return info;
return info!;
}
/**
@ -916,7 +919,7 @@ export class OlmDevice {
await this.cryptoStore.storeEndToEndSessionProblem(deviceKey, type, fixed);
}
public sessionMayHaveProblems(deviceKey: string, timestamp: number): Promise<IProblem> {
public sessionMayHaveProblems(deviceKey: string, timestamp: number): Promise<IProblem | null> {
return this.cryptoStore.getEndToEndSessionProblem(deviceKey, timestamp);
}
@ -1056,10 +1059,14 @@ export class OlmDevice {
senderKey: string,
sessionId: string,
txn: unknown,
func: (session: InboundGroupSession, data: InboundGroupSessionData, withheld?: IWithheld) => void,
func: (
session: InboundGroupSession | null,
data: InboundGroupSessionData | null,
withheld: IWithheld | null,
) => void,
): void {
this.cryptoStore.getEndToEndInboundGroupSession(
senderKey, sessionId, txn, (sessionData: InboundGroupSessionData, withheld: IWithheld | null) => {
senderKey, sessionId, txn, (sessionData: InboundGroupSessionData | null, withheld: IWithheld | null) => {
if (sessionData === null) {
func(null, null, withheld);
return;
@ -1112,94 +1119,94 @@ export class OlmDevice {
IndexedDBCryptoStore.STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS,
], (txn) => {
/* if we already have this session, consider updating it */
this.getInboundGroupSession(
roomId, senderKey, sessionId, txn,
(existingSession: InboundGroupSession, existingSessionData: InboundGroupSessionData) => {
// new session.
const session = new global.Olm.InboundGroupSession();
try {
if (exportFormat) {
session.import_session(sessionKey);
} else {
session.create(sessionKey);
}
if (sessionId != session.session_id()) {
throw new Error(
"Mismatched group session ID from senderKey: " +
senderKey,
);
}
if (existingSession) {
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}`);
return;
}
if (existingSession.first_known_index() < session.first_known_index()) {
// We want to upgrade the existing session's trust,
// but we can't just use the new session because we'll
// lose the lower index. Check that the sessions connect
// properly, and then manually set the existing session
// as trusted.
if (
existingSession.export_session(session.first_known_index())
=== session.export_session(session.first_known_index())
) {
logger.info(
"Upgrading trust of existing megolm session " +
sessionId + " based on newly-received trusted session",
);
existingSessionData.untrusted = false;
this.cryptoStore.storeEndToEndInboundGroupSession(
senderKey, sessionId, existingSessionData, txn,
);
} else {
logger.warn(
"Newly-received megolm session " + sessionId +
" does not match existing session! Keeping existing session",
);
}
return;
}
// If the sessions have the same index, go ahead and store the new trusted one.
}
}
logger.info(
"Storing megolm session " + senderKey + "/" + sessionId +
" with first index " + session.first_known_index(),
);
const sessionData = Object.assign({}, extraSessionData, {
room_id: roomId,
session: session.pickle(this.pickleKey),
keysClaimed: keysClaimed,
forwardingCurve25519KeyChain: forwardingCurve25519KeyChain,
});
this.cryptoStore.storeEndToEndInboundGroupSession(
senderKey, sessionId, sessionData, txn,
);
if (!existingSession && extraSessionData.sharedHistory) {
this.cryptoStore.addSharedHistoryInboundGroupSession(
roomId, senderKey, sessionId, txn,
);
}
} finally {
session.free();
this.getInboundGroupSession(roomId, senderKey, sessionId, txn, (
existingSession: InboundGroupSession | null,
existingSessionData: InboundGroupSessionData | null,
) => {
// new session.
const session = new global.Olm.InboundGroupSession();
try {
if (exportFormat) {
session.import_session(sessionKey);
} else {
session.create(sessionKey);
}
},
);
if (sessionId != session.session_id()) {
throw new Error(
"Mismatched group session ID from senderKey: " +
senderKey,
);
}
if (existingSession) {
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}`);
return;
}
if (existingSession.first_known_index() < session.first_known_index()) {
// We want to upgrade the existing session's trust,
// but we can't just use the new session because we'll
// lose the lower index. Check that the sessions connect
// properly, and then manually set the existing session
// as trusted.
if (
existingSession.export_session(session.first_known_index())
=== session.export_session(session.first_known_index())
) {
logger.info(
"Upgrading trust of existing megolm session " +
sessionId + " based on newly-received trusted session",
);
existingSessionData!.untrusted = false;
this.cryptoStore.storeEndToEndInboundGroupSession(
senderKey, sessionId, existingSessionData!, txn,
);
} else {
logger.warn(
"Newly-received megolm session " + sessionId +
" does not match existing session! Keeping existing session",
);
}
return;
}
// If the sessions have the same index, go ahead and store the new trusted one.
}
}
logger.info(
"Storing megolm session " + senderKey + "/" + sessionId +
" with first index " + session.first_known_index(),
);
const sessionData = Object.assign({}, extraSessionData, {
room_id: roomId,
session: session.pickle(this.pickleKey),
keysClaimed: keysClaimed,
forwardingCurve25519KeyChain: forwardingCurve25519KeyChain,
});
this.cryptoStore.storeEndToEndInboundGroupSession(
senderKey, sessionId, sessionData, txn,
);
if (!existingSession && extraSessionData.sharedHistory) {
this.cryptoStore.addSharedHistoryInboundGroupSession(
roomId, senderKey, sessionId, txn,
);
}
} finally {
session.free();
}
});
},
logger.withPrefix("[addInboundGroupSession]"),
);
@ -1261,7 +1268,7 @@ export class OlmDevice {
eventId: string,
timestamp: number,
): Promise<IDecryptedGroupMessage | null> {
let result: IDecryptedGroupMessage;
let result: IDecryptedGroupMessage | null = null;
// when the localstorage crypto store is used as an indexeddb backend,
// exceptions thrown from within the inner function are not passed through
// to the top level, so we store exceptions in a variable and raise them at
@ -1275,7 +1282,7 @@ export class OlmDevice {
], (txn) => {
this.getInboundGroupSession(
roomId, senderKey, sessionId, txn, (session, sessionData, withheld) => {
if (session === null) {
if (session === null || sessionData === null) {
if (withheld) {
error = new algorithms.DecryptionError(
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
@ -1292,7 +1299,7 @@ export class OlmDevice {
try {
res = session.decrypt(body);
} catch (e) {
if (e && e.message === 'OLM.UNKNOWN_MESSAGE_INDEX' && withheld) {
if ((<Error>e)?.message === 'OLM.UNKNOWN_MESSAGE_INDEX' && withheld) {
error = new algorithms.DecryptionError(
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
calculateWithheldMessage(withheld),
@ -1301,7 +1308,7 @@ export class OlmDevice {
},
);
} else {
error = e;
error = <Error>e;
}
return;
}
@ -1350,7 +1357,7 @@ export class OlmDevice {
forwardingCurve25519KeyChain: (
sessionData.forwardingCurve25519KeyChain || []
),
untrusted: sessionData.untrusted,
untrusted: !!sessionData.untrusted,
};
},
);
@ -1358,10 +1365,10 @@ export class OlmDevice {
logger.withPrefix("[decryptGroupMessage]"),
);
if (error) {
if (error!) {
throw error;
}
return result;
return result!;
}
/**
@ -1404,7 +1411,7 @@ export class OlmDevice {
logger.withPrefix("[hasInboundSessionKeys]"),
);
return result;
return result!;
}
/**
@ -1431,8 +1438,8 @@ export class OlmDevice {
senderKey: string,
sessionId: string,
chainIndex?: number,
): Promise<IInboundGroupSessionKey> {
let result: IInboundGroupSessionKey;
): Promise<IInboundGroupSessionKey | null> {
let result: IInboundGroupSessionKey | null = null;
await this.cryptoStore.doTxn(
'readonly', [
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
@ -1440,7 +1447,7 @@ export class OlmDevice {
], (txn) => {
this.getInboundGroupSession(
roomId, senderKey, sessionId, txn, (session, sessionData) => {
if (session === null) {
if (session === null || sessionData === null) {
result = null;
return;
}
@ -1520,7 +1527,7 @@ export class OlmDevice {
},
logger.withPrefix("[getSharedHistoryInboundGroupSessionsForRoom]"),
);
return result;
return result!;
}
// Utilities

View File

@ -182,7 +182,7 @@ export class SecretStorage {
* the form [keyId, keyInfo]. Otherwise, null is returned.
* XXX: why is this an array when addKey returns an object?
*/
public async getKey(keyId: string): Promise<SecretStorageKeyTuple | null> {
public async getKey(keyId?: string | null): Promise<SecretStorageKeyTuple | null> {
if (!keyId) {
keyId = await this.getDefaultKeyId();
}
@ -237,7 +237,7 @@ export class SecretStorage {
* @param {Array} keys The IDs of the keys to use to encrypt the secret
* or null/undefined to use the default key.
*/
public async store(name: string, secret: string, keys?: string[]): Promise<void> {
public async store(name: string, secret: string, keys?: string[] | null): Promise<void> {
const encrypted: Record<string, IEncryptedPayload> = {};
if (!keys) {
@ -284,7 +284,7 @@ export class SecretStorage {
*
* @return {string} the contents of the secret
*/
public async get(name: string): Promise<string> {
public async get(name: string): Promise<string | undefined> {
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer<ISecretInfo>(name);
if (!secretInfo) {
return;

View File

@ -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>) {
constructor(public readonly code: string, msg: string, details?: Record<string, string | Error>) {
super(msg);
this.code = code;
this.name = 'DecryptionError';
@ -250,7 +250,7 @@ export class DecryptionError extends Error {
}
}
function detailedStringForDecryptionError(err: DecryptionError, details?: Record<string, string>): string {
function detailedStringForDecryptionError(err: DecryptionError, details?: Record<string, string | Error>): string {
let result = err.name + '[msg: ' + err.message;
if (details) {
@ -272,7 +272,11 @@ function detailedStringForDecryptionError(err: DecryptionError, details?: Record
* @extends Error
*/
export class UnknownDeviceError extends Error {
constructor(msg: string, public readonly devices: Record<string, Record<string, object>>) {
constructor(
msg: string,
public readonly devices: Record<string, Record<string, object>>,
public event?: MatrixEvent,
) {
super(msg);
this.name = "UnknownDeviceError";
this.devices = devices;
@ -295,7 +299,7 @@ export class UnknownDeviceError extends Error {
export function registerAlgorithm(
algorithm: string,
encryptor: new (params: IParams) => EncryptionAlgorithm,
decryptor: new (params: Omit<IParams, "deviceId">) => DecryptionAlgorithm,
decryptor: new (params: DecryptionClassParams) => DecryptionAlgorithm,
): void {
ENCRYPTION_CLASSES.set(algorithm, encryptor);
DECRYPTION_CLASSES.set(algorithm, decryptor);

View File

@ -40,6 +40,7 @@ import { EventType, MsgType } from '../../@types/event';
import { IEventDecryptionResult, IMegolmSessionData, IncomingRoomKeyRequest } from "../index";
import { RoomKeyRequestState } from '../OutgoingRoomKeyRequestManager';
import { OlmGroupSessionExtraData } from "../../@types/crypto";
import { MatrixError } from "../../http-api";
// determine whether the key can be shared with invitees
export function isRoomSharedHistory(room: Room): boolean {
@ -492,13 +493,13 @@ class MegolmEncryption extends EncryptionAlgorithm {
const key = this.olmDevice.getOutboundGroupSessionKey(sessionId);
await this.olmDevice.addInboundGroupSession(
this.roomId, this.olmDevice.deviceCurve25519Key, [], sessionId,
key.key, { ed25519: this.olmDevice.deviceEd25519Key }, false,
this.roomId, this.olmDevice.deviceCurve25519Key!, [], sessionId,
key.key, { ed25519: this.olmDevice.deviceEd25519Key! }, false,
{ sharedHistory },
);
// don't wait for it to complete
this.crypto.backupManager.backupGroupSession(this.olmDevice.deviceCurve25519Key, sessionId);
this.crypto.backupManager.backupGroupSession(this.olmDevice.deviceCurve25519Key!, sessionId);
return new OutboundSessionInfo(sessionId, sharedHistory);
}
@ -929,7 +930,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
room_id: this.roomId,
session_id: session.sessionId,
algorithm: olmlib.MEGOLM_ALGORITHM,
sender_key: this.olmDevice.deviceCurve25519Key,
sender_key: this.olmDevice.deviceCurve25519Key!,
};
const userDeviceMaps = this.splitDevices(devicesByUser);
@ -1259,21 +1260,21 @@ class MegolmDecryption extends DecryptionAlgorithm {
// (fixes https://github.com/vector-im/element-web/issues/5001)
this.addEventToPendingList(event);
let res: IDecryptedGroupMessage;
let res: IDecryptedGroupMessage | null;
try {
res = await this.olmDevice.decryptGroupMessage(
event.getRoomId(), content.sender_key, content.session_id, content.ciphertext,
event.getRoomId()!, content.sender_key, content.session_id, content.ciphertext,
event.getId(), event.getTs(),
);
} catch (e) {
if (e.name === "DecryptionError") {
if ((<Error>e).name === "DecryptionError") {
// re-throw decryption errors as-is
throw e;
}
let errorCode = "OLM_DECRYPT_GROUP_MESSAGE_ERROR";
if (e && e.message === 'OLM.UNKNOWN_MESSAGE_INDEX') {
if ((<MatrixError>e)?.message === 'OLM.UNKNOWN_MESSAGE_INDEX') {
this.requestKeysForEvent(event);
errorCode = 'OLM_UNKNOWN_MESSAGE_INDEX';
@ -1363,7 +1364,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
const recipients = event.getKeyRequestRecipients(this.userId);
this.crypto.requestRoomKey({
room_id: event.getRoomId(),
room_id: event.getRoomId()!,
algorithm: wireContent.algorithm,
sender_key: wireContent.sender_key,
session_id: wireContent.session_id,
@ -1384,7 +1385,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
if (!this.pendingEvents.has(senderKey)) {
this.pendingEvents.set(senderKey, new Map<string, Set<MatrixEvent>>());
}
const senderPendingEvents = this.pendingEvents.get(senderKey);
const senderPendingEvents = this.pendingEvents.get(senderKey)!;
if (!senderPendingEvents.has(sessionId)) {
senderPendingEvents.set(sessionId, new Set());
}
@ -1410,9 +1411,9 @@ class MegolmDecryption extends DecryptionAlgorithm {
pendingEvents.delete(event);
if (pendingEvents.size === 0) {
senderPendingEvents.delete(sessionId);
senderPendingEvents!.delete(sessionId);
}
if (senderPendingEvents.size === 0) {
if (senderPendingEvents!.size === 0) {
this.pendingEvents.delete(senderKey);
}
}
@ -1424,7 +1425,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
*/
public async onRoomKeyEvent(event: MatrixEvent): Promise<void> {
const content = event.getContent<Partial<IMessage["content"]>>();
let senderKey = event.getSenderKey();
let senderKey = event.getSenderKey()!;
let forwardingKeyChain: string[] = [];
let exportFormat = false;
let keysClaimed: ReturnType<MatrixEvent["getKeysClaimed"]>;
@ -1454,7 +1455,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
olmlib.OLM_ALGORITHM,
senderKey,
);
const senderKeyUser = this.baseApis.crypto.deviceList.getUserByIdentityKey(
const senderKeyUser = this.baseApis.crypto!.deviceList.getUserByIdentityKey(
olmlib.OLM_ALGORITHM,
senderKey,
);
@ -1533,13 +1534,16 @@ class MegolmDecryption extends DecryptionAlgorithm {
await this.crypto.cryptoStore.doTxn(
'readwrite',
['parked_shared_history'],
(txn) => this.crypto.cryptoStore.addParkedSharedHistory(content.room_id, parkedData, txn),
(txn) => this.crypto.cryptoStore.addParkedSharedHistory(content.room_id!, parkedData, txn),
logger.withPrefix("[addParkedSharedHistory]"),
);
return;
}
const sendingDevice = this.crypto.deviceList.getDeviceByIdentityKey(olmlib.OLM_ALGORITHM, senderKey);
const sendingDevice = this.crypto.deviceList.getDeviceByIdentityKey(
olmlib.OLM_ALGORITHM,
senderKey,
) ?? undefined;
const deviceTrust = this.crypto.checkDeviceInfoTrust(event.getSender(), sendingDevice);
if (fromUs && !deviceTrust.isVerified()) {
@ -1698,7 +1702,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
public shareKeysWithDevice(keyRequest: IncomingRoomKeyRequest): void {
const userId = keyRequest.userId;
const deviceId = keyRequest.deviceId;
const deviceInfo = this.crypto.getStoredDevice(userId, deviceId);
const deviceInfo = this.crypto.getStoredDevice(userId, deviceId)!;
const body = keyRequest.requestBody;
this.olmlib.ensureOlmSessionsForDevices(
@ -1739,7 +1743,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
this.olmDevice,
userId,
deviceInfo,
payload,
payload!,
).then(() => {
const contentMap = {
[userId]: {
@ -1766,12 +1770,12 @@ class MegolmDecryption extends DecryptionAlgorithm {
"algorithm": olmlib.MEGOLM_ALGORITHM,
"room_id": roomId,
"sender_key": senderKey,
"sender_claimed_ed25519_key": key.sender_claimed_ed25519_key,
"sender_claimed_ed25519_key": key!.sender_claimed_ed25519_key!,
"session_id": sessionId,
"session_key": key.key,
"chain_index": key.chain_index,
"forwarding_curve25519_key_chain": key.forwarding_curve25519_key_chain,
"org.matrix.msc3061.shared_history": key.shared_history || false,
"session_key": key!.key,
"chain_index": key!.chain_index,
"forwarding_curve25519_key_chain": key!.forwarding_curve25519_key_chain,
"org.matrix.msc3061.shared_history": key!.shared_history || false,
},
};
}
@ -1901,7 +1905,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
for (const deviceInfo of devices) {
const encryptedContent: IEncryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this.olmDevice.deviceCurve25519Key,
sender_key: this.olmDevice.deviceCurve25519Key!,
ciphertext: {},
};
contentMap[userId][deviceInfo.deviceId] = encryptedContent;

View File

@ -180,14 +180,14 @@ class OlmDecryption extends DecryptionAlgorithm {
);
}
if (!(this.olmDevice.deviceCurve25519Key in ciphertext)) {
if (!(this.olmDevice.deviceCurve25519Key! in ciphertext)) {
throw new DecryptionError(
"OLM_NOT_INCLUDED_IN_RECIPIENTS",
"Not included in recipients",
);
}
const message = ciphertext[this.olmDevice.deviceCurve25519Key];
let payloadString;
const message = ciphertext[this.olmDevice.deviceCurve25519Key!];
let payloadString: string;
try {
payloadString = await this.decryptMessage(deviceKey, message);
@ -196,7 +196,7 @@ class OlmDecryption extends DecryptionAlgorithm {
"OLM_BAD_ENCRYPTED_MESSAGE",
"Bad Encrypted Message", {
sender: deviceKey,
err: e,
err: e as Error,
},
);
}
@ -217,7 +217,7 @@ class OlmDecryption extends DecryptionAlgorithm {
"OLM_BAD_RECIPIENT_KEY",
"Message not intended for this device", {
intended: payload.recipient_keys.ed25519,
our_key: this.olmDevice.deviceEd25519Key,
our_key: this.olmDevice.deviceEd25519Key!,
},
);
}
@ -233,7 +233,7 @@ class OlmDecryption extends DecryptionAlgorithm {
olmlib.OLM_ALGORITHM,
deviceKey,
);
if (senderKeyUser !== event.getSender() && senderKeyUser !== undefined) {
if (senderKeyUser !== event.getSender() && senderKeyUser != undefined) {
throw new DecryptionError(
"OLM_BAD_SENDER",
"Message claimed to be from " + event.getSender(), {
@ -325,13 +325,13 @@ class OlmDecryption extends DecryptionAlgorithm {
// session, so it should have worked.
throw new Error(
"Error decrypting prekey message with existing session id " +
sessionId + ": " + e.message,
sessionId + ": " + (<Error>e).message,
);
}
// otherwise it's probably a message for another session; carry on, but
// keep a record of the error
decryptionErrors[sessionId] = e.message;
decryptionErrors[sessionId] = (<Error>e).message;
}
}
@ -358,7 +358,7 @@ class OlmDecryption extends DecryptionAlgorithm {
theirDeviceIdentityKey, message.type, message.body,
);
} catch (e) {
decryptionErrors["(new)"] = e.message;
decryptionErrors["(new)"] = (<Error>e).message;
throw new Error(
"Error decrypting prekey message: " +
JSON.stringify(decryptionErrors),

View File

@ -40,6 +40,7 @@ import {
import { UnstableValue } from "../NamespacedValue";
import { CryptoEvent, IMegolmSessionData } from "./index";
import { crypto } from "./crypto";
import { HTTPError, MatrixError } from "../http-api";
const KEY_BACKUP_KEYS_PER_REQUEST = 200;
const KEY_BACKUP_CHECK_RATE_LIMIT = 5000; // ms
@ -62,7 +63,7 @@ export type TrustInfo = {
};
export interface IKeyBackupCheck {
backupInfo: IKeyBackupInfo;
backupInfo?: IKeyBackupInfo;
trustInfo: TrustInfo;
}
@ -85,9 +86,7 @@ interface BackupAlgorithmClass {
init(authData: AuthData, getKey: GetKey): Promise<BackupAlgorithm>;
// prepare a brand new backup
prepare(
key: string | Uint8Array | null,
): Promise<[Uint8Array, AuthData]>;
prepare(key?: string | Uint8Array | null): Promise<[Uint8Array, AuthData]>;
checkBackupVersion(info: IKeyBackupInfo): void;
}
@ -221,19 +220,19 @@ export class BackupManager {
* one of the user's verified devices, start backing up
* to it.
*/
public async checkAndStart(): Promise<IKeyBackupCheck> {
public async checkAndStart(): Promise<IKeyBackupCheck | null> {
logger.log("Checking key backup status...");
if (this.baseApis.isGuest()) {
logger.log("Skipping key backup check since user is guest");
this.checkedForBackup = true;
return null;
}
let backupInfo: IKeyBackupInfo;
let backupInfo: IKeyBackupInfo | undefined;
try {
backupInfo = await this.baseApis.getKeyBackupVersion();
backupInfo = await this.baseApis.getKeyBackupVersion() ?? undefined;
} catch (e) {
logger.log("Error checking for active key backup", e);
if (e.httpStatus === 404) {
if ((<HTTPError>e).httpStatus === 404) {
// 404 is returned when the key backup does not exist, so that
// counts as successfully checking.
this.checkedForBackup = true;
@ -245,11 +244,8 @@ export class BackupManager {
const trustInfo = await this.isKeyBackupTrusted(backupInfo);
if (trustInfo.usable && !this.backupInfo) {
logger.log(
"Found usable key backup v" + backupInfo.version +
": enabling key backups",
);
await this.enableKeyBackup(backupInfo);
logger.log(`Found usable key backup v${backupInfo!.version}: enabling key backups`);
await this.enableKeyBackup(backupInfo!);
} else if (!trustInfo.usable && this.backupInfo) {
logger.log("No usable key backup: disabling key backup");
this.disableKeyBackup();
@ -257,13 +253,11 @@ export class BackupManager {
logger.log("No usable key backup: not enabling key backup");
} else if (trustInfo.usable && this.backupInfo) {
// may not be the same version: if not, we should switch
if (backupInfo.version !== this.backupInfo.version) {
logger.log(
"On backup version " + this.backupInfo.version + " but found " +
"version " + backupInfo.version + ": switching.",
);
if (backupInfo!.version !== this.backupInfo.version) {
logger.log(`On backup version ${this.backupInfo.version} but ` +
`found version ${backupInfo!.version}: switching.`);
this.disableKeyBackup();
await this.enableKeyBackup(backupInfo);
await this.enableKeyBackup(backupInfo!);
// We're now using a new backup, so schedule all the keys we have to be
// uploaded to the new backup. This is a bit of a workaround to upload
// keys to a new backup in *most* cases, but it won't cover all cases
@ -271,7 +265,7 @@ export class BackupManager {
// see https://github.com/vector-im/element-web/issues/14833
await this.scheduleAllGroupSessionsForBackup();
} else {
logger.log("Backup version " + backupInfo.version + " still current");
logger.log(`Backup version ${backupInfo!.version} still current`);
}
}
@ -287,7 +281,7 @@ export class BackupManager {
* trust information (as returned by isKeyBackupTrusted)
* in trustInfo.
*/
public async checkKeyBackup(): Promise<IKeyBackupCheck> {
public async checkKeyBackup(): Promise<IKeyBackupCheck | null> {
this.checkedForBackup = false;
return this.checkAndStart();
}
@ -325,7 +319,7 @@ export class BackupManager {
* ]
* }
*/
public async isKeyBackupTrusted(backupInfo: IKeyBackupInfo): Promise<TrustInfo> {
public async isKeyBackupTrusted(backupInfo?: IKeyBackupInfo): Promise<TrustInfo> {
const ret = {
usable: false,
trusted_locally: false,
@ -342,9 +336,10 @@ export class BackupManager {
return ret;
}
const privKey = await this.baseApis.crypto.getSessionBackupPrivateKey();
const userId = this.baseApis.getUserId()!;
const privKey = await this.baseApis.crypto!.getSessionBackupPrivateKey();
if (privKey) {
let algorithm;
let algorithm: BackupAlgorithm | null = null;
try {
algorithm = await BackupManager.makeAlgorithm(backupInfo, async () => privKey);
@ -356,13 +351,11 @@ export class BackupManager {
// do nothing -- if we have an error, then we don't mark it as
// locally trusted
} finally {
if (algorithm) {
algorithm.free();
}
algorithm?.free();
}
}
const mySigs = backupInfo.auth_data.signatures[this.baseApis.getUserId()] || {};
const mySigs = backupInfo.auth_data.signatures[userId] || {};
for (const keyId of Object.keys(mySigs)) {
const keyIdParts = keyId.split(':');
@ -375,14 +368,14 @@ export class BackupManager {
const sigInfo: SigInfo = { deviceId: keyIdParts[1] };
// first check to see if it's from our cross-signing key
const crossSigningId = this.baseApis.crypto.crossSigningInfo.getId();
const crossSigningId = this.baseApis.crypto!.crossSigningInfo.getId();
if (crossSigningId === sigInfo.deviceId) {
sigInfo.crossSigningId = true;
try {
await verifySignature(
this.baseApis.crypto.olmDevice,
this.baseApis.crypto!.olmDevice,
backupInfo.auth_data,
this.baseApis.getUserId(),
userId,
sigInfo.deviceId,
crossSigningId,
);
@ -400,17 +393,16 @@ export class BackupManager {
// Now look for a sig from a device
// At some point this can probably go away and we'll just support
// it being signed by the cross-signing master key
const device = this.baseApis.crypto.deviceList.getStoredDevice(
this.baseApis.getUserId(), sigInfo.deviceId,
const device = this.baseApis.crypto!.deviceList.getStoredDevice(userId, sigInfo.deviceId,
);
if (device) {
sigInfo.device = device;
sigInfo.deviceTrust = this.baseApis.checkDeviceTrust(this.baseApis.getUserId(), sigInfo.deviceId);
sigInfo.deviceTrust = this.baseApis.checkDeviceTrust(userId, sigInfo.deviceId);
try {
await verifySignature(
this.baseApis.crypto.olmDevice,
this.baseApis.crypto!.olmDevice,
backupInfo.auth_data,
this.baseApis.getUserId(),
userId,
device.deviceId,
device.getFingerprint(),
);
@ -431,12 +423,7 @@ export class BackupManager {
}
ret.usable = ret.sigs.some((s) => {
return (
s.valid && (
(s.device && s.deviceTrust.isVerified()) ||
(s.crossSigningId)
)
);
return s.valid && ((s.device && s.deviceTrust?.isVerified()) || (s.crossSigningId));
});
return ret;
}
@ -474,17 +461,17 @@ export class BackupManager {
} catch (err) {
numFailures++;
logger.log("Key backup request failed", err);
if (err.data) {
if ((<MatrixError>err).data) {
if (
err.data.errcode == 'M_NOT_FOUND' ||
err.data.errcode == 'M_WRONG_ROOM_KEYS_VERSION'
(<MatrixError>err).data.errcode == 'M_NOT_FOUND' ||
(<MatrixError>err).data.errcode == 'M_WRONG_ROOM_KEYS_VERSION'
) {
// Re-check key backup status on error, so we can be
// sure to present the current situation when asked.
await this.checkKeyBackup();
// Backup version has changed or this backup version
// has been deleted
this.baseApis.crypto.emit(CryptoEvent.KeyBackupFailed, err.data.errcode);
this.baseApis.crypto!.emit(CryptoEvent.KeyBackupFailed, (<MatrixError>err).data.errcode!);
throw err;
}
}
@ -507,50 +494,50 @@ export class BackupManager {
* @returns {number} Number of sessions backed up
*/
public async backupPendingKeys(limit: number): Promise<number> {
const sessions = await this.baseApis.crypto.cryptoStore.getSessionsNeedingBackup(limit);
const sessions = await this.baseApis.crypto!.cryptoStore.getSessionsNeedingBackup(limit);
if (!sessions.length) {
return 0;
}
let remaining = await this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup();
this.baseApis.crypto.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining);
let remaining = await this.baseApis.crypto!.cryptoStore.countSessionsNeedingBackup();
this.baseApis.crypto!.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining);
const rooms: IKeyBackup["rooms"] = {};
for (const session of sessions) {
const roomId = session.sessionData.room_id;
const roomId = session.sessionData!.room_id;
if (rooms[roomId] === undefined) {
rooms[roomId] = { sessions: {} };
}
const sessionData = this.baseApis.crypto.olmDevice.exportInboundGroupSession(
session.senderKey, session.sessionId, session.sessionData,
const sessionData = this.baseApis.crypto!.olmDevice.exportInboundGroupSession(
session.senderKey, session.sessionId, session.sessionData!,
);
sessionData.algorithm = MEGOLM_ALGORITHM;
const forwardedCount =
(sessionData.forwarding_curve25519_key_chain || []).length;
const userId = this.baseApis.crypto.deviceList.getUserByIdentityKey(
const userId = this.baseApis.crypto!.deviceList.getUserByIdentityKey(
MEGOLM_ALGORITHM, session.senderKey,
);
const device = this.baseApis.crypto.deviceList.getDeviceByIdentityKey(
const device = this.baseApis.crypto!.deviceList.getDeviceByIdentityKey(
MEGOLM_ALGORITHM, session.senderKey,
);
const verified = this.baseApis.crypto.checkDeviceInfoTrust(userId, device).isVerified();
) ?? undefined;
const verified = this.baseApis.crypto!.checkDeviceInfoTrust(userId!, device).isVerified();
rooms[roomId]['sessions'][session.sessionId] = {
first_message_index: sessionData.first_known_index,
forwarded_count: forwardedCount,
is_verified: verified,
session_data: await this.algorithm.encryptSession(sessionData),
session_data: await this.algorithm!.encryptSession(sessionData),
};
}
await this.baseApis.sendKeyBackup(undefined, undefined, this.backupInfo.version, { rooms });
await this.baseApis.sendKeyBackup(undefined, undefined, this.backupInfo!.version, { rooms });
await this.baseApis.crypto.cryptoStore.unmarkSessionsNeedingBackup(sessions);
remaining = await this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup();
this.baseApis.crypto.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining);
await this.baseApis.crypto!.cryptoStore.unmarkSessionsNeedingBackup(sessions);
remaining = await this.baseApis.crypto!.cryptoStore.countSessionsNeedingBackup();
this.baseApis.crypto!.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining);
return sessions.length;
}
@ -558,7 +545,7 @@ export class BackupManager {
public async backupGroupSession(
senderKey: string, sessionId: string,
): Promise<void> {
await this.baseApis.crypto.cryptoStore.markSessionsNeedingBackup([{
await this.baseApis.crypto!.cryptoStore.markSessionsNeedingBackup([{
senderKey: senderKey,
sessionId: sessionId,
}]);
@ -590,22 +577,22 @@ export class BackupManager {
* (which will be equal to the number of sessions in the store).
*/
public async flagAllGroupSessionsForBackup(): Promise<number> {
await this.baseApis.crypto.cryptoStore.doTxn(
await this.baseApis.crypto!.cryptoStore.doTxn(
'readwrite',
[
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
IndexedDBCryptoStore.STORE_BACKUP,
],
(txn) => {
this.baseApis.crypto.cryptoStore.getAllEndToEndInboundGroupSessions(txn, (session) => {
this.baseApis.crypto!.cryptoStore.getAllEndToEndInboundGroupSessions(txn, (session) => {
if (session !== null) {
this.baseApis.crypto.cryptoStore.markSessionsNeedingBackup([session], txn);
this.baseApis.crypto!.cryptoStore.markSessionsNeedingBackup([session], txn);
}
});
},
);
const remaining = await this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup();
const remaining = await this.baseApis.crypto!.cryptoStore.countSessionsNeedingBackup();
this.baseApis.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining);
return remaining;
}
@ -615,7 +602,7 @@ export class BackupManager {
* @returns {Promise<int>} Resolves to the number of sessions requiring backup
*/
public countSessionsNeedingBackup(): Promise<number> {
return this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup();
return this.baseApis.crypto!.cryptoStore.countSessionsNeedingBackup();
}
}
@ -641,7 +628,7 @@ export class Curve25519 implements BackupAlgorithm {
}
public static async prepare(
key: string | Uint8Array | null,
key?: string | Uint8Array | null,
): Promise<[Uint8Array, AuthData]> {
const decryption = new global.Olm.PkDecryption();
try {
@ -741,7 +728,10 @@ function randomBytes(size: number): Uint8Array {
return buf;
}
const UNSTABLE_MSC3270_NAME = new UnstableValue(null, "org.matrix.msc3270.v1.aes-hmac-sha2");
const UNSTABLE_MSC3270_NAME = new UnstableValue(
"m.megolm_backup.v1.aes-hmac-sha2",
"org.matrix.msc3270.v1.aes-hmac-sha2",
);
export class Aes256 implements BackupAlgorithm {
public static algorithmName = UNSTABLE_MSC3270_NAME.name;
@ -769,7 +759,7 @@ export class Aes256 implements BackupAlgorithm {
}
public static async prepare(
key: string | Uint8Array | null,
key?: string | Uint8Array | null,
): Promise<[Uint8Array, AuthData]> {
let outKey: Uint8Array;
const authData: Partial<IAes256AuthData> = {};

View File

@ -58,9 +58,9 @@ const oneweek = 7 * 24 * 60 * 60 * 1000;
export class DehydrationManager {
private inProgress = false;
private timeoutId: any;
private key: Uint8Array;
private keyInfo: {[props: string]: any};
private deviceDisplayName: string;
private key?: Uint8Array;
private keyInfo?: {[props: string]: any};
private deviceDisplayName?: string;
constructor(private readonly crypto: Crypto) {
this.getDehydrationKeyFromCache();
@ -97,7 +97,7 @@ export class DehydrationManager {
/** set the key, and queue periodic dehydration to the server in the background */
public async setKeyAndQueueDehydration(
key: Uint8Array, keyInfo: {[props: string]: any} = {},
deviceDisplayName: string = undefined,
deviceDisplayName?: string,
): Promise<void> {
const matches = await this.setKey(key, keyInfo, deviceDisplayName);
if (!matches) {
@ -108,8 +108,8 @@ export class DehydrationManager {
public async setKey(
key: Uint8Array, keyInfo: {[props: string]: any} = {},
deviceDisplayName: string = undefined,
): Promise<boolean> {
deviceDisplayName?: string,
): Promise<boolean | undefined> {
if (!key) {
// unsetting the key -- cancel any pending dehydration task
if (this.timeoutId) {
@ -135,9 +135,9 @@ export class DehydrationManager {
// dehydrate a new device. If it's the same, we can keep the same
// device. (Assume that keyInfo and deviceDisplayName will be the
// same if the key is the same.)
let matches: boolean = this.key && key.length == this.key.length;
let matches: boolean = !!this.key && key.length == this.key.length;
for (let i = 0; matches && i < key.length; i++) {
if (key[i] != this.key[i]) {
if (key[i] != this.key![i]) {
matches = false;
}
}
@ -150,7 +150,7 @@ export class DehydrationManager {
}
/** returns the device id of the newly created dehydrated device */
public async dehydrateDevice(): Promise<string> {
public async dehydrateDevice(): Promise<string | undefined> {
if (this.inProgress) {
logger.log("Dehydration already in progress -- not starting new dehydration");
return;
@ -164,7 +164,7 @@ export class DehydrationManager {
const pickleKey = Buffer.from(this.crypto.olmDevice.pickleKey);
// update the crypto store with the timestamp
const key = await encryptAES(encodeBase64(this.key), pickleKey, DEHYDRATION_ALGORITHM);
const key = await encryptAES(encodeBase64(this.key!), pickleKey, DEHYDRATION_ALGORITHM);
await this.crypto.cryptoStore.doTxn(
'readwrite',
[IndexedDBCryptoStore.STORE_ACCOUNT],
@ -174,7 +174,7 @@ export class DehydrationManager {
{
keyInfo: this.keyInfo,
key,
deviceDisplayName: this.deviceDisplayName,
deviceDisplayName: this.deviceDisplayName!,
time: Date.now(),
},
);
@ -197,14 +197,14 @@ export class DehydrationManager {
account.mark_keys_as_published();
// dehydrate the account and store it on the server
const pickledAccount = account.pickle(new Uint8Array(this.key));
const pickledAccount = account.pickle(new Uint8Array(this.key!));
const deviceData: {[props: string]: any} = {
algorithm: DEHYDRATION_ALGORITHM,
account: pickledAccount,
};
if (this.keyInfo.passphrase) {
deviceData.passphrase = this.keyInfo.passphrase;
if (this.keyInfo!.passphrase) {
deviceData.passphrase = this.keyInfo!.passphrase;
}
logger.log("Uploading account to server");

View File

@ -23,6 +23,7 @@ limitations under the License.
import anotherjson from "another-json";
import type { PkDecryption, PkSigning } from "@matrix-org/olm";
import { EventType } from "../@types/event";
import { TypedReEmitter } from '../ReEmitter';
import { logger } from '../logger';
@ -274,7 +275,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
private trustCrossSignedDevices = true;
// the last time we did a check for the number of one-time-keys on the server.
private lastOneTimeKeyCheck: number = null;
private lastOneTimeKeyCheck: number | null = null;
private oneTimeKeyCheckInProgress = false;
// EncryptionAlgorithm instance for each room
@ -317,8 +318,8 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
// processing the response.
private sendKeyRequestsImmediately = false;
private oneTimeKeyCount: number;
private needsNewFallback: boolean;
private oneTimeKeyCount?: number;
private needsNewFallback?: boolean;
private fallbackCleanup?: ReturnType<typeof setTimeout>;
/**
@ -399,8 +400,8 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
// store the fixed version
const fixedKey = fixBackupKey(storedKey);
if (fixedKey) {
const [keyId] = await this.getSecretStorageKey();
await this.storeSecret("m.megolm_backup.v1", fixedKey, [keyId]);
const keys = await this.getSecretStorageKey();
await this.storeSecret("m.megolm_backup.v1", fixedKey, [keys![0]]);
}
return olmlib.decodeBase64(fixedKey || storedKey);
@ -468,8 +469,8 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
await this.deviceList.load();
// build our device keys: these will later be uploaded
this.deviceKeys["ed25519:" + this.deviceId] = this.olmDevice.deviceEd25519Key;
this.deviceKeys["curve25519:" + this.deviceId] = this.olmDevice.deviceCurve25519Key;
this.deviceKeys["ed25519:" + this.deviceId] = this.olmDevice.deviceEd25519Key!;
this.deviceKeys["curve25519:" + this.deviceId] = this.olmDevice.deviceCurve25519Key!;
logger.log("Crypto: fetching own devices...");
let myDevices = this.deviceList.getRawStoredDevicesForUser(this.userId);
@ -547,7 +548,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
!deviceTrust.isLocallyVerified() &&
deviceTrust.isCrossSigningVerified()
) {
const deviceObj = this.deviceList.getStoredDevice(userId, deviceId);
const deviceObj = this.deviceList.getStoredDevice(userId, deviceId)!;
this.emit(CryptoEvent.DeviceVerificationChanged, userId, deviceId, deviceObj);
}
}
@ -695,9 +696,9 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
builder.addCrossSigningKeys(authUploadDeviceSigningKeys, crossSigningInfo.keys);
// Cross-sign own device
const device = this.deviceList.getStoredDevice(this.userId, this.deviceId);
const device = this.deviceList.getStoredDevice(this.userId, this.deviceId)!;
const deviceSignature = await crossSigningInfo.signDevice(this.userId, device);
builder.addKeySignature(this.userId, this.deviceId, deviceSignature);
builder.addKeySignature(this.userId, this.deviceId, deviceSignature!);
// Sign message key backup with cross-signing master key
if (this.backupManager.backupInfo) {
@ -838,10 +839,10 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
);
// the ID of the new SSSS key, if we create one
let newKeyId = null;
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) => {
if (privateKey) {
opts.key = privateKey;
}
@ -859,7 +860,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
const ensureCanCheckPassphrase = async (keyId: string, keyInfo: ISecretStorageKeyInfo) => {
if (!keyInfo.mac) {
const key = await this.baseApis.cryptoCallbacks.getSecretStorageKey(
const key = await this.baseApis.cryptoCallbacks.getSecretStorageKey?.(
{ keys: { [keyId]: keyInfo } }, "",
);
if (key) {
@ -934,7 +935,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
// if we have the backup key already cached, use it; otherwise use the
// callback to prompt for the key
const backupKey = await this.getSessionBackupPrivateKey() || await getKeyBackupPassphrase();
const backupKey = await this.getSessionBackupPrivateKey() || await getKeyBackupPassphrase?.();
// create a new SSSS key and use the backup key as the new SSSS key
const opts = {} as IAddSecretStorageKeyOpts;
@ -955,7 +956,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
newKeyId = await createSSSS(opts, backupKey);
// store the backup key in secret storage
await secretStorage.store("m.megolm_backup.v1", olmlib.encodeBase64(backupKey), [newKeyId]);
await secretStorage.store("m.megolm_backup.v1", olmlib.encodeBase64(backupKey!), [newKeyId]);
// The backup is trusted because the user provided the private key.
// Sign the backup with the cross-signing key so the key backup can
@ -1025,8 +1026,9 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
// in secret storage
const fixedBackupKey = fixBackupKey(sessionBackupKey);
if (fixedBackupKey) {
const keyId = newKeyId || oldKeyId;
await secretStorage.store("m.megolm_backup.v1",
fixedBackupKey, [newKeyId || oldKeyId],
fixedBackupKey, keyId ? [keyId] : null,
);
}
const decodedBackupKey = new Uint8Array(olmlib.decodeBase64(
@ -1036,7 +1038,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
} else if (this.backupManager.getKeyBackupEnabled()) {
// key backup is enabled but we don't have a session backup key in SSSS: see if we have one in
// the cache or the user can provide one, and if so, write it to SSSS
const backupKey = await this.getSessionBackupPrivateKey() || await getKeyBackupPassphrase();
const backupKey = await this.getSessionBackupPrivateKey() || await getKeyBackupPassphrase?.();
if (!backupKey) {
// This will require user intervention to recover from since we don't have the key
// backup key anywhere. The user should probably just set up a new key backup and
@ -1061,16 +1063,16 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
public addSecretStorageKey(
algorithm: string,
opts: IAddSecretStorageKeyOpts,
keyID: string,
keyID?: string,
): Promise<SecretStorageKeyObject> {
return this.secretStorage.addKey(algorithm, opts, keyID);
}
public hasSecretStorageKey(keyID: string): Promise<boolean> {
public hasSecretStorageKey(keyID?: string): Promise<boolean> {
return this.secretStorage.hasKey(keyID);
}
public getSecretStorageKey(keyID?: string): Promise<SecretStorageKeyTuple> {
public getSecretStorageKey(keyID?: string): Promise<SecretStorageKeyTuple | null> {
return this.secretStorage.getKey(keyID);
}
@ -1078,7 +1080,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
return this.secretStorage.store(name, secret, keys);
}
public getSecret(name: string): Promise<string> {
public getSecret(name: string): Promise<string | undefined> {
return this.secretStorage.get(name);
}
@ -1115,14 +1117,14 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* @returns {boolean} true if the key matches, otherwise false
*/
public checkSecretStoragePrivateKey(privateKey: Uint8Array, expectedPublicKey: string): boolean {
let decryption = null;
let decryption: PkDecryption | null = null;
try {
decryption = new global.Olm.PkDecryption();
const gotPubkey = decryption.init_with_private_key(privateKey);
// make sure it agrees with the given pubkey
return gotPubkey === expectedPublicKey;
} finally {
if (decryption) decryption.free();
decryption?.free();
}
}
@ -1188,14 +1190,14 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* @returns {boolean} true if the key matches, otherwise false
*/
public checkCrossSigningPrivateKey(privateKey: Uint8Array, expectedPublicKey: string): boolean {
let signing = null;
let signing: PkSigning | null = null;
try {
signing = new global.Olm.PkSigning();
const gotPubkey = signing.init_with_seed(privateKey);
// make sure it agrees with the given pubkey
return gotPubkey === expectedPublicKey;
} finally {
if (signing) signing.free();
signing?.free();
}
}
@ -1209,14 +1211,14 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
logger.info("Starting cross-signing key change post-processing");
// sign the current device with the new key, and upload to the server
const device = this.deviceList.getStoredDevice(this.userId, this.deviceId);
const device = this.deviceList.getStoredDevice(this.userId, this.deviceId)!;
const signedDevice = await this.crossSigningInfo.signDevice(this.userId, device);
logger.info(`Starting background key sig upload for ${this.deviceId}`);
const upload = ({ shouldEmit = false }) => {
return this.baseApis.uploadKeySignatures({
[this.userId]: {
[this.deviceId]: signedDevice,
[this.deviceId]: signedDevice!,
},
}).then((response) => {
const { failures } = response || {};
@ -1267,9 +1269,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
if (usersToUpgrade) {
for (const userId of usersToUpgrade) {
if (userId in users) {
await this.baseApis.setDeviceVerified(
userId, users[userId].crossSigningInfo.getId(),
);
await this.baseApis.setDeviceVerified(userId, users[userId].crossSigningInfo.getId()!);
}
}
}
@ -1296,7 +1296,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
private async checkForDeviceVerificationUpgrade(
userId: string,
crossSigningInfo: CrossSigningInfo,
): Promise<IDeviceVerificationUpgrade> {
): Promise<IDeviceVerificationUpgrade | undefined> {
// only upgrade if this is the first cross-signing key that we've seen for
// them, and if their cross-signing key isn't already verified
const trustLevel = this.crossSigningInfo.checkUserTrust(crossSigningInfo);
@ -1359,7 +1359,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
*
* @returns {string} the key ID
*/
public getCrossSigningId(type: string): string {
public getCrossSigningId(type: string): string | null {
return this.crossSigningInfo.getId(type);
}
@ -1370,7 +1370,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
*
* @returns {CrossSigningInfo} the cross signing information for the user.
*/
public getStoredCrossSigningForUser(userId: string): CrossSigningInfo {
public getStoredCrossSigningForUser(userId: string): CrossSigningInfo | null {
return this.deviceList.getStoredCrossSigningForUser(userId);
}
@ -1410,8 +1410,8 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
*
* @returns {DeviceTrustLevel}
*/
public checkDeviceInfoTrust(userId: string, device: DeviceInfo): DeviceTrustLevel {
const trustedLocally = !!(device && device.isVerified());
public checkDeviceInfoTrust(userId: string, device?: DeviceInfo): DeviceTrustLevel {
const trustedLocally = !!device?.isVerified();
const userCrossSigning = this.deviceList.getStoredCrossSigningForUser(userId);
if (device && userCrossSigning) {
@ -1436,13 +1436,14 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
*/
public checkIfOwnDeviceCrossSigned(deviceId: string): boolean {
const device = this.deviceList.getStoredDevice(this.userId, deviceId);
if (!device) return false;
const userCrossSigning = this.deviceList.getStoredCrossSigningForUser(this.userId);
return userCrossSigning.checkDeviceTrust(
return userCrossSigning?.checkDeviceTrust(
userCrossSigning,
device,
false,
true,
).isCrossSigningVerified();
).isCrossSigningVerified() ?? false;
}
/*
@ -1494,7 +1495,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* Check the copy of our cross-signing key that we have in the device list and
* see if we can get the private key. If so, mark it as trusted.
*/
async checkOwnCrossSigningTrust({
public async checkOwnCrossSigningTrust({
allowPrivateKeyRequests = false,
}: ICheckOwnCrossSigningTrustOpts = {}): Promise<void> {
const userId = this.userId;
@ -1520,7 +1521,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
return;
}
const seenPubkey = newCrossSigning.getId();
const seenPubkey = newCrossSigning.getId()!;
const masterChanged = this.crossSigningInfo.getId() !== seenPubkey;
const masterExistsNotLocallyCached =
newCrossSigning.getId() && !crossSigningPrivateKeys.has("master");
@ -1532,18 +1533,16 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
(masterChanged || masterExistsNotLocallyCached)
) {
logger.info("Attempting to retrieve cross-signing master private key");
let signing = null;
let signing: PkSigning | null = null;
// It's important for control flow that we leave any errors alone for
// higher levels to handle so that e.g. cancelling access properly
// aborts any larger operation as well.
try {
const ret = await this.crossSigningInfo.getCrossSigningKey(
'master', seenPubkey,
);
const ret = await this.crossSigningInfo.getCrossSigningKey('master', seenPubkey);
signing = ret[1];
logger.info("Got cross-signing master private key");
} finally {
if (signing) signing.free();
signing?.free();
}
}
@ -1575,22 +1574,20 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
(selfSigningChanged || selfSigningExistsNotLocallyCached)
) {
logger.info("Attempting to retrieve cross-signing self-signing private key");
let signing = null;
let signing: PkSigning | null = null;
try {
const ret = await this.crossSigningInfo.getCrossSigningKey(
"self_signing", newCrossSigning.getId("self_signing"),
"self_signing", newCrossSigning.getId("self_signing")!,
);
signing = ret[1];
logger.info("Got cross-signing self-signing private key");
} finally {
if (signing) signing.free();
signing?.free();
}
const device = this.deviceList.getStoredDevice(this.userId, this.deviceId);
const signedDevice = await this.crossSigningInfo.signDevice(
this.userId, device,
);
keySignatures[this.deviceId] = signedDevice;
const device = this.deviceList.getStoredDevice(this.userId, this.deviceId)!;
const signedDevice = await this.crossSigningInfo.signDevice(this.userId, device);
keySignatures[this.deviceId] = signedDevice!;
}
if (userSigningChanged) {
logger.info("Got new user-signing key", newCrossSigning.getId("user_signing"));
@ -1600,26 +1597,26 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
(userSigningChanged || userSigningExistsNotLocallyCached)
) {
logger.info("Attempting to retrieve cross-signing user-signing private key");
let signing = null;
let signing: PkSigning | null = null;
try {
const ret = await this.crossSigningInfo.getCrossSigningKey(
"user_signing", newCrossSigning.getId("user_signing"),
"user_signing", newCrossSigning.getId("user_signing")!,
);
signing = ret[1];
logger.info("Got cross-signing user-signing private key");
} finally {
if (signing) signing.free();
signing?.free();
}
}
if (masterChanged) {
const masterKey = this.crossSigningInfo.keys.master;
await this.signObject(masterKey);
const deviceSig = masterKey.signatures[this.userId]["ed25519:" + this.deviceId];
const deviceSig = masterKey.signatures![this.userId]["ed25519:" + this.deviceId];
// Include only the _new_ device signature in the upload.
// We may have existing signatures from deleted devices, which will cause
// the entire upload to fail.
keySignatures[this.crossSigningInfo.getId()] = Object.assign(
keySignatures[this.crossSigningInfo.getId()!] = Object.assign(
{} as ISignedKey,
masterKey,
{
@ -1679,7 +1676,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
*
* @param {object} keys The new trusted set of keys
*/
private async storeTrustedSelfKeys(keys: Record<string, ICrossSigningKey>): Promise<void> {
private async storeTrustedSelfKeys(keys: Record<string, ICrossSigningKey> | null): Promise<void> {
if (keys) {
this.crossSigningInfo.setKeys(keys);
} else {
@ -1721,9 +1718,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
},
});
if (usersToUpgrade.includes(userId)) {
await this.baseApis.setDeviceVerified(
userId, crossSigningInfo.getId(),
);
await this.baseApis.setDeviceVerified(userId, crossSigningInfo.getId()!);
}
}
}
@ -1771,7 +1766,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
*
* @return {string} base64-encoded ed25519 key.
*/
public getDeviceEd25519Key(): string {
public getDeviceEd25519Key(): string | null {
return this.olmDevice.deviceEd25519Key;
}
@ -1780,7 +1775,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
*
* @return {string} base64-encoded curve25519 key.
*/
public getDeviceCurve25519Key(): string {
public getDeviceCurve25519Key(): string | null {
return this.olmDevice.deviceCurve25519Key;
}
@ -1859,11 +1854,11 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
}
public setNeedsNewFallback(needsNewFallback: boolean) {
this.needsNewFallback = !!needsNewFallback;
this.needsNewFallback = needsNewFallback;
}
public getNeedsNewFallback(): boolean {
return this.needsNewFallback;
return !!this.needsNewFallback;
}
// check if it's time to upload one-time keys, and do so if so.
@ -1983,10 +1978,10 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
}
// returns a promise which resolves to the response
private async uploadOneTimeKeys() {
const promises = [];
private async uploadOneTimeKeys(): Promise<IKeysUploadResponse> {
const promises: Promise<unknown>[] = [];
let fallbackJson: Record<string, IOneTimeKey>;
let fallbackJson: Record<string, IOneTimeKey> | undefined;
if (this.getNeedsNewFallback()) {
fallbackJson = {};
const fallbackKeys = await this.olmDevice.getFallbackKey();
@ -2045,7 +2040,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* module:crypto/deviceinfo|DeviceInfo}.
*/
public downloadKeys(userIds: string[], forceDownload?: boolean): Promise<DeviceInfoMap> {
return this.deviceList.downloadKeys(userIds, forceDownload);
return this.deviceList.downloadKeys(userIds, !!forceDownload);
}
/**
@ -2114,17 +2109,11 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
public async setDeviceVerification(
userId: string,
deviceId: string,
verified?: boolean,
blocked?: boolean,
known?: boolean,
verified: boolean | null = null,
blocked: boolean | null = null,
known: boolean | null = null,
keys?: Record<string, string>,
): Promise<DeviceInfo | CrossSigningInfo> {
// get rid of any `undefined`s here so we can just check
// for null rather than null or undefined
if (verified === undefined) verified = null;
if (blocked === undefined) blocked = null;
if (known === undefined) known = null;
// Check if the 'device' is actually a cross signing key
// The js-sdk's verification treats cross-signing keys as devices
// and so uses this method to mark them verified.
@ -2240,9 +2229,9 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
if (deviceTrust.isCrossSigningVerified()) {
logger.log(`Own device ${deviceId} already cross-signing verified`);
} else {
device = await this.crossSigningInfo.signDevice(
device = (await this.crossSigningInfo.signDevice(
userId, DeviceInfo.fromStorage(dev, deviceId),
);
))!;
}
if (device) {
@ -2293,7 +2282,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
return this.requestVerificationWithChannel(userId, channel, this.inRoomVerificationRequests);
}
public requestVerification(userId: string, devices: string[]): Promise<VerificationRequest> {
public requestVerification(userId: string, devices?: string[]): Promise<VerificationRequest> {
if (!devices) {
devices = Object.keys(this.deviceList.getRawStoredDevicesForUser(userId));
}
@ -2597,7 +2586,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
// because it first stores in memory. We should await the promise only
// after all the in-memory state (roomEncryptors and _roomList) has been updated
// to avoid races when calling this method multiple times. Hence keep a hold of the promise.
let storeConfigPromise: Promise<void> = null;
let storeConfigPromise: Promise<void> | null = null;
if (!existingConfig) {
storeConfigPromise = this.roomList.setRoomEncryption(roomId, config);
}
@ -2754,7 +2743,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
const total = keys.length;
function updateProgress() {
opts.progressCallback({
opts.progressCallback?.({
stage: "load_keys",
successes,
failures,
@ -2809,7 +2798,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* @return {Promise?} Promise which resolves when the event has been
* encrypted, or null if nothing was needed
*/
public async encryptEvent(event: MatrixEvent, room: Room): Promise<void> {
public async encryptEvent(event: MatrixEvent, room?: Room): Promise<void> {
if (!room) {
throw new Error("Cannot send encrypted messages in unknown rooms");
}
@ -2862,8 +2851,8 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
event.makeEncrypted(
"m.room.encrypted",
encryptedContent,
this.olmDevice.deviceCurve25519Key,
this.olmDevice.deviceEd25519Key,
this.olmDevice.deviceCurve25519Key!,
this.olmDevice.deviceEd25519Key!,
);
}
@ -3143,7 +3132,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
const deviceId = deviceInfo.deviceId;
const encryptedContent: IEncryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this.olmDevice.deviceCurve25519Key,
sender_key: this.olmDevice.deviceCurve25519Key!,
ciphertext: {},
};
@ -3753,8 +3742,8 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* unknown
*/
public getRoomDecryptor(roomId: string, algorithm: string): DecryptionAlgorithm {
let decryptors: Map<string, DecryptionAlgorithm>;
let alg: DecryptionAlgorithm;
let decryptors: Map<string, DecryptionAlgorithm> | undefined;
let alg: DecryptionAlgorithm | undefined;
roomId = roomId || null;
if (roomId) {
@ -3799,10 +3788,10 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* @return {array} An array of room decryptors
*/
private getRoomDecryptors(algorithm: string): DecryptionAlgorithm[] {
const decryptors = [];
const decryptors: DecryptionAlgorithm[] = [];
for (const d of this.roomDecryptors.values()) {
if (d.has(algorithm)) {
decryptors.push(d.get(algorithm));
decryptors.push(d.get(algorithm)!);
}
}
return decryptors;
@ -3839,7 +3828,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* key will be returned. Otherwise null will be returned.
*
*/
export function fixBackupKey(key: string): string | null {
export function fixBackupKey(key?: string): string | null {
if (typeof key !== "string" || key.indexOf(",") < 0) {
return null;
}

View File

@ -21,7 +21,6 @@ limitations under the License.
*/
import anotherjson from "another-json";
import { Logger } from "loglevel";
import type { PkSigning } from "@matrix-org/olm";
import { OlmDevice } from "./OlmDevice";
@ -56,7 +55,7 @@ export const MEGOLM_BACKUP_ALGORITHM = Algorithm.MegolmBackup;
export interface IOlmSessionResult {
device: DeviceInfo;
sessionId?: string;
sessionId: string | null;
}
/**
@ -137,7 +136,7 @@ export async function encryptMessageForDevice(
interface IExistingOlmSession {
device: DeviceInfo;
sessionId?: string;
sessionId: string | null;
}
/**
@ -225,19 +224,8 @@ export async function ensureOlmSessionsForDevices(
force = false,
otkTimeout?: number,
failedServers?: string[],
log: Logger = logger,
log = logger,
): Promise<Record<string, Record<string, IOlmSessionResult>>> {
if (typeof force === "number") {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - backwards compatibility
log = failedServers;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - backwards compatibility
failedServers = otkTimeout;
otkTimeout = force;
force = false;
}
const devicesWithoutSession: [string, string][] = [
// [userId, deviceId], ...
];
@ -365,7 +353,7 @@ export async function ensureOlmSessionsForDevices(
}
const deviceRes = userRes[deviceId] || {};
let oneTimeKey: IOneTimeKey = null;
let oneTimeKey: IOneTimeKey | null = null;
for (const keyId in deviceRes) {
if (keyId.indexOf(oneTimeKeyAlgorithm + ":") === 0) {
oneTimeKey = deviceRes[keyId];
@ -388,7 +376,7 @@ export async function ensureOlmSessionsForDevices(
olmDevice, oneTimeKey, userId, deviceInfo,
).then((sid) => {
if (resolveSession[key]) {
resolveSession[key](sid);
resolveSession[key](sid ?? undefined);
}
result[userId][deviceId].sessionId = sid;
}, (e) => {
@ -413,7 +401,7 @@ async function _verifyKeyAndStartSession(
oneTimeKey: IOneTimeKey,
userId: string,
deviceInfo: DeviceInfo,
): Promise<string> {
): Promise<string | null> {
const deviceId = deviceInfo.deviceId;
try {
await verifySignature(

View File

@ -90,14 +90,14 @@ export interface CryptoStore {
deviceKey: string,
sessionId: string,
txn: unknown,
func: (session: ISessionInfo) => void,
func: (session: ISessionInfo | null) => void,
): void;
getEndToEndSessions(
deviceKey: string,
txn: unknown,
func: (sessions: { [sessionId: string]: ISessionInfo }) => void,
): void;
getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo) => void): void;
getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo | null) => void): void;
storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: unknown): void;
storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void>;
getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null>;

View File

@ -307,7 +307,7 @@ export class Backend implements CryptoStore {
expectedState: number,
updates: Partial<OutgoingRoomKeyRequest>,
): Promise<OutgoingRoomKeyRequest | null> {
let result: OutgoingRoomKeyRequest = null;
let result: OutgoingRoomKeyRequest | null = null;
function onsuccess(this: IDBRequest<IDBCursorWithValue>) {
const cursor = this.result;
@ -375,7 +375,7 @@ export class Backend implements CryptoStore {
try {
func(getReq.result || null);
} catch (e) {
abortWithException(txn, e);
abortWithException(txn, <Error>e);
}
};
}
@ -395,7 +395,7 @@ export class Backend implements CryptoStore {
try {
func(getReq.result || null);
} catch (e) {
abortWithException(txn, e);
abortWithException(txn, <Error>e);
}
};
}
@ -411,7 +411,7 @@ export class Backend implements CryptoStore {
try {
func(getReq.result || null);
} catch (e) {
abortWithException(txn, e);
abortWithException(txn, <Error>e);
}
};
}
@ -439,7 +439,7 @@ export class Backend implements CryptoStore {
try {
func(countReq.result);
} catch (e) {
abortWithException(txn, e);
abortWithException(txn, <Error>e);
}
};
}
@ -465,7 +465,7 @@ export class Backend implements CryptoStore {
try {
func(results);
} catch (e) {
abortWithException(txn, e);
abortWithException(txn, <Error>e);
}
}
};
@ -475,7 +475,7 @@ export class Backend implements CryptoStore {
deviceKey: string,
sessionId: string,
txn: IDBTransaction,
func: (sessions: { [ sessionId: string ]: ISessionInfo }) => void,
func: (session: ISessionInfo | null) => void,
): void {
const objectStore = txn.objectStore("sessions");
const getReq = objectStore.get([deviceKey, sessionId]);
@ -490,12 +490,12 @@ export class Backend implements CryptoStore {
func(null);
}
} catch (e) {
abortWithException(txn, e);
abortWithException(txn, <Error>e);
}
};
}
public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo) => void): void {
public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo | null) => void): void {
const objectStore = txn.objectStore("sessions");
const getReq = objectStore.openCursor();
getReq.onsuccess = function() {
@ -508,7 +508,7 @@ export class Backend implements CryptoStore {
func(null);
}
} catch (e) {
abortWithException(txn, e);
abortWithException(txn, <Error>e);
}
};
}
@ -537,11 +537,11 @@ export class Backend implements CryptoStore {
fixed,
time: Date.now(),
});
return promiseifyTxn(txn);
await promiseifyTxn(txn);
}
public async getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null> {
let result;
let result: IProblem | null = null;
const txn = this.db.transaction("session_problems", "readwrite");
const objectStore = txn.objectStore("session_problems");
const index = objectStore.index("deviceKey");
@ -604,8 +604,8 @@ export class Backend implements CryptoStore {
txn: IDBTransaction,
func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void,
): void {
let session: InboundGroupSessionData | boolean = false;
let withheld: IWithheld | boolean = false;
let session: InboundGroupSessionData | null | boolean = false;
let withheld: IWithheld | null | boolean = false;
const objectStore = txn.objectStore("inbound_group_sessions");
const getReq = objectStore.get([senderCurve25519Key, sessionId]);
getReq.onsuccess = function() {
@ -619,7 +619,7 @@ export class Backend implements CryptoStore {
func(session as InboundGroupSessionData, withheld as IWithheld);
}
} catch (e) {
abortWithException(txn, e);
abortWithException(txn, <Error>e);
}
};
@ -636,7 +636,7 @@ export class Backend implements CryptoStore {
func(session as InboundGroupSessionData, withheld as IWithheld);
}
} catch (e) {
abortWithException(txn, e);
abortWithException(txn, <Error>e);
}
};
}
@ -654,14 +654,14 @@ export class Backend implements CryptoStore {
sessionData: cursor.value.session,
});
} catch (e) {
abortWithException(txn, e);
abortWithException(txn, <Error>e);
}
cursor.continue();
} else {
try {
func(null);
} catch (e) {
abortWithException(txn, e);
abortWithException(txn, <Error>e);
}
}
};
@ -726,7 +726,7 @@ export class Backend implements CryptoStore {
try {
func(getReq.result || null);
} catch (e) {
abortWithException(txn, e);
abortWithException(txn, <Error>e);
}
};
}
@ -754,7 +754,7 @@ export class Backend implements CryptoStore {
try {
func(rooms);
} catch (e) {
abortWithException(txn, e);
abortWithException(txn, <Error>e);
}
}
};
@ -1050,7 +1050,7 @@ function abortWithException(txn: IDBTransaction, e: Error) {
}
}
function promiseifyTxn<T>(txn: IDBTransaction): Promise<T> {
function promiseifyTxn<T>(txn: IDBTransaction): Promise<T | null> {
return new Promise((resolve, reject) => {
txn.oncomplete = () => {
if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) {

View File

@ -18,7 +18,7 @@ import { logger, PrefixedLogger } from '../../logger';
import { LocalStorageCryptoStore } from './localStorage-crypto-store';
import { MemoryCryptoStore } from './memory-crypto-store';
import * as IndexedDBCryptoStoreBackend from './indexeddb-crypto-store-backend';
import { InvalidCryptoStoreError } from '../../errors';
import { InvalidCryptoStoreError, InvalidCryptoStoreState } from '../../errors';
import * as IndexedDBHelpers from "../../indexeddb-helpers";
import {
CryptoStore,
@ -64,8 +64,8 @@ export class IndexedDBCryptoStore implements CryptoStore {
return IndexedDBHelpers.exists(indexedDB, dbName);
}
private backendPromise: Promise<CryptoStore> = null;
private backend: CryptoStore = null;
private backendPromise?: Promise<CryptoStore>;
private backend?: CryptoStore;
/**
* Create a new IndexedDBCryptoStore
@ -141,7 +141,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
logger.warn("Crypto DB is too new for us to use!", e);
// don't fall back to a different store: the user has crypto data
// in this db so we should use it or nothing at all.
throw new InvalidCryptoStoreError(InvalidCryptoStoreError.TOO_NEW);
throw new InvalidCryptoStoreError(InvalidCryptoStoreState.TooNew);
}
logger.warn(
`unable to connect to indexeddb ${this.dbName}` +
@ -213,7 +213,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
* same instance as passed in, or the existing one.
*/
public getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise<OutgoingRoomKeyRequest> {
return this.backend.getOrAddOutgoingRoomKeyRequest(request);
return this.backend!.getOrAddOutgoingRoomKeyRequest(request);
}
/**
@ -227,7 +227,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
* not found
*/
public getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise<OutgoingRoomKeyRequest | null> {
return this.backend.getOutgoingRoomKeyRequest(requestBody);
return this.backend!.getOutgoingRoomKeyRequest(requestBody);
}
/**
@ -241,7 +241,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
* requests in those states, an arbitrary one is chosen.
*/
public getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise<OutgoingRoomKeyRequest | null> {
return this.backend.getOutgoingRoomKeyRequestByState(wantedStates);
return this.backend!.getOutgoingRoomKeyRequestByState(wantedStates);
}
/**
@ -252,7 +252,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
* @return {Promise<Array<*>>} Returns an array of requests in the given state
*/
public getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise<OutgoingRoomKeyRequest[]> {
return this.backend.getAllOutgoingRoomKeyRequestsByState(wantedState);
return this.backend!.getAllOutgoingRoomKeyRequestsByState(wantedState);
}
/**
@ -270,7 +270,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
deviceId: string,
wantedStates: number[],
): Promise<OutgoingRoomKeyRequest[]> {
return this.backend.getOutgoingRoomKeyRequestsByTarget(
return this.backend!.getOutgoingRoomKeyRequestsByTarget(
userId, deviceId, wantedStates,
);
}
@ -292,7 +292,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
expectedState: number,
updates: Partial<OutgoingRoomKeyRequest>,
): Promise<OutgoingRoomKeyRequest | null> {
return this.backend.updateOutgoingRoomKeyRequest(
return this.backend!.updateOutgoingRoomKeyRequest(
requestId, expectedState, updates,
);
}
@ -310,7 +310,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
requestId: string,
expectedState: number,
): Promise<OutgoingRoomKeyRequest | null> {
return this.backend.deleteOutgoingRoomKeyRequest(requestId, expectedState);
return this.backend!.deleteOutgoingRoomKeyRequest(requestId, expectedState);
}
// Olm Account
@ -323,7 +323,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
* @param {function(string)} func Called with the account pickle
*/
public getAccount(txn: IDBTransaction, func: (accountPickle: string | null) => void) {
this.backend.getAccount(txn, func);
this.backend!.getAccount(txn, func);
}
/**
@ -334,7 +334,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
* @param {string} accountPickle The new account pickle to store.
*/
public storeAccount(txn: IDBTransaction, accountPickle: string): void {
this.backend.storeAccount(txn, accountPickle);
this.backend!.storeAccount(txn, accountPickle);
}
/**
@ -349,7 +349,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
txn: IDBTransaction,
func: (keys: Record<string, ICrossSigningKey> | null) => void,
): void {
this.backend.getCrossSigningKeys(txn, func);
this.backend!.getCrossSigningKeys(txn, func);
}
/**
@ -362,7 +362,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
func: (key: SecretStorePrivateKeys[K] | null) => void,
type: K,
): void {
this.backend.getSecretStorePrivateKey(txn, func, type);
this.backend!.getSecretStorePrivateKey(txn, func, type);
}
/**
@ -372,7 +372,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
* @param {string} keys keys object as getCrossSigningKeys()
*/
public storeCrossSigningKeys(txn: IDBTransaction, keys: Record<string, ICrossSigningKey>): void {
this.backend.storeCrossSigningKeys(txn, keys);
this.backend!.storeCrossSigningKeys(txn, keys);
}
/**
@ -387,7 +387,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
type: K,
key: SecretStorePrivateKeys[K],
): void {
this.backend.storeSecretStorePrivateKey(txn, type, key);
this.backend!.storeSecretStorePrivateKey(txn, type, key);
}
// Olm sessions
@ -398,7 +398,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
* @param {function(int)} func Called with the count of sessions
*/
public countEndToEndSessions(txn: IDBTransaction, func: (count: number) => void): void {
this.backend.countEndToEndSessions(txn, func);
this.backend!.countEndToEndSessions(txn, func);
}
/**
@ -417,9 +417,9 @@ export class IndexedDBCryptoStore implements CryptoStore {
deviceKey: string,
sessionId: string,
txn: IDBTransaction,
func: (sessions: { [ sessionId: string ]: ISessionInfo }) => void,
func: (session: ISessionInfo | null) => void,
): void {
this.backend.getEndToEndSession(deviceKey, sessionId, txn, func);
this.backend!.getEndToEndSession(deviceKey, sessionId, txn, func);
}
/**
@ -438,7 +438,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
txn: IDBTransaction,
func: (sessions: { [sessionId: string]: ISessionInfo }) => void,
): void {
this.backend.getEndToEndSessions(deviceKey, txn, func);
this.backend!.getEndToEndSessions(deviceKey, txn, func);
}
/**
@ -448,8 +448,8 @@ export class IndexedDBCryptoStore implements CryptoStore {
* an object with, deviceKey, lastReceivedMessageTs, sessionId
* and session keys.
*/
public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo) => void): void {
this.backend.getAllEndToEndSessions(txn, func);
public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo | null) => void): void {
this.backend!.getAllEndToEndSessions(txn, func);
}
/**
@ -465,19 +465,19 @@ export class IndexedDBCryptoStore implements CryptoStore {
sessionInfo: ISessionInfo,
txn: IDBTransaction,
): void {
this.backend.storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn);
this.backend!.storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn);
}
public storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void> {
return this.backend.storeEndToEndSessionProblem(deviceKey, type, fixed);
return this.backend!.storeEndToEndSessionProblem(deviceKey, type, fixed);
}
public getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null> {
return this.backend.getEndToEndSessionProblem(deviceKey, timestamp);
return this.backend!.getEndToEndSessionProblem(deviceKey, timestamp);
}
public filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]> {
return this.backend.filterOutNotifiedErrorDevices(devices);
return this.backend!.filterOutNotifiedErrorDevices(devices);
}
// Inbound group sessions
@ -497,7 +497,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
txn: IDBTransaction,
func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void,
): void {
this.backend.getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func);
this.backend!.getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func);
}
/**
@ -511,7 +511,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
txn: IDBTransaction,
func: (session: ISession | null) => void,
): void {
this.backend.getAllEndToEndInboundGroupSessions(txn, func);
this.backend!.getAllEndToEndInboundGroupSessions(txn, func);
}
/**
@ -529,7 +529,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
sessionData: InboundGroupSessionData,
txn: IDBTransaction,
): void {
this.backend.addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn);
this.backend!.addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn);
}
/**
@ -547,7 +547,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
sessionData: InboundGroupSessionData,
txn: IDBTransaction,
): void {
this.backend.storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn);
this.backend!.storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn);
}
public storeEndToEndInboundGroupSessionWithheld(
@ -556,7 +556,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
sessionData: IWithheld,
txn: IDBTransaction,
): void {
this.backend.storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn);
this.backend!.storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn);
}
// End-to-end device tracking
@ -572,7 +572,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
* @param {*} txn An active transaction. See doTxn().
*/
public storeEndToEndDeviceData(deviceData: IDeviceData, txn: IDBTransaction): void {
this.backend.storeEndToEndDeviceData(deviceData, txn);
this.backend!.storeEndToEndDeviceData(deviceData, txn);
}
/**
@ -583,7 +583,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
* device data
*/
public getEndToEndDeviceData(txn: IDBTransaction, func: (deviceData: IDeviceData | null) => void): void {
this.backend.getEndToEndDeviceData(txn, func);
this.backend!.getEndToEndDeviceData(txn, func);
}
// End to End Rooms
@ -595,7 +595,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
* @param {*} txn An active transaction. See doTxn().
*/
public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: IDBTransaction): void {
this.backend.storeEndToEndRoom(roomId, roomInfo, txn);
this.backend!.storeEndToEndRoom(roomId, roomInfo, txn);
}
/**
@ -604,7 +604,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
* @param {function(Object)} func Function called with the end to end encrypted rooms
*/
public getEndToEndRooms(txn: IDBTransaction, func: (rooms: Record<string, IRoomEncryption>) => void): void {
this.backend.getEndToEndRooms(txn, func);
this.backend!.getEndToEndRooms(txn, func);
}
// session backups
@ -616,7 +616,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
* @returns {Promise} resolves to an array of inbound group sessions
*/
public getSessionsNeedingBackup(limit: number): Promise<ISession[]> {
return this.backend.getSessionsNeedingBackup(limit);
return this.backend!.getSessionsNeedingBackup(limit);
}
/**
@ -625,7 +625,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
* @returns {Promise} resolves to the number of sessions
*/
public countSessionsNeedingBackup(txn?: IDBTransaction): Promise<number> {
return this.backend.countSessionsNeedingBackup(txn);
return this.backend!.countSessionsNeedingBackup(txn);
}
/**
@ -635,7 +635,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
* @returns {Promise} resolves when the sessions are unmarked
*/
public unmarkSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise<void> {
return this.backend.unmarkSessionsNeedingBackup(sessions, txn);
return this.backend!.unmarkSessionsNeedingBackup(sessions, txn);
}
/**
@ -645,7 +645,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
* @returns {Promise} resolves when the sessions are marked
*/
public markSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise<void> {
return this.backend.markSessionsNeedingBackup(sessions, txn);
return this.backend!.markSessionsNeedingBackup(sessions, txn);
}
/**
@ -661,7 +661,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
sessionId: string,
txn?: IDBTransaction,
): void {
this.backend.addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn);
this.backend!.addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn);
}
/**
@ -674,7 +674,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
roomId: string,
txn?: IDBTransaction,
): Promise<[senderKey: string, sessionId: string][]> {
return this.backend.getSharedHistoryInboundGroupSessions(roomId, txn);
return this.backend!.getSharedHistoryInboundGroupSessions(roomId, txn);
}
/**
@ -685,7 +685,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
parkedData: ParkedSharedHistory,
txn?: IDBTransaction,
): void {
this.backend.addParkedSharedHistory(roomId, parkedData, txn);
this.backend!.addParkedSharedHistory(roomId, parkedData, txn);
}
/**
@ -695,7 +695,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
roomId: string,
txn?: IDBTransaction,
): Promise<ParkedSharedHistory[]> {
return this.backend.takeParkedSharedHistory(roomId, txn);
return this.backend!.takeParkedSharedHistory(roomId, txn);
}
/**
@ -720,7 +720,12 @@ export class IndexedDBCryptoStore implements CryptoStore {
* reject with that exception. On synchronous backends, the
* exception will propagate to the caller of the getFoo method.
*/
doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn: IDBTransaction) => T, log?: PrefixedLogger): Promise<T> {
return this.backend.doTxn(mode, stores, func, log);
public doTxn<T>(
mode: Mode,
stores: Iterable<string>,
func: (txn: IDBTransaction) => T,
log?: PrefixedLogger,
): Promise<T> {
return this.backend!.doTxn<T>(mode, stores, func as (txn: unknown) => T, log);
}
}

View File

@ -69,7 +69,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
public static exists(store: Storage): boolean {
const length = store.length;
for (let i = 0; i < length; i++) {
if (store.key(i).startsWith(E2E_PREFIX)) {
if (store.key(i)?.startsWith(E2E_PREFIX)) {
return true;
}
}
@ -85,7 +85,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
public countEndToEndSessions(txn: unknown, func: (count: number) => void): void {
let count = 0;
for (let i = 0; i < this.store.length; ++i) {
if (this.store.key(i).startsWith(keyEndToEndSessions(''))) ++count;
if (this.store.key(i)?.startsWith(keyEndToEndSessions(''))) ++count;
}
func(count);
}
@ -129,8 +129,8 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
public getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo) => void): void {
for (let i = 0; i < this.store.length; ++i) {
if (this.store.key(i).startsWith(keyEndToEndSessions(''))) {
const deviceKey = this.store.key(i).split('/')[1];
if (this.store.key(i)?.startsWith(keyEndToEndSessions(''))) {
const deviceKey = this.store.key(i)!.split('/')[1];
for (const sess of Object.values(this._getEndToEndSessions(deviceKey))) {
func(sess);
}
@ -220,7 +220,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
public getAllEndToEndInboundGroupSessions(txn: unknown, func: (session: ISession | null) => void): void {
for (let i = 0; i < this.store.length; ++i) {
const key = this.store.key(i);
if (key.startsWith(KEY_INBOUND_SESSION_PREFIX)) {
if (key?.startsWith(KEY_INBOUND_SESSION_PREFIX)) {
// we can't use split, as the components we are trying to split out
// might themselves contain '/' characters. We rely on the
// senderKey being a (32-byte) curve25519 key, base64-encoded
@ -229,7 +229,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
func({
senderKey: key.slice(KEY_INBOUND_SESSION_PREFIX.length, KEY_INBOUND_SESSION_PREFIX.length + 43),
sessionId: key.slice(KEY_INBOUND_SESSION_PREFIX.length + 44),
sessionData: getJsonItem(this.store, key),
sessionData: getJsonItem(this.store, key)!,
});
}
}
@ -297,9 +297,9 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
for (let i = 0; i < this.store.length; ++i) {
const key = this.store.key(i);
if (key.startsWith(prefix)) {
if (key?.startsWith(prefix)) {
const roomId = key.slice(prefix.length);
result[roomId] = getJsonItem(this.store, key);
result[roomId] = getJsonItem(this.store, key)!;
}
}
func(result);
@ -320,7 +320,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
sessions.push({
senderKey: senderKey,
sessionId: sessionId,
sessionData: sessionData,
sessionData: sessionData!,
});
},
);
@ -417,10 +417,10 @@ function getJsonItem<T>(store: Storage, key: string): T | null {
try {
// if the key is absent, store.getItem() returns null, and
// JSON.parse(null) === null, so this returns null.
return JSON.parse(store.getItem(key));
return JSON.parse(store.getItem(key)!);
} catch (e) {
logger.log("Error: Failed to get key %s: %s", key, e.stack || e);
logger.log(e.stack);
logger.log("Error: Failed to get key %s: %s", key, (<Error>e).message);
logger.log((<Error>e).stack);
}
return null;
}

View File

@ -54,7 +54,7 @@ export class MemoryCryptoStore implements CryptoStore {
private inboundGroupSessions: { [sessionKey: string]: InboundGroupSessionData } = {};
private inboundGroupSessionsWithheld: Record<string, IWithheld> = {};
// Opaque device data object
private deviceData: IDeviceData = null;
private deviceData: IDeviceData | null = null;
private rooms: { [roomId: string]: IRoomEncryption } = {};
private sessionsNeedingBackup: { [sessionKey: string]: boolean } = {};
private sharedHistoryInboundGroupSessions: { [roomId: string]: [senderKey: string, sessionId: string][] } = {};

View File

@ -55,14 +55,14 @@ export class VerificationBase<
> extends TypedEventEmitter<Events | VerificationEvent, Arguments, VerificationEventHandlerMap> {
private cancelled = false;
private _done = false;
private promise: Promise<void> = null;
private transactionTimeoutTimer: ReturnType<typeof setTimeout> = null;
protected expectedEvent: string;
private resolve: () => void;
private reject: (e: Error | MatrixEvent) => void;
private resolveEvent: (e: MatrixEvent) => void;
private rejectEvent: (e: Error) => void;
private started: boolean;
private promise: Promise<void> | null = null;
private transactionTimeoutTimer: ReturnType<typeof setTimeout> | null = null;
protected expectedEvent?: string;
private resolve?: () => void;
private reject?: (e: Error | MatrixEvent) => void;
private resolveEvent?: (e: MatrixEvent) => void;
private rejectEvent?: (e: Error) => void;
private started?: boolean;
/**
* Base class for verification methods.
@ -187,7 +187,7 @@ export class VerificationBase<
this.expectedEvent = undefined;
this.rejectEvent = undefined;
this.resetTimer();
this.resolveEvent(e);
this.resolveEvent?.(e);
}
} else if (e.getType() === EventType.KeyVerificationCancel) {
const reject = this.reject;
@ -218,11 +218,11 @@ export class VerificationBase<
}
}
public done(): Promise<KeysDuringVerification | void> {
public async done(): Promise<KeysDuringVerification | void> {
this.endTimer(); // always kill the activity timer
if (!this._done) {
this.request.onVerifierFinished();
this.resolve();
this.resolve?.();
return requestKeysDuringVerification(this.baseApis, this.userId, this.deviceId);
}
}
@ -291,7 +291,7 @@ export class VerificationBase<
this.endTimer();
resolve(...args);
};
this.reject = (e: Error) => {
this.reject = (e: Error | MatrixEvent) => {
this._done = true;
this.endTimer();
reject(e);
@ -301,12 +301,12 @@ export class VerificationBase<
this.started = true;
this.resetTimer(); // restart the timeout
new Promise<void>((resolve, reject) => {
const crossSignId = this.baseApis.crypto.deviceList.getStoredCrossSigningForUser(this.userId)?.getId();
const crossSignId = this.baseApis.crypto!.deviceList.getStoredCrossSigningForUser(this.userId)?.getId();
if (crossSignId === this.deviceId) {
reject(new Error("Device ID is the same as the cross-signing ID"));
}
resolve();
}).then(() => this.doVerification()).then(this.done.bind(this), this.cancel.bind(this));
}).then(() => this.doVerification!()).then(this.done.bind(this), this.cancel.bind(this));
}
return this.promise;
}
@ -326,7 +326,7 @@ export class VerificationBase<
verifier(keyId, device, keyInfo);
verifiedDevices.push([deviceId, keyId, device.keys[keyId]]);
} else {
const crossSigningInfo = this.baseApis.crypto.deviceList.getStoredCrossSigningForUser(userId);
const crossSigningInfo = this.baseApis.crypto!.deviceList.getStoredCrossSigningForUser(userId);
if (crossSigningInfo && crossSigningInfo.getId() === deviceId) {
verifier(keyId, DeviceInfo.fromStorage({
keys: {
@ -356,7 +356,7 @@ export class VerificationBase<
// to upload each signature in a separate API call which is silly because the
// API supports as many signatures as you like.
for (const [deviceId, keyId, key] of verifiedDevices) {
await this.baseApis.crypto.setDeviceVerification(userId, deviceId, true, null, null, { [keyId]: key });
await this.baseApis.crypto!.setDeviceVerification(userId, deviceId, true, null, null, { [keyId]: key });
}
// if one of the user's own devices is being marked as verified / unverified,

View File

@ -49,7 +49,7 @@ type EventHandlerMap = {
* @extends {module:crypto/verification/Base}
*/
export class ReciprocateQRCode extends Base<QrCodeEvent, EventHandlerMap> {
public reciprocateQREvent: IReciprocateQr;
public reciprocateQREvent?: IReciprocateQr;
public static factory(
channel: IVerificationChannel,
@ -76,7 +76,7 @@ export class ReciprocateQRCode extends Base<QrCodeEvent, EventHandlerMap> {
const { qrCodeData } = this.request;
// 1. check the secret
if (this.startEvent.getContent()['secret'] !== qrCodeData.encodedSharedSecret) {
if (this.startEvent.getContent()['secret'] !== qrCodeData?.encodedSharedSecret) {
throw newKeyMismatchError();
}
@ -92,21 +92,21 @@ export class ReciprocateQRCode extends Base<QrCodeEvent, EventHandlerMap> {
// 3. determine key to sign / mark as trusted
const keys: Record<string, string> = {};
switch (qrCodeData.mode) {
switch (qrCodeData?.mode) {
case Mode.VerifyOtherUser: {
// add master key to keys to be signed, only if we're not doing self-verification
const masterKey = qrCodeData.otherUserMasterKey;
keys[`ed25519:${masterKey}`] = masterKey;
keys[`ed25519:${masterKey}`] = masterKey!;
break;
}
case Mode.VerifySelfTrusted: {
const deviceId = this.request.targetDevice.deviceId;
keys[`ed25519:${deviceId}`] = qrCodeData.otherDeviceKey;
keys[`ed25519:${deviceId}`] = qrCodeData.otherDeviceKey!;
break;
}
case Mode.VerifySelfUntrusted: {
const masterKey = qrCodeData.myMasterKey;
keys[`ed25519:${masterKey}`] = masterKey;
keys[`ed25519:${masterKey}`] = masterKey!;
break;
}
}
@ -158,41 +158,41 @@ export class QRCodeData {
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
public readonly otherUserMasterKey: string | undefined,
public readonly otherUserMasterKey: string | null,
// only set when mode is MODE_VERIFY_SELF_TRUSTED, device key of other party at time of generating QR code
public readonly otherDeviceKey: string | undefined,
public readonly otherDeviceKey: string | null,
// only set when mode is MODE_VERIFY_SELF_UNTRUSTED, own master key at time of generating QR code
public readonly myMasterKey: string | undefined,
public readonly myMasterKey: string | null,
private readonly buffer: Buffer,
) {}
public static async create(request: VerificationRequest, client: MatrixClient): Promise<QRCodeData> {
const sharedSecret = QRCodeData.generateSharedSecret();
const mode = QRCodeData.determineMode(request, client);
let otherUserMasterKey = null;
let otherDeviceKey = null;
let myMasterKey = null;
let otherUserMasterKey: string | null = null;
let otherDeviceKey: string | null = null;
let myMasterKey: string | null = null;
if (mode === Mode.VerifyOtherUser) {
const otherUserCrossSigningInfo =
client.getStoredCrossSigningForUser(request.otherUserId);
otherUserMasterKey = otherUserCrossSigningInfo.getId("master");
const otherUserCrossSigningInfo = client.getStoredCrossSigningForUser(request.otherUserId);
otherUserMasterKey = otherUserCrossSigningInfo!.getId("master");
} else if (mode === Mode.VerifySelfTrusted) {
otherDeviceKey = await QRCodeData.getOtherDeviceKey(request, client);
} else if (mode === Mode.VerifySelfUntrusted) {
const myUserId = client.getUserId();
const myUserId = client.getUserId()!;
const myCrossSigningInfo = client.getStoredCrossSigningForUser(myUserId);
myMasterKey = myCrossSigningInfo.getId("master");
myMasterKey = myCrossSigningInfo!.getId("master");
}
const qrData = QRCodeData.generateQrData(
request, client, mode,
request,
client,
mode,
sharedSecret,
otherUserMasterKey,
otherDeviceKey,
myMasterKey,
otherUserMasterKey!,
otherDeviceKey!,
myMasterKey!,
);
const buffer = QRCodeData.generateBuffer(qrData);
return new QRCodeData(mode, sharedSecret,
otherUserMasterKey, otherDeviceKey, myMasterKey, buffer);
return new QRCodeData(mode, sharedSecret, otherUserMasterKey, otherDeviceKey, myMasterKey, buffer);
}
/**
@ -213,12 +213,11 @@ export class QRCodeData {
}
private static async getOtherDeviceKey(request: VerificationRequest, client: MatrixClient): Promise<string> {
const myUserId = client.getUserId();
const myUserId = client.getUserId()!;
const otherDevice = request.targetDevice;
const otherDeviceId = otherDevice ? otherDevice.deviceId : null;
const device = client.getStoredDevice(myUserId, otherDeviceId);
const device = otherDevice.deviceId ? client.getStoredDevice(myUserId, otherDevice.deviceId) : undefined;
if (!device) {
throw new Error("could not find device " + otherDeviceId);
throw new Error("could not find device " + otherDevice?.deviceId);
}
return device.getFingerprint();
}
@ -245,11 +244,11 @@ export class QRCodeData {
client: MatrixClient,
mode: Mode,
encodedSharedSecret: string,
otherUserMasterKey: string,
otherDeviceKey: string,
myMasterKey: string,
otherUserMasterKey?: string,
otherDeviceKey?: string,
myMasterKey?: string,
): IQrData {
const myUserId = client.getUserId();
const myUserId = client.getUserId()!;
const transactionId = request.channel.transactionId;
const qrData = {
prefix: BINARY_PREFIX,
@ -265,18 +264,18 @@ export class QRCodeData {
if (mode === Mode.VerifyOtherUser) {
// First key is our master cross signing key
qrData.firstKeyB64 = myCrossSigningInfo.getId("master");
qrData.firstKeyB64 = myCrossSigningInfo!.getId("master")!;
// Second key is the other user's master cross signing key
qrData.secondKeyB64 = otherUserMasterKey;
qrData.secondKeyB64 = otherUserMasterKey!;
} else if (mode === Mode.VerifySelfTrusted) {
// First key is our master cross signing key
qrData.firstKeyB64 = myCrossSigningInfo.getId("master");
qrData.secondKeyB64 = otherDeviceKey;
qrData.firstKeyB64 = myCrossSigningInfo!.getId("master")!;
qrData.secondKeyB64 = otherDeviceKey!;
} else if (mode === Mode.VerifySelfUntrusted) {
// First key is our device's key
qrData.firstKeyB64 = client.getDeviceEd25519Key();
qrData.firstKeyB64 = client.getDeviceEd25519Key()!;
// Second key is what we think our master cross signing key is
qrData.secondKeyB64 = myMasterKey;
qrData.secondKeyB64 = myMasterKey!;
}
return qrData;
}

View File

@ -96,25 +96,25 @@ export class VerificationRequest<
private eventsByUs = new Map<string, MatrixEvent>();
private eventsByThem = new Map<string, MatrixEvent>();
private _observeOnly = false;
private timeoutTimer: ReturnType<typeof setTimeout> = null;
private timeoutTimer: ReturnType<typeof setTimeout> | null = null;
private _accepting = false;
private _declining = false;
private verifierHasFinished = false;
private _cancelled = false;
private _chosenMethod: VerificationMethod = null;
private _chosenMethod: VerificationMethod | null = null;
// we keep a copy of the QR Code data (including other user master key) around
// for QR reciprocate verification, to protect against
// cross-signing identity reset between the .ready and .start event
// and signing the wrong key after .start
private _qrCodeData: QRCodeData = null;
private _qrCodeData: QRCodeData | null = null;
// The timestamp when we received the request event from the other side
private requestReceivedAt: number = null;
private requestReceivedAt: number | null = null;
private commonMethods: VerificationMethod[] = [];
private _phase: Phase;
public _cancellingUserId: string; // Used in tests only
private _verifier: VerificationBase<any, any>;
private _verifier?: VerificationBase<any, any>;
constructor(
public readonly channel: C,
@ -204,7 +204,7 @@ export class VerificationRequest<
}
/** the method picked in the .start event */
public get chosenMethod(): VerificationMethod {
public get chosenMethod(): VerificationMethod | null {
return this._chosenMethod;
}
@ -236,7 +236,7 @@ export class VerificationRequest<
* The key verification request event.
* @returns {MatrixEvent} The request event, or falsey if not found.
*/
public get requestEvent(): MatrixEvent {
public get requestEvent(): MatrixEvent | undefined {
return this.getEventByEither(REQUEST_TYPE);
}
@ -246,7 +246,7 @@ export class VerificationRequest<
}
/** The verifier to do the actual verification, once the method has been established. Only defined when the `phase` is PHASE_STARTED. */
public get verifier(): VerificationBase<any, any> {
public get verifier(): VerificationBase<any, any> | undefined {
return this._verifier;
}
@ -270,7 +270,7 @@ export class VerificationRequest<
}
/** Only set after a .ready if the other party can scan a QR code */
public get qrCodeData(): QRCodeData {
public get qrCodeData(): QRCodeData | null {
return this._qrCodeData;
}
@ -340,7 +340,7 @@ export class VerificationRequest<
/** The id of the user that initiated the request */
public get requestingUserId(): string {
if (this.initiatedByMe) {
return this.client.getUserId();
return this.client.getUserId()!;
} else {
return this.otherUserId;
}
@ -351,7 +351,7 @@ export class VerificationRequest<
if (this.initiatedByMe) {
return this.otherUserId;
} else {
return this.client.getUserId();
return this.client.getUserId()!;
}
}
@ -368,7 +368,7 @@ export class VerificationRequest<
* The id of the user that cancelled the request,
* only defined when phase is PHASE_CANCELLED
*/
public get cancellingUserId(): string {
public get cancellingUserId(): string | undefined {
const myCancel = this.eventsByUs.get(CANCEL_TYPE);
const theirCancel = this.eventsByThem.get(CANCEL_TYPE);
@ -422,7 +422,7 @@ export class VerificationRequest<
*/
public beginKeyVerification(
method: VerificationMethod,
targetDevice: ITargetDevice = null,
targetDevice: ITargetDevice | null = null,
): VerificationBase<any, any> {
// need to allow also when unsent in case of to_device
if (!this.observeOnly && !this._verifier) {
@ -443,7 +443,7 @@ export class VerificationRequest<
this._chosenMethod = method;
}
}
return this._verifier;
return this._verifier!;
}
/**
@ -470,7 +470,7 @@ export class VerificationRequest<
if (this._verifier) {
return this._verifier.cancel(errorFactory(code, reason)());
} else {
this._cancellingUserId = this.client.getUserId();
this._cancellingUserId = this.client.getUserId()!;
await this.channel.send(CANCEL_TYPE, { code, reason });
}
}
@ -525,11 +525,11 @@ export class VerificationRequest<
}
}
private getEventByEither(type: string): MatrixEvent {
private getEventByEither(type: string): MatrixEvent | undefined {
return this.eventsByThem.get(type) || this.eventsByUs.get(type);
}
private getEventBy(type: string, byThem = false): MatrixEvent {
private getEventBy(type: string, byThem = false): MatrixEvent | undefined {
if (byThem) {
return this.eventsByThem.get(type);
} else {
@ -548,20 +548,18 @@ export class VerificationRequest<
transitions.push({ phase: PHASE_REQUESTED, event: requestEvent });
}
const readyEvent =
requestEvent && this.getEventBy(READY_TYPE, !hasRequestByThem);
const readyEvent = requestEvent && this.getEventBy(READY_TYPE, !hasRequestByThem);
if (readyEvent && phase() === PHASE_REQUESTED) {
transitions.push({ phase: PHASE_READY, event: readyEvent });
}
let startEvent;
let startEvent: MatrixEvent | undefined;
if (readyEvent || !requestEvent) {
const theirStartEvent = this.eventsByThem.get(START_TYPE);
const ourStartEvent = this.eventsByUs.get(START_TYPE);
// any party can send .start after a .ready or unsent
if (theirStartEvent && ourStartEvent) {
startEvent = theirStartEvent.getSender() < ourStartEvent.getSender() ?
theirStartEvent : ourStartEvent;
startEvent = theirStartEvent.getSender() < ourStartEvent.getSender() ? theirStartEvent : ourStartEvent;
} else {
startEvent = theirStartEvent ? theirStartEvent : ourStartEvent;
}
@ -569,7 +567,9 @@ export class VerificationRequest<
startEvent = this.getEventBy(START_TYPE, !hasRequestByThem);
}
if (startEvent) {
const fromRequestPhase = phase() === PHASE_REQUESTED && requestEvent.getSender() !== startEvent.getSender();
const fromRequestPhase = (
phase() === PHASE_REQUESTED && requestEvent?.getSender() !== startEvent.getSender()
);
const fromUnsentPhase = phase() === PHASE_UNSENT && this.channel.canCreateRequest(START_TYPE);
if (fromRequestPhase || phase() === PHASE_READY || fromUnsentPhase) {
transitions.push({ phase: PHASE_STARTED, event: startEvent });
@ -651,7 +651,7 @@ export class VerificationRequest<
if (newEvent.getType() !== START_TYPE) {
return false;
}
const oldEvent = this._verifier.startEvent;
const oldEvent = this._verifier!.startEvent;
let oldRaceIdentifier;
if (this.isSelfVerification) {
@ -890,9 +890,9 @@ export class VerificationRequest<
private createVerifier(
method: VerificationMethod,
startEvent: MatrixEvent = null,
targetDevice: ITargetDevice = null,
): VerificationBase<any, any> {
startEvent: MatrixEvent | null = null,
targetDevice: ITargetDevice | null = null,
): VerificationBase<any, any> | undefined {
if (!targetDevice) {
targetDevice = this.targetDevice;
}
@ -941,7 +941,7 @@ export class VerificationRequest<
}
}
public getEventFromOtherParty(type: string): MatrixEvent {
public getEventFromOtherParty(type: string): MatrixEvent | undefined {
return this.eventsByThem.get(type);
}
}

View File

@ -1,52 +0,0 @@
// can't just do InvalidStoreError extends Error
// because of http://babeljs.io/docs/usage/caveats/#classes
export function InvalidStoreError(reason, value) {
const message = `Store is invalid because ${reason}, ` +
`please stop the client, delete all data and start the client again`;
const instance = Reflect.construct(Error, [message]);
Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this));
instance.reason = reason;
instance.value = value;
return instance;
}
InvalidStoreError.TOGGLED_LAZY_LOADING = "TOGGLED_LAZY_LOADING";
InvalidStoreError.prototype = Object.create(Error.prototype, {
constructor: {
value: Error,
enumerable: false,
writable: true,
configurable: true,
},
});
Reflect.setPrototypeOf(InvalidStoreError, Error);
export function InvalidCryptoStoreError(reason) {
const message = `Crypto store is invalid because ${reason}, ` +
`please stop the client, delete all data and start the client again`;
const instance = Reflect.construct(Error, [message]);
Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this));
instance.reason = reason;
instance.name = 'InvalidCryptoStoreError';
return instance;
}
InvalidCryptoStoreError.TOO_NEW = "TOO_NEW";
InvalidCryptoStoreError.prototype = Object.create(Error.prototype, {
constructor: {
value: Error,
enumerable: false,
writable: true,
configurable: true,
},
});
Reflect.setPrototypeOf(InvalidCryptoStoreError, Error);
export class KeySignatureUploadError extends Error {
constructor(message, value) {
super(message);
this.value = value;
}
}

51
src/errors.ts Normal file
View File

@ -0,0 +1,51 @@
/*
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.
*/
export enum InvalidStoreState {
ToggledLazyLoading,
}
export class InvalidStoreError extends Error {
public static TOGGLED_LAZY_LOADING = InvalidStoreState.ToggledLazyLoading;
public constructor(public readonly reason: InvalidStoreState, public readonly value: any) {
const message = `Store is invalid because ${reason}, ` +
`please stop the client, delete all data and start the client again`;
super(message);
this.name = "InvalidStoreError";
}
}
export enum InvalidCryptoStoreState {
TooNew = "TOO_NEW",
}
export class InvalidCryptoStoreError extends Error {
public static TOO_NEW = InvalidCryptoStoreState.TooNew;
public constructor(public readonly reason: InvalidCryptoStoreState) {
const message = `Crypto store is invalid because ${reason}, ` +
`please stop the client, delete all data and start the client again`;
super(message);
this.name = 'InvalidCryptoStoreError';
}
}
export class KeySignatureUploadError extends Error {
public constructor(message: string, public readonly value: any) {
super(message);
}
}

View File

@ -36,11 +36,11 @@ export function eventMapperFor(client: MatrixClient, options: MapperOpts): Event
const room = client.getRoom(plainOldJsObject.room_id);
let event: MatrixEvent;
let event: MatrixEvent | undefined;
// If the event is already known to the room, let's re-use the model rather than duplicating.
// We avoid doing this to state events as they may be forward or backwards looking which tweaks behaviour.
if (room && plainOldJsObject.state_key === undefined) {
event = room.findEventById(plainOldJsObject.event_id);
event = room.findEventById(plainOldJsObject.event_id!);
}
if (!event || event.status) {

View File

@ -15,6 +15,7 @@ limitations under the License.
*/
import { IUsageLimit } from "../@types/partials";
import { MatrixEvent } from "../models/event";
interface IErrorJson extends Partial<IUsageLimit> {
[key: string]: any; // extensible
@ -50,7 +51,12 @@ export class MatrixError extends HTTPError {
public readonly errcode?: string;
public readonly data: IErrorJson;
constructor(errorJson: IErrorJson = {}, public readonly httpStatus?: number, public url?: string) {
constructor(
errorJson: IErrorJson = {},
public readonly httpStatus?: number,
public url?: string,
public event?: MatrixEvent,
) {
let message = errorJson.error || "Unknown message";
if (httpStatus) {
message = `[${httpStatus}] ${message}`;

View File

@ -73,7 +73,7 @@ export class FetchHttpApi<O extends IHttpOpts> {
public idServerRequest<T extends {}>(
method: Method,
path: string,
params: Record<string, string | string[]>,
params: Record<string, string | string[]> | undefined,
prefix: string,
accessToken?: string,
): Promise<ResponseType<T, O>> {
@ -96,7 +96,7 @@ export class FetchHttpApi<O extends IHttpOpts> {
headers: {},
};
if (accessToken) {
opts.headers.Authorization = `Bearer ${accessToken}`;
opts.headers!.Authorization = `Bearer ${accessToken}`;
}
return this.requestOtherUrl(method, fullUri, body, opts);
@ -286,10 +286,10 @@ export class FetchHttpApi<O extends IHttpOpts> {
credentials: "omit", // we send credentials via headers
});
} catch (e) {
if (e.name === "AbortError") {
if ((<Error>e).name === "AbortError") {
throw e;
}
throw new ConnectionError("fetch failed", e);
throw new ConnectionError("fetch failed", <Error>e);
} finally {
cleanup();
}

View File

@ -72,11 +72,11 @@ export function anySignal(signals: AbortSignal[]): {
* @returns {Error}
*/
export function parseErrorResponse(response: XMLHttpRequest | Response, body?: string): Error {
let contentType: ParsedMediaType;
let contentType: ParsedMediaType | null;
try {
contentType = getResponseContentType(response);
} catch (e) {
return e;
return <Error>e;
}
if (contentType?.type === "application/json" && body) {

View File

@ -59,16 +59,16 @@ log.methodFactory = function(methodName, logLevel, loggerName) {
* Drop-in replacement for <code>console</code> using {@link https://www.npmjs.com/package/loglevel|loglevel}.
* Can be tailored down to specific use cases if needed.
*/
export const logger: PrefixedLogger = log.getLogger(DEFAULT_NAMESPACE);
export const logger = log.getLogger(DEFAULT_NAMESPACE) as PrefixedLogger;
logger.setLevel(log.levels.DEBUG, false);
export interface PrefixedLogger extends Logger {
withPrefix?: (prefix: string) => PrefixedLogger;
prefix?: string;
withPrefix: (prefix: string) => PrefixedLogger;
prefix: string;
}
function extendLogger(logger: PrefixedLogger) {
logger.withPrefix = function(prefix: string): PrefixedLogger {
function extendLogger(logger: Logger) {
(<PrefixedLogger>logger).withPrefix = function(prefix: string): PrefixedLogger {
const existingPrefix = this.prefix || "";
return getPrefixedLogger(existingPrefix + prefix);
};
@ -77,7 +77,7 @@ function extendLogger(logger: PrefixedLogger) {
extendLogger(logger);
function getPrefixedLogger(prefix: string): PrefixedLogger {
const prefixLogger: PrefixedLogger = log.getLogger(`${DEFAULT_NAMESPACE}-${prefix}`);
const prefixLogger = log.getLogger(`${DEFAULT_NAMESPACE}-${prefix}`) as PrefixedLogger;
if (prefixLogger.prefix !== prefix) {
// Only do this setup work the first time through, as loggers are saved by name.
extendLogger(prefixLogger);

View File

@ -68,7 +68,7 @@ export function setCryptoStoreFactory(fac) {
}
export interface ICryptoCallbacks {
getCrossSigningKey?: (keyType: string, pubKey: string) => Promise<Uint8Array>;
getCrossSigningKey?: (keyType: string, pubKey: string) => Promise<Uint8Array | null>;
saveCrossSigningKeys?: (keys: Record<string, Uint8Array>) => void;
shouldUpgradeDeviceVerifications?: (
users: Record<string, any>

View File

@ -86,7 +86,7 @@ export class MSC3089TreeSpace {
public readonly room: Room;
public constructor(private client: MatrixClient, public readonly roomId: string) {
this.room = this.client.getRoom(this.roomId);
this.room = this.client.getRoom(this.roomId)!;
if (!this.room) throw new Error("Unknown room");
}
@ -282,7 +282,7 @@ export class MSC3089TreeSpace {
const members = this.room.currentState.getStateEvents(EventType.RoomMember);
for (const member of members) {
const isNotUs = member.getStateKey() !== this.client.getUserId();
if (isNotUs && kickMemberships.includes(member.getContent().membership)) {
if (isNotUs && kickMemberships.includes(member.getContent().membership!)) {
const stateKey = member.getStateKey();
if (!stateKey) {
throw new Error("State key not found for branch");

View File

@ -51,21 +51,21 @@ export const getBeaconInfoIdentifier = (event: MatrixEvent): BeaconIdentifier =>
// https://github.com/matrix-org/matrix-spec-proposals/pull/3672
export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.New>, BeaconEventHandlerMap> {
public readonly roomId: string;
private _beaconInfo: BeaconInfoState;
private _isLive: boolean;
private livenessWatchTimeout: ReturnType<typeof setTimeout>;
private _latestLocationEvent: MatrixEvent | undefined;
private _beaconInfo?: BeaconInfoState;
private _isLive?: boolean;
private livenessWatchTimeout?: ReturnType<typeof setTimeout>;
private _latestLocationEvent?: MatrixEvent;
constructor(
private rootEvent: MatrixEvent,
) {
super();
this.setBeaconInfo(this.rootEvent);
this.roomId = this.rootEvent.getRoomId();
this.roomId = this.rootEvent.getRoomId()!;
}
public get isLive(): boolean {
return this._isLive;
return !!this._isLive;
}
public get identifier(): BeaconIdentifier {
@ -77,14 +77,14 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
}
public get beaconInfoOwner(): string {
return this.rootEvent.getStateKey();
return this.rootEvent.getStateKey()!;
}
public get beaconInfoEventType(): string {
return this.rootEvent.getType();
}
public get beaconInfo(): BeaconInfoState {
public get beaconInfo(): BeaconInfoState | undefined {
return this._beaconInfo;
}
@ -101,7 +101,7 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
throw new Error('Invalid updating event');
}
// don't update beacon with an older event
if (beaconInfoEvent.event.origin_server_ts < this.rootEvent.event.origin_server_ts) {
if (beaconInfoEvent.getTs() < this.rootEvent.getTs()) {
return;
}
this.rootEvent = beaconInfoEvent;
@ -130,20 +130,21 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
}
this.checkLiveness();
if (!this.beaconInfo) return;
if (this.isLive) {
const expiryInMs = (this._beaconInfo?.timestamp + this._beaconInfo?.timeout) - Date.now();
const expiryInMs = (this.beaconInfo.timestamp + this.beaconInfo.timeout) - Date.now();
if (expiryInMs > 1) {
this.livenessWatchTimeout = setTimeout(
() => { this.monitorLiveness(); },
expiryInMs,
);
}
} else if (this._beaconInfo?.timestamp > Date.now()) {
} else if (this.beaconInfo.timestamp > Date.now()) {
// beacon start timestamp is in the future
// check liveness again then
this.livenessWatchTimeout = setTimeout(
() => { this.monitorLiveness(); },
this.beaconInfo?.timestamp - Date.now(),
this.beaconInfo.timestamp - Date.now(),
);
}
}
@ -165,22 +166,22 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
const { timestamp } = parsed;
return (
// only include positions that were taken inside the beacon's live period
isTimestampInDuration(this._beaconInfo.timestamp, this._beaconInfo.timeout, timestamp) &&
isTimestampInDuration(this._beaconInfo!.timestamp, this._beaconInfo!.timeout, timestamp) &&
// ignore positions older than our current latest location
(!this.latestLocationState || timestamp > this.latestLocationState.timestamp)
(!this.latestLocationState || timestamp > this.latestLocationState.timestamp!)
);
});
const latestLocationEvent = validLocationEvents.sort(sortEventsByLatestContentTimestamp)?.[0];
if (latestLocationEvent) {
this._latestLocationEvent = latestLocationEvent;
this.emit(BeaconEvent.LocationUpdate, this.latestLocationState);
this.emit(BeaconEvent.LocationUpdate, this.latestLocationState!);
}
}
private clearLatestLocation = () => {
this._latestLocationEvent = undefined;
this.emit(BeaconEvent.LocationUpdate, this.latestLocationState);
this.emit(BeaconEvent.LocationUpdate, this.latestLocationState!);
};
private setBeaconInfo(event: MatrixEvent): void {
@ -195,9 +196,10 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
// when Alice's system clock deviates slightly from Bob's a beacon Alice intended to be live
// may have a start timestamp in the future from Bob's POV
// handle this by adding 6min of leniency to the start timestamp when it is in the future
const startTimestamp = this._beaconInfo?.timestamp > Date.now() ?
this._beaconInfo?.timestamp - 360000 /* 6min */ :
this._beaconInfo?.timestamp;
if (!this.beaconInfo) return;
const startTimestamp = this.beaconInfo.timestamp > Date.now() ?
this.beaconInfo.timestamp - 360000 /* 6min */ :
this.beaconInfo.timestamp;
this._isLive = !!this._beaconInfo?.live &&
isTimestampInDuration(startTimestamp, this._beaconInfo?.timeout, Date.now());

View File

@ -822,7 +822,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
// linkedlist to see which comes first.
// first work forwards from timeline1
let tl = timeline1;
let tl: EventTimeline | null = timeline1;
while (tl) {
if (tl === timeline2) {
// timeline1 is before timeline2

View File

@ -77,7 +77,7 @@ export class EventTimeline {
event.sender = stateContext.getSentinelMember(event.getSender());
}
if (!event.target?.events?.member && event.getType() === EventType.RoomMember) {
event.target = stateContext.getSentinelMember(event.getStateKey());
event.target = stateContext.getSentinelMember(event.getStateKey()!);
}
if (event.isState()) {
@ -97,8 +97,8 @@ export class EventTimeline {
private baseIndex = 0;
private startState: RoomState;
private endState: RoomState;
private prevTimeline?: EventTimeline;
private nextTimeline?: EventTimeline;
private prevTimeline: EventTimeline | null = null;
private nextTimeline: EventTimeline | null = null;
public paginationRequests: Record<Direction, Promise<boolean> | null> = {
[Direction.Backward]: null,
[Direction.Forward]: null,
@ -131,9 +131,6 @@ export class EventTimeline {
this.endState = new RoomState(this.roomId);
this.endState.paginationToken = null;
this.prevTimeline = null;
this.nextTimeline = null;
// this is used by client.js
this.paginationRequests = { 'b': null, 'f': null };
@ -226,7 +223,7 @@ export class EventTimeline {
* Get the ID of the room for this timeline
* @return {string} room ID
*/
public getRoomId(): string {
public getRoomId(): string | null {
return this.roomId;
}
@ -234,7 +231,7 @@ export class EventTimeline {
* Get the filter for this timeline's timelineSet (if any)
* @return {Filter} filter
*/
public getFilter(): Filter {
public getFilter(): Filter | undefined {
return this.eventTimelineSet.getFilter();
}
@ -324,7 +321,7 @@ export class EventTimeline {
* @return {?EventTimeline} previous or following timeline, if they have been
* joined up.
*/
public getNeighbouringTimeline(direction: Direction): EventTimeline {
public getNeighbouringTimeline(direction: Direction): EventTimeline | null {
if (direction == EventTimeline.BACKWARDS) {
return this.prevTimeline;
} else if (direction == EventTimeline.FORWARDS) {
@ -391,7 +388,7 @@ export class EventTimeline {
roomState?: RoomState,
): void {
let toStartOfTimeline = !!toStartOfTimelineOrOpts;
let timelineWasEmpty: boolean;
let timelineWasEmpty: boolean | undefined;
if (typeof (toStartOfTimelineOrOpts) === 'object') {
({ toStartOfTimeline, roomState, timelineWasEmpty } = toStartOfTimelineOrOpts);
} else if (toStartOfTimelineOrOpts !== undefined) {

View File

@ -34,6 +34,7 @@ import { TypedReEmitter } from '../ReEmitter';
import { MatrixError } from "../http-api";
import { TypedEventEmitter } from "./typed-event-emitter";
import { EventStatus } from "./event-status";
import { DecryptionError } from "../crypto/algorithms";
export { EventStatus } from "./event-status";
@ -685,14 +686,6 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
* attempt is completed.
*/
public async attemptDecryption(crypto: Crypto, options: IDecryptOptions = {}): Promise<void> {
// For backwards compatibility purposes
// The function signature used to be attemptDecryption(crypto, isRetry)
if (typeof options === "boolean") {
options = {
isRetry: options,
};
}
// start with a couple of sanity checks.
if (!this.isEncrypted()) {
throw new Error("Attempt to decrypt event which isn't encrypted");
@ -822,15 +815,19 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
//
if (this.retryDecryption) {
// decryption error, but we have a retry queued.
logger.log(`Got error decrypting event (id=${this.getId()}: ${e.detailedString}), but retrying`, e);
logger.log(`Got error decrypting event (id=${this.getId()}: ` +
`${(<DecryptionError>e).detailedString}), but retrying`, e);
continue;
}
// decryption error, no retries queued. Warn about the error and
// set it to m.bad.encrypted.
logger.warn(`Got error decrypting event (id=${this.getId()}: ${e.detailedString})`, e);
logger.warn(
`Got error decrypting event (id=${this.getId()}: ${(<DecryptionError>e).detailedString})`,
e,
);
res = this.badEncryptedMessage(e.message);
res = this.badEncryptedMessage((<DecryptionError>e).message);
}
// at this point, we've either successfully decrypted the event, or have given up

View File

@ -118,31 +118,31 @@ export class RelationsContainer {
const { event_id: relatesToEventId, rel_type: relationType } = relation;
const eventType = event.getType();
let relationsForEvent = this.relations.get(relatesToEventId);
let relationsForEvent = this.relations.get(relatesToEventId!);
if (!relationsForEvent) {
relationsForEvent = new Map<RelationType | string, Map<EventType | string, Relations>>();
this.relations.set(relatesToEventId, relationsForEvent);
this.relations.set(relatesToEventId!, relationsForEvent);
}
let relationsWithRelType = relationsForEvent.get(relationType);
let relationsWithRelType = relationsForEvent.get(relationType!);
if (!relationsWithRelType) {
relationsWithRelType = new Map<EventType | string, Relations>();
relationsForEvent.set(relationType, relationsWithRelType);
relationsForEvent.set(relationType!, relationsWithRelType);
}
let relationsWithEventType = relationsWithRelType.get(eventType);
if (!relationsWithEventType) {
relationsWithEventType = new Relations(
relationType,
relationType!,
eventType,
this.client,
);
relationsWithRelType.set(eventType, relationsWithEventType);
const room = this.room ?? timelineSet?.room;
const relatesToEvent = timelineSet?.findEventById(relatesToEventId)
?? room?.findEventById(relatesToEventId)
?? room?.getPendingEvent(relatesToEventId);
const relatesToEvent = timelineSet?.findEventById(relatesToEventId!)
?? room?.findEventById(relatesToEventId!)
?? room?.getPendingEvent(relatesToEventId!);
if (relatesToEvent) {
relationsWithEventType.setTargetEvent(relatesToEvent);
}

View File

@ -47,7 +47,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
private annotationsByKey: Record<string, Set<MatrixEvent>> = {};
private annotationsBySender: Record<string, Set<MatrixEvent>> = {};
private sortedAnnotationsByKey: [string, Set<MatrixEvent>][] = [];
private targetEvent: MatrixEvent = null;
private targetEvent: MatrixEvent | null = null;
private creationEmitted = false;
private readonly client: MatrixClient;
@ -107,7 +107,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
this.addAnnotationToAggregation(event);
} else if (this.relationType === RelationType.Replace && this.targetEvent && !this.targetEvent.isState()) {
const lastReplacement = await this.getLastReplacement();
this.targetEvent.makeReplaced(lastReplacement);
this.targetEvent.makeReplaced(lastReplacement!);
}
event.on(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
@ -148,7 +148,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
this.removeAnnotationFromAggregation(event);
} else if (this.relationType === RelationType.Replace && this.targetEvent && !this.targetEvent.isState()) {
const lastReplacement = await this.getLastReplacement();
this.targetEvent.makeReplaced(lastReplacement);
this.targetEvent.makeReplaced(lastReplacement!);
}
this.emit(RelationsEvent.Remove, event);
@ -189,10 +189,8 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
}
private addAnnotationToAggregation(event: MatrixEvent): void {
const { key } = event.getRelation();
if (!key) {
return;
}
const { key } = event.getRelation() ?? {};
if (!key) return;
let eventsForKey = this.annotationsByKey[key];
if (!eventsForKey) {
@ -218,10 +216,8 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
}
private removeAnnotationFromAggregation(event: MatrixEvent): void {
const { key } = event.getRelation();
if (!key) {
return;
}
const { key } = event.getRelation() ?? {};
if (!key) return;
const eventsForKey = this.annotationsByKey[key];
if (eventsForKey) {
@ -265,7 +261,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
this.removeAnnotationFromAggregation(redactedEvent);
} else if (this.relationType === RelationType.Replace && this.targetEvent && !this.targetEvent.isState()) {
const lastReplacement = await this.getLastReplacement();
this.targetEvent.makeReplaced(lastReplacement);
this.targetEvent.makeReplaced(lastReplacement!);
}
redactedEvent.removeListener(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
@ -283,7 +279,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
* An array of [key, events] pairs sorted by descending event count.
* The events are stored in a Set (which preserves insertion order).
*/
public getSortedAnnotationsByKey() {
public getSortedAnnotationsByKey(): [string, Set<MatrixEvent>][] | null {
if (this.relationType !== RelationType.Annotation) {
// Other relation types are not grouped currently.
return null;
@ -301,7 +297,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
* An object with each relation sender as a key and the matching Set of
* events for that sender as a value.
*/
public getAnnotationsBySender() {
public getAnnotationsBySender(): Record<string, Set<MatrixEvent>> | null {
if (this.relationType !== RelationType.Annotation) {
// Other relation types are not grouped currently.
return null;
@ -335,8 +331,8 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
const replaceRelation = this.targetEvent.getServerAggregatedRelation<IAggregatedRelation>(RelationType.Replace);
const minTs = replaceRelation?.origin_server_ts;
const lastReplacement = this.getRelations().reduce((last, event) => {
if (event.getSender() !== this.targetEvent.getSender()) {
const lastReplacement = this.getRelations().reduce<MatrixEvent | null>((last, event) => {
if (event.getSender() !== this.targetEvent!.getSender()) {
return last;
}
if (minTs && minTs > event.getTs()) {
@ -348,8 +344,8 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
return event;
}, null);
if (lastReplacement?.shouldAttemptDecryption()) {
await lastReplacement.attemptDecryption(this.client.crypto);
if (lastReplacement?.shouldAttemptDecryption() && this.client.isCryptoEnabled()) {
await lastReplacement.attemptDecryption(this.client.crypto!);
} else if (lastReplacement?.isBeingDecrypted()) {
await lastReplacement.getDecryptionPromise();
}

View File

@ -1604,7 +1604,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
// find the earliest unfiltered timeline
let timeline = unfilteredLiveTimeline;
while (timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS)) {
timeline = timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS);
timeline = timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS)!;
}
timelineSet.getLiveTimeline().setPaginationToken(

View File

@ -83,7 +83,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
private reEmitter: TypedReEmitter<EmittedEvents, EventHandlerMap>;
private lastEvent: MatrixEvent;
private lastEvent!: MatrixEvent;
private replyCount = 0;
public readonly room: Room;
@ -185,7 +185,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
this.lastEvent = events.find(e => (
!e.isRedacted() &&
e.isRelation(THREAD_RELATION_TYPE.name)
)) ?? this.rootEvent;
)) ?? this.rootEvent!;
this.emit(ThreadEvent.Update, this);
};
@ -267,7 +267,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
this.client.decryptEventIfNeeded(event, {});
} else if (!toStartOfTimeline &&
this.initialEventsFetched &&
event.localTimestamp > this.lastReply()?.localTimestamp
event.localTimestamp > this.lastReply()!.localTimestamp
) {
this.fetchEditsWhereNeeded(event);
this.addEventToTimeline(event, false);
@ -289,7 +289,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
}
}
private getRootEventBundledRelationship(rootEvent = this.rootEvent): IThreadBundledRelationship {
private getRootEventBundledRelationship(rootEvent = this.rootEvent): IThreadBundledRelationship | undefined {
return rootEvent?.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
}
@ -302,7 +302,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
if (Thread.hasServerSideSupport && bundledRelationship) {
this.replyCount = bundledRelationship.count;
this._currentUserParticipated = bundledRelationship.current_user_participated;
this._currentUserParticipated = !!bundledRelationship.current_user_participated;
const event = new MatrixEvent({
room_id: this.rootEvent.getRoomId(),
@ -407,7 +407,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
}
public async fetchEvents(opts: IRelationsRequestOpts = { limit: 20, dir: Direction.Backward }): Promise<{
originalEvent: MatrixEvent;
originalEvent?: MatrixEvent;
events: MatrixEvent[];
nextBatch?: string | null;
prevBatch?: string;
@ -427,7 +427,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
// When there's no nextBatch returned with a `from` request we have reached
// the end of the thread, and therefore want to return an empty one
if (!opts.to && !nextBatch) {
if (!opts.to && !nextBatch && originalEvent) {
events = [...events, originalEvent];
}

View File

@ -184,7 +184,7 @@ export class PushProcessor {
private static cachedGlobToRegex: Record<string, RegExp> = {}; // $glob: RegExp
private matchingRuleFromKindSet(ev: MatrixEvent, kindset: PushRuleSet): IAnnotatedPushRule {
private matchingRuleFromKindSet(ev: MatrixEvent, kindset: PushRuleSet): IAnnotatedPushRule | null {
for (let ruleKindIndex = 0; ruleKindIndex < RULEKINDS_IN_ORDER.length; ++ruleKindIndex) {
const kind = RULEKINDS_IN_ORDER[ruleKindIndex];
const ruleset = kindset[kind];
@ -338,19 +338,19 @@ export class PushProcessor {
private eventFulfillsDisplayNameCondition(cond: IContainsDisplayNameCondition, ev: MatrixEvent): boolean {
let content = ev.getContent();
if (ev.isEncrypted() && ev.getClearContent()) {
content = ev.getClearContent();
content = ev.getClearContent()!;
}
if (!content || !content.body || typeof content.body != 'string') {
return false;
}
const room = this.client.getRoom(ev.getRoomId());
if (!room || !room.currentState || !room.currentState.members ||
!room.currentState.getMember(this.client.credentials.userId)) {
const member = room?.currentState?.getMember(this.client.credentials.userId!);
if (!member) {
return false;
}
const displayName = room.currentState.getMember(this.client.credentials.userId).name;
const displayName = member.name;
// N.B. we can't use \b as it chokes on unicode. however \W seems to be okay
// as shorthand for [^0-9A-Za-z_].
@ -396,7 +396,7 @@ export class PushProcessor {
private valueForDottedKey(key: string, ev: MatrixEvent): any {
const parts = key.split('.');
let val;
let val: any;
// special-case the first component to deal with encrypted messages
const firstPart = parts[0];
@ -412,7 +412,7 @@ export class PushProcessor {
}
while (parts.length > 0) {
const thisPart = parts.shift();
const thisPart = parts.shift()!;
if (isNullOrUndefined(val[thisPart])) {
return null;
}
@ -421,7 +421,7 @@ export class PushProcessor {
return val;
}
private matchingRuleForEventWithRulesets(ev: MatrixEvent, rulesets): IAnnotatedPushRule {
private matchingRuleForEventWithRulesets(ev: MatrixEvent, rulesets?: IPushRules): IAnnotatedPushRule | null {
if (!rulesets) {
return null;
}
@ -432,7 +432,7 @@ export class PushProcessor {
return this.matchingRuleFromKindSet(ev, rulesets.global);
}
private pushActionsForEventAndRulesets(ev: MatrixEvent, rulesets): IActionsObject {
private pushActionsForEventAndRulesets(ev: MatrixEvent, rulesets?: IPushRules): IActionsObject {
const rule = this.matchingRuleForEventWithRulesets(ev, rulesets);
if (!rule) {
return {} as IActionsObject;
@ -504,9 +504,9 @@ export class PushProcessor {
* @param {string} ruleId The ID of the rule to search for
* @return {object} The push rule, or null if no such rule was found
*/
public getPushRuleById(ruleId: string): IPushRule {
public getPushRuleById(ruleId: string): IPushRule | null {
for (const scope of ['global']) {
if (this.client.pushRules[scope] === undefined) continue;
if (this.client.pushRules?.[scope] === undefined) continue;
for (const kind of RULEKINDS_IN_ORDER) {
if (this.client.pushRules[scope][kind] === undefined) continue;

View File

@ -36,14 +36,16 @@ let count = 0;
// the key for our callback with the real global.setTimeout
let realCallbackKey: NodeJS.Timeout | number;
// a sorted list of the callbacks to be run.
// each is an object with keys [runAt, func, params, key].
const callbackList: {
type Callback = {
runAt: number;
func: (...params: any[]) => void;
params: any[];
key: number;
}[] = [];
};
// a sorted list of the callbacks to be run.
// each is an object with keys [runAt, func, params, key].
const callbackList: Callback[] = [];
// var debuglog = logger.log.bind(logger);
const debuglog = function(...params: any[]) {};
@ -135,19 +137,19 @@ function scheduleRealCallback(): void {
}
function runCallbacks(): void {
let cb;
let cb: Callback;
const timestamp = Date.now();
debuglog("runCallbacks: now:", timestamp);
// get the list of things to call
const callbacksToRun = [];
const callbacksToRun: Callback[] = [];
// eslint-disable-next-line
while (true) {
const first = callbackList[0];
if (!first || first.runAt > timestamp) {
break;
}
cb = callbackList.shift();
cb = callbackList.shift()!;
debuglog("runCallbacks: popping", cb.key);
callbacksToRun.push(cb);
}
@ -162,8 +164,7 @@ function runCallbacks(): void {
try {
cb.func.apply(global, cb.params);
} catch (e) {
logger.error("Uncaught exception in callback function",
e.stack || e);
logger.error("Uncaught exception in callback function", e);
}
}
}

View File

@ -203,13 +203,13 @@ export class MSC3906Rendezvous {
true, false, true,
);
const masterPublicKey = this.client.crypto.crossSigningInfo.getId('master');
const masterPublicKey = this.client.crypto.crossSigningInfo.getId('master')!;
await this.send({
type: PayloadType.Finish,
outcome: Outcome.Verified,
verifying_device_id: this.client.getDeviceId(),
verifying_device_key: this.client.getDeviceEd25519Key(),
verifying_device_key: this.client.getDeviceEd25519Key()!,
master_key: masterPublicKey,
});

View File

@ -22,6 +22,7 @@ import { Room } from "./models/room";
import { IHierarchyRoom, IHierarchyRelation } from "./@types/spaces";
import { MatrixClient } from "./client";
import { EventType } from "./@types/event";
import { MatrixError } from "./http-api";
export class RoomHierarchy {
// Map from room id to list of servers which are listed as a via somewhere in the loaded hierarchy
@ -30,7 +31,7 @@ export class RoomHierarchy {
public readonly backRefs = new Map<string, string[]>();
// Map from room id to object
public readonly roomMap = new Map<string, IHierarchyRoom>();
private loadRequest: ReturnType<MatrixClient["getRoomHierarchy"]>;
private loadRequest?: ReturnType<MatrixClient["getRoomHierarchy"]>;
private nextBatch?: string;
private _rooms?: IHierarchyRoom[];
private serverSupportError?: Error;
@ -65,7 +66,7 @@ export class RoomHierarchy {
return !!this.loadRequest;
}
public get rooms(): IHierarchyRoom[] {
public get rooms(): IHierarchyRoom[] | undefined {
return this._rooms;
}
@ -84,15 +85,15 @@ export class RoomHierarchy {
try {
({ rooms, next_batch: this.nextBatch } = await this.loadRequest);
} catch (e) {
if (e.errcode === "M_UNRECOGNIZED") {
this.serverSupportError = e;
if ((<MatrixError>e).errcode === "M_UNRECOGNIZED") {
this.serverSupportError = <MatrixError>e;
} else {
throw e;
}
return [];
} finally {
this.loadRequest = null;
this.loadRequest = undefined;
}
if (this._rooms) {
@ -112,14 +113,14 @@ export class RoomHierarchy {
if (!this.backRefs.has(childRoomId)) {
this.backRefs.set(childRoomId, []);
}
this.backRefs.get(childRoomId).push(room.room_id);
this.backRefs.get(childRoomId)!.push(room.room_id);
// fill viaMap
if (Array.isArray(ev.content.via)) {
if (!this.viaMap.has(childRoomId)) {
this.viaMap.set(childRoomId, new Set());
}
const vias = this.viaMap.get(childRoomId);
const vias = this.viaMap.get(childRoomId)!;
ev.content.via.forEach(via => vias.add(via));
}
});
@ -128,11 +129,11 @@ export class RoomHierarchy {
return rooms;
}
public getRelation(parentId: string, childId: string): IHierarchyRelation {
public getRelation(parentId: string, childId: string): IHierarchyRelation | undefined {
return this.roomMap.get(parentId)?.children_state.find(e => e.state_key === childId);
}
public isSuggested(parentId: string, childId: string): boolean {
public isSuggested(parentId: string, childId: string): boolean | undefined {
return this.getRelation(parentId, childId)?.content.suggested;
}

View File

@ -63,7 +63,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
* @see module:scheduler~retryAlgorithm
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
public static RETRY_BACKOFF_RATELIMIT(event: MatrixEvent, attempts: number, err: MatrixError): number {
public static RETRY_BACKOFF_RATELIMIT(event: MatrixEvent | null, attempts: number, err: MatrixError): number {
if (err.httpStatus === 400 || err.httpStatus === 403 || err.httpStatus === 401) {
// client error; no amount of retrying with save you now.
return -1;
@ -114,7 +114,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
// }, ...]
private readonly queues: Record<string, IQueueEntry<T>[]> = {};
private activeQueues: string[] = [];
private procFn: ProcessFunction<T> = null;
private procFn: ProcessFunction<T> | null = null;
constructor(
public readonly retryAlgorithm = MatrixScheduler.RETRY_BACKOFF_RATELIMIT,
@ -130,7 +130,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
* this array <i>will</i> modify the underlying event in the queue.
* @see MatrixScheduler.removeEventFromQueue To remove an event from the queue.
*/
public getQueueForEvent(event: MatrixEvent): MatrixEvent[] {
public getQueueForEvent(event: MatrixEvent): MatrixEvent[] | null {
const name = this.queueAlgorithm(event);
if (!name || !this.queues[name]) {
return null;
@ -159,6 +159,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
removed = true;
return true;
}
return false;
});
return removed;
}
@ -239,7 +240,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
// This way enqueued relations/redactions to enqueued events can receive
// the remove id of their target before being sent.
Promise.resolve().then(() => {
return this.procFn(obj.event);
return this.procFn!(obj.event);
}).then((res) => {
// remove this from the queue
this.removeNextEvent(queueName);
@ -265,18 +266,18 @@ export class MatrixScheduler<T = ISendEventResponse> {
});
};
private peekNextEvent(queueName: string): IQueueEntry<T> {
private peekNextEvent(queueName: string): IQueueEntry<T> | undefined {
const queue = this.queues[queueName];
if (!Array.isArray(queue)) {
return null;
return undefined;
}
return queue[0];
}
private removeNextEvent(queueName: string): IQueueEntry<T> {
private removeNextEvent(queueName: string): IQueueEntry<T> | undefined {
const queue = this.queues[queueName];
if (!Array.isArray(queue)) {
return null;
return undefined;
}
return queue.shift();
}

View File

@ -52,7 +52,7 @@ class ExtensionE2EE implements Extension {
return ExtensionState.PreProcess;
}
public onRequest(isInitial: boolean): object {
public onRequest(isInitial: boolean): object | undefined {
if (!isInitial) {
return undefined;
}
@ -90,7 +90,7 @@ class ExtensionE2EE implements Extension {
}
class ExtensionToDevice implements Extension {
private nextBatch?: string = null;
private nextBatch: string | null = null;
constructor(private readonly client: MatrixClient) {}
@ -114,7 +114,7 @@ class ExtensionToDevice implements Extension {
}
public async onResponse(data: object): Promise<void> {
const cancelledKeyVerificationTxns = [];
const cancelledKeyVerificationTxns: string[] = [];
data["events"] = data["events"] || [];
data["events"]
.map(this.client.getEventMapper())
@ -125,7 +125,7 @@ class ExtensionToDevice implements Extension {
// so we can flag the verification events as cancelled in the loop
// below.
if (toDeviceEvent.getType() === "m.key.verification.cancel") {
const txnId = toDeviceEvent.getContent()['transaction_id'];
const txnId: string | undefined = toDeviceEvent.getContent()['transaction_id'];
if (txnId) {
cancelledKeyVerificationTxns.push(txnId);
}
@ -177,7 +177,7 @@ class ExtensionAccountData implements Extension {
return ExtensionState.PostProcess;
}
public onRequest(isInitial: boolean): object {
public onRequest(isInitial: boolean): object | undefined {
if (!isInitial) {
return undefined;
}
@ -235,9 +235,9 @@ class ExtensionAccountData implements Extension {
* sliding sync API, see sliding-sync.ts or the class SlidingSync.
*/
export class SlidingSyncSdk {
private syncState: SyncState = null;
private syncStateData: ISyncStateData;
private lastPos: string = null;
private syncState: SyncState | null = null;
private syncStateData?: ISyncStateData;
private lastPos: string | null = null;
private failCount = 0;
private notifEvents: MatrixEvent[] = []; // accumulator of sync events in the current sync response
@ -259,7 +259,7 @@ export class SlidingSyncSdk {
}
if (client.getNotifTimelineSet()) {
client.reEmitter.reEmit(client.getNotifTimelineSet(), [
client.reEmitter.reEmit(client.getNotifTimelineSet()!, [
RoomEvent.Timeline,
RoomEvent.TimelineReset,
]);
@ -293,7 +293,7 @@ export class SlidingSyncSdk {
this.processRoomData(this.client, room, roomData);
}
private onLifecycle(state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, err: Error | null): void {
private onLifecycle(state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, err?: Error): void {
if (err) {
logger.debug("onLifecycle", state, err);
}
@ -306,7 +306,7 @@ export class SlidingSyncSdk {
// Element won't stop showing the initial loading spinner unless we fire SyncState.Prepared
if (!this.lastPos) {
this.updateSyncState(SyncState.Prepared, {
oldSyncToken: this.lastPos,
oldSyncToken: undefined,
nextSyncToken: resp.pos,
catchingUp: false,
fromCache: false,
@ -315,7 +315,7 @@ export class SlidingSyncSdk {
// Conversely, Element won't show the room list unless there is at least 1x SyncState.Syncing
// so hence for the very first sync we will fire prepared then immediately syncing.
this.updateSyncState(SyncState.Syncing, {
oldSyncToken: this.lastPos,
oldSyncToken: this.lastPos!,
nextSyncToken: resp.pos,
catchingUp: false,
fromCache: false,
@ -357,7 +357,7 @@ export class SlidingSyncSdk {
* store.
*/
public async peek(_roomId: string): Promise<Room> {
return null; // TODO
return null!; // TODO
}
/**
@ -373,7 +373,7 @@ export class SlidingSyncSdk {
* @see module:client~MatrixClient#event:"sync"
* @return {?String}
*/
public getSyncState(): SyncState {
public getSyncState(): SyncState | null {
return this.syncState;
}
@ -385,8 +385,8 @@ export class SlidingSyncSdk {
* this object.
* @return {?Object}
*/
public getSyncStateData(): ISyncStateData {
return this.syncStateData;
public getSyncStateData(): ISyncStateData | null {
return this.syncStateData ?? null;
}
private shouldAbortSync(error: MatrixError): boolean {
@ -500,8 +500,7 @@ export class SlidingSyncSdk {
if (roomData.initial) {
// set the back-pagination token. Do this *before* adding any
// events so that clients can start back-paginating.
room.getLiveTimeline().setPaginationToken(
roomData.prev_batch, EventTimeline.BACKWARDS);
room.getLiveTimeline().setPaginationToken(roomData.prev_batch ?? null, EventTimeline.BACKWARDS);
}
/* TODO
@ -687,7 +686,7 @@ export class SlidingSyncSdk {
// slightly naughty by doctoring the invite event but this means all
// the code paths remain the same between invite/join display name stuff
// which is a worthy trade-off for some minor pollution.
const inviteEvent = member.events.member;
const inviteEvent = member.events.member!;
if (inviteEvent.getContent().membership !== "invite") {
// between resolving and now they have since joined, so don't clobber
return;
@ -723,7 +722,7 @@ export class SlidingSyncSdk {
break;
} catch (err) {
logger.error("Getting push rules failed", err);
if (this.shouldAbortSync(err)) {
if (this.shouldAbortSync(<MatrixError>err)) {
return;
}
}
@ -787,7 +786,7 @@ export class SlidingSyncSdk {
return a.getTs() - b.getTs();
});
this.notifEvents.forEach((event) => {
this.client.getNotifTimelineSet().addLiveEvent(event);
this.client.getNotifTimelineSet()?.addLiveEvent(event);
});
this.notifEvents = [];
}
@ -815,7 +814,7 @@ function ensureNameEvent(client: MatrixClient, roomId: string, roomData: MSC3575
content: {
name: roomData.name,
},
sender: client.getUserId(),
sender: client.getUserId()!,
origin_server_ts: new Date().getTime(),
});
return roomData;
@ -824,7 +823,7 @@ function ensureNameEvent(client: MatrixClient, roomId: string, roomData: MSC3575
// Helper functions which set up JS SDK structs are below and are identical to the sync v2 counterparts,
// just outside the class.
function mapEvents(client: MatrixClient, roomId: string, events: object[], decrypt = true): MatrixEvent[] {
function mapEvents(client: MatrixClient, roomId: string | undefined, events: object[], decrypt = true): MatrixEvent[] {
const mapper = client.getEventMapper({ decrypt });
return (events as Array<IStrippedState | IRoomEvent | IStateEvent | IMinimalEvent>).map(function(e) {
e["room_id"] = roomId;

View File

@ -19,6 +19,7 @@ import { MatrixClient } from "./client";
import { IRoomEvent, IStateEvent } from "./sync-accumulator";
import { TypedEventEmitter } from "./models/typed-event-emitter";
import { sleep, IDeferred, defer } from "./utils";
import { HTTPError } from "./http-api";
// /sync requests allow you to set a timeout= but the request may continue
// beyond that and wedge forever, so we need to track how long we are willing
@ -153,12 +154,12 @@ export enum SlidingSyncState {
* multiple sliding windows, and maintains the index->room_id mapping.
*/
class SlidingList {
private list: MSC3575List;
private isModified: boolean;
private list!: MSC3575List;
private isModified?: boolean;
// returned data
public roomIndexToRoomId: Record<number, string>;
public joinedCount: number;
public roomIndexToRoomId: Record<number, string> = {};
public joinedCount = 0;
/**
* Construct a new sliding list.
@ -271,7 +272,7 @@ export interface Extension {
* @param isInitial True when this is part of the initial request (send sticky params)
* @returns The request JSON to send.
*/
onRequest(isInitial: boolean): object;
onRequest(isInitial: boolean): object | undefined;
/**
* A function which is called when there is response JSON under this extension.
* @param data The response JSON under the extension name.
@ -322,7 +323,7 @@ export enum SlidingSyncEvent {
export type SlidingSyncEventHandlerMap = {
[SlidingSyncEvent.RoomData]: (roomId: string, roomData: MSC3575RoomData) => void;
[SlidingSyncEvent.Lifecycle]: (
state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, err: Error | null,
state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, err?: Error,
) => void;
[SlidingSyncEvent.List]: (
listIndex: number, joinedCount: number, roomIndexToRoomId: Record<number, string>,
@ -342,7 +343,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
// flag set when resend() is called because we cannot rely on detecting AbortError in JS SDK :(
private needsResend = false;
// the txn_id to send with the next request.
private txnId?: string = null;
private txnId: string | null = null;
// a list (in chronological order of when they were sent) of objects containing the txn ID and
// a defer to resolve/reject depending on whether they were successfully sent or not.
private txnIdDefers: (IDeferred<string> & { txnId: string})[] = [];
@ -387,7 +388,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
* @param index The list index
* @returns The list data which contains the rooms in this list
*/
public getListData(index: number): {joinedCount: number, roomIndexToRoomId: Record<number, string>} {
public getListData(index: number): {joinedCount: number, roomIndexToRoomId: Record<number, string>} | null {
if (!this.lists[index]) {
return null;
}
@ -403,7 +404,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
* @param index The list index to get the list for.
* @returns A copy of the list or undefined.
*/
public getList(index: number): MSC3575List {
public getList(index: number): MSC3575List | null {
if (!this.lists[index]) {
return null;
}
@ -532,7 +533,11 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
* @param {object} resp The raw sync response JSON
* @param {Error?} err Any error that occurred when making the request e.g. network errors.
*/
private invokeLifecycleListeners(state: SlidingSyncState, resp: MSC3575SlidingSyncResponse, err?: Error): void {
private invokeLifecycleListeners(
state: SlidingSyncState,
resp: MSC3575SlidingSyncResponse | null,
err?: Error,
): void {
this.emit(SlidingSyncEvent.Lifecycle, state, resp, err);
}
@ -772,11 +777,11 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
public async start() {
this.abortController = new AbortController();
let currentPos: string;
let currentPos: string | undefined;
while (!this.terminated) {
this.needsResend = false;
let doNotUpdateList = false;
let resp: MSC3575SlidingSyncResponse;
let resp: MSC3575SlidingSyncResponse | undefined;
try {
const listModifiedCount = this.listModifiedCount;
const reqBody: MSC3575SlidingSyncRequest = {
@ -837,13 +842,13 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
resp,
);
} catch (err) {
if (err.httpStatus) {
if ((<HTTPError>err).httpStatus) {
this.invokeLifecycleListeners(
SlidingSyncState.RequestFinished,
null,
err,
<Error>err,
);
if (err.httpStatus === 400) {
if ((<HTTPError>err).httpStatus === 400) {
// session probably expired TODO: assign an errcode
// so drop state and re-request
this.resetup();
@ -851,7 +856,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
await sleep(50); // in case the 400 was for something else; don't tightloop
continue;
} // else fallthrough to generic error handling
} else if (this.needsResend || err.name === "AbortError") {
} else if (this.needsResend || (<Error>err).name === "AbortError") {
continue; // don't sleep as we caused this error by abort()ing the request.
}
logger.error(err);
@ -865,7 +870,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
Object.keys(resp.rooms).forEach((roomId) => {
this.invokeRoomDataListeners(
roomId,
resp.rooms[roomId],
resp!.rooms[roomId],
);
});

View File

@ -150,7 +150,7 @@ export interface IStore {
* @param {string} filterName
* @param {string} filterId
*/
setFilterIdByName(filterName: string, filterId: string): void;
setFilterIdByName(filterName: string, filterId?: string): void;
/**
* Store user-scoped account data events

View File

@ -274,14 +274,14 @@ export class MemoryStore implements IStore {
* @param {string} filterName
* @param {string} filterId
*/
public setFilterIdByName(filterName: string, filterId: string) {
public setFilterIdByName(filterName: string, filterId?: string) {
if (!this.localStorage) {
return;
}
const key = "mxjssdk_memory_filter_" + filterName;
try {
if (isValidFilterId(filterId)) {
this.localStorage.setItem(key, filterId);
this.localStorage.setItem(key, filterId!);
} else {
this.localStorage.removeItem(key);
}

View File

@ -36,7 +36,7 @@ import { IndexedToDeviceBatch, ToDeviceBatch } from "../models/ToDeviceMessage";
*/
export class StubStore implements IStore {
public readonly accountData = {}; // stub
private fromToken: string = null;
private fromToken: string | null = null;
/** @return {Promise<boolean>} whether or not the database was newly created in this session. */
public isNewlyCreated(): Promise<boolean> {
@ -170,7 +170,7 @@ export class StubStore implements IStore {
* @param {string} filterName
* @param {string} filterId
*/
public setFilterIdByName(filterName: string, filterId: string) {}
public setFilterIdByName(filterName: string, filterId?: string) {}
/**
* Store user-scoped account data events
@ -223,7 +223,7 @@ export class StubStore implements IStore {
* client state to where it was at the last save, or null if there
* is no saved sync data.
*/
public getSavedSync(): Promise<ISavedSync> {
public getSavedSync(): Promise<ISavedSync | null> {
return Promise.resolve(null);
}
@ -244,7 +244,7 @@ export class StubStore implements IStore {
return Promise.resolve();
}
public getOutOfBandMembers(): Promise<IStateEventWithRoomId[]> {
public getOutOfBandMembers(): Promise<IStateEventWithRoomId[] | null> {
return Promise.resolve(null);
}

View File

@ -191,7 +191,7 @@ export class SyncAccumulator {
// accumulated. We remember this so that any caller can obtain a
// coherent /sync response and know at what point they should be
// streaming from without losing events.
private nextBatch: string = null;
private nextBatch: string | null = null;
/**
* @param {Object} opts
@ -384,8 +384,8 @@ export class SyncAccumulator {
if (data.unread_notifications) {
currentData._unreadNotifications = data.unread_notifications;
}
currentData._unreadThreadNotifications = data[UNREAD_THREAD_NOTIFICATIONS.stable]
?? data[UNREAD_THREAD_NOTIFICATIONS.unstable]
currentData._unreadThreadNotifications = data[UNREAD_THREAD_NOTIFICATIONS.stable!]
?? data[UNREAD_THREAD_NOTIFICATIONS.unstable!]
?? undefined;
if (data.summary) {
@ -429,7 +429,7 @@ export class SyncAccumulator {
Object.entries(e.content[eventId]).forEach(([key, value]) => {
if (!isSupportedReceiptType(key)) return;
Object.keys(value).forEach((userId) => {
Object.keys(value!).forEach((userId) => {
// clobber on user ID
currentData._readReceipts[userId] = {
data: e.content[eventId][key][userId],
@ -477,16 +477,16 @@ export class SyncAccumulator {
currentData._timeline.push({
event: transformedEvent,
token: index === 0 ? data.timeline.prev_batch : null,
token: index === 0 ? (data.timeline.prev_batch ?? null) : null,
});
});
}
// attempt to prune the timeline by jumping between events which have
// pagination tokens.
if (currentData._timeline.length > this.opts.maxTimelineEntries) {
if (currentData._timeline.length > this.opts.maxTimelineEntries!) {
const startIndex = (
currentData._timeline.length - this.opts.maxTimelineEntries
currentData._timeline.length - this.opts.maxTimelineEntries!
);
for (let i = startIndex; i < currentData._timeline.length; i++) {
if (currentData._timeline[i].token) {
@ -657,14 +657,14 @@ export class SyncAccumulator {
});
return {
nextBatch: this.nextBatch,
nextBatch: this.nextBatch!,
roomsData: data,
accountData: accData,
};
}
public getNextBatchToken(): string {
return this.nextBatch;
return this.nextBatch!;
}
}

View File

@ -33,7 +33,7 @@ import { Filter } from "./filter";
import { EventTimeline } from "./models/event-timeline";
import { PushProcessor } from "./pushprocessor";
import { logger } from './logger';
import { InvalidStoreError } from './errors';
import { InvalidStoreError, InvalidStoreState } from './errors';
import { ClientEvent, IStoredClientOpts, MatrixClient, PendingEventOrdering } from "./client";
import {
IEphemeral,
@ -117,7 +117,7 @@ interface ISyncOptions {
}
export interface ISyncStateData {
error?: MatrixError;
error?: Error;
oldSyncToken?: string;
nextSyncToken?: string;
catchingUp?: boolean;
@ -163,14 +163,14 @@ type WrappedRoom<T> = T & {
*/
export class SyncApi {
private _peekRoom: Optional<Room> = null;
private currentSyncRequest: Optional<Promise<ISyncResponse>> = null;
private currentSyncRequest?: Promise<ISyncResponse>;
private abortController?: AbortController;
private syncState: Optional<SyncState> = null;
private syncStateData: Optional<ISyncStateData> = null; // additional data (eg. error object for failed sync)
private syncState: SyncState | null = null;
private syncStateData?: ISyncStateData; // additional data (eg. error object for failed sync)
private catchingUp = false;
private running = false;
private keepAliveTimer: Optional<ReturnType<typeof setTimeout>> = null;
private connectionReturnedDefer: Optional<IDeferred<boolean>> = null;
private keepAliveTimer?: ReturnType<typeof setTimeout>;
private connectionReturnedDefer?: IDeferred<boolean>;
private notifEvents: MatrixEvent[] = []; // accumulator of sync events in the current sync response
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
@ -189,7 +189,7 @@ export class SyncApi {
}
if (client.getNotifTimelineSet()) {
client.reEmitter.reEmit(client.getNotifTimelineSet(), [
client.reEmitter.reEmit(client.getNotifTimelineSet()!, [
RoomEvent.Timeline,
RoomEvent.TimelineReset,
]);
@ -281,7 +281,7 @@ export class SyncApi {
* Sync rooms the user has left.
* @return {Promise} Resolved when they've been added to the store.
*/
public syncLeftRooms() {
public async syncLeftRooms(): Promise<Room[]> {
const client = this.client;
// grab a filter with limit=1 and include_leave=true
@ -289,55 +289,62 @@ export class SyncApi {
filter.setTimelineLimit(1);
filter.setIncludeLeaveRooms(true);
const localTimeoutMs = this.opts.pollTimeout + BUFFER_PERIOD_MS;
const localTimeoutMs = this.opts.pollTimeout! + BUFFER_PERIOD_MS;
const filterId = await client.getOrCreateFilter(
getFilterName(client.credentials.userId!, "LEFT_ROOMS"), filter,
);
const qps: ISyncParams = {
timeout: 0, // don't want to block since this is a single isolated req
filter: filterId,
};
return client.getOrCreateFilter(
getFilterName(client.credentials.userId, "LEFT_ROOMS"), filter,
).then(function(filterId) {
qps.filter = filterId;
return client.http.authedRequest<ISyncResponse>(Method.Get, "/sync", qps as any, undefined, {
localTimeoutMs,
});
}).then(async (data) => {
let leaveRooms = [];
if (data.rooms?.leave) {
leaveRooms = this.mapSyncResponseToRoomArray(data.rooms.leave);
}
return Promise.all(leaveRooms.map(async (leaveObj) => {
const room = leaveObj.room;
if (!leaveObj.isBrandNewRoom) {
// the intention behind syncLeftRooms is to add in rooms which were
// *omitted* from the initial /sync. Rooms the user were joined to
// but then left whilst the app is running will appear in this list
// and we do not want to bother with them since they will have the
// current state already (and may get dupe messages if we add
// yet more timeline events!), so skip them.
// NB: When we persist rooms to localStorage this will be more
// complicated...
return;
}
leaveObj.timeline = leaveObj.timeline || {};
const events = this.mapSyncEventsFormat(leaveObj.timeline, room);
const stateEvents = this.mapSyncEventsFormat(leaveObj.state, room);
// set the back-pagination token. Do this *before* adding any
// events so that clients can start back-paginating.
room.getLiveTimeline().setPaginationToken(leaveObj.timeline.prev_batch, EventTimeline.BACKWARDS);
await this.processRoomEvents(room, stateEvents, events);
room.recalculate();
client.store.storeRoom(room);
client.emit(ClientEvent.Room, room);
this.processEventsForNotifs(room, events);
return room;
}));
const data = await client.http.authedRequest<ISyncResponse>(Method.Get, "/sync", qps as any, undefined, {
localTimeoutMs,
});
let leaveRooms: WrappedRoom<ILeftRoom>[] = [];
if (data.rooms?.leave) {
leaveRooms = this.mapSyncResponseToRoomArray(data.rooms.leave);
}
const rooms = await Promise.all(leaveRooms.map(async (leaveObj) => {
const room = leaveObj.room;
if (!leaveObj.isBrandNewRoom) {
// the intention behind syncLeftRooms is to add in rooms which were
// *omitted* from the initial /sync. Rooms the user were joined to
// but then left whilst the app is running will appear in this list
// and we do not want to bother with them since they will have the
// current state already (and may get dupe messages if we add
// yet more timeline events!), so skip them.
// NB: When we persist rooms to localStorage this will be more
// complicated...
return;
}
leaveObj.timeline = leaveObj.timeline || {
prev_batch: null,
events: [],
};
const events = this.mapSyncEventsFormat(leaveObj.timeline, room);
const stateEvents = this.mapSyncEventsFormat(leaveObj.state, room);
// set the back-pagination token. Do this *before* adding any
// events so that clients can start back-paginating.
room.getLiveTimeline().setPaginationToken(leaveObj.timeline.prev_batch, EventTimeline.BACKWARDS);
await this.processRoomEvents(room, stateEvents, events);
room.recalculate();
client.store.storeRoom(room);
client.emit(ClientEvent.Room, room);
this.processEventsForNotifs(room, events);
return room;
}));
return rooms.filter(Boolean) as Room[];
}
/**
@ -348,7 +355,7 @@ export class SyncApi {
* store.
*/
public peek(roomId: string): Promise<Room> {
if (this._peekRoom && this._peekRoom.roomId === roomId) {
if (this._peekRoom?.roomId === roomId) {
return Promise.resolve(this._peekRoom);
}
@ -388,28 +395,28 @@ export class SyncApi {
// fire off pagination requests in response to the Room.timeline
// events.
if (response.messages.start) {
this._peekRoom.oldState.paginationToken = response.messages.start;
this._peekRoom!.oldState.paginationToken = response.messages.start;
}
// set the state of the room to as it was after the timeline executes
this._peekRoom.oldState.setStateEvents(oldStateEvents);
this._peekRoom.currentState.setStateEvents(stateEvents);
this._peekRoom!.oldState.setStateEvents(oldStateEvents);
this._peekRoom!.currentState.setStateEvents(stateEvents);
this.resolveInvites(this._peekRoom);
this._peekRoom.recalculate();
this.resolveInvites(this._peekRoom!);
this._peekRoom!.recalculate();
// roll backwards to diverge old state. addEventsToTimeline
// will overwrite the pagination token, so make sure it overwrites
// it with the right thing.
this._peekRoom.addEventsToTimeline(messages.reverse(), true,
this._peekRoom.getLiveTimeline(),
this._peekRoom!.addEventsToTimeline(messages.reverse(), true,
this._peekRoom!.getLiveTimeline(),
response.messages.start);
client.store.storeRoom(this._peekRoom);
client.emit(ClientEvent.Room, this._peekRoom);
client.store.storeRoom(this._peekRoom!);
client.emit(ClientEvent.Room, this._peekRoom!);
this.peekPoll(this._peekRoom);
return this._peekRoom;
this.peekPoll(this._peekRoom!);
return this._peekRoom!;
});
}
@ -437,7 +444,10 @@ export class SyncApi {
room_id: peekRoom.roomId,
timeout: String(30 * 1000),
from: token,
}, undefined, { localTimeoutMs: 50 * 1000 }).then((res) => {
}, undefined, {
localTimeoutMs: 50 * 1000,
abortSignal: this.abortController?.signal,
}).then((res) => {
if (this._peekRoom !== peekRoom) {
debuglog("Stopped peeking in room %s", peekRoom.roomId);
return;
@ -487,7 +497,7 @@ export class SyncApi {
* @see module:client~MatrixClient#event:"sync"
* @return {?String}
*/
public getSyncState(): SyncState {
public getSyncState(): SyncState | null {
return this.syncState;
}
@ -499,18 +509,18 @@ export class SyncApi {
* this object.
* @return {?Object}
*/
public getSyncStateData(): ISyncStateData {
return this.syncStateData;
public getSyncStateData(): ISyncStateData | null {
return this.syncStateData ?? null;
}
public async recoverFromSyncStartupError(savedSyncPromise: Promise<void>, err: MatrixError): Promise<void> {
public async recoverFromSyncStartupError(savedSyncPromise: Promise<void> | undefined, error: Error): Promise<void> {
// Wait for the saved sync to complete - we send the pushrules and filter requests
// before the saved sync has finished so they can run in parallel, but only process
// the results after the saved sync is done. Equivalently, we wait for it to finish
// before reporting failures from these functions.
await savedSyncPromise;
const keepaliveProm = this.startKeepAlives();
this.updateSyncState(SyncState.Error, { error: err });
this.updateSyncState(SyncState.Error, { error });
await keepaliveProm;
}
@ -553,11 +563,11 @@ export class SyncApi {
this.client.pushRules = result;
} catch (err) {
logger.error("Getting push rules failed", err);
if (this.shouldAbortSync(err)) return;
if (this.shouldAbortSync(<MatrixError>err)) return;
// wait for saved sync to complete before doing anything else,
// otherwise the sync state will end up being incorrect
debuglog("Waiting for saved sync before retrying push rules...");
await this.recoverFromSyncStartupError(this.savedSyncPromise, err);
await this.recoverFromSyncStartupError(this.savedSyncPromise, <Error>err);
return this.getPushRules(); // try again
}
};
@ -595,8 +605,7 @@ export class SyncApi {
const shouldClear = await this.wasLazyLoadingToggled(this.opts.lazyLoadMembers);
if (shouldClear) {
this.storeIsInvalid = true;
const reason = InvalidStoreError.TOGGLED_LAZY_LOADING;
const error = new InvalidStoreError(reason, !!this.opts.lazyLoadMembers);
const error = new InvalidStoreError(InvalidStoreState.ToggledLazyLoading, !!this.opts.lazyLoadMembers);
this.updateSyncState(SyncState.Error, { error });
// bail out of the sync loop now: the app needs to respond to this error.
// we leave the state as 'ERROR' which isn't great since this normally means
@ -632,20 +641,20 @@ export class SyncApi {
let filterId: string;
try {
filterId = await this.client.getOrCreateFilter(getFilterName(this.client.credentials.userId), filter);
filterId = await this.client.getOrCreateFilter(getFilterName(this.client.credentials.userId!), filter);
} catch (err) {
logger.error("Getting filter failed", err);
if (this.shouldAbortSync(err)) return {};
if (this.shouldAbortSync(<MatrixError>err)) return {};
// wait for saved sync to complete before doing anything else,
// otherwise the sync state will end up being incorrect
debuglog("Waiting for saved sync before retrying filter...");
await this.recoverFromSyncStartupError(this.savedSyncPromise, err);
await this.recoverFromSyncStartupError(this.savedSyncPromise, <Error>err);
return this.getFilter(); // try again
}
return { filter, filterId };
};
private savedSyncPromise: Promise<void>;
private savedSyncPromise?: Promise<void>;
/**
* Main entry point
@ -701,7 +710,7 @@ export class SyncApi {
// /notifications API somehow.
this.client.resetNotifTimelineSet();
if (this.currentSyncRequest === null) {
if (!this.currentSyncRequest) {
let firstSyncFilter = filterId;
const savedSyncToken = await savedSyncTokenPromise;
@ -711,7 +720,7 @@ export class SyncApi {
debuglog("Sending initial sync request...");
const initialFilter = this.buildDefaultFilter();
initialFilter.setDefinition(filter.getDefinition());
initialFilter.setTimelineLimit(this.opts.initialSyncLimit);
initialFilter.setTimelineLimit(this.opts.initialSyncLimit!);
// Use an inline filter, no point uploading it for a single usage
firstSyncFilter = JSON.stringify(initialFilter.getDefinition());
}
@ -742,7 +751,7 @@ export class SyncApi {
this.abortController?.abort();
if (this.keepAliveTimer) {
clearTimeout(this.keepAliveTimer);
this.keepAliveTimer = null;
this.keepAliveTimer = undefined;
}
}
@ -813,16 +822,16 @@ export class SyncApi {
let data: ISyncResponse;
try {
//debuglog('Starting sync since=' + syncToken);
if (this.currentSyncRequest === null) {
if (!this.currentSyncRequest) {
this.currentSyncRequest = this.doSyncRequest(syncOptions, syncToken);
}
data = await this.currentSyncRequest;
} catch (e) {
const abort = await this.onSyncError(e);
const abort = await this.onSyncError(<MatrixError>e);
if (abort) return;
continue;
} finally {
this.currentSyncRequest = null;
this.currentSyncRequest = undefined;
}
//debuglog('Completed sync, next_batch=' + data.next_batch);
@ -838,7 +847,7 @@ export class SyncApi {
await this.client.store.setSyncData(data);
const syncEventData = {
oldSyncToken: syncToken,
oldSyncToken: syncToken ?? undefined,
nextSyncToken: data.next_batch,
catchingUp: this.catchingUp,
};
@ -857,7 +866,7 @@ export class SyncApi {
logger.error("Caught /sync error", e);
// Emit the exception for client handling
this.client.emit(ClientEvent.SyncUnexpectedError, e);
this.client.emit(ClientEvent.SyncUnexpectedError, <Error>e);
}
// update this as it may have changed
@ -897,13 +906,13 @@ export class SyncApi {
debuglog("Sync no longer running: exiting.");
if (this.connectionReturnedDefer) {
this.connectionReturnedDefer.reject();
this.connectionReturnedDefer = null;
this.connectionReturnedDefer = undefined;
}
this.updateSyncState(SyncState.Stopped);
}
}
private doSyncRequest(syncOptions: ISyncOptions, syncToken: string): Promise<ISyncResponse> {
private doSyncRequest(syncOptions: ISyncOptions, syncToken: string | null): Promise<ISyncResponse> {
const qps = this.getSyncParams(syncOptions, syncToken);
return this.client.http.authedRequest<ISyncResponse>(Method.Get, "/sync", qps as any, undefined, {
localTimeoutMs: qps.timeout + BUFFER_PERIOD_MS,
@ -911,8 +920,8 @@ export class SyncApi {
});
}
private getSyncParams(syncOptions: ISyncOptions, syncToken: string): ISyncParams {
let pollTimeout = this.opts.pollTimeout;
private getSyncParams(syncOptions: ISyncOptions, syncToken: string | null): ISyncParams {
let timeout = this.opts.pollTimeout!;
if (this.getSyncState() !== SyncState.Syncing || this.catchingUp) {
// unless we are happily syncing already, we want the server to return
@ -927,7 +936,7 @@ export class SyncApi {
// for us. We do that by calling it with a zero timeout until it
// doesn't give us any more to_device messages.
this.catchingUp = true;
pollTimeout = 0;
timeout = 0;
}
let filter = syncOptions.filter;
@ -935,10 +944,7 @@ export class SyncApi {
filter = this.getGuestFilter();
}
const qps: ISyncParams = {
filter,
timeout: pollTimeout,
};
const qps: ISyncParams = { filter, timeout };
if (this.opts.disablePresence) {
qps.set_presence = SetPresence.Offline;
@ -953,7 +959,7 @@ export class SyncApi {
qps._cacheBuster = Date.now();
}
if ([SyncState.Reconnecting, SyncState.Error].includes(this.getSyncState())) {
if ([SyncState.Reconnecting, SyncState.Error].includes(this.getSyncState()!)) {
// we think the connection is dead. If it comes back up, we won't know
// about it till /sync returns. If the timeout= is high, this could
// be a long time. Set it to 0 when doing retries so we don't have to wait
@ -969,7 +975,7 @@ export class SyncApi {
debuglog("Sync no longer running: exiting");
if (this.connectionReturnedDefer) {
this.connectionReturnedDefer.reject();
this.connectionReturnedDefer = null;
this.connectionReturnedDefer = undefined;
}
this.updateSyncState(SyncState.Stopped);
return true; // abort
@ -994,7 +1000,7 @@ export class SyncApi {
// if they wish.
const keepAlivePromise = this.startKeepAlives();
this.currentSyncRequest = null;
this.currentSyncRequest = undefined;
// Transition from RECONNECTING to ERROR after a given number of failed syncs
this.updateSyncState(
this.failedSyncCount >= FAILED_SYNC_ERROR_THRESHOLD ?
@ -1073,7 +1079,7 @@ export class SyncApi {
// handle presence events (User objects)
if (Array.isArray(data.presence?.events)) {
data.presence.events.map(client.getEventMapper()).forEach(
data.presence!.events.map(client.getEventMapper()).forEach(
function(presenceEvent) {
let user = client.store.getUser(presenceEvent.getSender());
if (user) {
@ -1113,9 +1119,9 @@ export class SyncApi {
}
// handle to-device events
if (Array.isArray(data.to_device?.events) && data.to_device.events.length > 0) {
const cancelledKeyVerificationTxns = [];
data.to_device.events
if (Array.isArray(data.to_device?.events) && data.to_device!.events.length > 0) {
const cancelledKeyVerificationTxns: string[] = [];
data.to_device!.events
.filter((eventJSON) => {
if (
eventJSON.type === EventType.RoomMessageEncrypted &&
@ -1137,7 +1143,7 @@ export class SyncApi {
// so we can flag the verification events as cancelled in the loop
// below.
if (toDeviceEvent.getType() === "m.key.verification.cancel") {
const txnId = toDeviceEvent.getContent()['transaction_id'];
const txnId: string = toDeviceEvent.getContent()['transaction_id'];
if (txnId) {
cancelledKeyVerificationTxns.push(txnId);
}
@ -1206,13 +1212,13 @@ export class SyncApi {
await this.processRoomEvents(room, stateEvents);
const inviter = room.currentState.getStateEvents(EventType.RoomMember, client.getUserId())?.getSender();
const inviter = room.currentState.getStateEvents(EventType.RoomMember, client.getUserId()!)?.getSender();
if (client.isCryptoEnabled()) {
const parkedHistory = await client.crypto.cryptoStore.takeParkedSharedHistory(room.roomId);
const parkedHistory = await client.crypto!.cryptoStore.takeParkedSharedHistory(room.roomId);
for (const parked of parkedHistory) {
if (parked.senderId === inviter) {
await client.crypto.olmDevice.addInboundGroupSession(
await client.crypto!.olmDevice.addInboundGroupSession(
room.roomId,
parked.senderKey,
parked.forwardingCurve25519KeyChain,
@ -1256,7 +1262,7 @@ export class SyncApi {
if (joinObj.unread_notifications) {
room.setUnreadNotificationCount(
NotificationCountType.Total,
joinObj.unread_notifications.notification_count,
joinObj.unread_notifications.notification_count ?? 0,
);
// We track unread notifications ourselves in encrypted rooms, so don't
@ -1266,13 +1272,13 @@ export class SyncApi {
if (!encrypted || room.getUnreadNotificationCount(NotificationCountType.Highlight) <= 0) {
room.setUnreadNotificationCount(
NotificationCountType.Highlight,
joinObj.unread_notifications.highlight_count,
joinObj.unread_notifications.highlight_count ?? 0,
);
}
}
const unreadThreadNotifications = joinObj[UNREAD_THREAD_NOTIFICATIONS.name]
?? joinObj[UNREAD_THREAD_NOTIFICATIONS.altName];
?? joinObj[UNREAD_THREAD_NOTIFICATIONS.altName!];
if (unreadThreadNotifications) {
// Only partially reset unread notification
// We want to keep the client-generated count. Particularly important
@ -1283,7 +1289,7 @@ export class SyncApi {
room.setThreadUnreadNotificationCount(
threadId,
NotificationCountType.Total,
unreadNotification.notification_count,
unreadNotification.notification_count ?? 0,
);
const hasNoNotifications =
@ -1292,7 +1298,7 @@ export class SyncApi {
room.setThreadUnreadNotificationCount(
threadId,
NotificationCountType.Highlight,
unreadNotification.highlight_count,
unreadNotification.highlight_count ?? 0,
);
}
}
@ -1349,8 +1355,8 @@ export class SyncApi {
if (limited) {
room.resetLiveTimeline(
joinObj.timeline.prev_batch,
this.opts.canResetEntireTimeline(room.roomId) ?
null : syncEventData.oldSyncToken,
this.opts.canResetEntireTimeline!(room.roomId) ?
null : (syncEventData.oldSyncToken ?? null),
);
// We have to assume any gap in any timeline is
@ -1448,7 +1454,7 @@ export class SyncApi {
return a.getTs() - b.getTs();
});
this.notifEvents.forEach(function(event) {
client.getNotifTimelineSet().addLiveEvent(event);
client.getNotifTimelineSet()?.addLiveEvent(event);
});
}
@ -1523,7 +1529,7 @@ export class SyncApi {
clearTimeout(this.keepAliveTimer);
if (this.connectionReturnedDefer) {
this.connectionReturnedDefer.resolve(connDidFail);
this.connectionReturnedDefer = null;
this.connectionReturnedDefer = undefined;
}
};
@ -1639,7 +1645,7 @@ export class SyncApi {
// the code paths remain the same between invite/join display name stuff
// which is a worthy trade-off for some minor pollution.
const inviteEvent = member.events.member;
if (inviteEvent.getContent().membership !== "invite") {
if (inviteEvent?.getContent().membership !== "invite") {
// between resolving and now they have since joined, so don't clobber
return;
}
@ -1802,7 +1808,7 @@ function createNewUser(client: MatrixClient, userId: string): User {
export function _createAndReEmitRoom(client: MatrixClient, roomId: string, opts: Partial<IStoredClientOpts>): Room {
const { timelineSupport } = client;
const room = new Room(roomId, client, client.getUserId(), {
const room = new Room(roomId, client, client.getUserId()!, {
lazyLoadMembers: opts.lazyLoadMembers,
pendingEventOrdering: opts.pendingEventOrdering,
timelineSupport,
@ -1832,7 +1838,7 @@ export function _createAndReEmitRoom(client: MatrixClient, roomId: string, opts:
// We need to add a listener for RoomState.members in order to hook them
// correctly.
room.on(RoomStateEvent.NewMember, (event, state, member) => {
member.user = client.getUser(member.userId);
member.user = client.getUser(member.userId) ?? undefined;
client.reEmitter.reEmit(member, [
RoomMemberEvent.Name,
RoomMemberEvent.Typing,

View File

@ -16,6 +16,8 @@ limitations under the License.
/** @module timeline-window */
import { Optional } from "matrix-events-sdk";
import { Direction, EventTimeline } from './models/event-timeline';
import { logger } from './logger';
import { MatrixClient } from "./client";
@ -49,8 +51,8 @@ export class TimelineWindow {
// 'end' of the window.
//
// start.index is inclusive; end.index is exclusive.
private start?: TimelineIndex = null;
private end?: TimelineIndex = null;
private start?: TimelineIndex;
private end?: TimelineIndex;
private eventCount = 0;
/**
@ -102,7 +104,11 @@ 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: EventTimeline) => {
const initFields = (timeline: Optional<EventTimeline>) => {
if (!timeline) {
throw new Error("No timeline given to initFields");
}
let eventIndex: number;
const events = timeline.getEvents();
@ -153,11 +159,11 @@ export class TimelineWindow {
* @return {TimelineIndex} The requested timeline index if one exists, null
* otherwise.
*/
public getTimelineIndex(direction: Direction): TimelineIndex {
public getTimelineIndex(direction: Direction): TimelineIndex | null {
if (direction == EventTimeline.BACKWARDS) {
return this.start;
return this.start ?? null;
} else if (direction == EventTimeline.FORWARDS) {
return this.end;
return this.end ?? null;
} else {
throw new Error("Invalid direction '" + direction + "'");
}
@ -299,7 +305,7 @@ export class TimelineWindow {
backwards: direction == EventTimeline.BACKWARDS,
limit: size,
}).finally(function() {
tl.pendingPaginate = null;
tl.pendingPaginate = undefined;
}).then((r) => {
debuglog("TimelineWindow: request completed with result " + r);
if (!r) {
@ -334,11 +340,17 @@ export class TimelineWindow {
*/
public unpaginate(delta: number, startOfTimeline: boolean): void {
const tl = startOfTimeline ? this.start : this.end;
if (!tl) {
throw new Error(
`Attempting to unpaginate startOfTimeline=${startOfTimeline} but don't have this direction`,
);
}
// sanity-check the delta
if (delta > this.eventCount || delta < 0) {
throw new Error("Attemting to unpaginate " + delta + " events, but " +
"only have " + this.eventCount + " in the timeline");
throw new Error(
`Attemting to unpaginate ${delta} events, but only have ${this.eventCount} in the timeline`,
);
}
while (delta > 0) {
@ -368,7 +380,7 @@ export class TimelineWindow {
return [];
}
const result = [];
const result: MatrixEvent[] = [];
// iterate through each timeline between this.start and this.end
// (inclusive).
@ -390,7 +402,7 @@ export class TimelineWindow {
if (timeline === this.start.timeline) {
startIndex = this.start.index + timeline.getBaseIndex();
}
if (timeline === this.end.timeline) {
if (timeline === this.end?.timeline) {
endIndex = this.end.index + timeline.getBaseIndex();
}
@ -399,10 +411,10 @@ export class TimelineWindow {
}
// if we're not done, iterate to the next timeline.
if (timeline === this.end.timeline) {
if (timeline === this.end?.timeline) {
break;
} else {
timeline = timeline.getNeighbouringTimeline(EventTimeline.FORWARDS);
timeline = timeline.getNeighbouringTimeline(EventTimeline.FORWARDS)!;
}
}

View File

@ -48,7 +48,7 @@ export function internaliseString(str: string): string {
}
// Return any cached string reference
return interns.get(str);
return interns.get(str)!;
}
/**
@ -412,7 +412,7 @@ export function defer<T = void>(): IDeferred<T> {
export async function promiseMapSeries<T>(
promises: Array<T | Promise<T>>,
fn: (t: T) => Promise<unknown> | void, // if async/promise we don't care about the type as we only await resolution
fn: (t: T) => Promise<unknown> | undefined, // if async we don't care about the type as we only await resolution
): Promise<void> {
for (const o of promises) {
await fn(await o);

View File

@ -47,6 +47,7 @@ import { CallFeed } from './callFeed';
import { MatrixClient } from "../client";
import { ISendEventResponse } from "../@types/requests";
import { EventEmitterEvents, TypedEventEmitter } from "../models/typed-event-emitter";
import { MatrixError } from "../http-api";
// events: hangup, error(err), replaced(call), state(state, oldState)
@ -68,7 +69,7 @@ import { EventEmitterEvents, TypedEventEmitter } from "../models/typed-event-emi
interface CallOpts {
roomId?: string;
client?: any; // Fix when client is TSified
client: MatrixClient;
forceTURN?: boolean;
turnServers?: Array<TurnServer>;
}
@ -227,7 +228,7 @@ const FALLBACK_ICE_SERVER = 'stun:turn.matrix.org';
const CALL_TIMEOUT_MS = 60000;
export class CallError extends Error {
code: string;
public readonly code: string;
constructor(code: CallErrorCode, msg: string, err: Error) {
// Still don't think there's any way to have proper nested errors
@ -271,33 +272,33 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
public roomId: string;
public callId: string;
public state = CallState.Fledgling;
public hangupParty: CallParty;
public hangupReason: string;
public direction: CallDirection;
public hangupParty?: CallParty;
public hangupReason?: string;
public direction?: CallDirection;
public ourPartyId: string;
private client: MatrixClient;
private forceTURN: boolean;
private turnServers: Array<TurnServer>;
private readonly client: MatrixClient;
private readonly forceTURN: boolean;
private readonly turnServers: Array<TurnServer>;
// A queue for candidates waiting to go out.
// We try to amalgamate candidates into a single candidate message where
// possible
private candidateSendQueue: Array<RTCIceCandidate> = [];
private candidateSendTries = 0;
private sentEndOfCandidates = false;
private peerConn: RTCPeerConnection;
private peerConn?: RTCPeerConnection;
private feeds: Array<CallFeed> = [];
private usermediaSenders: Array<RTCRtpSender> = [];
private screensharingSenders: Array<RTCRtpSender> = [];
private inviteOrAnswerSent = false;
private waitForLocalAVStream: boolean;
private successor: MatrixCall;
private opponentMember: RoomMember;
private opponentVersion: number | string;
private successor?: MatrixCall;
private opponentMember?: RoomMember;
private opponentVersion?: number | string;
// The party ID of the other side: undefined if we haven't chosen a partner
// yet, null if we have but they didn't send a party ID.
private opponentPartyId: string;
private opponentCaps: CallCapabilities;
private opponentPartyId?: string | null;
private opponentCaps?: CallCapabilities;
private inviteTimeout?: ReturnType<typeof setTimeout>;
// The logic of when & if a call is on hold is nontrivial and explained in is*OnHold
@ -307,7 +308,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
// the stats for the call at the point it ended. We can't get these after we
// tear the call down, so we just grab a snapshot before we stop the call.
// The typescript definitions have this type as 'any' :(
private callStatsAtEnd: any[];
private callStatsAtEnd?: any[];
// Perfect negotiation state: https://www.w3.org/TR/webrtc/#perfect-negotiation-example
private makingOffer = false;
@ -318,9 +319,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
// the call) we buffer them up here so we can then add the ones from the party we pick
private remoteCandidateBuffer = new Map<string, RTCIceCandidate[]>();
private remoteAssertedIdentity: AssertedIdentity;
private remoteSDPStreamMetadata: SDPStreamMetadata;
private remoteAssertedIdentity?: AssertedIdentity;
private remoteSDPStreamMetadata?: SDPStreamMetadata;
private callLengthInterval?: ReturnType<typeof setInterval>;
private callLength = 0;
@ -366,13 +366,13 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
* @param options An object providing configuration options for the data channel.
*/
public createDataChannel(label: string, options: RTCDataChannelInit) {
const dataChannel = this.peerConn.createDataChannel(label, options);
const dataChannel = this.peerConn!.createDataChannel(label, options);
this.emit(CallEvent.DataChannel, dataChannel);
logger.debug("created data channel");
return dataChannel;
}
public getOpponentMember(): RoomMember {
public getOpponentMember(): RoomMember | undefined {
return this.opponentMember;
}
@ -384,7 +384,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
return Boolean(this.opponentCaps && this.opponentCaps["m.call.dtmf"]);
}
public getRemoteAssertedIdentity(): AssertedIdentity {
public getRemoteAssertedIdentity(): AssertedIdentity | undefined {
return this.remoteAssertedIdentity;
}
@ -395,64 +395,58 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
}
public get hasLocalUserMediaVideoTrack(): boolean {
return this.localUsermediaStream?.getVideoTracks().length > 0;
return !!this.localUsermediaStream?.getVideoTracks().length;
}
public get hasRemoteUserMediaVideoTrack(): boolean {
return this.getRemoteFeeds().some((feed) => {
return (
feed.purpose === SDPStreamMetadataPurpose.Usermedia &&
feed.stream.getVideoTracks().length > 0
);
return feed.purpose === SDPStreamMetadataPurpose.Usermedia && feed.stream?.getVideoTracks().length;
});
}
public get hasLocalUserMediaAudioTrack(): boolean {
return this.localUsermediaStream?.getAudioTracks().length > 0;
return !!this.localUsermediaStream?.getAudioTracks().length;
}
public get hasRemoteUserMediaAudioTrack(): boolean {
return this.getRemoteFeeds().some((feed) => {
return (
feed.purpose === SDPStreamMetadataPurpose.Usermedia &&
feed.stream.getAudioTracks().length > 0
);
return feed.purpose === SDPStreamMetadataPurpose.Usermedia && !!feed.stream?.getAudioTracks().length;
});
}
public get localUsermediaFeed(): CallFeed {
public get localUsermediaFeed(): CallFeed | undefined {
return this.getLocalFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Usermedia);
}
public get localScreensharingFeed(): CallFeed {
public get localScreensharingFeed(): CallFeed | undefined {
return this.getLocalFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare);
}
public get localUsermediaStream(): MediaStream {
public get localUsermediaStream(): MediaStream | undefined {
return this.localUsermediaFeed?.stream;
}
public get localScreensharingStream(): MediaStream {
public get localScreensharingStream(): MediaStream | undefined {
return this.localScreensharingFeed?.stream;
}
public get remoteUsermediaFeed(): CallFeed {
public get remoteUsermediaFeed(): CallFeed | undefined {
return this.getRemoteFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Usermedia);
}
public get remoteScreensharingFeed(): CallFeed {
public get remoteScreensharingFeed(): CallFeed | undefined {
return this.getRemoteFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare);
}
public get remoteUsermediaStream(): MediaStream {
public get remoteUsermediaStream(): MediaStream | undefined {
return this.remoteUsermediaFeed?.stream;
}
public get remoteScreensharingStream(): MediaStream {
public get remoteScreensharingStream(): MediaStream | undefined {
return this.remoteScreensharingFeed?.stream;
}
private getFeedByStreamId(streamId: string): CallFeed {
private getFeedByStreamId(streamId: string): CallFeed | undefined {
return this.getFeeds().find((feed) => feed.stream.id === streamId);
}
@ -513,10 +507,10 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
return;
}
const userId = this.getOpponentMember().userId;
const purpose = this.remoteSDPStreamMetadata[stream.id].purpose;
const audioMuted = this.remoteSDPStreamMetadata[stream.id].audio_muted;
const videoMuted = this.remoteSDPStreamMetadata[stream.id].video_muted;
const userId = this.getOpponentMember()!.userId;
const purpose = this.remoteSDPStreamMetadata![stream.id].purpose;
const audioMuted = this.remoteSDPStreamMetadata![stream.id].audio_muted;
const videoMuted = this.remoteSDPStreamMetadata![stream.id].video_muted;
if (!purpose) {
logger.warn(`Ignoring stream with id ${stream.id} because we didn't get any metadata about it`);
@ -546,7 +540,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
* This method is used ONLY if the other client doesn't support sending SDPStreamMetadata
*/
private pushRemoteFeedWithoutMetadata(stream: MediaStream): void {
const userId = this.getOpponentMember().userId;
const userId = this.getOpponentMember()!.userId;
// We can guess the purpose here since the other client can only send one stream
const purpose = SDPStreamMetadataPurpose.Usermedia;
const oldRemoteStream = this.feeds.find((feed) => !feed.isLocal())?.stream;
@ -580,7 +574,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
}
private pushNewLocalFeed(stream: MediaStream, purpose: SDPStreamMetadataPurpose, addToPeerConnection = true): void {
const userId = this.client.getUserId();
const userId = this.client.getUserId()!;
// Tracks don't always start off enabled, eg. chrome will give a disabled
// audio track if you ask for user media audio and already had one that
@ -631,7 +625,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
`enabled=${track.enabled}` +
`) to peer connection`,
);
senderArray.push(this.peerConn.addTrack(track, callFeed.stream));
senderArray.push(this.peerConn!.addTrack(track, callFeed.stream));
}
}
@ -656,7 +650,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
: this.screensharingSenders;
for (const sender of senderArray) {
this.peerConn.removeTrack(sender);
this.peerConn?.removeTrack(sender);
}
// Empty the array
senderArray.splice(0, senderArray.length);
@ -687,7 +681,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
}
// The typescript definitions have this type as 'any' :(
public async getCurrentCallStats(): Promise<any[]> {
public async getCurrentCallStats(): Promise<any[] | undefined> {
if (this.callHasEnded()) {
return this.callStatsAtEnd;
}
@ -695,7 +689,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
return this.collectCallStats();
}
private async collectCallStats(): Promise<any[]> {
private async collectCallStats(): Promise<any[] | undefined> {
// This happens when the call fails before it starts.
// For example when we fail to get capture sources
if (!this.peerConn) return;
@ -765,8 +759,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
this.hangupParty = CallParty.Remote; // effectively
this.setState(CallState.Ended);
this.stopAllMedia();
if (this.peerConn.signalingState != 'closed') {
this.peerConn.close();
if (this.peerConn?.signalingState != 'closed') {
this.peerConn?.close();
}
this.emit(CallEvent.Hangup);
}
@ -786,7 +780,9 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
}
private shouldAnswerWithMediaType(
wantedValue: boolean | undefined, valueOfTheOtherSide: boolean | undefined, type: "audio" | "video",
wantedValue: boolean | undefined,
valueOfTheOtherSide: boolean | undefined,
type: "audio" | "video",
): boolean {
if (wantedValue && !valueOfTheOtherSide) {
// TODO: Figure out how to do this
@ -832,7 +828,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
const usermediaFeed = new CallFeed({
client: this.client,
roomId: this.roomId,
userId: this.client.getUserId(),
userId: this.client.getUserId()!,
stream,
purpose: SDPStreamMetadataPurpose.Usermedia,
audioMuted: false,
@ -854,7 +850,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
this.waitForLocalAVStream = false;
await this.answer(answerWithAudio, false);
} else {
this.getUserMediaFailed(e);
this.getUserMediaFailed(<Error>e);
return;
}
}
@ -953,7 +949,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
} catch (error) {
logger.error("Failed to upgrade the call", error);
this.emit(CallEvent.Error,
new CallError(CallErrorCode.NoUserMedia, "Failed to get camera access: ", error),
new CallError(CallErrorCode.NoUserMedia, "Failed to get camera access: ", <Error>error),
);
}
}
@ -1008,10 +1004,10 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
}
} else {
for (const sender of this.screensharingSenders) {
this.peerConn.removeTrack(sender);
this.peerConn?.removeTrack(sender);
}
this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream);
this.deleteFeedByStream(this.localScreensharingStream);
this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream!);
this.deleteFeedByStream(this.localScreensharingStream!);
return false;
}
}
@ -1032,13 +1028,9 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
const stream = await this.client.getMediaHandler().getScreensharingStream(desktopCapturerSourceId);
if (!stream) return false;
const track = stream.getTracks().find((track) => {
return track.kind === "video";
});
const sender = this.usermediaSenders.find((sender) => {
return sender.track?.kind === "video";
});
sender.replaceTrack(track);
const track = stream.getTracks().find(track => track.kind === "video");
const sender = this.usermediaSenders.find(sender => sender.track?.kind === "video");
sender?.replaceTrack(track ?? null);
this.pushNewLocalFeed(stream, SDPStreamMetadataPurpose.Screenshare, false);
@ -1048,16 +1040,12 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
return false;
}
} else {
const track = this.localUsermediaStream.getTracks().find((track) => {
return track.kind === "video";
});
const sender = this.usermediaSenders.find((sender) => {
return sender.track?.kind === "video";
});
sender.replaceTrack(track);
const track = this.localUsermediaStream?.getTracks().find((track) => track.kind === "video");
const sender = this.usermediaSenders.find((sender) => sender.track?.kind === "video");
sender?.replaceTrack(track ?? null);
this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream);
this.deleteFeedByStream(this.localScreensharingStream);
this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream!);
this.deleteFeedByStream(this.localScreensharingStream!);
return false;
}
@ -1070,22 +1058,22 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
public async updateLocalUsermediaStream(
stream: MediaStream, forceAudio = false, forceVideo = false,
): Promise<void> {
const callFeed = this.localUsermediaFeed;
const callFeed = this.localUsermediaFeed!;
const audioEnabled = forceAudio || (!callFeed.isAudioMuted() && !this.remoteOnHold);
const videoEnabled = forceVideo || (!callFeed.isVideoMuted() && !this.remoteOnHold);
setTracksEnabled(stream.getAudioTracks(), audioEnabled);
setTracksEnabled(stream.getVideoTracks(), videoEnabled);
// We want to keep the same stream id, so we replace the tracks rather than the whole stream
for (const track of this.localUsermediaStream.getTracks()) {
this.localUsermediaStream.removeTrack(track);
for (const track of this.localUsermediaStream!.getTracks()) {
this.localUsermediaStream!.removeTrack(track);
track.stop();
}
for (const track of stream.getTracks()) {
this.localUsermediaStream.addTrack(track);
this.localUsermediaStream!.addTrack(track);
}
const newSenders = [];
const newSenders: RTCRtpSender[] = [];
for (const track of stream.getTracks()) {
const oldSender = this.usermediaSenders.find((sender) => sender.track?.kind === track.kind);
@ -1111,7 +1099,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
`streamPurpose="${callFeed.purpose}"` +
`) to peer connection`,
);
newSender = this.peerConn.addTrack(track, this.localUsermediaStream);
newSender = this.peerConn!.addTrack(track, this.localUsermediaStream!);
}
newSenders.push(newSender);
@ -1149,7 +1137,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
* (including if the call is not set up yet).
*/
public isLocalVideoMuted(): boolean {
return this.localUsermediaFeed?.isVideoMuted();
return this.localUsermediaFeed?.isVideoMuted() ?? false;
}
/**
@ -1181,7 +1169,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
* is not set up yet).
*/
public isMicrophoneMuted(): boolean {
return this.localUsermediaFeed?.isAudioMuted();
return this.localUsermediaFeed?.isAudioMuted() ?? false;
}
/**
@ -1196,7 +1184,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
if (this.isRemoteOnHold() === onHold) return;
this.remoteOnHold = onHold;
for (const transceiver of this.peerConn.getTransceivers()) {
for (const transceiver of this.peerConn!.getTransceivers()) {
// We don't send hold music or anything so we're not actually
// sending anything, but sendrecv is fairly standard for hold and
// it makes it a lot easier to figure out who's put who on hold.
@ -1219,8 +1207,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
// We consider a call to be on hold only if *all* the tracks are on hold
// (is this the right thing to do?)
for (const transceiver of this.peerConn.getTransceivers()) {
const trackOnHold = ['inactive', 'recvonly'].includes(transceiver.currentDirection);
for (const transceiver of this.peerConn!.getTransceivers()) {
const trackOnHold = ['inactive', 'recvonly'].includes(transceiver.currentDirection!);
if (!trackOnHold) callOnHold = false;
}
@ -1233,8 +1221,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
* @param digit The digit (nb. string - '#' and '*' are dtmf too)
*/
public sendDtmfDigit(digit: string): void {
for (const sender of this.peerConn.getSenders()) {
if (sender.track.kind === 'audio' && sender.dtmf) {
for (const sender of this.peerConn!.getSenders()) {
if (sender.track?.kind === 'audio' && sender.dtmf) {
sender.dtmf.insertDTMF(digit);
return;
}
@ -1251,8 +1239,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
const micShouldBeMuted = this.isMicrophoneMuted() || this.remoteOnHold;
const vidShouldBeMuted = this.isLocalVideoMuted() || this.remoteOnHold;
setTracksEnabled(this.localUsermediaStream.getAudioTracks(), !micShouldBeMuted);
setTracksEnabled(this.localUsermediaStream.getVideoTracks(), !vidShouldBeMuted);
setTracksEnabled(this.localUsermediaStream!.getAudioTracks(), !micShouldBeMuted);
setTracksEnabled(this.localUsermediaStream!.getVideoTracks(), !vidShouldBeMuted);
}
private gotCallFeedsForInvite(callFeeds: CallFeed[]): void {
@ -1278,10 +1266,10 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
private async sendAnswer(): Promise<void> {
const answerContent = {
answer: {
sdp: this.peerConn.localDescription.sdp,
sdp: this.peerConn!.localDescription!.sdp,
// type is now deprecated as of Matrix VoIP v1, but
// required to still be sent for backwards compat
type: this.peerConn.localDescription.type,
type: this.peerConn!.localDescription!.type,
},
[SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(),
} as MCallAnswer;
@ -1305,15 +1293,15 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
} catch (error) {
// We've failed to answer: back to the ringing state
this.setState(CallState.Ringing);
this.client.cancelPendingEvent(error.event);
if (error instanceof MatrixError && error.event) this.client.cancelPendingEvent(error.event);
let code = CallErrorCode.SendAnswer;
let message = "Failed to send answer";
if (error.name == 'UnknownDeviceError') {
if ((<Error>error).name == 'UnknownDeviceError') {
code = CallErrorCode.UnknownDevices;
message = "Unknown devices present in the room";
}
this.emit(CallEvent.Error, new CallError(code, message, error));
this.emit(CallEvent.Error, new CallError(code, message, <Error>error));
throw error;
}
@ -1333,10 +1321,10 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
this.setState(CallState.CreateAnswer);
let myAnswer;
let myAnswer: RTCSessionDescriptionInit;
try {
this.getRidOfRTXCodecs();
myAnswer = await this.peerConn.createAnswer();
myAnswer = await this.peerConn!.createAnswer();
} catch (err) {
logger.debug("Failed to create answer: ", err);
this.terminate(CallParty.Local, CallErrorCode.CreateAnswer, true);
@ -1344,7 +1332,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
}
try {
await this.peerConn.setLocalDescription(myAnswer);
await this.peerConn!.setLocalDescription(myAnswer);
this.setState(CallState.Connecting);
// Allow a short time for initial candidates to be gathered
@ -1364,7 +1352,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
* Internal
* @param {Object} event
*/
private gotLocalIceCandidate = (event: RTCPeerConnectionIceEvent): Promise<void> => {
private gotLocalIceCandidate = (event: RTCPeerConnectionIceEvent): void => {
if (event.candidate) {
logger.debug(
"Call " + this.callId + " got local ICE " + event.candidate.sdpMid + " candidate: " +
@ -1384,8 +1372,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
};
private onIceGatheringStateChange = (event: Event): void => {
logger.debug("ice gathering state changed to " + this.peerConn.iceGatheringState);
if (this.peerConn.iceGatheringState === 'complete' && !this.sentEndOfCandidates) {
logger.debug("ice gathering state changed to " + this.peerConn?.iceGatheringState);
if (this.peerConn?.iceGatheringState === 'complete' && !this.sentEndOfCandidates) {
// If we didn't get an empty-string candidate to signal the end of candidates,
// create one ourselves now gathering has finished.
// We cast because the interface lists all the properties as required but we
@ -1471,7 +1459,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
}
try {
await this.peerConn.setRemoteDescription(content.answer);
await this.peerConn!.setRemoteDescription(content.answer);
} catch (e) {
logger.debug("Failed to set remote description", e);
this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false);
@ -1530,7 +1518,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
// https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Perfect_negotiation
const offerCollision = (
(description.type === 'offer') &&
(this.makingOffer || this.peerConn.signalingState !== 'stable')
(this.makingOffer || this.peerConn!.signalingState !== 'stable')
);
this.ignoreOffer = !polite && offerCollision;
@ -1549,15 +1537,15 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
}
try {
await this.peerConn.setRemoteDescription(description);
await this.peerConn!.setRemoteDescription(description);
if (description.type === 'offer') {
this.getRidOfRTXCodecs();
const localDescription = await this.peerConn.createAnswer();
await this.peerConn.setLocalDescription(localDescription);
const localDescription = await this.peerConn!.createAnswer();
await this.peerConn!.setLocalDescription(localDescription);
this.sendVoipEvent(EventType.CallNegotiate, {
description: this.peerConn.localDescription,
description: this.peerConn!.localDescription,
[SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(),
});
}
@ -1577,10 +1565,10 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
this.remoteSDPStreamMetadata = utils.recursivelyAssign(this.remoteSDPStreamMetadata || {}, metadata, true);
for (const feed of this.getRemoteFeeds()) {
const streamId = feed.stream.id;
const metadata = this.remoteSDPStreamMetadata[streamId];
const metadata = this.remoteSDPStreamMetadata![streamId];
feed.setAudioVideoMuted(metadata?.audio_muted, metadata?.video_muted);
feed.purpose = this.remoteSDPStreamMetadata[streamId]?.purpose;
feed.purpose = this.remoteSDPStreamMetadata![streamId]?.purpose;
}
}
@ -1618,14 +1606,14 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
}
try {
await this.peerConn.setLocalDescription(description);
await this.peerConn!.setLocalDescription(description);
} catch (err) {
logger.debug("Error setting local description!", err);
this.terminate(CallParty.Local, CallErrorCode.SetLocalDescription, true);
return;
}
if (this.peerConn.iceGatheringState === 'gathering') {
if (this.peerConn!.iceGatheringState === 'gathering') {
// Allow a short time for initial candidates to be gathered
await new Promise(resolve => {
setTimeout(resolve, 200);
@ -1642,9 +1630,9 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
// clunky because TypeScript can't follow the types through if we use an expression as the key
if (this.state === CallState.CreateOffer) {
content.offer = this.peerConn.localDescription;
content.offer = this.peerConn!.localDescription!;
} else {
content.description = this.peerConn.localDescription;
content.description = this.peerConn!.localDescription!;
}
content.capabilities = {
@ -1663,7 +1651,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
await this.sendVoipEvent(eventType, content);
} catch (error) {
logger.error("Failed to send invite", error);
if (error.event) this.client.cancelPendingEvent(error.event);
if (error instanceof MatrixError && error.event) this.client.cancelPendingEvent(error.event);
let code = CallErrorCode.SignallingFailed;
let message = "Signalling failed";
@ -1671,12 +1659,12 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
code = CallErrorCode.SendInvite;
message = "Failed to send invite";
}
if (error.name == 'UnknownDeviceError') {
if ((<Error>error).name == 'UnknownDeviceError') {
code = CallErrorCode.UnknownDevices;
message = "Unknown devices present in the room";
}
this.emit(CallEvent.Error, new CallError(code, message, error));
this.emit(CallEvent.Error, new CallError(code, message, <Error>error));
this.terminate(CallParty.Local, code, false);
// no need to carry on & send the candidate queue, but we also
@ -1734,11 +1722,11 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
return; // because ICE can still complete as we're ending the call
}
logger.debug(
"Call ID " + this.callId + ": ICE connection state changed to: " + this.peerConn.iceConnectionState,
"Call ID " + this.callId + ": ICE connection state changed to: " + this.peerConn?.iceConnectionState,
);
// ideally we'd consider the call to be connected when we get media but
// chrome doesn't implement any of the 'onstarted' events yet
if (this.peerConn.iceConnectionState == 'connected') {
if (this.peerConn?.iceConnectionState == 'connected') {
this.setState(CallState.Connected);
if (!this.callLengthInterval) {
@ -1747,16 +1735,13 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
this.emit(CallEvent.LengthChanged, this.callLength);
}, 1000);
}
} else if (this.peerConn.iceConnectionState == 'failed') {
} else if (this.peerConn?.iceConnectionState == 'failed') {
this.hangup(CallErrorCode.IceFailed, false);
}
};
private onSignallingStateChanged = (): void => {
logger.debug(
"call " + this.callId + ": Signalling state changed to: " +
this.peerConn.signalingState,
);
logger.debug(`call ${this.callId}: Signalling state changed to: ${this.peerConn?.signalingState}`);
};
private onTrack = (ev: RTCTrackEvent): void => {
@ -1796,8 +1781,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
// RTCRtpReceiver.getCapabilities and RTCRtpSender.getCapabilities don't seem to be supported on FF
if (!RTCRtpReceiver.getCapabilities || !RTCRtpSender.getCapabilities) return;
const recvCodecs = RTCRtpReceiver.getCapabilities("video").codecs;
const sendCodecs = RTCRtpSender.getCapabilities("video").codecs;
const recvCodecs = RTCRtpReceiver.getCapabilities("video")!.codecs;
const sendCodecs = RTCRtpSender.getCapabilities("video")!.codecs;
const codecs = [...sendCodecs, ...recvCodecs];
for (const codec of codecs) {
@ -1807,7 +1792,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
}
}
for (const trans of this.peerConn.getTransceivers()) {
for (const trans of this.peerConn!.getTransceivers()) {
if (
this.screensharingSenders.includes(trans.sender) &&
(
@ -1831,10 +1816,10 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
this.makingOffer = true;
try {
this.getRidOfRTXCodecs();
const myOffer = await this.peerConn.createOffer();
const myOffer = await this.peerConn!.createOffer();
await this.gotLocalOffer(myOffer);
} catch (e) {
this.getLocalOfferFailed(e);
this.getLocalOfferFailed(<Error>e);
return;
} finally {
this.makingOffer = false;
@ -1961,9 +1946,11 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
* Transfers this call to the target call, effectively 'joining' the
* two calls (so the remote parties on each call are connected together).
*/
public async transferToCall(transferTargetCall?: MatrixCall): Promise<void> {
const targetProfileInfo = await this.client.getProfileInfo(transferTargetCall.getOpponentMember().userId);
const transfereeProfileInfo = await this.client.getProfileInfo(this.getOpponentMember().userId);
public async transferToCall(transferTargetCall: MatrixCall): Promise<void> {
const targetUserId = transferTargetCall.getOpponentMember()?.userId;
const targetProfileInfo = targetUserId ? await this.client.getProfileInfo(targetUserId) : undefined;
const opponentUserId = this.getOpponentMember()?.userId;
const transfereeProfileInfo = opponentUserId ? await this.client.getProfileInfo(opponentUserId) : undefined;
const newCallId = genCallID();
@ -1972,9 +1959,9 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
// ID of the new call (but we can use the same function to generate it)
replacement_id: genCallID(),
target_user: {
id: this.getOpponentMember().userId,
display_name: transfereeProfileInfo.displayname,
avatar_url: transfereeProfileInfo.avatar_url,
id: opponentUserId,
display_name: transfereeProfileInfo?.displayname,
avatar_url: transfereeProfileInfo?.avatar_url,
},
await_call: newCallId,
} as MCallReplacesEvent;
@ -1984,9 +1971,9 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
const bodyToTransferee = {
replacement_id: genCallID(),
target_user: {
id: transferTargetCall.getOpponentMember().userId,
display_name: targetProfileInfo.displayname,
avatar_url: targetProfileInfo.avatar_url,
id: targetUserId,
display_name: targetProfileInfo?.displayname,
avatar_url: targetProfileInfo?.avatar_url,
},
create_call: newCallId,
} as MCallReplacesEvent;
@ -2072,7 +2059,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
} catch (error) {
// don't retry this event: we'll send another one later as we might
// have more candidates by then.
if (error.event) this.client.cancelPendingEvent(error.event);
if (error instanceof MatrixError && error.event) this.client.cancelPendingEvent(error.event);
// put all the candidates we failed to send back in the queue
this.candidateSendQueue.push(...candidates);
@ -2086,7 +2073,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
const code = CallErrorCode.SignallingFailed;
const message = "Signalling failed";
this.emit(CallEvent.Error, new CallError(code, message, error));
this.emit(CallEvent.Error, new CallError(code, message, <Error>error));
this.hangup(code, false);
return;
@ -2123,7 +2110,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
const callFeed = new CallFeed({
client: this.client,
roomId: this.roomId,
userId: this.client.getUserId(),
userId: this.client.getUserId()!,
stream,
purpose: SDPStreamMetadataPurpose.Usermedia,
audioMuted: false,
@ -2131,7 +2118,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
});
await this.placeCallWithCallFeeds([callFeed]);
} catch (e) {
this.getUserMediaFailed(e);
this.getUserMediaFailed(<Error>e);
return;
}
}
@ -2214,12 +2201,12 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
}
private async addBufferedIceCandidates(): Promise<void> {
const bufferedCandidates = this.remoteCandidateBuffer.get(this.opponentPartyId);
const bufferedCandidates = this.remoteCandidateBuffer.get(this.opponentPartyId!);
if (bufferedCandidates) {
logger.info(`Adding ${bufferedCandidates.length} buffered candidates for opponent ${this.opponentPartyId}`);
await this.addIceCandidates(bufferedCandidates);
}
this.remoteCandidateBuffer = null;
this.remoteCandidateBuffer.clear();
}
private async addIceCandidates(candidates: RTCIceCandidate[]): Promise<void> {
@ -2235,7 +2222,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
"Call " + this.callId + " got remote ICE " + candidate.sdpMid + " candidate: " + candidate.candidate,
);
try {
await this.peerConn.addIceCandidate(candidate);
await this.peerConn!.addIceCandidate(candidate);
} catch (err) {
if (!this.ignoreOffer) {
logger.info("Failed to add remote ICE candidate", err);
@ -2299,7 +2286,11 @@ export function supportsMatrixCall(): boolean {
* since it's only possible to set this option on outbound calls.
* @return {MatrixCall} the call or null if the browser doesn't support calling.
*/
export function createNewMatrixCall(client: any, roomId: string, options?: CallOpts): MatrixCall | null {
export function createNewMatrixCall(
client: MatrixClient,
roomId: string,
options?: Pick<CallOpts, "forceTURN">,
): MatrixCall | null {
if (!supportsMatrixCall()) return null;
const optionsForceTURN = options ? options.forceTURN : false;

View File

@ -163,7 +163,7 @@ export class CallEventHandler {
this.client,
event.getRoomId(),
{ forceTURN: this.client.forceTURN },
);
) ?? undefined;
if (!call) {
logger.log(
"Incoming call ID " + content.call_id + " but this client " +
@ -181,13 +181,13 @@ export class CallEventHandler {
// if we stashed candidate events for that call ID, play them back now
if (this.candidateEventsByCall.get(call.callId)) {
for (const ev of this.candidateEventsByCall.get(call.callId)) {
for (const ev of this.candidateEventsByCall.get(call.callId)!) {
call.onRemoteIceCandidatesReceived(ev);
}
}
// Were we trying to call that user (room)?
let existingCall: MatrixCall;
let existingCall: MatrixCall | undefined;
for (const thisCall of this.calls.values()) {
const isCalling = [CallState.WaitLocalMedia, CallState.CreateOffer, CallState.InviteSent].includes(
thisCall.state,
@ -238,7 +238,7 @@ export class CallEventHandler {
if (!this.candidateEventsByCall.has(content.call_id)) {
this.candidateEventsByCall.set(content.call_id, []);
}
this.candidateEventsByCall.get(content.call_id).push(event);
this.candidateEventsByCall.get(content.call_id)!.push(event);
} else {
call.onRemoteIceCandidatesReceived(event);
}
@ -250,7 +250,7 @@ export class CallEventHandler {
// if not live, store the fact that the call has ended because
// we're probably getting events backwards so
// the hangup will come before the invite
call = createNewMatrixCall(this.client, event.getRoomId());
call = createNewMatrixCall(this.client, event.getRoomId()) ?? undefined;
if (call) {
call.callId = content.call_id;
call.initWithHangup(event);

View File

@ -64,12 +64,12 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
private audioMuted: boolean;
private videoMuted: boolean;
private measuringVolumeActivity = false;
private audioContext: AudioContext;
private analyser: AnalyserNode;
private frequencyBinCount: Float32Array;
private audioContext?: AudioContext;
private analyser?: AnalyserNode;
private frequencyBinCount?: Float32Array;
private speakingThreshold = SPEAKING_THRESHOLD;
private speaking = false;
private volumeLooperTimeout: ReturnType<typeof setTimeout>;
private volumeLooperTimeout?: ReturnType<typeof setTimeout>;
constructor(opts: ICallFeedOpts) {
super();
@ -83,6 +83,7 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
this.speakingVolumeSamples = new Array(SPEAKING_SAMPLE_COUNT).fill(-Infinity);
this.updateStream(null, opts.stream);
this.stream = opts.stream; // updateStream does this, but this makes TS happier
if (this.hasAudioTrack) {
this.initVolumeMeasuring();
@ -93,22 +94,21 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
return this.stream.getAudioTracks().length > 0;
}
private updateStream(oldStream: MediaStream, newStream: MediaStream): void {
private updateStream(oldStream: MediaStream | null, newStream: MediaStream): void {
if (newStream === oldStream) return;
if (oldStream) {
oldStream.removeEventListener("addtrack", this.onAddTrack);
this.measureVolumeActivity(false);
}
if (newStream) {
this.stream = newStream;
newStream.addEventListener("addtrack", this.onAddTrack);
if (this.hasAudioTrack) {
this.initVolumeMeasuring();
} else {
this.measureVolumeActivity(false);
}
this.stream = newStream;
newStream.addEventListener("addtrack", this.onAddTrack);
if (this.hasAudioTrack) {
this.initVolumeMeasuring();
} else {
this.measureVolumeActivity(false);
}
this.emit(CallFeedEvent.NewStream, this.stream);
@ -138,9 +138,9 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
* Returns callRoom member
* @returns member of the callRoom
*/
public getMember(): RoomMember {
public getMember(): RoomMember | null {
const callRoom = this.client.getRoom(this.roomId);
return callRoom.getMember(this.userId);
return callRoom?.getMember(this.userId) ?? null;
}
/**
@ -177,9 +177,10 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
/**
* Set one or both of feed's internal audio and video video mute state
* Either value may be null to leave it as-is
* @param muted is the feed's video muted?
* @param audioMuted is the feed's audio muted?
* @param videoMuted is the feed's video muted?
*/
public setAudioVideoMuted(audioMuted: boolean, videoMuted: boolean): void {
public setAudioVideoMuted(audioMuted: boolean | null, videoMuted: boolean | null): void {
if (audioMuted !== null) {
if (this.audioMuted !== audioMuted) {
this.speakingVolumeSamples.fill(-Infinity);
@ -216,12 +217,12 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
if (!this.measuringVolumeActivity) return;
this.analyser.getFloatFrequencyData(this.frequencyBinCount);
this.analyser.getFloatFrequencyData(this.frequencyBinCount!);
let maxVolume = -Infinity;
for (let i = 0; i < this.frequencyBinCount.length; i++) {
if (this.frequencyBinCount[i] > maxVolume) {
maxVolume = this.frequencyBinCount[i];
for (let i = 0; i < this.frequencyBinCount!.length; i++) {
if (this.frequencyBinCount![i] > maxVolume) {
maxVolume = this.frequencyBinCount![i];
}
}

View File

@ -22,8 +22,8 @@ import { MatrixClient } from "../client";
import { CallState } from "./call";
export class MediaHandler {
private audioInput: string;
private videoInput: string;
private audioInput?: string;
private videoInput?: string;
private localUserMediaStream?: MediaStream;
public userMediaStreams: MediaStream[] = [];
public screensharingStreams: MediaStream[] = [];
@ -75,7 +75,7 @@ export class MediaHandler {
for (const call of this.client.callEventHandler.calls.values()) {
if (call.state === CallState.Ended || !callMediaStreamParams.has(call.callId)) continue;
const { audio, video } = callMediaStreamParams.get(call.callId);
const { audio, video } = callMediaStreamParams.get(call.callId)!;
// This stream won't be reusable as we will replace the tracks of the old stream
const stream = await this.getUserMediaStream(audio, video, false);
@ -121,9 +121,9 @@ export class MediaHandler {
const settings = track.getSettings();
if (track.kind === "audio") {
this.audioInput = settings.deviceId;
this.audioInput = settings.deviceId!;
} else if (track.kind === "video") {
this.videoInput = settings.deviceId;
this.videoInput = settings.deviceId!;
}
}
@ -179,7 +179,10 @@ export class MediaHandler {
* @param reusable is allowed to be reused by the MediaHandler
* @returns {MediaStream} based on passed parameters
*/
public async getScreensharingStream(desktopCapturerSourceId: string, reusable = true): Promise<MediaStream | null> {
public async getScreensharingStream(
desktopCapturerSourceId?: string,
reusable = true,
): Promise<MediaStream | null> {
let stream: MediaStream;
if (this.screensharingStreams.length === 0) {