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

Fix more typescript --strict violations (#2795)

* Stash tsc fixes

* Iterate

* Iterate

* Iterate

* Fix tests

* Iterate

* Iterate

* Iterate

* Iterate

* Add tests
This commit is contained in:
Michael Telatynski
2022-10-25 18:31:40 +01:00
committed by GitHub
parent 4b3e6939d6
commit 9f2f08dfd3
77 changed files with 829 additions and 733 deletions

View File

@ -658,7 +658,7 @@ describe("MatrixClient", function() {
// The vote event has been copied into the thread
const eventRefWithThreadId = withThreadId(
eventPollResponseReference, eventPollStartThreadRoot.getId());
eventPollResponseReference, eventPollStartThreadRoot.getId()!);
expect(eventRefWithThreadId.threadRootId).toBeTruthy();
expect(threaded).toEqual([
@ -695,7 +695,7 @@ describe("MatrixClient", function() {
expect(threaded).toEqual([
eventPollStartThreadRoot,
eventMessageInThread,
withThreadId(eventReaction, eventPollStartThreadRoot.getId()),
withThreadId(eventReaction, eventPollStartThreadRoot.getId()!),
]);
});
@ -725,7 +725,7 @@ describe("MatrixClient", function() {
expect(threaded).toEqual([
eventPollStartThreadRoot,
withThreadId(eventPollResponseReference, eventPollStartThreadRoot.getId()),
withThreadId(eventPollResponseReference, eventPollStartThreadRoot.getId()!),
eventMessageInThread,
]);
});
@ -757,7 +757,7 @@ describe("MatrixClient", function() {
expect(threaded).toEqual([
eventPollStartThreadRoot,
eventMessageInThread,
withThreadId(eventReaction, eventPollStartThreadRoot.getId()),
withThreadId(eventReaction, eventPollStartThreadRoot.getId()!),
]);
});
@ -813,7 +813,7 @@ describe("MatrixClient", function() {
// Thread should contain only stuff that happened in the thread - no room state events
expect(threaded).toEqual([
eventPollStartThreadRoot,
withThreadId(eventPollResponseReference, eventPollStartThreadRoot.getId()),
withThreadId(eventPollResponseReference, eventPollStartThreadRoot.getId()!),
eventMessageInThread,
]);
});
@ -1375,7 +1375,7 @@ const buildEventMessageInThread = (root: MatrixEvent) => new MatrixEvent({
"m.relates_to": {
"event_id": root.getId(),
"m.in_reply_to": {
"event_id": root.getId(),
"event_id": root.getId()!,
},
"rel_type": "m.thread",
},
@ -1474,13 +1474,13 @@ const buildEventReply = (target: MatrixEvent) => new MatrixEvent({
"device_id": "XISFUZSKHH",
"m.relates_to": {
"m.in_reply_to": {
"event_id": target.getId(),
"event_id": target.getId()!,
},
},
"sender_key": "i3N3CtG/CD2bGB8rA9fW6adLYSDvlUhf2iuU73L65Vg",
"session_id": "Ja11R/KG6ua0wdk8zAzognrxjio1Gm/RK2Gn6lFL804",
},
"event_id": target.getId() + Math.random(),
"event_id": target.getId()! + Math.random(),
"origin_server_ts": 1643815466378,
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
"sender": "@andybalaam-test1:matrix.org",

View File

@ -307,7 +307,7 @@ export function mkReplyMessage(
"rel_type": "m.in_reply_to",
"event_id": opts.replyToMessage.getId(),
"m.in_reply_to": {
"event_id": opts.replyToMessage.getId(),
"event_id": opts.replyToMessage.getId()!,
},
},
},

View File

@ -222,9 +222,9 @@ describe.each([
["IndexedDBCryptoStore",
() => new IndexedDBCryptoStore(global.indexedDB, "tests")],
["LocalStorageCryptoStore",
() => new IndexedDBCryptoStore(undefined, "tests")],
() => new IndexedDBCryptoStore(undefined!, "tests")],
["MemoryCryptoStore", () => {
const store = new IndexedDBCryptoStore(undefined, "tests");
const store = new IndexedDBCryptoStore(undefined!, "tests");
// @ts-ignore set private properties
store._backend = new MemoryCryptoStore();
// @ts-ignore
@ -255,6 +255,6 @@ describe.each([
expect(nokey).toBeNull();
const key = await getCrossSigningKeyCache!("self_signing", "");
expect(new Uint8Array(key)).toEqual(testKey);
expect(new Uint8Array(key!)).toEqual(testKey);
});
});

View File

@ -536,6 +536,7 @@ describe("MegolmDecryption", function() {
"@bob:example.com", BOB_DEVICES,
);
aliceClient.crypto!.deviceList.downloadKeys = async function(userIds) {
// @ts-ignore short-circuiting private method
return this.getDevicesFromStore(userIds);
};

View File

@ -437,6 +437,7 @@ describe("Secrets", function() {
return [keyId, secretStorageKeys[keyId]];
}
}
return null;
},
},
},
@ -571,6 +572,7 @@ describe("Secrets", function() {
return [keyId, secretStorageKeys[keyId]];
}
}
return null;
},
},
},

View File

@ -131,7 +131,11 @@ function makeRemoteEcho(event) {
}));
}
async function distributeEvent(ownRequest, theirRequest, event) {
async function distributeEvent(
ownRequest: VerificationRequest,
theirRequest: VerificationRequest,
event: MatrixEvent,
): Promise<void> {
await ownRequest.channel.handleEvent(
makeRemoteEcho(event),
ownRequest,

View File

@ -45,7 +45,7 @@ describe('EventTimelineSet', () => {
it('should return the related events', () => {
eventTimelineSet.relations.aggregateChildEvent(messageEvent);
const relations = eventTimelineSet.relations.getChildEventsForEvent(
messageEvent.getId(),
messageEvent.getId()!,
"m.in_reply_to",
EventType.RoomMessage,
);
@ -193,7 +193,7 @@ describe('EventTimelineSet', () => {
it('should not return the related events', () => {
eventTimelineSet.relations.aggregateChildEvent(messageEvent);
const relations = eventTimelineSet.relations.getChildEventsForEvent(
messageEvent.getId(),
messageEvent.getId()!,
"m.in_reply_to",
EventType.RoomMessage,
);
@ -236,7 +236,7 @@ describe('EventTimelineSet', () => {
"m.relates_to": {
"event_id": root.getId(),
"m.in_reply_to": {
"event_id": root.getId(),
"event_id": root.getId()!,
},
"rel_type": "m.thread",
},
@ -278,14 +278,14 @@ describe('EventTimelineSet', () => {
});
it("should return true if the timeline set is for a thread and the event is its thread root", () => {
const thread = new Thread(messageEvent.getId(), messageEvent, { room, client });
const thread = new Thread(messageEvent.getId()!, messageEvent, { room, client });
const eventTimelineSet = new EventTimelineSet(room, {}, client, thread);
messageEvent.setThread(thread);
expect(eventTimelineSet.canContain(messageEvent)).toBeTruthy();
});
it("should return true if the timeline set is for a thread and the event is a response to it", () => {
const thread = new Thread(messageEvent.getId(), messageEvent, { room, client });
const thread = new Thread(messageEvent.getId()!, messageEvent, { room, client });
const eventTimelineSet = new EventTimelineSet(room, {}, client, thread);
messageEvent.setThread(thread);
const event = mkThreadResponse(messageEvent);
@ -310,7 +310,7 @@ describe('EventTimelineSet', () => {
content: { body: "test" },
event_id: "!test1:server",
});
eventTimelineSet.handleRemoteEcho(roomMessageEvent, "~!local-event-id:server", roomMessageEvent.getId());
eventTimelineSet.handleRemoteEcho(roomMessageEvent, "~!local-event-id:server", roomMessageEvent.getId()!);
expect(eventTimelineSet.getLiveTimeline().getEvents()).toContain(roomMessageEvent);
const roomFilteredEvent = new MatrixEvent({
@ -318,7 +318,7 @@ describe('EventTimelineSet', () => {
content: { body: "test" },
event_id: "!test2:server",
});
eventTimelineSet.handleRemoteEcho(roomFilteredEvent, "~!local-event-id:server", roomFilteredEvent.getId());
eventTimelineSet.handleRemoteEcho(roomFilteredEvent, "~!local-event-id:server", roomFilteredEvent.getId()!);
expect(eventTimelineSet.getLiveTimeline().getEvents()).not.toContain(roomFilteredEvent);
});
});

View File

@ -341,11 +341,11 @@ describe("EventTimeline", function() {
timeline.addEvent(events[1], { toStartOfTimeline: false });
expect(timeline.getEvents().length).toEqual(2);
let ev = timeline.removeEvent(events[0].getId());
let ev = timeline.removeEvent(events[0].getId()!);
expect(ev).toBe(events[0]);
expect(timeline.getEvents().length).toEqual(1);
ev = timeline.removeEvent(events[1].getId());
ev = timeline.removeEvent(events[1].getId()!);
expect(ev).toBe(events[1]);
expect(timeline.getEvents().length).toEqual(0);
});
@ -357,11 +357,11 @@ describe("EventTimeline", function() {
expect(timeline.getEvents().length).toEqual(3);
expect(timeline.getBaseIndex()).toEqual(1);
timeline.removeEvent(events[2].getId());
timeline.removeEvent(events[2].getId()!);
expect(timeline.getEvents().length).toEqual(2);
expect(timeline.getBaseIndex()).toEqual(1);
timeline.removeEvent(events[1].getId());
timeline.removeEvent(events[1].getId()!);
expect(timeline.getEvents().length).toEqual(1);
expect(timeline.getBaseIndex()).toEqual(0);
});
@ -372,7 +372,7 @@ describe("EventTimeline", function() {
it("should not make baseIndex assplode when removing the last event",
function() {
timeline.addEvent(events[0], { toStartOfTimeline: true });
timeline.removeEvent(events[0].getId());
timeline.removeEvent(events[0].getId()!);
const initialIndex = timeline.getBaseIndex();
timeline.addEvent(events[1], { toStartOfTimeline: false });
timeline.addEvent(events[2], { toStartOfTimeline: false });

View File

@ -1,65 +0,0 @@
/*
Copyright 2017 New Vector Ltd
Copyright 2019, 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 { MatrixEvent } from "../../src/models/event";
describe("MatrixEvent", () => {
describe(".attemptDecryption", () => {
let encryptedEvent;
const eventId = 'test_encrypted_event';
beforeEach(() => {
encryptedEvent = new MatrixEvent({
event_id: eventId,
type: 'm.room.encrypted',
content: {
ciphertext: 'secrets',
},
});
});
it('should retry decryption if a retry is queued', async () => {
const eventAttemptDecryptionSpy = jest.spyOn(encryptedEvent, 'attemptDecryption');
const crypto = {
decryptEvent: jest.fn()
.mockImplementationOnce(() => {
// schedule a second decryption attempt while
// the first one is still running.
encryptedEvent.attemptDecryption(crypto);
const error = new Error("nope");
error.name = 'DecryptionError';
return Promise.reject(error);
})
.mockImplementationOnce(() => {
return Promise.resolve({
clearEvent: {
type: 'm.room.message',
},
});
}),
};
await encryptedEvent.attemptDecryption(crypto);
expect(eventAttemptDecryptionSpy).toHaveBeenCalledTimes(2);
expect(crypto.decryptEvent).toHaveBeenCalledTimes(2);
expect(encryptedEvent.getType()).toEqual('m.room.message');
});
});
});

View File

@ -312,7 +312,7 @@ describe("MSC3089Branch", () => {
} as MatrixEvent);
const events = [await branch.getFileEvent(), await branch2.getFileEvent(), {
replacingEventId: (): string => null,
replacingEventId: (): string | undefined => undefined,
getId: () => "$unknown",
}];
staticRoom.getLiveTimeline = () => ({ getEvents: () => events }) as EventTimeline;

View File

@ -135,7 +135,7 @@ describe("MSC3089TreeSpace", () => {
// noinspection ExceptionCaughtLocallyJS
throw new Error("Failed to fail");
} catch (e) {
expect(e.errcode).toEqual("M_FORBIDDEN");
expect((<MatrixError>e).errcode).toEqual("M_FORBIDDEN");
}
expect(fn).toHaveBeenCalledTimes(1);
@ -513,7 +513,7 @@ describe("MSC3089TreeSpace", () => {
function expectOrder(childRoomId: string, order: number) {
const child = childTrees.find(c => c.roomId === childRoomId);
expect(child).toBeDefined();
expect(child.getOrder()).toEqual(order);
expect(child!.getOrder()).toEqual(order);
}
function makeMockChildRoom(roomId: string): Room {
@ -608,7 +608,7 @@ describe("MSC3089TreeSpace", () => {
// noinspection ExceptionCaughtLocallyJS
throw new Error("Failed to fail");
} catch (e) {
expect(e.message).toEqual("Cannot set order of top level spaces currently");
expect((<Error>e).message).toEqual("Cannot set order of top level spaces currently");
}
});
@ -706,7 +706,7 @@ describe("MSC3089TreeSpace", () => {
const treeA = childTrees.find(c => c.roomId === a);
expect(treeA).toBeDefined();
await treeA.setOrder(1);
await treeA!.setOrder(1);
expect(clientSendStateFn).toHaveBeenCalledTimes(3);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
@ -743,7 +743,7 @@ describe("MSC3089TreeSpace", () => {
const treeA = childTrees.find(c => c.roomId === a);
expect(treeA).toBeDefined();
await treeA.setOrder(1);
await treeA!.setOrder(1);
expect(clientSendStateFn).toHaveBeenCalledTimes(1);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
@ -771,7 +771,7 @@ describe("MSC3089TreeSpace", () => {
const treeA = childTrees.find(c => c.roomId === a);
expect(treeA).toBeDefined();
await treeA.setOrder(2);
await treeA!.setOrder(2);
expect(clientSendStateFn).toHaveBeenCalledTimes(1);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
@ -800,7 +800,7 @@ describe("MSC3089TreeSpace", () => {
const treeB = childTrees.find(c => c.roomId === b);
expect(treeB).toBeDefined();
await treeB.setOrder(2);
await treeB!.setOrder(2);
expect(clientSendStateFn).toHaveBeenCalledTimes(1);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
@ -829,7 +829,7 @@ describe("MSC3089TreeSpace", () => {
const treeC = childTrees.find(ch => ch.roomId === c);
expect(treeC).toBeDefined();
await treeC.setOrder(1);
await treeC!.setOrder(1);
expect(clientSendStateFn).toHaveBeenCalledTimes(1);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
@ -858,7 +858,7 @@ describe("MSC3089TreeSpace", () => {
const treeB = childTrees.find(ch => ch.roomId === b);
expect(treeB).toBeDefined();
await treeB.setOrder(2);
await treeB!.setOrder(2);
expect(clientSendStateFn).toHaveBeenCalledTimes(2);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
@ -903,7 +903,7 @@ describe("MSC3089TreeSpace", () => {
url: mxc,
file: fileInfo,
metadata: true, // additional content from test
[UNSTABLE_MSC3089_LEAF.unstable]: {}, // test to ensure we're definitely using unstable
[UNSTABLE_MSC3089_LEAF.unstable!]: {}, // test to ensure we're definitely using unstable
});
return Promise.resolve({ event_id: fileEventId }); // eslint-disable-line camelcase
@ -965,7 +965,7 @@ describe("MSC3089TreeSpace", () => {
expect(contents).toMatchObject({
...content,
"m.new_content": content,
[UNSTABLE_MSC3089_LEAF.unstable]: {}, // test to ensure we're definitely using unstable
[UNSTABLE_MSC3089_LEAF.unstable!]: {}, // test to ensure we're definitely using unstable
});
return Promise.resolve({ event_id: fileEventId }); // eslint-disable-line camelcase
@ -1010,7 +1010,7 @@ describe("MSC3089TreeSpace", () => {
const file = tree.getFile(fileEventId);
expect(file).toBeDefined();
expect(file.indexEvent).toBe(fileEvent);
expect(file!.indexEvent).toBe(fileEvent);
});
it('should return falsy for unknown files', () => {

View File

@ -263,7 +263,7 @@ describe('Beacon', () => {
roomId,
);
// less than the original event
oldUpdateEvent.event.origin_server_ts = liveBeaconEvent.event.origin_server_ts - 1000;
oldUpdateEvent.event.origin_server_ts = liveBeaconEvent.event.origin_server_ts! - 1000;
beacon.update(oldUpdateEvent);
// didnt update

View File

@ -115,8 +115,53 @@ describe('MatrixEvent', () => {
});
const prom = emitPromise(ev, MatrixEventEvent.VisibilityChange);
ev.applyVisibilityEvent({ visible: false, eventId: ev.getId(), reason: null });
ev.applyVisibilityEvent({ visible: false, eventId: ev.getId()!, reason: null });
await prom;
});
});
describe(".attemptDecryption", () => {
let encryptedEvent;
const eventId = 'test_encrypted_event';
beforeEach(() => {
encryptedEvent = new MatrixEvent({
event_id: eventId,
type: 'm.room.encrypted',
content: {
ciphertext: 'secrets',
},
});
});
it('should retry decryption if a retry is queued', async () => {
const eventAttemptDecryptionSpy = jest.spyOn(encryptedEvent, 'attemptDecryption');
const crypto = {
decryptEvent: jest.fn()
.mockImplementationOnce(() => {
// schedule a second decryption attempt while
// the first one is still running.
encryptedEvent.attemptDecryption(crypto);
const error = new Error("nope");
error.name = 'DecryptionError';
return Promise.reject(error);
})
.mockImplementationOnce(() => {
return Promise.resolve({
clearEvent: {
type: 'm.room.message',
},
});
}),
};
await encryptedEvent.attemptDecryption(crypto);
expect(eventAttemptDecryptionSpy).toHaveBeenCalledTimes(2);
expect(crypto.decryptEvent).toHaveBeenCalledTimes(2);
expect(encryptedEvent.getType()).toEqual('m.room.message');
});
});
});

View File

@ -140,11 +140,11 @@ describe.each([
],
});
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0);
expect(httpBackend.flushSync(null, 1)).toEqual(1);
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0);
expect(httpBackend.flushSync(null, 1)).toEqual(1);
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
// flush, as per comment in first test
await flushPromises();
@ -164,7 +164,7 @@ describe.each([
],
});
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0);
expect(httpBackend.flushSync(null, 1)).toEqual(1);
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
// Asserting that another request is never made is obviously
// a bit tricky - we just flush the queue what should hopefully
@ -200,7 +200,7 @@ describe.each([
],
});
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0);
expect(httpBackend.flushSync(null, 1)).toEqual(1);
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
await flushPromises();
logger.info("Advancing clock to just before expected retry time...");
@ -215,7 +215,7 @@ describe.each([
jest.advanceTimersByTime(2000);
await flushPromises();
expect(httpBackend.flushSync(null, 1)).toEqual(1);
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
});
it("retries on retryImmediately()", async function() {
@ -223,7 +223,7 @@ describe.each([
versions: ["r0.0.1"],
});
await Promise.all([client.startClient(), httpBackend.flush(null, 1, 20)]);
await Promise.all([client.startClient(), httpBackend.flush(undefined, 1, 20)]);
httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/",
@ -239,13 +239,13 @@ describe.each([
FAKE_MSG,
],
});
expect(await httpBackend.flush(null, 1, 1)).toEqual(1);
expect(await httpBackend.flush(undefined, 1, 1)).toEqual(1);
await flushPromises();
client.retryImmediately();
// longer timeout here to try & avoid flakiness
expect(await httpBackend.flush(null, 1, 3000)).toEqual(1);
expect(await httpBackend.flush(undefined, 1, 3000)).toEqual(1);
});
it("retries on when client is started", async function() {
@ -269,13 +269,13 @@ describe.each([
FAKE_MSG,
],
});
expect(await httpBackend.flush(null, 1, 1)).toEqual(1);
expect(await httpBackend.flush(undefined, 1, 1)).toEqual(1);
await flushPromises();
client.stopClient();
await Promise.all([client.startClient(), httpBackend.flush("/_matrix/client/versions", 1, 20)]);
expect(await httpBackend.flush(null, 1, 20)).toEqual(1);
expect(await httpBackend.flush(undefined, 1, 20)).toEqual(1);
});
it("retries when a message is retried", async function() {
@ -283,7 +283,7 @@ describe.each([
versions: ["r0.0.1"],
});
await Promise.all([client.startClient(), httpBackend.flush(null, 1, 20)]);
await Promise.all([client.startClient(), httpBackend.flush(undefined, 1, 20)]);
httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/",
@ -300,7 +300,7 @@ describe.each([
],
});
expect(await httpBackend.flush(null, 1, 1)).toEqual(1);
expect(await httpBackend.flush(undefined, 1, 1)).toEqual(1);
await flushPromises();
const dummyEvent = new MatrixEvent({
@ -311,7 +311,7 @@ describe.each([
} as unknown as Room;
client.resendEvent(dummyEvent, mockRoom);
expect(await httpBackend.flush(null, 1, 20)).toEqual(1);
expect(await httpBackend.flush(undefined, 1, 20)).toEqual(1);
});
it("splits many messages into multiple HTTP requests", async function() {

View File

@ -97,7 +97,7 @@ describe("Read receipt", () => {
"POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
$roomId: ROOM_ID,
$receiptType: ReceiptType.Read,
$eventId: threadEvent.getId(),
$eventId: threadEvent.getId()!,
}),
).check((request) => {
expect(request.data.thread_id).toEqual(THREAD_ID);
@ -115,7 +115,7 @@ describe("Read receipt", () => {
"POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
$roomId: ROOM_ID,
$receiptType: ReceiptType.Read,
$eventId: roomEvent.getId(),
$eventId: roomEvent.getId()!,
}),
).check((request) => {
expect(request.data.thread_id).toEqual(MAIN_ROOM_TIMELINE);
@ -133,7 +133,7 @@ describe("Read receipt", () => {
"POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
$roomId: ROOM_ID,
$receiptType: ReceiptType.Read,
$eventId: threadEvent.getId(),
$eventId: threadEvent.getId()!,
}),
).check((request) => {
expect(request.data.thread_id).toBeUndefined();
@ -151,7 +151,7 @@ describe("Read receipt", () => {
"POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
$roomId: ROOM_ID,
$receiptType: ReceiptType.Read,
$eventId: threadEvent.getId(),
$eventId: threadEvent.getId()!,
}),
).check((request) => {
expect(request.data).toEqual({});

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 * as callbacks from "../../src/realtime-callbacks";
let wallTime = 1234567890;
@ -37,7 +53,7 @@ describe("realtime-callbacks", function() {
it("should set 'this' to the global object", function() {
let passed = false;
const callback = function() {
const callback = function(this: typeof global) {
expect(this).toBe(global); // eslint-disable-line @typescript-eslint/no-invalid-this
expect(this.console).toBeTruthy(); // eslint-disable-line @typescript-eslint/no-invalid-this
passed = true;

View File

@ -22,7 +22,7 @@ import { TestClient } from "../TestClient";
describe("Relations", function() {
it("should deduplicate annotations", function() {
const room = new Room("room123", null, null);
const room = new Room("room123", null!, null!);
const relations = new Relations("m.annotation", "m.reaction", room);
// Create an instance of an annotation
@ -99,7 +99,7 @@ describe("Relations", function() {
// Add the target event first, then the relation event
{
const room = new Room("room123", null, null);
const room = new Room("room123", null!, null!);
const relationsCreated = new Promise(resolve => {
targetEvent.once(MatrixEventEvent.RelationsCreated, resolve);
});
@ -113,7 +113,7 @@ describe("Relations", function() {
// Add the relation event first, then the target event
{
const room = new Room("room123", null, null);
const room = new Room("room123", null!, null!);
const relationsCreated = new Promise(resolve => {
targetEvent.once(MatrixEventEvent.RelationsCreated, resolve);
});
@ -127,7 +127,7 @@ describe("Relations", function() {
});
it("should re-use Relations between all timeline sets in a room", async () => {
const room = new Room("room123", null, null);
const room = new Room("room123", null!, null!);
const timelineSet1 = new EventTimelineSet(room);
const timelineSet2 = new EventTimelineSet(room);
expect(room.relations).toBe(timelineSet1.relations);
@ -136,7 +136,7 @@ describe("Relations", function() {
it("should ignore m.replace for state events", async () => {
const userId = "@bob:example.com";
const room = new Room("room123", null, userId);
const room = new Room("room123", null!, userId);
const relations = new Relations("m.replace", "m.room.topic", room);
// Create an instance of a state event with rel_type m.replace

View File

@ -172,7 +172,7 @@ describe("RoomState", function() {
state.on(RoomStateEvent.Members, function(ev, st, mem) {
expect(ev).toEqual(memberEvents[emitCount]);
expect(st).toEqual(state);
expect(mem).toEqual(state.getMember(ev.getSender()));
expect(mem).toEqual(state.getMember(ev.getSender()!));
emitCount += 1;
});
state.setStateEvents(memberEvents);

View File

@ -24,7 +24,7 @@ import {
DuplicateStrategy,
EventStatus,
EventTimelineSet,
EventType,
EventType, IStateEventWithRoomId,
JoinRule,
MatrixEvent,
MatrixEventEvent,
@ -66,7 +66,7 @@ describe("Room", function() {
"body": "Reply :: " + Math.random(),
"m.relates_to": {
"m.in_reply_to": {
"event_id": target.getId(),
"event_id": target.getId()!,
},
},
},
@ -84,7 +84,7 @@ describe("Room", function() {
},
"m.relates_to": {
rel_type: RelationType.Replace,
event_id: target.getId(),
event_id: target.getId()!,
},
},
}, room.client);
@ -97,9 +97,9 @@ describe("Room", function() {
content: {
"body": "Thread response :: " + Math.random(),
"m.relates_to": {
"event_id": root.getId(),
"event_id": root.getId()!,
"m.in_reply_to": {
"event_id": root.getId(),
"event_id": root.getId()!,
},
"rel_type": "m.thread",
},
@ -114,7 +114,7 @@ describe("Room", function() {
content: {
"m.relates_to": {
"rel_type": RelationType.Annotation,
"event_id": target.getId(),
"event_id": target.getId()!,
"key": Math.random().toString(),
},
},
@ -125,7 +125,7 @@ describe("Room", function() {
type: EventType.RoomRedaction,
user: userA,
room: roomId,
redacts: target.getId(),
redacts: target.getId()!,
content: {},
}, room.client);
@ -722,13 +722,13 @@ describe("Room", function() {
it("should handle events in the same timeline", function() {
room.addLiveEvents(events);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId(),
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!,
events[1].getId()))
.toBeLessThan(0);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[2].getId(),
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[2].getId()!,
events[1].getId()))
.toBeGreaterThan(0);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId(),
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!,
events[1].getId()))
.toEqual(0);
});
@ -741,10 +741,10 @@ describe("Room", function() {
room.addEventsToTimeline([events[0]], false, oldTimeline);
room.addLiveEvents([events[1]]);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId(),
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!,
events[1].getId()))
.toBeLessThan(0);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId(),
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!,
events[0].getId()))
.toBeGreaterThan(0);
});
@ -755,10 +755,10 @@ describe("Room", function() {
room.addEventsToTimeline([events[0]], false, oldTimeline);
room.addLiveEvents([events[1]]);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId(),
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!,
events[1].getId()))
.toBe(null);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId(),
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!,
events[0].getId()))
.toBe(null);
});
@ -767,13 +767,13 @@ describe("Room", function() {
room.addLiveEvents(events);
expect(room.getUnfilteredTimelineSet()
.compareEventOrdering(events[0].getId(), "xxx"))
.compareEventOrdering(events[0].getId()!, "xxx"))
.toBe(null);
expect(room.getUnfilteredTimelineSet()
.compareEventOrdering("xxx", events[0].getId()))
.toBe(null);
expect(room.getUnfilteredTimelineSet()
.compareEventOrdering(events[0].getId(), events[0].getId()))
.compareEventOrdering(events[0].getId()!, events[0].getId()))
.toBe(0);
});
});
@ -1228,7 +1228,7 @@ describe("Room", function() {
it("should store the receipt so it can be obtained via getReceiptsForEvent", function() {
const ts = 13787898424;
room.addReceipt(mkReceipt(roomId, [
mkRecord(eventToAck.getId(), "m.read", userB, ts),
mkRecord(eventToAck.getId()!, "m.read", userB, ts),
]));
expect(room.getReceiptsForEvent(eventToAck)).toEqual([{
type: "m.read",
@ -1247,7 +1247,7 @@ describe("Room", function() {
const ts = 13787898424;
const receiptEvent = mkReceipt(roomId, [
mkRecord(eventToAck.getId(), "m.read", userB, ts),
mkRecord(eventToAck.getId()!, "m.read", userB, ts),
]);
room.addReceipt(receiptEvent);
@ -1261,11 +1261,11 @@ describe("Room", function() {
});
const ts = 13787898424;
room.addReceipt(mkReceipt(roomId, [
mkRecord(eventToAck.getId(), "m.read", userB, ts),
mkRecord(eventToAck.getId()!, "m.read", userB, ts),
]));
const ts2 = 13787899999;
room.addReceipt(mkReceipt(roomId, [
mkRecord(nextEventToAck.getId(), "m.read", userB, ts2),
mkRecord(nextEventToAck.getId()!, "m.read", userB, ts2),
]));
expect(room.getReceiptsForEvent(eventToAck)).toEqual([]);
expect(room.getReceiptsForEvent(nextEventToAck)).toEqual([{
@ -1280,9 +1280,9 @@ describe("Room", function() {
it("should persist multiple receipts for a single event ID", function() {
const ts = 13787898424;
room.addReceipt(mkReceipt(roomId, [
mkRecord(eventToAck.getId(), "m.read", userB, ts),
mkRecord(eventToAck.getId(), "m.read", userC, ts),
mkRecord(eventToAck.getId(), "m.read", userD, ts),
mkRecord(eventToAck.getId()!, "m.read", userB, ts),
mkRecord(eventToAck.getId()!, "m.read", userC, ts),
mkRecord(eventToAck.getId()!, "m.read", userD, ts),
]));
expect(room.getUsersReadUpTo(eventToAck)).toEqual(
[userB, userC, userD],
@ -1300,9 +1300,9 @@ describe("Room", function() {
});
const ts = 13787898424;
room.addReceipt(mkReceipt(roomId, [
mkRecord(eventToAck.getId(), "m.read", userB, ts),
mkRecord(eventTwo.getId(), "m.read", userC, ts),
mkRecord(eventThree.getId(), "m.read", userD, ts),
mkRecord(eventToAck.getId()!, "m.read", userB, ts),
mkRecord(eventTwo.getId()!, "m.read", userC, ts),
mkRecord(eventThree.getId()!, "m.read", userD, ts),
]));
expect(room.getUsersReadUpTo(eventToAck)).toEqual([userB]);
expect(room.getUsersReadUpTo(eventTwo)).toEqual([userC]);
@ -1311,9 +1311,9 @@ describe("Room", function() {
it("should persist multiple receipts for a single user ID", function() {
room.addReceipt(mkReceipt(roomId, [
mkRecord(eventToAck.getId(), "m.delivered", userB, 13787898424),
mkRecord(eventToAck.getId(), "m.read", userB, 22222222),
mkRecord(eventToAck.getId(), "m.seen", userB, 33333333),
mkRecord(eventToAck.getId()!, "m.delivered", userB, 13787898424),
mkRecord(eventToAck.getId()!, "m.read", userB, 22222222),
mkRecord(eventToAck.getId()!, "m.seen", userB, 33333333),
]));
expect(room.getReceiptsForEvent(eventToAck)).toEqual([
{
@ -1361,19 +1361,19 @@ describe("Room", function() {
// check it initialises correctly
room.addReceipt(mkReceipt(roomId, [
mkRecord(events[0].getId(), "m.read", userB, ts),
mkRecord(events[0].getId()!, "m.read", userB, ts),
]));
expect(room.getEventReadUpTo(userB)).toEqual(events[0].getId());
// 2>0, so it should move forward
room.addReceipt(mkReceipt(roomId, [
mkRecord(events[2].getId(), "m.read", userB, ts),
mkRecord(events[2].getId()!, "m.read", userB, ts),
]));
expect(room.getEventReadUpTo(userB)).toEqual(events[2].getId());
// 1<2, so it should stay put
room.addReceipt(mkReceipt(roomId, [
mkRecord(events[1].getId(), "m.read", userB, ts),
mkRecord(events[1].getId()!, "m.read", userB, ts),
]));
expect(room.getEventReadUpTo(userB)).toEqual(events[2].getId());
});
@ -1399,13 +1399,13 @@ describe("Room", function() {
// check it initialises correctly
room.addReceipt(mkReceipt(roomId, [
mkRecord(events[0].getId(), "m.read", userB, ts),
mkRecord(events[0].getId()!, "m.read", userB, ts),
]));
expect(room.getEventReadUpTo(userB)).toEqual(events[0].getId());
// 2>0, so it should move forward
room.addReceipt(mkReceipt(roomId, [
mkRecord(events[2].getId(), "m.read", userB, ts),
mkRecord(events[2].getId()!, "m.read", userB, ts),
]), true);
expect(room.getEventReadUpTo(userB)).toEqual(events[2].getId());
expect(room.getReceiptsForEvent(events[2])).toEqual([
@ -1414,7 +1414,7 @@ describe("Room", function() {
// 1<2, so it should stay put
room.addReceipt(mkReceipt(roomId, [
mkRecord(events[1].getId(), "m.read", userB, ts),
mkRecord(events[1].getId()!, "m.read", userB, ts),
]));
expect(room.getEventReadUpTo(userB)).toEqual(events[2].getId());
expect(room.getEventReadUpTo(userB, true)).toEqual(events[1].getId());
@ -1428,7 +1428,7 @@ describe("Room", function() {
it("should return user IDs read up to the given event", function() {
const ts = 13787898424;
room.addReceipt(mkReceipt(roomId, [
mkRecord(eventToAck.getId(), "m.read", userB, ts),
mkRecord(eventToAck.getId()!, "m.read", userB, ts),
]));
expect(room.getUsersReadUpTo(eventToAck)).toEqual([userB]);
});
@ -1438,9 +1438,9 @@ describe("Room", function() {
it("should acknowledge if an event has been read", function() {
const ts = 13787898424;
room.addReceipt(mkReceipt(roomId, [
mkRecord(eventToAck.getId(), "m.read", userB, ts),
mkRecord(eventToAck.getId()!, "m.read", userB, ts),
]));
expect(room.hasUserReadEvent(userB, eventToAck.getId())).toEqual(true);
expect(room.hasUserReadEvent(userB, eventToAck.getId()!)).toEqual(true);
});
it("return false for an unknown event", function() {
expect(room.hasUserReadEvent(userB, "unknown_event")).toEqual(false);
@ -1556,7 +1556,7 @@ describe("Room", function() {
user: userA,
type: EventType.RoomRedaction,
content: {},
redacts: eventA.getId(),
redacts: eventA.getId()!,
event: true,
});
redactA.status = EventStatus.SENDING;
@ -1609,7 +1609,7 @@ describe("Room", function() {
});
it("should remove cancelled events from the timeline", function() {
const room = new Room(roomId, null, userA);
const room = new Room(roomId, null!, userA);
const eventA = utils.mkMessage({
room: roomId, user: userA, event: true,
});
@ -1643,7 +1643,7 @@ describe("Room", function() {
});
describe("loadMembersIfNeeded", function() {
function createClientMock(serverResponse, storageResponse = null) {
function createClientMock(serverResponse, storageResponse: MatrixEvent[] | Error | null = null) {
return {
getEventMapper: function() {
// events should already be MatrixEvents
@ -1664,7 +1664,7 @@ describe("Room", function() {
}),
store: {
storageResponse,
storedMembers: null,
storedMembers: [] as IStateEventWithRoomId[] | null,
getOutOfBandMembers: function() {
if (this.storageResponse instanceof Error) {
return Promise.reject(this.storageResponse);
@ -1693,11 +1693,11 @@ describe("Room", function() {
it("should load members from server on first call", async function() {
const client = createClientMock([memberEvent]);
const room = new Room(roomId, client as any, null, { lazyLoadMembers: true });
const room = new Room(roomId, client as any, null!, { lazyLoadMembers: true });
await room.loadMembersIfNeeded();
const memberA = room.getMember("@user_a:bar");
const memberA = room.getMember("@user_a:bar")!;
expect(memberA.name).toEqual("User A");
const storedMembers = client.store.storedMembers;
const storedMembers = client.store.storedMembers!;
expect(storedMembers.length).toEqual(1);
expect(storedMembers[0].event_id).toEqual(memberEvent.getId());
});
@ -1711,17 +1711,17 @@ describe("Room", function() {
name: "Ms A",
});
const client = createClientMock([memberEvent2], [memberEvent]);
const room = new Room(roomId, client as any, null, { lazyLoadMembers: true });
const room = new Room(roomId, client as any, null!, { lazyLoadMembers: true });
await room.loadMembersIfNeeded();
const memberA = room.getMember("@user_a:bar");
const memberA = room.getMember("@user_a:bar")!;
expect(memberA.name).toEqual("User A");
});
it("should allow retry on error", async function() {
const client = createClientMock(new Error("server says no"));
const room = new Room(roomId, client as any, null, { lazyLoadMembers: true });
const room = new Room(roomId, client as any, null!, { lazyLoadMembers: true });
let hasThrown = false;
try {
await room.loadMembersIfNeeded();
@ -1740,27 +1740,68 @@ describe("Room", function() {
describe("getMyMembership", function() {
it("should return synced membership if membership isn't available yet",
function() {
const room = new Room(roomId, null, userA);
const room = new Room(roomId, null!, userA);
room.updateMyMembership(JoinRule.Invite);
expect(room.getMyMembership()).toEqual(JoinRule.Invite);
});
it("should emit a Room.myMembership event on a change",
function() {
const room = new Room(roomId, null, userA);
const events = [];
room.on(RoomEvent.MyMembership, (_room, membership, oldMembership) => {
events.push({ membership, oldMembership });
});
room.updateMyMembership(JoinRule.Invite);
expect(room.getMyMembership()).toEqual(JoinRule.Invite);
expect(events[0]).toEqual({ membership: "invite", oldMembership: undefined });
events.splice(0); //clear
room.updateMyMembership(JoinRule.Invite);
expect(events.length).toEqual(0);
room.updateMyMembership("join");
expect(room.getMyMembership()).toEqual("join");
expect(events[0]).toEqual({ membership: "join", oldMembership: "invite" });
it("should emit a Room.myMembership event on a change", function() {
const room = new Room(roomId, null!, userA);
const events: {
membership: string;
oldMembership?: string;
}[] = [];
room.on(RoomEvent.MyMembership, (_room, membership, oldMembership) => {
events.push({ membership, oldMembership });
});
room.updateMyMembership(JoinRule.Invite);
expect(room.getMyMembership()).toEqual(JoinRule.Invite);
expect(events[0]).toEqual({ membership: "invite", oldMembership: undefined });
events.splice(0); //clear
room.updateMyMembership(JoinRule.Invite);
expect(events.length).toEqual(0);
room.updateMyMembership("join");
expect(room.getMyMembership()).toEqual("join");
expect(events[0]).toEqual({ membership: "join", oldMembership: "invite" });
});
});
describe("getDMInviter", () => {
it("should delegate to RoomMember::getDMInviter if available", () => {
const room = new Room(roomId, null!, userA);
room.currentState.markOutOfBandMembersStarted();
room.currentState.setOutOfBandMembers([
new MatrixEvent({
type: EventType.RoomMember,
state_key: userA,
sender: userB,
content: {
membership: "invite",
is_direct: true,
},
}),
]);
expect(room.getDMInviter()).toBe(userB);
});
it("should fall back to summary heroes and return the first one", () => {
const room = new Room(roomId, null!, userA);
room.updateMyMembership("invite");
room.setSummary({
"m.heroes": [userA, userC],
"m.joined_member_count": 1,
"m.invited_member_count": 1,
});
expect(room.getDMInviter()).toBe(userC);
});
it("should return undefined if we're not joined or invited to the room", () => {
const room = new Room(roomId, null!, userA);
expect(room.getDMInviter()).toBeUndefined();
room.updateMyMembership("leave");
expect(room.getDMInviter()).toBeUndefined();
});
});
describe("guessDMUserId", function() {
@ -1789,6 +1830,36 @@ describe("Room", function() {
});
});
describe("getAvatarFallbackMember", () => {
it("should should return undefined if the room isn't a 1:1", () => {
const room = new Room(roomId, null!, userA);
room.currentState.setJoinedMemberCount(2);
room.currentState.setInvitedMemberCount(1);
expect(room.getAvatarFallbackMember()).toBeUndefined();
});
it("should use summary heroes member if 1:1", () => {
const room = new Room(roomId, null!, userA);
room.currentState.markOutOfBandMembersStarted();
room.currentState.setOutOfBandMembers([
new MatrixEvent({
type: EventType.RoomMember,
state_key: userD,
sender: userD,
content: {
membership: "join",
},
}),
]);
room.setSummary({
"m.heroes": [userA, userD],
"m.joined_member_count": 1,
"m.invited_member_count": 1,
});
expect(room.getAvatarFallbackMember()?.userId).toBe(userD);
});
});
describe("maySendMessage", function() {
it("should return false if synced membership not join", function() {
const room = new Room(roomId, { isRoomEncrypted: () => false } as any, userA);
@ -2118,7 +2189,7 @@ describe("Room", function() {
},
});
expect(() => room.createThread(rootEvent.getId(), rootEvent, [])).not.toThrow();
expect(() => room.createThread(rootEvent.getId()!, rootEvent, [])).not.toThrow();
});
it("creating thread from edited event should not conflate old versions of the event", () => {
@ -2339,7 +2410,7 @@ describe("Room", function() {
const threadReaction2 = mkReaction(threadRoot);
const threadReaction2Redaction = mkRedaction(threadReaction2);
const roots = new Set([threadRoot.getId()]);
const roots = new Set([threadRoot.getId()!]);
const events = [
randomMessage,
threadRoot,
@ -2377,7 +2448,7 @@ describe("Room", function() {
const threadReaction2 = mkReaction(threadResponse1);
const threadReaction2Redaction = mkRedaction(threadReaction2);
const roots = new Set([threadRoot.getId()]);
const roots = new Set([threadRoot.getId()!]);
const events = [threadRoot, threadResponse1, threadReaction1, threadReaction2, threadReaction2Redaction];
expect(room.eventShouldLiveIn(threadReaction1, events, roots).shouldLiveInRoom).toBeFalsy();
@ -2399,7 +2470,7 @@ describe("Room", function() {
const reaction2 = mkReaction(reply1);
const reaction2Redaction = mkRedaction(reply1);
const roots = new Set([threadRoot.getId()]);
const roots = new Set([threadRoot.getId()!]);
const events = [
threadRoot,
threadResponse1,
@ -2425,7 +2496,7 @@ describe("Room", function() {
const reply1 = mkReply(threadRoot);
const reply2 = mkReply(reply1);
const roots = new Set([threadRoot.getId()]);
const roots = new Set([threadRoot.getId()!]);
const events = [
threadRoot,
threadResponse1,
@ -2459,7 +2530,7 @@ describe("Room", function() {
expect(thread.rootEvent).toBe(threadRoot);
const rootRelations = thread.timelineSet.relations.getChildEventsForEvent(
threadRoot.getId(),
threadRoot.getId()!,
RelationType.Annotation,
EventType.Reaction,
)!.getSortedAnnotationsByKey();
@ -2469,7 +2540,7 @@ describe("Room", function() {
expect(rootRelations![0][1].has(rootReaction)).toBeTruthy();
const responseRelations = thread.timelineSet.relations.getChildEventsForEvent(
threadResponse.getId(),
threadResponse.getId()!,
RelationType.Annotation,
EventType.Reaction,
)!.getSortedAnnotationsByKey();
@ -2744,7 +2815,7 @@ describe("Room", function() {
expect(pendingEvents[1].isBeingDecrypted()).toBeFalsy();
expect(pendingEvents[1].isEncrypted()).toBeTruthy();
for (const ev of pendingEvents) {
expect(room.getPendingEvent(ev.getId())).toBe(ev);
expect(room.getPendingEvent(ev.getId()!)).toBe(ev);
}
});
});

View File

@ -160,10 +160,10 @@ describe("MatrixScheduler", function() {
const eventD = utils.mkMessage({ user: "@b:bar", room: roomId, event: true });
const buckets = {};
buckets[eventA.getId()] = "queue_A";
buckets[eventD.getId()] = "queue_A";
buckets[eventB.getId()] = "queue_B";
buckets[eventC.getId()] = "queue_B";
buckets[eventA.getId()!] = "queue_A";
buckets[eventD.getId()!] = "queue_A";
buckets[eventB.getId()!] = "queue_B";
buckets[eventC.getId()!] = "queue_B";
retryFn = function() {
return 0;

View File

@ -62,7 +62,7 @@ export class ReEmitter {
if (!reEmittersByEvent) return; // We were never re-emitting these events in the first place
for (const eventName of eventNames) {
source.off(eventName, reEmittersByEvent.get(eventName));
source.off(eventName, reEmittersByEvent.get(eventName)!);
reEmittersByEvent.delete(eventName);
}

View File

@ -59,7 +59,11 @@ import {
retryNetworkOperation,
ClientPrefix,
MediaPrefix,
IdentityPrefix, IHttpOpts, FileType, UploadResponse,
IdentityPrefix,
IHttpOpts,
FileType,
UploadResponse,
HTTPError,
} from "./http-api";
import {
Crypto,
@ -909,25 +913,25 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
public static readonly RESTORE_BACKUP_ERROR_BAD_KEY = 'RESTORE_BACKUP_ERROR_BAD_KEY';
public reEmitter = new TypedReEmitter<EmittedEvents, ClientEventHandlerMap>(this);
public olmVersion: [number, number, number] = null; // populated after initCrypto
public olmVersion: [number, number, number] | null = null; // populated after initCrypto
public usingExternalCrypto = false;
public store: Store;
public deviceId: string | null;
public credentials: { userId?: string };
public credentials: { userId: string | null };
public pickleKey?: string;
public scheduler: MatrixScheduler;
public scheduler?: MatrixScheduler;
public clientRunning = false;
public timelineSupport = false;
public urlPreviewCache: { [key: string]: Promise<IPreviewUrlResponse> } = {};
public identityServer: IIdentityServerProvider;
public identityServer?: IIdentityServerProvider;
public http: MatrixHttpApi<IHttpOpts & { onlyData: true }>; // XXX: Intended private, used in code.
public crypto?: Crypto; // XXX: Intended private, used in code.
public cryptoCallbacks: ICryptoCallbacks; // XXX: Intended private, used in code.
public callEventHandler: CallEventHandler; // XXX: Intended private, used in code.
public callEventHandler?: CallEventHandler; // XXX: Intended private, used in code.
public supportsCallTransfer = false; // XXX: Intended private, used in code.
public forceTURN = false; // XXX: Intended private, used in code.
public iceCandidatePoolSize = 0; // XXX: Intended private, used in code.
public idBaseUrl: string;
public idBaseUrl?: string;
public baseUrl: string;
// Note: these are all `protected` to let downstream consumers make mistakes if they want to.
@ -938,8 +942,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
protected isGuestAccount = false;
protected ongoingScrollbacks: {[roomId: string]: {promise?: Promise<Room>, errorTs?: number}} = {};
protected notifTimelineSet: EventTimelineSet | null = null;
protected cryptoStore: CryptoStore;
protected verificationMethods: VerificationMethod[];
protected cryptoStore?: CryptoStore;
protected verificationMethods?: VerificationMethod[];
protected fallbackICEServerAllowed = false;
protected roomList: RoomList;
protected syncApi?: SlidingSyncSdk | SyncApi;
@ -960,16 +964,16 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// TODO: This should expire: https://github.com/matrix-org/matrix-js-sdk/issues/1020
protected serverVersionsPromise?: Promise<IServerVersions>;
public cachedCapabilities: {
public cachedCapabilities?: {
capabilities: ICapabilities;
expiration: number;
};
protected clientWellKnown: IClientWellKnown;
protected clientWellKnownPromise: Promise<IClientWellKnown>;
protected clientWellKnown?: IClientWellKnown;
protected clientWellKnownPromise?: Promise<IClientWellKnown>;
protected turnServers: ITurnServer[] = [];
protected turnServersExpiry = 0;
protected checkTurnServersIntervalID: ReturnType<typeof setInterval> | null = null;
protected exportedOlmDeviceToImport: IExportedOlmDevice;
protected checkTurnServersIntervalID?: ReturnType<typeof setInterval>;
protected exportedOlmDeviceToImport?: IExportedOlmDevice;
protected txnCtr = 0;
protected mediaHandler = new MediaHandler(this);
protected pendingEventEncryption = new Map<string, Promise<void>>();
@ -1096,7 +1100,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
if (!utils.isSupportedReceiptType(key)) continue;
if (!value) continue;
if (Object.keys(value).includes(this.getUserId())) return true;
if (Object.keys(value).includes(this.getUserId()!)) return true;
}
return false;
@ -1117,14 +1121,13 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
const event = events[i];
if (room.hasUserReadEvent(this.getUserId(), event.getId())) {
if (room.hasUserReadEvent(this.getUserId()!, event.getId()!)) {
// If the user has read the event, then the counting is done.
break;
}
const pushActions = this.getPushActionsForEvent(event);
highlightCount += pushActions.tweaks &&
pushActions.tweaks.highlight ? 1 : 0;
highlightCount += pushActions?.tweaks?.highlight ? 1 : 0;
}
// Note: we don't need to handle 'total' notifications because the counts
@ -1238,10 +1241,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
this.peekSync?.stopPeeking();
this.callEventHandler?.stop();
this.callEventHandler = null;
this.callEventHandler = undefined;
global.clearInterval(this.checkTurnServersIntervalID);
this.checkTurnServersIntervalID = null;
this.checkTurnServersIntervalID = undefined;
if (this.clientWellKnownIntervalID !== undefined) {
global.clearInterval(this.clientWellKnownIntervalID);
@ -1334,7 +1337,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* Get the current dehydrated device, if any
* @return {Promise} A promise of an object containing the dehydrated device
*/
public async getDehydratedDevice(): Promise<IDehydratedDevice> {
public async getDehydratedDevice(): Promise<IDehydratedDevice | undefined> {
try {
return await this.http.authedRequest<IDehydratedDevice>(
Method.Get,
@ -1345,7 +1348,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
},
);
} catch (e) {
logger.info("could not get dehydrated device", e.toString());
logger.info("could not get dehydrated device", e);
return;
}
}
@ -1361,7 +1364,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* dehydrated device.
* @return {Promise} A promise that resolves when the dehydrated device is stored.
*/
public setDehydrationKey(
public async setDehydrationKey(
key: Uint8Array,
keyInfo: IDehydratedDeviceKeyInfo,
deviceDisplayName?: string,
@ -1401,8 +1404,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
return;
}
return {
userId: this.credentials.userId,
deviceId: this.deviceId,
userId: this.credentials.userId!,
deviceId: this.deviceId!,
// XXX: Private member access.
olmDevice: await this.crypto.olmDevice.export(),
};
@ -1418,7 +1421,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
throw new Error("Cannot clear stores while client is running");
}
const promises = [];
const promises: Promise<void>[] = [];
promises.push(this.store.deleteAllData());
if (this.cryptoStore) {
@ -1465,7 +1468,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* Get the device ID of this client
* @return {?string} device ID
*/
public getDeviceId(): string {
public getDeviceId(): string | null {
return this.deviceId;
}
@ -1572,9 +1575,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
/**
* Return the provided scheduler, if any.
* @return {?module:scheduler~MatrixScheduler} The scheduler or null
* @return {?module:scheduler~MatrixScheduler} The scheduler or undefined
*/
public getScheduler(): MatrixScheduler {
public getScheduler(): MatrixScheduler | undefined {
return this.scheduler;
}
@ -1626,10 +1629,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
}
return this.http.authedRequest(Method.Get, "/capabilities").catch((e: Error): void => {
return this.http.authedRequest<{
capabilities?: ICapabilities;
}>(Method.Get, "/capabilities").catch((e: Error): void => {
// We swallow errors because we need a default object anyhow
logger.error(e);
}).then((r: { capabilities?: ICapabilities } = {}) => {
}).then((r = {}) => {
const capabilities: ICapabilities = r["capabilities"] || {};
// If the capabilities missed the cache, cache it for a shorter amount
@ -1703,7 +1708,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
this.store,
this.cryptoStore,
this.roomList,
this.verificationMethods,
this.verificationMethods!,
);
this.reEmitter.reEmit(crypto, [
@ -1919,7 +1924,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*
* @returns {module:crypto/verification/request/VerificationRequest?} the VerificationRequest that is in progress, if any
*/
public findVerificationRequestDMInProgress(roomId: string): VerificationRequest {
public findVerificationRequestDMInProgress(roomId: string): VerificationRequest | undefined {
if (!this.crypto) {
throw new Error("End-to-end encryption disabled");
}
@ -2488,7 +2493,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*
* @return {Promise<module:crypto/deviceinfo?>}
*/
public async getEventSenderDeviceInfo(event: MatrixEvent): Promise<DeviceInfo> {
public async getEventSenderDeviceInfo(event: MatrixEvent): Promise<DeviceInfo | null> {
if (!this.crypto) {
return null;
}
@ -2519,7 +2524,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @return {Promise} A promise that will resolve when the key request is queued
*/
public cancelAndResendEventRoomKeyRequest(event: MatrixEvent): Promise<void> {
return event.cancelAndResendKeyRequest(this.crypto, this.getUserId());
if (!this.crypto) {
throw new Error("End-to-End encryption disabled");
}
return event.cancelAndResendKeyRequest(this.crypto, this.getUserId()!);
}
/**
@ -2857,10 +2865,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
private makeKeyBackupPath(roomId: string, sessionId: undefined, version?: string): IKeyBackupPath;
private makeKeyBackupPath(roomId: string, sessionId: string, version?: string): IKeyBackupPath;
private makeKeyBackupPath(roomId?: string, sessionId?: string, version?: string): IKeyBackupPath {
let path;
let path: string;
if (sessionId !== undefined) {
path = utils.encodeUri("/room_keys/keys/$roomId/$sessionId", {
$roomId: roomId,
$roomId: roomId!,
$sessionId: sessionId,
});
} else if (roomId !== undefined) {
@ -2911,7 +2919,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
throw new Error("End-to-end encryption disabled");
}
const path = this.makeKeyBackupPath(roomId, sessionId, version);
const path = this.makeKeyBackupPath(roomId!, sessionId!, version!);
await this.http.authedRequest(
Method.Put, path.path, path.queryData, data,
{ prefix: ClientPrefix.V3 },
@ -3021,9 +3029,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
opts: IKeyBackupRestoreOpts,
): Promise<IKeyBackupRestoreResult> {
const privKey = await keyFromAuthData(backupInfo.auth_data, password);
return this.restoreKeyBackup(
privKey, targetRoomId, targetSessionId, backupInfo, opts,
);
return this.restoreKeyBackup(privKey, targetRoomId!, targetSessionId!, backupInfo, opts);
}
/**
@ -3059,9 +3065,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
const privKey = decodeBase64(fixedKey || storedKey!);
return this.restoreKeyBackup(
privKey, targetRoomId, targetSessionId, backupInfo, opts,
);
return this.restoreKeyBackup(privKey, targetRoomId!, targetSessionId!, backupInfo, opts);
}
/**
@ -3134,11 +3138,14 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
backupInfo: IKeyBackupInfo,
opts?: IKeyBackupRestoreOpts,
): Promise<IKeyBackupRestoreResult> {
if (!this.crypto) {
throw new Error("End-to-end encryption disabled");
}
const privKey = await this.crypto.getSessionBackupPrivateKey();
if (!privKey) {
throw new Error("Couldn't get key");
}
return this.restoreKeyBackup(privKey, targetRoomId, targetSessionId, backupInfo, opts);
return this.restoreKeyBackup(privKey, targetRoomId!, targetSessionId!, backupInfo, opts);
}
private async restoreKeyBackup(
@ -3179,7 +3186,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
let totalKeyCount = 0;
let keys: IMegolmSessionData[] = [];
const path = this.makeKeyBackupPath(targetRoomId, targetSessionId, backupInfo.version);
const path = this.makeKeyBackupPath(targetRoomId!, targetSessionId!, backupInfo.version);
const algorithm = await BackupManager.makeAlgorithm(backupInfo, async () => { return privKey; });
@ -3228,16 +3235,16 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
totalKeyCount = Object.keys(sessions).length;
keys = await algorithm.decryptSessions(sessions);
for (const k of keys) {
k.room_id = targetRoomId;
k.room_id = targetRoomId!;
}
} else {
totalKeyCount = 1;
try {
const [key] = await algorithm.decryptSessions({
[targetSessionId]: res as IKeyBackupSession,
[targetSessionId!]: res as IKeyBackupSession,
});
key.room_id = targetRoomId;
key.session_id = targetSessionId;
key.room_id = targetRoomId!;
key.session_id = targetSessionId!;
keys.push(key);
} catch (e) {
logger.log("Failed to decrypt megolm session from backup", e);
@ -3443,7 +3450,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
try {
return await this.http.authedRequest(Method.Get, path);
} catch (e) {
if (e.data?.errcode === 'M_NOT_FOUND') {
if ((<MatrixError>e).data?.errcode === 'M_NOT_FOUND') {
return null;
}
throw e;
@ -3527,9 +3534,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
const path = utils.encodeUri("/join/$roomid", { $roomid: roomIdOrAlias });
const res = await this.http.authedRequest(Method.Post, path, queryString, data);
const res = await this.http.authedRequest<{ room_id: string }>(Method.Post, path, queryString, data);
const roomId = res['room_id'];
const roomId = res.room_id;
const syncApi = new SyncApi(this, this.clientOpts);
const room = syncApi.createRoom(roomId);
if (opts.syncRoom) {
@ -3571,7 +3578,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// if the event is currently being encrypted then
if (event.status === EventStatus.ENCRYPTING) {
this.pendingEventEncryption.delete(event.getId());
this.pendingEventEncryption.delete(event.getId()!);
} else if (this.scheduler && event.status === EventStatus.QUEUED) {
// tell the scheduler to forget about it, if it's queued
this.scheduler.removeEventFromQueue(event);
@ -3843,7 +3850,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
if (targetId?.startsWith("~")) {
const target = room?.getPendingEvents().find(e => e.getId() === targetId);
target?.once(MatrixEventEvent.LocalEventIdReplaced, () => {
localEvent.updateAssociatedId(target.getId());
localEvent.updateAssociatedId(target.getId()!);
});
}
@ -3882,10 +3889,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
const encryptionPromise = this.encryptEventIfNeeded(event, room ?? undefined);
if (!encryptionPromise) return null; // doesn't need encryption
this.pendingEventEncryption.set(event.getId(), encryptionPromise);
this.pendingEventEncryption.set(event.getId()!, encryptionPromise);
this.updatePendingEventStatus(room, event, EventStatus.ENCRYPTING);
return encryptionPromise.then(() => {
if (!this.pendingEventEncryption.has(event.getId())) {
if (!this.pendingEventEncryption.has(event.getId()!)) {
// cancelled via MatrixClient::cancelPendingEvent
cancelled = true;
return;
@ -3951,7 +3958,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
return null;
}
if (!this.isRoomEncrypted(event.getRoomId())) {
if (!this.isRoomEncrypted(event.getRoomId()!)) {
return null;
}
@ -3993,7 +4000,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @param {string} eventType the event type
* @return {string} the event type taking encryption into account
*/
private getEncryptedIfNeededEventType(roomId: string, eventType: string): string {
private getEncryptedIfNeededEventType(
roomId: string,
eventType?: EventType | string | null,
): EventType | string | null | undefined {
if (eventType === EventType.Reaction) return eventType;
return this.isRoomEncrypted(roomId) ? EventType.RoomMessageEncrypted : eventType;
}
@ -4014,9 +4024,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
const pathParams = {
$roomId: event.getRoomId(),
$roomId: event.getRoomId()!,
$eventType: event.getWireType(),
$stateKey: event.getStateKey(),
$stateKey: event.getStateKey()!,
$txnId: txnId,
};
@ -4024,15 +4034,16 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
if (event.isState()) {
let pathTemplate = "/rooms/$roomId/state/$eventType";
if (event.getStateKey() && event.getStateKey().length > 0) {
if (event.getStateKey() && event.getStateKey()!.length > 0) {
pathTemplate = "/rooms/$roomId/state/$eventType/$stateKey";
}
path = utils.encodeUri(pathTemplate, pathParams);
} else if (event.isRedaction()) {
const pathTemplate = `/rooms/$roomId/redact/$redactsEventId/$txnId`;
path = utils.encodeUri(pathTemplate, Object.assign({
$redactsEventId: event.event.redacts,
}, pathParams));
path = utils.encodeUri(pathTemplate, {
$redactsEventId: event.event.redacts!,
...pathParams,
});
} else {
path = utils.encodeUri("/rooms/$roomId/send/$eventType/$txnId", pathParams);
}
@ -4386,7 +4397,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
body = threadId;
threadId = null;
}
const content = ContentHelpers.makeHtmlMessage(body, htmlBody);
const content = ContentHelpers.makeHtmlMessage(body, htmlBody!);
return this.sendMessage(roomId, threadId, content);
}
@ -4419,7 +4430,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
body = threadId;
threadId = null;
}
const content = ContentHelpers.makeHtmlNotice(body, htmlBody);
const content = ContentHelpers.makeHtmlNotice(body, htmlBody!);
return this.sendMessage(roomId, threadId, content);
}
@ -4453,7 +4464,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
body = threadId;
threadId = null;
}
const content = ContentHelpers.makeHtmlEmote(body, htmlBody);
const content = ContentHelpers.makeHtmlEmote(body, htmlBody!);
return this.sendMessage(roomId, threadId, content);
}
@ -4476,9 +4487,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
const path = utils.encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
$roomId: event.getRoomId(),
$roomId: event.getRoomId()!,
$receiptType: receiptType,
$eventId: event.getId(),
$eventId: event.getId()!,
});
// TODO: Add a check for which spec version this will be released in
@ -4489,7 +4500,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
: MAIN_ROOM_TIMELINE;
}
const promise = this.http.authedRequest(Method.Post, path, undefined, body || {});
const promise = this.http.authedRequest<{}>(Method.Post, path, undefined, body || {});
const room = this.getRoom(event.getRoomId());
if (room && this.credentials.userId) {
@ -4510,9 +4521,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
receiptType = ReceiptType.Read,
): Promise<{} | undefined> {
if (!event) return;
const eventId = event.getId();
const eventId = event.getId()!;
const room = this.getRoom(event.getRoomId());
if (room && room.hasPendingEvent(eventId)) {
if (room?.hasPendingEvent(eventId)) {
throw new Error(`Cannot set read receipt to a pending event (${eventId})`);
}
@ -4545,23 +4556,23 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
// Add the optional RR update, do local echo like `sendReceipt`
let rrEventId: string;
let rrEventId: string | undefined;
if (rrEvent) {
rrEventId = rrEvent.getId();
rrEventId = rrEvent.getId()!;
if (room?.hasPendingEvent(rrEventId)) {
throw new Error(`Cannot set read receipt to a pending event (${rrEventId})`);
}
room?.addLocalEchoReceipt(this.credentials.userId, rrEvent, ReceiptType.Read);
room?.addLocalEchoReceipt(this.credentials.userId!, rrEvent, ReceiptType.Read);
}
// Add the optional private RR update, do local echo like `sendReceipt`
let rpEventId: string;
let rpEventId: string | undefined;
if (rpEvent) {
rpEventId = rpEvent.getId();
rpEventId = rpEvent.getId()!;
if (room?.hasPendingEvent(rpEventId)) {
throw new Error(`Cannot set read receipt to a pending event (${rpEventId})`);
}
room?.addLocalEchoReceipt(this.credentials.userId, rpEvent, ReceiptType.ReadPrivate);
room?.addLocalEchoReceipt(this.credentials.userId!, rpEvent, ReceiptType.ReadPrivate);
}
return await this.setRoomReadMarkersHttpRequest(roomId, rmEventId, rrEventId, rpEventId);
@ -4623,7 +4634,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
const path = utils.encodeUri("/rooms/$roomId/typing/$userId", {
$roomId: roomId,
$userId: this.credentials.userId,
$userId: this.getUserId()!,
});
const data: any = {
typing: isTyping,
@ -4816,7 +4827,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
const doLeave = (roomId: string) => {
return this.leave(roomId).then(() => {
populationResults[roomId] = null;
delete populationResults[roomId];
}).catch((err) => {
populationResults[roomId] = err;
return null; // suppress error
@ -4925,7 +4936,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* Useful when an event just got decrypted
* @return {module:pushprocessor~PushAction} A dict of actions to perform.
*/
public getPushActionsForEvent(event: MatrixEvent, forceRecalculate = false): IActionsObject {
public getPushActionsForEvent(event: MatrixEvent, forceRecalculate = false): IActionsObject | null {
if (!event.getPushActions() || forceRecalculate) {
event.setPushActions(this.pushProcessor.actionsForEvent(event));
}
@ -4943,7 +4954,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
public setProfileInfo(info: "displayname", data: { displayname: string }): Promise<{}>;
public setProfileInfo(info: "avatar_url" | "displayname", data: object): Promise<{}> {
const path = utils.encodeUri("/profile/$userId/$info", {
$userId: this.credentials.userId,
$userId: this.credentials.userId!,
$info: info,
});
return this.http.authedRequest(Method.Put, path, undefined, data);
@ -4957,7 +4968,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
public async setDisplayName(name: string): Promise<{}> {
const prom = await this.setProfileInfo("displayname", { displayname: name });
// XXX: synthesise a profile update for ourselves because Synapse is broken and won't
const user = this.getUser(this.getUserId());
const user = this.getUser(this.getUserId()!);
if (user) {
user.displayName = name;
user.emit(UserEvent.DisplayName, user.events.presence, user);
@ -4973,7 +4984,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
public async setAvatarUrl(url: string): Promise<{}> {
const prom = await this.setProfileInfo("avatar_url", { avatar_url: url });
// XXX: synthesise a profile update for ourselves because Synapse is broken and won't
const user = this.getUser(this.getUserId());
const user = this.getUser(this.getUserId()!);
if (user) {
user.avatarUrl = url;
user.emit(UserEvent.AvatarUrl, user.events.presence, user);
@ -5227,7 +5238,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
// Here we handle non-thread timelines only, but still process any thread events to populate thread summaries.
let timeline = timelineSet.getTimelineForEvent(events[0].getId());
let timeline = timelineSet.getTimelineForEvent(events[0].getId()!);
if (timeline) {
timeline.getState(EventTimeline.BACKWARDS).setUnknownStateEvents(res.state.map(mapper));
} else {
@ -5379,7 +5390,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
params.from = fromToken;
}
let filter: IRoomEventFilter | null = null;
let filter: IRoomEventFilter = {};
if (this.clientOpts?.lazyLoadMembers) {
// create a shallow copy of LAZY_LOADING_MESSAGES_FILTER,
// so the timelineFilter doesn't get written into it below
@ -5396,7 +5407,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
...timelineFilter.getRoomTimelineFilterComponent()?.toJSON(),
};
}
if (filter) {
if (Object.keys(filter).length) {
params.filter = JSON.stringify(filter);
}
@ -5941,7 +5952,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
if (!mute) {
// Remove the rule only if it is a muting rule
if (hasDontNotifyRule) {
promise = this.deletePushRule(scope, PushRuleKind.RoomSpecific, roomPushRule.rule_id);
promise = this.deletePushRule(scope, PushRuleKind.RoomSpecific, roomPushRule!.rule_id);
}
} else {
if (!roomPushRule) {
@ -6078,14 +6089,14 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
const searchOpts = {
body: searchResults._query,
body: searchResults._query!,
next_batch: searchResults.next_batch,
};
const promise = this.search(searchOpts)
.then(res => this.processRoomEventsSearch(searchResults, res))
.finally(() => {
searchResults.pendingRequest = null;
searchResults.pendingRequest = undefined;
});
searchResults.pendingRequest = promise;
@ -6127,7 +6138,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
if (room) {
// Copy over a known event sender if we can
for (const ev of sr.context.getTimeline()) {
const sender = room.getMember(ev.getSender());
const sender = room.getMember(ev.getSender()!);
if (!ev.sender && sender) ev.sender = sender;
}
}
@ -6172,7 +6183,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*/
public createFilter(content: IFilterDefinition): Promise<Filter> {
const path = utils.encodeUri("/user/$userId/filter", {
$userId: this.credentials.userId,
$userId: this.credentials.userId!,
});
return this.http.authedRequest<IFilterResponse>(Method.Post, path, undefined, content)
.then((response) => {
@ -6264,7 +6275,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// debuglog("Created new filter ID %s: %s", createdFilter.filterId,
// JSON.stringify(createdFilter.getDefinition()));
this.store.setFilterIdByName(filterName, createdFilter.filterId);
return createdFilter.filterId;
return createdFilter.filterId!;
}
/**
@ -6276,7 +6287,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
*/
public getOpenIdToken(): Promise<IOpenIDToken> {
const path = utils.encodeUri("/user/$userId/openid/request_token", {
$userId: this.credentials.userId,
$userId: this.credentials.userId!,
});
return this.http.authedRequest(Method.Post, path, undefined, {});
@ -6284,7 +6295,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
private startCallEventHandler = (): void => {
if (this.isInitialSyncComplete()) {
this.callEventHandler.start();
this.callEventHandler?.start();
this.off(ClientEvent.Sync, this.startCallEventHandler);
}
};
@ -6308,18 +6319,18 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
/**
* Get the unix timestamp (in milliseconds) at which the current
* TURN credentials (from getTurnServers) expire
* @return {number} The expiry timestamp, in milliseconds, or null if no credentials
* @return {number} The expiry timestamp in milliseconds
*/
public getTurnServersExpiry(): number | null {
public getTurnServersExpiry(): number {
return this.turnServersExpiry;
}
public get pollingTurnServers(): boolean {
return this.checkTurnServersIntervalID !== null;
return this.checkTurnServersIntervalID !== undefined;
}
// XXX: Intended private, used in code.
public async checkTurnServers(): Promise<boolean> {
public async checkTurnServers(): Promise<boolean | undefined> {
if (!this.canSupportVoip) {
return;
}
@ -6349,15 +6360,15 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
} catch (err) {
logger.error("Failed to get TURN URIs", err);
if (err.httpStatus === 403) {
if ((<HTTPError>err).httpStatus === 403) {
// We got a 403, so there's no point in looping forever.
logger.info("TURN access unavailable for this account: stopping credentials checks");
if (this.checkTurnServersIntervalID !== null) global.clearInterval(this.checkTurnServersIntervalID);
this.checkTurnServersIntervalID = null;
this.emit(ClientEvent.TurnServersError, err, true); // fatal
this.checkTurnServersIntervalID = undefined;
this.emit(ClientEvent.TurnServersError, <HTTPError>err, true); // fatal
} else {
// otherwise, if we failed for whatever reason, try again the next time we're called.
this.emit(ClientEvent.TurnServersError, err, false); // non-fatal
this.emit(ClientEvent.TurnServersError, <Error>err, false); // non-fatal
}
}
}
@ -6399,9 +6410,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
"/_synapse/admin/v1/users/$userId/admin",
{ $userId: this.getUserId()! },
);
return this.http.authedRequest(
return this.http.authedRequest<{ admin: boolean }>(
Method.Get, path, undefined, undefined, { prefix: '' },
).then(r => r['admin']); // pull out the specific boolean we want
).then(r => r.admin); // pull out the specific boolean we want
}
/**
@ -6441,12 +6452,15 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
this.emit(ClientEvent.ClientWellKnown, this.clientWellKnown);
}
public getClientWellKnown(): IClientWellKnown {
public getClientWellKnown(): IClientWellKnown | undefined {
return this.clientWellKnown;
}
public waitForClientWellKnown(): Promise<IClientWellKnown> {
return this.clientWellKnownPromise;
if (!this.clientRunning) {
throw new Error("Client is not running");
}
return this.clientWellKnownPromise!;
}
/**
@ -6817,9 +6831,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @param {boolean} stripProto whether or not to strip the protocol from the URL
* @return {string} Identity server URL of this client
*/
public getIdentityServerUrl(stripProto = false): string {
if (stripProto && (this.idBaseUrl.startsWith("http://") ||
this.idBaseUrl.startsWith("https://"))) {
public getIdentityServerUrl(stripProto = false): string | undefined {
if (stripProto && (this.idBaseUrl?.startsWith("http://") || this.idBaseUrl?.startsWith("https://"))) {
return this.idBaseUrl.split("://")[1];
}
return this.idBaseUrl;
@ -7286,8 +7299,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
templatedUrl + "?" + queryString, {
$roomId: roomId,
$eventId: eventId,
$relationType: relationType,
$eventType: eventType,
$relationType: relationType!,
$eventType: eventType!,
});
return this.http.authedRequest(
Method.Get, path, undefined, undefined, {
@ -7453,8 +7466,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
public async setRoomReadMarkersHttpRequest(
roomId: string,
rmEventId: string,
rrEventId: string,
rpEventId: string,
rrEventId?: string,
rpEventId?: string,
): Promise<{}> {
const path = utils.encodeUri("/rooms/$roomId/read_markers", {
$roomId: roomId,
@ -9021,8 +9034,8 @@ export function fixNotificationCountOnDecryption(cli: MatrixClient, event: Matri
// TODO: Handle mentions received while the client is offline
// See also https://github.com/vector-im/element-web/issues/9069
const hasReadEvent = isThreadEvent
? room.getThread(event.threadRootId)?.hasUserReadEvent(cli.getUserId()!, event.getId())
: room.hasUserReadEvent(cli.getUserId()!, event.getId());
? room.getThread(event.threadRootId)?.hasUserReadEvent(cli.getUserId()!, event.getId()!)
: room.hasUserReadEvent(cli.getUserId()!, event.getId()!);
if (!hasReadEvent) {
let newCount = currentCount;

View File

@ -139,8 +139,7 @@ export const getTextForLocationEvent = (
/**
* Generates the content for a Location event
* @param uri a geo:// uri for the location
* @param timestamp the timestamp when the location was correct (milliseconds since
* the UNIX epoch)
* @param timestamp the timestamp when the location was correct (milliseconds since the UNIX epoch)
* @param description the (optional) label for this location on the map
* @param assetType the (optional) asset type of this location e.g. "m.self"
* @param text optional. A text for the location
@ -150,7 +149,7 @@ export const makeLocationContent = (
// to avoid a breaking change
text: string | undefined,
uri: string,
timestamp?: number,
timestamp: number,
description?: string,
assetType?: LocationAssetType,
): LegacyLocationEventContent & MLocationEventContent => {

View File

@ -44,8 +44,8 @@ function publicKeyFromKeyInfo(keyInfo: ICrossSigningKey): string {
}
export interface ICacheCallbacks {
getCrossSigningKeyCache?(type: string, expectedPublicKey?: string): Promise<Uint8Array>;
storeCrossSigningKeyCache?(type: string, key: Uint8Array): Promise<void>;
getCrossSigningKeyCache?(type: string, expectedPublicKey?: string): Promise<Uint8Array | null>;
storeCrossSigningKeyCache?(type: string, key?: Uint8Array): Promise<void>;
}
export interface ICrossSigningInfo {
@ -169,7 +169,9 @@ 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> | null> {
public async isStoredInSecretStorage(
secretStorage: SecretStorage<MatrixClient | undefined>,
): 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
@ -196,7 +198,7 @@ export class CrossSigningInfo {
*/
public static async storeInSecretStorage(
keys: Map<string, Uint8Array>,
secretStorage: SecretStorage,
secretStorage: SecretStorage<undefined>,
): Promise<void> {
for (const [type, privateKey] of keys) {
const encodedKey = encodeBase64(privateKey);
@ -433,10 +435,9 @@ export class CrossSigningInfo {
// if everything checks out, then save the keys
if (keys.master) {
this.keys.master = keys.master;
// if the master key is set, then the old self-signing and
// user-signing keys are obsolete
this.keys.self_signing = null;
this.keys.user_signing = null;
// if the master key is set, then the old self-signing and user-signing keys are obsolete
delete this.keys["self_signing"];
delete this.keys["user_signing"];
}
if (keys.self_signing) {
this.keys.self_signing = keys.self_signing;
@ -723,7 +724,7 @@ export function createCryptoStoreCacheCallbacks(store: CryptoStore, olmDevice: O
},
storeCrossSigningKeyCache: async function(
type: keyof SecretStorePrivateKeys,
key: Uint8Array,
key?: Uint8Array,
): Promise<void> {
if (!(key instanceof Uint8Array)) {
throw new Error(

View File

@ -252,7 +252,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
*
* @param {string} st The sync token
*/
public setSyncToken(st: string): void {
public setSyncToken(st: string | null): void {
this.syncToken = st;
}

View File

@ -15,7 +15,7 @@ limitations under the License.
*/
import { logger } from "../logger";
import { MatrixEvent } from "../models/event";
import { IContent, MatrixEvent } from "../models/event";
import { createCryptoStoreCacheCallbacks, ICacheCallbacks } from "./CrossSigning";
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
import { Method, ClientPrefix } from "../http-api";
@ -53,10 +53,10 @@ export class EncryptionSetupBuilder {
public readonly crossSigningCallbacks: CrossSigningCallbacks;
public readonly ssssCryptoCallbacks: SSSSCryptoCallbacks;
private crossSigningKeys: ICrossSigningKeys = null;
private keySignatures: KeySignatures = null;
private keyBackupInfo: IKeyBackupInfo = null;
private sessionBackupPrivateKey: Uint8Array;
private crossSigningKeys?: ICrossSigningKeys;
private keySignatures?: KeySignatures;
private keyBackupInfo?: IKeyBackupInfo;
private sessionBackupPrivateKey?: Uint8Array;
/**
* @param {Object.<String, MatrixEvent>} accountData pre-existing account data, will only be read, not written.
@ -162,14 +162,14 @@ export class EncryptionSetupBuilder {
for (const type of ["master", "self_signing", "user_signing"]) {
logger.log(`Cache ${type} cross-signing private key locally`);
const privateKey = this.crossSigningCallbacks.privateKeys.get(type);
await cacheCallbacks.storeCrossSigningKeyCache(type, privateKey);
await cacheCallbacks.storeCrossSigningKeyCache?.(type, privateKey);
}
// store own cross-sign pubkeys as trusted
await crypto.cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
crypto.cryptoStore.storeCrossSigningKeys(
txn, this.crossSigningKeys.keys);
txn, this.crossSigningKeys!.keys);
},
);
}
@ -195,9 +195,9 @@ export class EncryptionSetupOperation {
*/
constructor(
private readonly accountData: Map<string, object>,
private readonly crossSigningKeys: ICrossSigningKeys,
private readonly keyBackupInfo: IKeyBackupInfo,
private readonly keySignatures: KeySignatures,
private readonly crossSigningKeys?: ICrossSigningKeys,
private readonly keyBackupInfo?: IKeyBackupInfo,
private readonly keySignatures?: KeySignatures,
) {}
/**
@ -215,7 +215,7 @@ export class EncryptionSetupOperation {
// We must only call `uploadDeviceSigningKeys` from inside this auth
// helper to ensure we properly handle auth errors.
await this.crossSigningKeys.authUpload(authDict => {
await this.crossSigningKeys.authUpload?.(authDict => {
return baseApis.uploadDeviceSigningKeys(authDict, keys as CrossSigningKeys);
});
@ -281,15 +281,15 @@ class AccountDataClientAdapter
* @param {String} type
* @return {Promise<Object>} the content of the account data
*/
public getAccountDataFromServer(type: string): Promise<any> {
return Promise.resolve(this.getAccountData(type));
public getAccountDataFromServer<T extends {[k: string]: any}>(type: string): Promise<T> {
return Promise.resolve(this.getAccountData(type) as T);
}
/**
* @param {String} type
* @return {Object} the content of the account data
*/
public getAccountData(type: string): MatrixEvent {
public getAccountData(type: string): IContent | null {
const modifiedValue = this.values.get(type);
if (modifiedValue) {
return modifiedValue;
@ -329,7 +329,7 @@ class CrossSigningCallbacks implements ICryptoCallbacks, ICacheCallbacks {
public readonly privateKeys = new Map<string, Uint8Array>();
// cache callbacks
public getCrossSigningKeyCache(type: string, expectedPublicKey: string): Promise<Uint8Array> {
public getCrossSigningKeyCache(type: string, expectedPublicKey: string): Promise<Uint8Array | null> {
return this.getCrossSigningKey(type, expectedPublicKey);
}
@ -339,8 +339,8 @@ class CrossSigningCallbacks implements ICryptoCallbacks, ICacheCallbacks {
}
// non-cache callbacks
public getCrossSigningKey(type: string, expectedPubkey: string): Promise<Uint8Array> {
return Promise.resolve(this.privateKeys.get(type));
public getCrossSigningKey(type: string, expectedPubkey: string): Promise<Uint8Array | null> {
return Promise.resolve(this.privateKeys.get(type) ?? null);
}
public saveCrossSigningKeys(privateKeys: Record<string, Uint8Array>) {

View File

@ -680,7 +680,7 @@ export class OlmDevice {
public async getSessionIdsForDevice(theirDeviceIdentityKey: string): Promise<string[]> {
const log = logger.withPrefix("[getSessionIdsForDevice]");
if (this.sessionsInProgress[theirDeviceIdentityKey]) {
if (theirDeviceIdentityKey in this.sessionsInProgress) {
log.debug(`Waiting for Olm session for ${theirDeviceIdentityKey} to be created`);
try {
await this.sessionsInProgress[theirDeviceIdentityKey];
@ -770,7 +770,7 @@ export class OlmDevice {
): Promise<{ sessionId: string, lastReceivedMessageTs: number, hasReceivedMessage: boolean }[]> {
log = log.withPrefix("[getSessionInfoForDevice]");
if (this.sessionsInProgress[deviceIdentityKey] && !nowait) {
if (deviceIdentityKey in this.sessionsInProgress && !nowait) {
log.debug(`Waiting for Olm session for ${deviceIdentityKey} to be created`);
try {
await this.sessionsInProgress[deviceIdentityKey];

View File

@ -75,10 +75,26 @@ export enum RoomKeyRequestState {
CancellationPendingAndWillResend,
}
interface RequestMessageBase {
requesting_device_id: string;
request_id: string;
}
interface RequestMessageRequest extends RequestMessageBase {
action: "request";
body: IRoomKeyRequestBody;
}
interface RequestMessageCancellation extends RequestMessageBase {
action: "request_cancellation";
}
type RequestMessage = RequestMessageRequest | RequestMessageCancellation;
export class OutgoingRoomKeyRequestManager {
// handle for the delayed call to sendOutgoingRoomKeyRequests. Non-null
// if the callback has been set, or if it is still running.
private sendOutgoingRoomKeyRequestsTimer: ReturnType<typeof setTimeout> = null;
private sendOutgoingRoomKeyRequestsTimer?: ReturnType<typeof setTimeout>;
// sanity check to ensure that we don't end up with two concurrent runs
// of sendOutgoingRoomKeyRequests
@ -369,43 +385,42 @@ export class OutgoingRoomKeyRequestManager {
// look for and send any queued requests. Runs itself recursively until
// there are no more requests, or there is an error (in which case, the
// timer will be restarted before the promise resolves).
private sendOutgoingRoomKeyRequests(): Promise<void> {
private async sendOutgoingRoomKeyRequests(): Promise<void> {
if (!this.clientRunning) {
this.sendOutgoingRoomKeyRequestsTimer = null;
return Promise.resolve();
this.sendOutgoingRoomKeyRequestsTimer = undefined;
return;
}
return this.cryptoStore.getOutgoingRoomKeyRequestByState([
const req = await this.cryptoStore.getOutgoingRoomKeyRequestByState([
RoomKeyRequestState.CancellationPending,
RoomKeyRequestState.CancellationPendingAndWillResend,
RoomKeyRequestState.Unsent,
]).then((req: OutgoingRoomKeyRequest) => {
if (!req) {
this.sendOutgoingRoomKeyRequestsTimer = null;
return;
}
]);
let prom;
if (!req) {
this.sendOutgoingRoomKeyRequestsTimer = undefined;
return;
}
try {
switch (req.state) {
case RoomKeyRequestState.Unsent:
prom = this.sendOutgoingRoomKeyRequest(req);
await this.sendOutgoingRoomKeyRequest(req);
break;
case RoomKeyRequestState.CancellationPending:
prom = this.sendOutgoingRoomKeyRequestCancellation(req);
await this.sendOutgoingRoomKeyRequestCancellation(req);
break;
case RoomKeyRequestState.CancellationPendingAndWillResend:
prom = this.sendOutgoingRoomKeyRequestCancellation(req, true);
await this.sendOutgoingRoomKeyRequestCancellation(req, true);
break;
}
return prom.then(() => {
// go around the loop again
return this.sendOutgoingRoomKeyRequests();
}).catch((e) => {
logger.error("Error sending room key request; will retry later.", e);
this.sendOutgoingRoomKeyRequestsTimer = null;
});
});
// go around the loop again
return this.sendOutgoingRoomKeyRequests();
} catch (e) {
logger.error("Error sending room key request; will retry later.", e);
this.sendOutgoingRoomKeyRequestsTimer = undefined;
}
}
// given a RoomKeyRequest, send it and update the request record
@ -416,16 +431,14 @@ export class OutgoingRoomKeyRequestManager {
`(id ${req.requestId})`,
);
const requestMessage = {
const requestMessage: RequestMessage = {
action: "request",
requesting_device_id: this.deviceId,
request_id: req.requestId,
body: req.requestBody,
};
return this.sendMessageToDevices(
requestMessage, req.recipients, req.requestTxnId || req.requestId,
).then(() => {
return this.sendMessageToDevices(requestMessage, req.recipients, req.requestTxnId || req.requestId).then(() => {
return this.cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, RoomKeyRequestState.Unsent,
{ state: RoomKeyRequestState.Sent },
@ -443,7 +456,7 @@ export class OutgoingRoomKeyRequestManager {
`(cancellation id ${req.cancellationTxnId})`,
);
const requestMessage = {
const requestMessage: RequestMessage = {
action: "request_cancellation",
requesting_device_id: this.deviceId,
request_id: req.requestId,
@ -467,7 +480,11 @@ export class OutgoingRoomKeyRequestManager {
}
// send a RoomKeyRequest to a list of recipients
private sendMessageToDevices(message, recipients, txnId: string): Promise<{}> {
private sendMessageToDevices(
message: RequestMessage,
recipients: IRoomKeyRequestRecipient[],
txnId?: string,
): Promise<{}> {
const contentMap: Record<string, Record<string, Record<string, any>>> = {};
for (const recip of recipients) {
if (!contentMap[recip.userId]) {
@ -480,15 +497,13 @@ export class OutgoingRoomKeyRequestManager {
}
}
function stringifyRequestBody(requestBody) {
function stringifyRequestBody(requestBody: IRoomKeyRequestBody): string {
// we assume that the request is for megolm keys, which are identified by
// room id and session id
return requestBody.room_id + " / " + requestBody.session_id;
}
function stringifyRecipientList(recipients) {
return '['
+ recipients.map((r) => `${r.userId}:${r.deviceId}`).join(",")
+ ']';
function stringifyRecipientList(recipients: IRoomKeyRequestRecipient[]): string {
return `[${recipients.map((r) => `${r.userId}:${r.deviceId}`).join(",")}]`;
}

View File

@ -38,12 +38,12 @@ export class RoomList {
// Object of roomId -> room e2e info object (body of the m.room.encryption event)
private roomEncryption: Record<string, IRoomEncryption> = {};
constructor(private readonly cryptoStore: CryptoStore) {}
constructor(private readonly cryptoStore?: CryptoStore) {}
public async init(): Promise<void> {
await this.cryptoStore.doTxn(
await this.cryptoStore!.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
this.cryptoStore.getEndToEndRooms(txn, (result) => {
this.cryptoStore!.getEndToEndRooms(txn, (result) => {
this.roomEncryption = result;
});
},
@ -63,9 +63,9 @@ export class RoomList {
// as it prevents the Crypto::setRoomEncryption from calling
// this twice for consecutive m.room.encryption events
this.roomEncryption[roomId] = roomInfo;
await this.cryptoStore.doTxn(
await this.cryptoStore!.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
this.cryptoStore.storeEndToEndRoom(roomId, roomInfo, txn);
this.cryptoStore!.storeEndToEndRoom(roomId, roomInfo, txn);
},
);
}

View File

@ -19,10 +19,11 @@ import * as olmlib from './olmlib';
import { encodeBase64 } from './olmlib';
import { randomString } from '../randomstring';
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from './aes';
import { ClientEvent, ICryptoCallbacks, MatrixEvent } from '../matrix';
import { ClientEvent, IContent, ICryptoCallbacks, MatrixEvent } from '../matrix';
import { ClientEventHandlerMap, MatrixClient } from "../client";
import { IAddSecretStorageKeyOpts, ISecretStorageKeyInfo } from './api';
import { TypedEventEmitter } from '../models/typed-event-emitter';
import { defer, IDeferred } from "../utils";
export const SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha2";
@ -39,15 +40,14 @@ export interface ISecretRequest {
export interface IAccountDataClient extends TypedEventEmitter<ClientEvent.AccountData, ClientEventHandlerMap> {
// Subset of MatrixClient (which also uses any for the event content)
getAccountDataFromServer: <T extends {[k: string]: any}>(eventType: string) => Promise<T>;
getAccountData: (eventType: string) => MatrixEvent;
getAccountData: (eventType: string) => IContent | null;
setAccountData: (eventType: string, content: any) => Promise<{}>;
}
interface ISecretRequestInternal {
name: string;
devices: string[];
resolve: (reason: string) => void;
reject: (error: Error) => void;
deferred: IDeferred<string>;
}
interface IDecryptors {
@ -66,7 +66,7 @@ interface ISecretInfo {
* Implements Secure Secret Storage and Sharing (MSC1946)
* @module crypto/SecretStorage
*/
export class SecretStorage {
export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
private requests = new Map<string, ISecretRequestInternal>();
// In it's pure javascript days, this was relying on some proper Javascript-style
@ -80,7 +80,7 @@ export class SecretStorage {
constructor(
private readonly accountDataAdapter: IAccountDataClient,
private readonly cryptoCallbacks: ICryptoCallbacks,
private readonly baseApis?: MatrixClient,
private readonly baseApis: B,
) {}
public async getDefaultKeyId(): Promise<string | null> {
@ -129,13 +129,11 @@ export class SecretStorage {
*/
public async addKey(
algorithm: string,
opts: IAddSecretStorageKeyOpts,
opts: IAddSecretStorageKeyOpts = {},
keyId?: string,
): Promise<SecretStorageKeyObject> {
const keyInfo = { algorithm } as ISecretStorageKeyInfo;
if (!opts) opts = {} as IAddSecretStorageKeyOpts;
if (opts.name) {
keyInfo.name = opts.name;
}
@ -376,21 +374,11 @@ export class SecretStorage {
* @param {string} name the name of the secret to request
* @param {string[]} devices the devices to request the secret from
*/
public request(name: string, devices: string[]): ISecretRequest {
public request(this: SecretStorage<MatrixClient>, name: string, devices: string[]): ISecretRequest {
const requestId = this.baseApis.makeTxnId();
let resolve: (s: string) => void;
let reject: (e: Error) => void;
const promise = new Promise<string>((res, rej) => {
resolve = res;
reject = rej;
});
this.requests.set(requestId, {
name,
devices,
resolve,
reject,
});
const deferred = defer<string>();
this.requests.set(requestId, { name, devices, deferred });
const cancel = (reason: string) => {
// send cancellation event
@ -404,12 +392,12 @@ export class SecretStorage {
toDevice[device] = cancelData;
}
this.baseApis.sendToDevice("m.secret.request", {
[this.baseApis.getUserId()]: toDevice,
[this.baseApis.getUserId()!]: toDevice,
});
// and reject the promise so that anyone waiting on it will be
// notified
reject(new Error(reason || "Cancelled"));
deferred.reject(new Error(reason || "Cancelled"));
};
// send request to devices
@ -425,22 +413,23 @@ export class SecretStorage {
}
logger.info(`Request secret ${name} from ${devices}, id ${requestId}`);
this.baseApis.sendToDevice("m.secret.request", {
[this.baseApis.getUserId()]: toDevice,
[this.baseApis.getUserId()!]: toDevice,
});
return {
requestId,
promise,
promise: deferred.promise,
cancel,
};
}
public async onRequestReceived(event: MatrixEvent): Promise<void> {
public async onRequestReceived(this: SecretStorage<MatrixClient>, event: MatrixEvent): Promise<void> {
const sender = event.getSender();
const content = event.getContent();
if (sender !== this.baseApis.getUserId()
|| !(content.name && content.action
&& content.requesting_device_id && content.request_id)) {
&& content.requesting_device_id && content.request_id)
) {
// ignore requests from anyone else, for now
return;
}
@ -498,25 +487,25 @@ export class SecretStorage {
};
const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this.baseApis.crypto.olmDevice.deviceCurve25519Key,
sender_key: this.baseApis.crypto!.olmDevice.deviceCurve25519Key,
ciphertext: {},
};
await olmlib.ensureOlmSessionsForDevices(
this.baseApis.crypto.olmDevice,
this.baseApis.crypto!.olmDevice,
this.baseApis,
{
[sender]: [
this.baseApis.getStoredDevice(sender, deviceId),
this.baseApis.getStoredDevice(sender, deviceId)!,
],
},
);
await olmlib.encryptMessageForDevice(
encryptedContent.ciphertext,
this.baseApis.getUserId(),
this.baseApis.deviceId,
this.baseApis.crypto.olmDevice,
this.baseApis.getUserId()!,
this.baseApis.deviceId!,
this.baseApis.crypto!.olmDevice,
sender,
this.baseApis.getStoredDevice(sender, deviceId),
this.baseApis.getStoredDevice(sender, deviceId)!,
payload,
);
const contentMap = {
@ -533,7 +522,7 @@ export class SecretStorage {
}
}
public onSecretReceived(event: MatrixEvent): void {
public onSecretReceived(this: SecretStorage<MatrixClient>, event: MatrixEvent): void {
if (event.getSender() !== this.baseApis.getUserId()) {
// we shouldn't be receiving secrets from anyone else, so ignore
// because someone could be trying to send us bogus data
@ -547,7 +536,7 @@ export class SecretStorage {
const content = event.getContent();
const senderKeyUser = this.baseApis.crypto.deviceList.getUserByIdentityKey(
const senderKeyUser = this.baseApis.crypto!.deviceList.getUserByIdentityKey(
olmlib.OLM_ALGORITHM,
event.getSenderKey() || "",
);
@ -561,9 +550,9 @@ export class SecretStorage {
if (requestControl) {
// make sure that the device that sent it is one of the devices that
// we requested from
const deviceInfo = this.baseApis.crypto.deviceList.getDeviceByIdentityKey(
const deviceInfo = this.baseApis.crypto!.deviceList.getDeviceByIdentityKey(
olmlib.OLM_ALGORITHM,
event.getSenderKey(),
event.getSenderKey()!,
);
if (!deviceInfo) {
logger.log(
@ -578,7 +567,7 @@ export class SecretStorage {
// unsure that the sender is trusted. In theory, this check is
// unnecessary since we only accept secret shares from devices that
// we requested from, but it doesn't hurt.
const deviceTrust = this.baseApis.crypto.checkDeviceInfoTrust(event.getSender(), deviceInfo);
const deviceTrust = this.baseApis.crypto!.checkDeviceInfoTrust(event.getSender()!, deviceInfo);
if (!deviceTrust.isVerified()) {
logger.log("secret share from unverified device");
return;
@ -588,7 +577,7 @@ export class SecretStorage {
`Successfully received secret ${requestControl.name} ` +
`from ${deviceInfo.deviceId}`,
);
requestControl.resolve(content.secret);
requestControl.deferred.resolve(content.secret);
}
}

View File

@ -36,7 +36,7 @@ import { IRoomEncryption } from "../RoomList";
*/
export const ENCRYPTION_CLASSES = new Map<string, new (params: IParams) => EncryptionAlgorithm>();
type DecryptionClassParams = Omit<IParams, "deviceId" | "config">;
export type DecryptionClassParams<P extends IParams = IParams> = Omit<P, "deviceId" | "config">;
/**
* map of registered encryption algorithm classes. Map from string to {@link
@ -52,7 +52,7 @@ export interface IParams {
crypto: Crypto;
olmDevice: OlmDevice;
baseApis: MatrixClient;
roomId: string;
roomId?: string;
config: IRoomEncryption & object;
}
@ -76,7 +76,7 @@ export abstract class EncryptionAlgorithm {
protected readonly crypto: Crypto;
protected readonly olmDevice: OlmDevice;
protected readonly baseApis: MatrixClient;
protected readonly roomId: string;
protected readonly roomId?: string;
constructor(params: IParams) {
this.userId = params.userId;
@ -148,7 +148,7 @@ export abstract class DecryptionAlgorithm {
protected readonly crypto: Crypto;
protected readonly olmDevice: OlmDevice;
protected readonly baseApis: MatrixClient;
protected readonly roomId: string;
protected readonly roomId?: string;
constructor(params: DecryptionClassParams) {
this.userId = params.userId;
@ -296,11 +296,11 @@ export class UnknownDeviceError extends Error {
* module:crypto/algorithms/base.DecryptionAlgorithm|DecryptionAlgorithm}
* implementation
*/
export function registerAlgorithm(
export function registerAlgorithm<P extends IParams = IParams>(
algorithm: string,
encryptor: new (params: IParams) => EncryptionAlgorithm,
decryptor: new (params: DecryptionClassParams) => DecryptionAlgorithm,
encryptor: new (params: P) => EncryptionAlgorithm,
decryptor: new (params: DecryptionClassParams<P>) => DecryptionAlgorithm,
): void {
ENCRYPTION_CLASSES.set(algorithm, encryptor);
DECRYPTION_CLASSES.set(algorithm, decryptor);
ENCRYPTION_CLASSES.set(algorithm, encryptor as new (params: IParams) => EncryptionAlgorithm);
DECRYPTION_CLASSES.set(algorithm, decryptor as new (params: DecryptionClassParams) => DecryptionAlgorithm);
}

View File

@ -24,6 +24,7 @@ import { logger } from '../../logger';
import * as olmlib from "../olmlib";
import {
DecryptionAlgorithm,
DecryptionClassParams,
DecryptionError,
EncryptionAlgorithm,
IParams,
@ -251,8 +252,11 @@ class MegolmEncryption extends EncryptionAlgorithm {
startTime: number;
};
constructor(params: IParams) {
protected readonly roomId: string;
constructor(params: IParams & Required<Pick<IParams, "roomId">>) {
super(params);
this.roomId = params.roomId;
this.sessionRotationPeriodMsgs = params.config?.rotation_period_msgs ?? 100;
this.sessionRotationPeriodMs = params.config?.rotation_period_ms ?? 7 * 24 * 3600 * 1000;
@ -1231,6 +1235,13 @@ class MegolmDecryption extends DecryptionAlgorithm {
// this gets stubbed out by the unit tests.
private olmlib = olmlib;
protected readonly roomId: string;
constructor(params: DecryptionClassParams<IParams & Required<Pick<IParams, "roomId">>>) {
super(params);
this.roomId = params.roomId;
}
/**
* @inheritdoc
*
@ -1264,7 +1275,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
try {
res = await this.olmDevice.decryptGroupMessage(
event.getRoomId()!, content.sender_key, content.session_id, content.ciphertext,
event.getId(), event.getTs(),
event.getId()!, event.getTs(),
);
} catch (e) {
if ((<Error>e).name === "DecryptionError") {
@ -1464,7 +1475,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
return;
}
const outgoingRequests = deviceInfo ? await this.crypto.cryptoStore.getOutgoingRoomKeyRequestsByTarget(
event.getSender(), deviceInfo.deviceId, [RoomKeyRequestState.Sent],
event.getSender()!, deviceInfo.deviceId, [RoomKeyRequestState.Sent],
) : [];
const weRequested = outgoingRequests.some((req) => (
req.requestBody.room_id === content.room_id && req.requestBody.session_id === content.session_id
@ -1524,7 +1535,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
// that room later
if (!room) {
const parkedData = {
senderId: event.getSender(),
senderId: event.getSender()!,
senderKey: content.sender_key,
sessionId: content.session_id,
sessionKey: content.session_key,
@ -1544,7 +1555,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
olmlib.OLM_ALGORITHM,
senderKey,
) ?? undefined;
const deviceTrust = this.crypto.checkDeviceInfoTrust(event.getSender(), sendingDevice);
const deviceTrust = this.crypto.checkDeviceInfoTrust(event.getSender()!, sendingDevice);
if (fromUs && !deviceTrust.isVerified()) {
return;
@ -1608,7 +1619,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
const senderKey = content.sender_key;
if (content.code === "m.no_olm") {
const sender = event.getSender();
const sender = event.getSender()!;
logger.warn(
`${sender}:${senderKey} was unable to establish an olm session with us`,
);

View File

@ -228,7 +228,7 @@ class OlmDecryption extends DecryptionAlgorithm {
// assume that the device logged out. Some event handlers, such as
// secret sharing, may be more strict and reject events that come from
// unknown devices.
await this.crypto.deviceList.downloadKeys([event.getSender()], false);
await this.crypto.deviceList.downloadKeys([event.getSender()!], false);
const senderKeyUser = this.crypto.deviceList.getUserByIdentityKey(
olmlib.OLM_ALGORITHM,
deviceKey,
@ -250,7 +250,7 @@ class OlmDecryption extends DecryptionAlgorithm {
throw new DecryptionError(
"OLM_FORWARDED_MESSAGE",
"Message forwarded from " + payload.sender, {
reported_sender: event.getSender(),
reported_sender: event.getSender()!,
},
);
}

View File

@ -117,10 +117,10 @@ export interface IPassphraseInfo {
}
export interface IAddSecretStorageKeyOpts {
pubkey: string;
pubkey?: string;
passphrase?: IPassphraseInfo;
name?: string;
key: Uint8Array;
key?: Uint8Array;
}
export interface IImportOpts {

View File

@ -201,7 +201,7 @@ export class BackupManager {
}
const [privateKey, authData] = await Algorithm.prepare(key);
const recoveryKey = encodeRecoveryKey(privateKey);
const recoveryKey = encodeRecoveryKey(privateKey)!;
return {
algorithm: Algorithm.algorithmName,
auth_data: authData,
@ -298,10 +298,10 @@ export class BackupManager {
const now = new Date().getTime();
if (
!this.sessionLastCheckAttemptedTime[targetSessionId]
|| now - this.sessionLastCheckAttemptedTime[targetSessionId] > KEY_BACKUP_CHECK_RATE_LIMIT
!this.sessionLastCheckAttemptedTime[targetSessionId!]
|| now - this.sessionLastCheckAttemptedTime[targetSessionId!] > KEY_BACKUP_CHECK_RATE_LIMIT
) {
this.sessionLastCheckAttemptedTime[targetSessionId] = now;
this.sessionLastCheckAttemptedTime[targetSessionId!] = now;
await this.baseApis.restoreKeyBackupWithCache(targetRoomId, targetSessionId, this.backupInfo, {});
}
}

View File

@ -87,7 +87,7 @@ export class DeviceInfo {
BLOCKED: DeviceVerification.Blocked,
};
public algorithms: string[];
public algorithms: string[] = [];
public keys: Record<string, string> = {};
public verified = DeviceVerification.Unverified;
public known = false;

View File

@ -199,8 +199,8 @@ export interface IEventDecryptionResult {
}
export interface IRequestsMap {
getRequest(event: MatrixEvent): VerificationRequest;
getRequestByChannel(channel: IVerificationChannel): VerificationRequest;
getRequest(event: MatrixEvent): VerificationRequest | undefined;
getRequestByChannel(channel: IVerificationChannel): VerificationRequest | undefined;
setRequest(event: MatrixEvent, request: VerificationRequest): void;
setRequestByChannel(channel: IVerificationChannel, request: VerificationRequest): void;
}
@ -302,7 +302,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
// track if an initial tracking of all the room members
// has happened for a given room. This is delayed
// to avoid loading room members as long as possible.
private roomDeviceTrackingState: Record<string, Promise<void>> = {}; // roomId: Promise<void
private roomDeviceTrackingState: { [roomId: string]: Promise<void> } = {};
// The timestamp of the last time we forced establishment
// of a new session for each device, in milliseconds.
@ -588,7 +588,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
privateKey,
};
} finally {
if (decryption) decryption.free();
decryption?.free();
}
}
@ -764,7 +764,9 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
) {
const secretStorage = new SecretStorage(
builder.accountDataClientAdapter,
builder.ssssCryptoCallbacks);
builder.ssssCryptoCallbacks,
undefined,
);
if (await secretStorage.hasKey()) {
logger.log("Storing new cross-signing private keys in secret storage");
// This is writing to in-memory account data in
@ -836,6 +838,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
const secretStorage = new SecretStorage(
builder.accountDataClientAdapter,
builder.ssssCryptoCallbacks,
undefined,
);
// the ID of the new SSSS key, if we create one
@ -2224,7 +2227,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
logger.info("Own device " + deviceId + " marked verified: signing");
// Signing only needed if other device not already signed
let device: ISignedKey;
let device: ISignedKey | undefined;
const deviceTrust = this.checkDeviceTrust(userId, deviceId);
if (deviceTrust.isCrossSigningVerified()) {
logger.log(`Own device ${deviceId} already cross-signing verified`);
@ -2239,7 +2242,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
logger.info("Uploading signature for " + deviceId);
const response = await this.baseApis.uploadKeySignatures({
[userId]: {
[deviceId]: device,
[deviceId]: device!,
},
});
const { failures } = response || {};
@ -2265,7 +2268,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
return deviceObj;
}
public findVerificationRequestDMInProgress(roomId: string): VerificationRequest {
public findVerificationRequestDMInProgress(roomId: string): VerificationRequest | undefined {
return this.inRoomVerificationRequests.findRequestInProgress(roomId);
}
@ -2321,9 +2324,9 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
method: string,
userId: string,
deviceId: string,
transactionId: string = null,
transactionId: string | null = null,
): VerificationBase<any, any> {
let request: Request;
let request: Request | undefined;
if (transactionId) {
request = this.toDeviceVerificationRequests.getRequestBySenderAndTxnId(userId, transactionId);
if (!request) {
@ -2467,7 +2470,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
public getEventEncryptionInfo(event: MatrixEvent): IEncryptedEventInfo {
const ret: Partial<IEncryptedEventInfo> = {};
ret.senderKey = event.getSenderKey();
ret.senderKey = event.getSenderKey() ?? undefined;
ret.algorithm = event.getWireContent().algorithm;
if (!ret.senderKey || !ret.algorithm) {
@ -2488,7 +2491,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
// was sent from. In the case of Megolm, it's actually the Curve25519
// identity key of the device which set up the Megolm session.
ret.sender = this.deviceList.getDeviceByIdentityKey(ret.algorithm, ret.senderKey);
ret.sender = this.deviceList.getDeviceByIdentityKey(ret.algorithm, ret.senderKey) ?? undefined;
// so far so good, but now we need to check that the sender of this event
// hadn't advertised someone else's Curve25519 key as their own. We do that
@ -2655,7 +2658,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
if (!promise) {
promise = trackMembers();
this.roomDeviceTrackingState[roomId] = promise.catch(err => {
this.roomDeviceTrackingState[roomId] = null;
delete this.roomDeviceTrackingState[roomId];
throw err;
});
}
@ -2716,9 +2719,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
this.cryptoStore.getAllEndToEndInboundGroupSessions(txn, (s) => {
if (s === null) return;
const sess = this.olmDevice.exportInboundGroupSession(
s.senderKey, s.sessionId, s.sessionData,
);
const sess = this.olmDevice.exportInboundGroupSession(s.senderKey, s.sessionId, s.sessionData!);
delete sess.first_known_index;
sess.algorithm = olmlib.MEGOLM_ALGORITHM;
exportedSessions.push(sess);
@ -2803,7 +2804,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
throw new Error("Cannot send encrypted messages in unknown rooms");
}
const roomId = event.getRoomId();
const roomId = event.getRoomId()!;
const alg = this.roomEncryptors.get(roomId);
if (!alg) {
@ -2885,7 +2886,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
};
} else {
const content = event.getWireContent();
const alg = this.getRoomDecryptor(event.getRoomId(), content.algorithm);
const alg = this.getRoomDecryptor(event.getRoomId()!, content.algorithm);
return alg.decryptEvent(event);
}
}
@ -2970,7 +2971,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* @param {module:models/event.MatrixEvent} event encryption event
*/
public async onCryptoEvent(event: MatrixEvent): Promise<void> {
const roomId = event.getRoomId();
const roomId = event.getRoomId()!;
const content = event.getContent<IRoomEncryption>();
try {
@ -2978,8 +2979,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
// finished processing the sync, in onSyncCompleted.
await this.setRoomEncryption(roomId, content, true);
} catch (e) {
logger.error("Error configuring encryption in room " + roomId +
":", e);
logger.error(`Error configuring encryption in room ${roomId}`, e);
}
}
@ -3013,7 +3013,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* @param {Object} syncData the data from the 'MatrixClient.sync' event
*/
public async onSyncCompleted(syncData: ISyncStateData): Promise<void> {
this.deviceList.setSyncToken(syncData.nextSyncToken);
this.deviceList.setSyncToken(syncData.nextSyncToken ?? null);
this.deviceList.saveIfDirty();
// we always track our own device list (for key backups etc)
@ -3075,7 +3075,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* @returns {string[]} List of user IDs
*/
private async getTrackedE2eUsers(): Promise<string[]> {
const e2eUserIds = [];
const e2eUserIds: string[] = [];
for (const room of this.getTrackedE2eRooms()) {
const members = await room.getEncryptionTargetMembers();
for (const member of members) {
@ -3295,7 +3295,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
if (!ToDeviceChannel.validateEvent(event, this.baseApis)) {
return;
}
const createRequest = (event: MatrixEvent) => {
const createRequest = (event: MatrixEvent): VerificationRequest | undefined => {
if (!ToDeviceChannel.canCreateRequest(ToDeviceChannel.getEventType(event))) {
return;
}
@ -3304,7 +3304,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
if (!deviceId) {
return;
}
const userId = event.getSender();
const userId = event.getSender()!;
const channel = new ToDeviceChannel(
this.baseApis,
userId,
@ -3337,10 +3337,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
return;
}
const createRequest = (event: MatrixEvent) => {
const channel = new InRoomChannel(
this.baseApis,
event.getRoomId(),
);
const channel = new InRoomChannel(this.baseApis, event.getRoomId()!);
return new VerificationRequest(
channel, this.verificationMethods, this.baseApis);
};
@ -3350,15 +3347,15 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
private async handleVerificationEvent(
event: MatrixEvent,
requestsMap: IRequestsMap,
createRequest: (event: MatrixEvent) => VerificationRequest,
createRequest: (event: MatrixEvent) => VerificationRequest | undefined,
isLiveEvent = true,
): Promise<void> {
// Wait for event to get its final ID with pendingEventOrdering: "chronological", since DM channels depend on it.
if (event.isSending() && event.status != EventStatus.SENT) {
let eventIdListener;
let statusListener;
let eventIdListener: () => void;
let statusListener: () => void;
try {
await new Promise((resolve, reject) => {
await new Promise<void>((resolve, reject) => {
eventIdListener = resolve;
statusListener = () => {
if (event.status == EventStatus.CANCELLED) {
@ -3372,11 +3369,11 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
logger.error("error while waiting for the verification event to be sent: ", err);
return;
} finally {
event.removeListener(MatrixEventEvent.LocalEventIdReplaced, eventIdListener);
event.removeListener(MatrixEventEvent.Status, statusListener);
event.removeListener(MatrixEventEvent.LocalEventIdReplaced, eventIdListener!);
event.removeListener(MatrixEventEvent.Status, statusListener!);
}
}
let request = requestsMap.getRequest(event);
let request: VerificationRequest | undefined = requestsMap.getRequest(event);
let isNewRequest = false;
if (!request) {
request = createRequest(event);
@ -3539,13 +3536,14 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
// this way we don't start device queries after sync on behalf of this room which we won't use
// the result of anyway, as we'll need to do a query again once all the members are fetched
// by calling _trackRoomDevices
if (this.roomDeviceTrackingState[roomId]) {
if (roomId in this.roomDeviceTrackingState) {
if (member.membership == 'join') {
logger.log('Join event for ' + member.userId + ' in ' + roomId);
// make sure we are tracking the deviceList for this user
this.deviceList.startTrackingDeviceList(member.userId);
} else if (member.membership == 'invite' &&
this.clientStore.getRoom(roomId).shouldEncryptForInvitedMembers()) {
this.clientStore.getRoom(roomId)?.shouldEncryptForInvitedMembers()
) {
logger.log('Invite event for ' + member.userId + ' in ' + roomId);
this.deviceList.startTrackingDeviceList(member.userId);
}
@ -3635,7 +3633,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
logger.debug(`room key request for unencrypted room ${roomId}`);
return;
}
const encryptor = this.roomEncryptors.get(roomId);
const encryptor = this.roomEncryptors.get(roomId)!;
const device = this.deviceList.getStoredDevice(userId, deviceId);
if (!device) {
logger.debug(`Ignoring keyshare for unknown device ${userId}:${deviceId}`);
@ -3643,7 +3641,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
}
try {
await encryptor.reshareKeyWithDevice(body.sender_key, body.session_id, userId, device);
await encryptor.reshareKeyWithDevice!(body.sender_key, body.session_id, userId, device);
} catch (e) {
logger.warn(
"Failed to re-share keys for session " + body.session_id +
@ -3676,7 +3674,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
return;
}
const decryptor = this.roomDecryptors.get(roomId).get(alg);
const decryptor = this.roomDecryptors.get(roomId)!.get(alg);
if (!decryptor) {
logger.log(`room key request for unknown alg ${alg} in room ${roomId}`);
return;
@ -3741,11 +3739,10 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* @raises {module:crypto.algorithms.DecryptionError} if the algorithm is
* unknown
*/
public getRoomDecryptor(roomId: string, algorithm: string): DecryptionAlgorithm {
public getRoomDecryptor(roomId: string | null, algorithm: string): DecryptionAlgorithm {
let decryptors: Map<string, DecryptionAlgorithm> | undefined;
let alg: DecryptionAlgorithm | undefined;
roomId = roomId || null;
if (roomId) {
decryptors = this.roomDecryptors.get(roomId);
if (!decryptors) {
@ -3771,7 +3768,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
crypto: this,
olmDevice: this.olmDevice,
baseApis: this.baseApis,
roomId: roomId,
roomId: roomId ?? undefined,
});
if (decryptors) {
@ -3865,7 +3862,7 @@ export class IncomingRoomKeyRequest {
constructor(event: MatrixEvent) {
const content = event.getContent();
this.userId = event.getSender();
this.userId = event.getSender()!;
this.deviceId = content.requesting_device_id;
this.requestId = content.request_id;
this.requestBody = content.body || {};
@ -3890,7 +3887,7 @@ class IncomingRoomKeyRequestCancellation {
constructor(event: MatrixEvent) {
const content = event.getContent();
this.userId = event.getSender();
this.userId = event.getSender()!;
this.deviceId = content.requesting_device_id;
this.requestId = content.request_id;
}

View File

@ -20,7 +20,7 @@ import * as bs58 from 'bs58';
// (which are also base58 encoded, but bitcoin's involve a lot more hashing)
const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01];
export function encodeRecoveryKey(key: ArrayLike<number>): string {
export function encodeRecoveryKey(key: ArrayLike<number>): string | undefined {
const buf = Buffer.alloc(OLM_RECOVERY_KEY_PREFIX.length + key.length + 1);
buf.set(OLM_RECOVERY_KEY_PREFIX, 0);
buf.set(key, OLM_RECOVERY_KEY_PREFIX.length);
@ -32,7 +32,7 @@ export function encodeRecoveryKey(key: ArrayLike<number>): string {
buf[buf.length - 1] = parity;
const base58key = bs58.encode(buf);
return base58key.match(/.{1,4}/g).join(" ");
return base58key.match(/.{1,4}/g)?.join(" ");
}
export function decodeRecoveryKey(recoveryKey: string): Uint8Array {

View File

@ -678,7 +678,7 @@ export class Backend implements CryptoStore {
senderCurve25519Key, sessionId, session: sessionData,
});
addReq.onerror = (ev) => {
if (addReq.error.name === 'ConstraintError') {
if (addReq.error?.name === 'ConstraintError') {
// This stops the error from triggering the txn's onerror
ev.stopPropagation();
// ...and this stops it from aborting the transaction

View File

@ -34,7 +34,7 @@ import { ListenerMap, TypedEventEmitter } from "../../models/typed-event-emitter
const timeoutException = new Error("Verification timed out");
export class SwitchStartEventError extends Error {
constructor(public readonly startEvent: MatrixEvent) {
constructor(public readonly startEvent: MatrixEvent | null) {
super();
}
}
@ -96,7 +96,7 @@ export class VerificationBase<
public readonly baseApis: MatrixClient,
public readonly userId: string,
public readonly deviceId: string,
public startEvent: MatrixEvent,
public startEvent: MatrixEvent | null,
public readonly request: VerificationRequest,
) {
super();

View File

@ -23,7 +23,7 @@ limitations under the License.
import { MatrixEvent } from "../../models/event";
import { EventType } from '../../@types/event';
export function newVerificationError(code: string, reason: string, extraData: Record<string, any>): MatrixEvent {
export function newVerificationError(code: string, reason: string, extraData?: Record<string, any>): MatrixEvent {
const content = Object.assign({}, { code, reason }, extraData);
return new MatrixEvent({
type: EventType.KeyVerificationCancel,

View File

@ -147,7 +147,7 @@ interface IQrData {
prefix: string;
version: number;
mode: Mode;
transactionId: string;
transactionId?: string;
firstKeyB64: string;
secondKeyB64: string;
secretB64: string;
@ -250,7 +250,7 @@ export class QRCodeData {
): IQrData {
const myUserId = client.getUserId()!;
const transactionId = request.channel.transactionId;
const qrData = {
const qrData: IQrData = {
prefix: BINARY_PREFIX,
version: CODE_VERSION,
mode,

View File

@ -233,10 +233,10 @@ type EventHandlerMap = {
* @extends {module:crypto/verification/Base}
*/
export class SAS extends Base<SasEvent, EventHandlerMap> {
private waitingForAccept: boolean;
public ourSASPubKey: string;
public theirSASPubKey: string;
public sasEvent: ISasEvent;
private waitingForAccept?: boolean;
public ourSASPubKey?: string;
public theirSASPubKey?: string;
public sasEvent?: ISasEvent;
// eslint-disable-next-line @typescript-eslint/naming-convention
public static get NAME(): string {
@ -279,7 +279,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
return false;
}
const content = event.getContent();
return content && content.method === SAS.NAME && this.waitingForAccept;
return content?.method === SAS.NAME && !!this.waitingForAccept;
}
private async sendStart(): Promise<Record<string, any>> {
@ -400,7 +400,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
private async doRespondVerification(): Promise<void> {
// as m.related_to is not included in the encrypted content in e2e rooms,
// we need to make sure it is added
let content = this.channel.completedContentFromEvent(this.startEvent);
let content = this.channel.completedContentFromEvent(this.startEvent!);
// Note: we intersect using our pre-made lists, rather than the sets,
// so that the result will be in our order of preference. Then

View File

@ -19,10 +19,10 @@ import { VerificationRequest } from "./VerificationRequest";
export interface IVerificationChannel {
request?: VerificationRequest;
readonly userId: string;
readonly userId?: string;
readonly roomId?: string;
readonly deviceId?: string;
readonly transactionId: string;
readonly transactionId?: string;
readonly receiveStartFromOtherDevices?: boolean;
getTimestamp(event: MatrixEvent): number;
send(type: string, uncompletedContent: Record<string, any>): Promise<void>;

View File

@ -37,7 +37,7 @@ const M_RELATES_TO = "m.relates_to";
* Uses the event id of the initial m.key.verification.request event as a transaction id.
*/
export class InRoomChannel implements IVerificationChannel {
private requestEventId: string = null;
private requestEventId?: string;
/**
* @param {MatrixClient} client the matrix client, to send messages with and get current user & device from.
@ -47,7 +47,7 @@ export class InRoomChannel implements IVerificationChannel {
constructor(
private readonly client: MatrixClient,
public readonly roomId: string,
public userId: string = null,
public userId?: string,
) {
}
@ -56,11 +56,11 @@ export class InRoomChannel implements IVerificationChannel {
}
/** The transaction id generated/used by this verification channel */
public get transactionId(): string {
public get transactionId(): string | undefined {
return this.requestEventId;
}
public static getOtherPartyUserId(event: MatrixEvent, client: MatrixClient): string {
public static getOtherPartyUserId(event: MatrixEvent, client: MatrixClient): string | undefined {
const type = InRoomChannel.getEventType(event);
if (type !== REQUEST_TYPE) {
return;
@ -103,12 +103,12 @@ export class InRoomChannel implements IVerificationChannel {
* @param {MatrixEvent} event the event
* @returns {string} the transaction id
*/
public static getTransactionId(event: MatrixEvent): string {
public static getTransactionId(event: MatrixEvent): string | undefined {
if (InRoomChannel.getEventType(event) === REQUEST_TYPE) {
return event.getId();
} else {
const relation = event.getRelation();
if (relation && relation.rel_type === M_REFERENCE) {
if (relation?.rel_type === M_REFERENCE) {
return relation.event_id;
}
}
@ -184,10 +184,10 @@ export class InRoomChannel implements IVerificationChannel {
* @param {boolean} isLiveEvent whether this is an even received through sync or not
* @returns {Promise} a promise that resolves when any requests as an answer to the passed-in event are sent.
*/
public handleEvent(event: MatrixEvent, request: VerificationRequest, isLiveEvent = false): Promise<void> {
public async handleEvent(event: MatrixEvent, request: VerificationRequest, isLiveEvent = false): Promise<void> {
// prevent processing the same event multiple times, as under
// some circumstances Room.timeline can get emitted twice for the same event
if (request.hasEventId(event.getId())) {
if (request.hasEventId(event.getId()!)) {
return;
}
const type = InRoomChannel.getEventType(event);
@ -198,7 +198,7 @@ export class InRoomChannel implements IVerificationChannel {
return;
}
// set userId if not set already
if (this.userId === null) {
if (!this.userId) {
const userId = InRoomChannel.getOtherPartyUserId(event, this.client);
if (userId) {
this.userId = userId;
@ -207,14 +207,13 @@ export class InRoomChannel implements IVerificationChannel {
// ignore events not sent by us or the other party
const ownUserId = this.client.getUserId();
const sender = event.getSender();
if (this.userId !== null) {
if (this.userId) {
if (sender !== ownUserId && sender !== this.userId) {
logger.log(`InRoomChannel: ignoring verification event from ` +
`non-participating sender ${sender}`);
logger.log(`InRoomChannel: ignoring verification event from non-participating sender ${sender}`);
return;
}
}
if (this.requestEventId === null) {
if (!this.requestEventId) {
this.requestEventId = InRoomChannel.getTransactionId(event);
}
@ -236,7 +235,7 @@ export class InRoomChannel implements IVerificationChannel {
// ensure m.related_to is included in e2ee rooms
// as the field is excluded from encryption
const content = Object.assign({}, event.getContent());
content[M_RELATES_TO] = event.getRelation();
content[M_RELATES_TO] = event.getRelation()!;
return content;
}
@ -307,17 +306,17 @@ export class InRoomChannel implements IVerificationChannel {
export class InRoomRequests implements IRequestsMap {
private requestsByRoomId = new Map<string, Map<string, VerificationRequest>>();
public getRequest(event: MatrixEvent): VerificationRequest {
const roomId = event.getRoomId();
const txnId = InRoomChannel.getTransactionId(event);
public getRequest(event: MatrixEvent): VerificationRequest | undefined {
const roomId = event.getRoomId()!;
const txnId = InRoomChannel.getTransactionId(event)!;
return this.getRequestByTxnId(roomId, txnId);
}
public getRequestByChannel(channel: InRoomChannel): VerificationRequest {
return this.getRequestByTxnId(channel.roomId, channel.transactionId);
public getRequestByChannel(channel: InRoomChannel): VerificationRequest | undefined {
return this.getRequestByTxnId(channel.roomId, channel.transactionId!);
}
private getRequestByTxnId(roomId: string, txnId: string): VerificationRequest {
private getRequestByTxnId(roomId: string, txnId: string): VerificationRequest | undefined {
const requestsByTxnId = this.requestsByRoomId.get(roomId);
if (requestsByTxnId) {
return requestsByTxnId.get(txnId);
@ -325,11 +324,11 @@ export class InRoomRequests implements IRequestsMap {
}
public setRequest(event: MatrixEvent, request: VerificationRequest): void {
this.doSetRequest(event.getRoomId(), InRoomChannel.getTransactionId(event), request);
this.doSetRequest(event.getRoomId()!, InRoomChannel.getTransactionId(event)!, request);
}
public setRequestByChannel(channel: IVerificationChannel, request: VerificationRequest): void {
this.doSetRequest(channel.roomId, channel.transactionId, request);
this.doSetRequest(channel.roomId!, channel.transactionId!, request);
}
private doSetRequest(roomId: string, txnId: string, request: VerificationRequest): void {
@ -342,17 +341,17 @@ export class InRoomRequests implements IRequestsMap {
}
public removeRequest(event: MatrixEvent): void {
const roomId = event.getRoomId();
const roomId = event.getRoomId()!;
const requestsByTxnId = this.requestsByRoomId.get(roomId);
if (requestsByTxnId) {
requestsByTxnId.delete(InRoomChannel.getTransactionId(event));
requestsByTxnId.delete(InRoomChannel.getTransactionId(event)!);
if (requestsByTxnId.size === 0) {
this.requestsByRoomId.delete(roomId);
}
}
}
public findRequestInProgress(roomId: string): VerificationRequest {
public findRequestInProgress(roomId: string): VerificationRequest | undefined {
const requestsByTxnId = this.requestsByRoomId.get(roomId);
if (requestsByTxnId) {
for (const request of requestsByTxnId.values()) {

View File

@ -46,8 +46,8 @@ export class ToDeviceChannel implements IVerificationChannel {
private readonly client: MatrixClient,
public readonly userId: string,
private readonly devices: string[],
public transactionId: string = null,
public deviceId: string = null,
public transactionId?: string,
public deviceId?: string,
) {}
public isToDevices(devices: string[]): boolean {
@ -173,13 +173,11 @@ export class ToDeviceChannel implements IVerificationChannel {
return this.sendToDevices(CANCEL_TYPE, cancelContent, [deviceId]);
}
}
const wasStarted = request.phase === PHASE_STARTED ||
request.phase === PHASE_READY;
const wasStarted = request.phase === PHASE_STARTED || request.phase === PHASE_READY;
await request.handleEvent(event.getType(), event, isLiveEvent, false, false);
const isStarted = request.phase === PHASE_STARTED ||
request.phase === PHASE_READY;
const isStarted = request.phase === PHASE_STARTED || request.phase === PHASE_READY;
const isAcceptingEvent = type === START_TYPE || type === READY_TYPE;
// the request has picked a ready or start event, tell the other devices about it
@ -256,16 +254,16 @@ export class ToDeviceChannel implements IVerificationChannel {
if (type === REQUEST_TYPE || (type === CANCEL_TYPE && !this.deviceId)) {
result = await this.sendToDevices(type, content, this.devices);
} else {
result = await this.sendToDevices(type, content, [this.deviceId]);
result = await this.sendToDevices(type, content, [this.deviceId!]);
}
// the VerificationRequest state machine requires remote echos of the event
// the client sends itself, so we fake this for to_device messages
const remoteEchoEvent = new MatrixEvent({
sender: this.client.getUserId(),
sender: this.client.getUserId()!,
content,
type,
});
await this.request.handleEvent(
await this.request!.handleEvent(
type,
remoteEchoEvent,
/*isLiveEvent=*/true,
@ -298,18 +296,18 @@ export class ToDeviceChannel implements IVerificationChannel {
export class ToDeviceRequests implements IRequestsMap {
private requestsByUserId = new Map<string, Map<string, Request>>();
public getRequest(event: MatrixEvent): Request {
public getRequest(event: MatrixEvent): Request | undefined {
return this.getRequestBySenderAndTxnId(
event.getSender(),
event.getSender()!,
ToDeviceChannel.getTransactionId(event),
);
}
public getRequestByChannel(channel: ToDeviceChannel): Request {
return this.getRequestBySenderAndTxnId(channel.userId, channel.transactionId);
public getRequestByChannel(channel: ToDeviceChannel): Request | undefined {
return this.getRequestBySenderAndTxnId(channel.userId, channel.transactionId!);
}
public getRequestBySenderAndTxnId(sender: string, txnId: string): Request {
public getRequestBySenderAndTxnId(sender: string, txnId: string): Request | undefined {
const requestsByTxnId = this.requestsByUserId.get(sender);
if (requestsByTxnId) {
return requestsByTxnId.get(txnId);
@ -317,11 +315,11 @@ export class ToDeviceRequests implements IRequestsMap {
}
public setRequest(event: MatrixEvent, request: Request): void {
this.setRequestBySenderAndTxnId(event.getSender(), ToDeviceChannel.getTransactionId(event), request);
this.setRequestBySenderAndTxnId(event.getSender()!, ToDeviceChannel.getTransactionId(event), request);
}
public setRequestByChannel(channel: ToDeviceChannel, request: Request): void {
this.setRequestBySenderAndTxnId(channel.userId, channel.transactionId, request);
this.setRequestBySenderAndTxnId(channel.userId, channel.transactionId!, request);
}
public setRequestBySenderAndTxnId(sender: string, txnId: string, request: Request): void {
@ -334,7 +332,7 @@ export class ToDeviceRequests implements IRequestsMap {
}
public removeRequest(event: MatrixEvent): void {
const userId = event.getSender();
const userId = event.getSender()!;
const requestsByTxnId = this.requestsByUserId.get(userId);
if (requestsByTxnId) {
requestsByTxnId.delete(ToDeviceChannel.getTransactionId(event));
@ -344,7 +342,7 @@ export class ToDeviceRequests implements IRequestsMap {
}
}
public findRequestInProgress(userId: string, devices: string[]): Request {
public findRequestInProgress(userId: string, devices: string[]): Request | undefined {
const requestsByTxnId = this.requestsByUserId.get(userId);
if (requestsByTxnId) {
for (const request of requestsByTxnId.values()) {

View File

@ -112,8 +112,8 @@ export class VerificationRequest<
private requestReceivedAt: number | null = null;
private commonMethods: VerificationMethod[] = [];
private _phase: Phase;
public _cancellingUserId: string; // Used in tests only
private _phase!: Phase;
public _cancellingUserId?: string; // Used in tests only
private _verifier?: VerificationBase<any, any>;
constructor(
@ -357,7 +357,7 @@ export class VerificationRequest<
/** The user id of the other party in this request */
public get otherUserId(): string {
return this.channel.userId;
return this.channel.userId!;
}
public get isSelfVerification(): boolean {
@ -372,7 +372,7 @@ export class VerificationRequest<
const myCancel = this.eventsByUs.get(CANCEL_TYPE);
const theirCancel = this.eventsByThem.get(CANCEL_TYPE);
if (myCancel && (!theirCancel || myCancel.getId() < theirCancel.getId())) {
if (myCancel && (!theirCancel || myCancel.getId()! < theirCancel.getId()!)) {
return myCancel.getSender();
}
if (theirCancel) {
@ -405,8 +405,8 @@ export class VerificationRequest<
this.eventsByThem.get(REQUEST_TYPE) ||
this.eventsByThem.get(READY_TYPE) ||
this.eventsByThem.get(START_TYPE);
const theirFirstContent = theirFirstEvent.getContent();
const fromDevice = theirFirstContent.from_device;
const theirFirstContent = theirFirstEvent?.getContent();
const fromDevice = theirFirstContent?.from_device;
return {
userId: this.otherUserId,
deviceId: fromDevice,
@ -559,7 +559,9 @@ export class VerificationRequest<
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;
}
@ -595,7 +597,7 @@ export class VerificationRequest<
// get common methods
if (phase === PHASE_REQUESTED || phase === PHASE_READY) {
if (!this.wasSentByOwnDevice(event)) {
const content = event.getContent<{
const content = event!.getContent<{
methods: string[];
}>();
this.commonMethods =
@ -620,7 +622,7 @@ export class VerificationRequest<
}
// create verifier
if (phase === PHASE_STARTED) {
const { method } = event.getContent();
const { method } = event!.getContent();
if (!this._verifier && !this.observeOnly) {
this._verifier = this.createVerifier(method, event);
if (!this._verifier) {
@ -903,19 +905,19 @@ export class VerificationRequest<
logger.warn("could not find verifier constructor for method", method);
return;
}
return new VerifierCtor(this.channel, this.client, userId, deviceId, startEvent, this);
return new VerifierCtor(this.channel, this.client, userId!, deviceId!, startEvent, this);
}
private wasSentByOwnUser(event: MatrixEvent): boolean {
return event.getSender() === this.client.getUserId();
private wasSentByOwnUser(event?: MatrixEvent): boolean {
return event?.getSender() === this.client.getUserId();
}
// only for .request, .ready or .start
private wasSentByOwnDevice(event: MatrixEvent): boolean {
private wasSentByOwnDevice(event?: MatrixEvent): boolean {
if (!this.wasSentByOwnUser(event)) {
return false;
}
const content = event.getContent();
const content = event!.getContent();
if (!content || content.from_device !== this.client.getDeviceId()) {
return false;
}

View File

@ -88,7 +88,7 @@ export class FilterComponent {
// as sending a whole list of participants could be proven problematic in terms
// of performance
// This should be improved when bundled relationships solve that problem
const relationSenders = [];
const relationSenders: string[] = [];
if (this.userId && bundledRelationships?.[THREAD_RELATION_TYPE.name]?.current_user_participated) {
relationSenders.push(this.userId);
}
@ -131,8 +131,8 @@ export class FilterComponent {
* @return {boolean} true if the event fields match the filter
*/
private checkFields(
roomId: string,
sender: string,
roomId: string | undefined,
sender: string | undefined,
eventType: string,
containsUrl: boolean,
relationTypes: Array<RelationType | string>,

View File

@ -207,15 +207,15 @@ export class InteractiveAuth {
private data: IAuthData;
private emailSid?: string;
private requestingEmailToken = false;
private attemptAuthDeferred: IDeferred<IAuthData> = null;
private chosenFlow: IFlow = null;
private currentStage: string = null;
private attemptAuthDeferred: IDeferred<IAuthData> | null = null;
private chosenFlow: IFlow | null = null;
private currentStage: string | null = null;
private emailAttempt = 1;
// if we are currently trying to submit an auth dict (which includes polling)
// the promise the will resolve/reject when it completes
private submitPromise: Promise<void> = null;
private submitPromise: Promise<void> | null = null;
constructor(opts: IOpts) {
this.matrixClient = opts.matrixClient;
@ -229,7 +229,7 @@ export class InteractiveAuth {
if (opts.sessionId) this.data.session = opts.sessionId;
this.clientSecret = opts.clientSecret || this.matrixClient.generateClientSecret();
this.emailSid = opts.emailSid ?? null;
this.emailSid = opts.emailSid;
}
/**
@ -286,7 +286,7 @@ export class InteractiveAuth {
client_secret: this.clientSecret,
};
if (await this.matrixClient.doesServerRequireIdServerParam()) {
const idServerParsedUrl = new URL(this.matrixClient.getIdentityServerUrl());
const idServerParsedUrl = new URL(this.matrixClient.getIdentityServerUrl()!);
creds.id_server = idServerParsedUrl.host;
}
authDict = {
@ -308,7 +308,7 @@ export class InteractiveAuth {
*
* @return {string} session id
*/
public getSessionId(): string {
public getSessionId(): string | undefined {
return this.data?.session;
}
@ -332,7 +332,7 @@ export class InteractiveAuth {
return this.data.params?.[loginType];
}
public getChosenFlow(): IFlow {
public getChosenFlow(): IFlow | null {
return this.chosenFlow;
}
@ -399,7 +399,7 @@ export class InteractiveAuth {
*
* @returns {string} The sid of the email auth session
*/
public getEmailSid(): string {
public getEmailSid(): string | undefined {
return this.emailSid;
}
@ -457,7 +457,7 @@ export class InteractiveAuth {
private async doRequest(auth: IAuthData, background = false): Promise<void> {
try {
const result = await this.requestCallback(auth, background);
this.attemptAuthDeferred.resolve(result);
this.attemptAuthDeferred!.resolve(result);
this.attemptAuthDeferred = null;
} catch (error) {
// sometimes UI auth errors don't come with flows
@ -491,12 +491,12 @@ export class InteractiveAuth {
try {
this.startNextAuthStage();
} catch (e) {
this.attemptAuthDeferred.reject(e);
this.attemptAuthDeferred!.reject(e);
this.attemptAuthDeferred = null;
return;
}
if (!this.emailSid && this.chosenFlow.stages.includes(AuthType.Email)) {
if (!this.emailSid && this.chosenFlow?.stages.includes(AuthType.Email)) {
try {
await this.requestEmailToken();
// NB. promise is not resolved here - at some point, doRequest
@ -512,7 +512,7 @@ export class InteractiveAuth {
// to do) or it could be a network failure. Either way, pass
// the failure up as the user can't complete auth if we can't
// send the email, for whatever reason.
this.attemptAuthDeferred.reject(e);
this.attemptAuthDeferred!.reject(e);
this.attemptAuthDeferred = null;
}
}
@ -559,7 +559,7 @@ export class InteractiveAuth {
* @return {string?} login type
* @throws {NoAuthFlowFoundError} If no suitable authentication flow can be found
*/
private chooseStage(): AuthType {
private chooseStage(): AuthType | undefined {
if (this.chosenFlow === null) {
this.chosenFlow = this.chooseFlow();
}
@ -625,7 +625,7 @@ export class InteractiveAuth {
* @param {object} flow
* @return {string} login type
*/
private firstUncompletedStage(flow: IFlow): AuthType {
private firstUncompletedStage(flow: IFlow): AuthType | undefined {
const completed = this.data.completed || [];
for (let i = 0; i < flow.stages.length; ++i) {
const stageType = flow.stages[i];

View File

@ -35,7 +35,7 @@ const DEFAULT_NAMESPACE = "matrix";
// console methods at initialization time by a factory that looks up the console methods
// when logging so we always get the current value of console methods.
log.methodFactory = function(methodName, logLevel, loggerName) {
return function(...args) {
return function(this: PrefixedLogger, ...args) {
/* eslint-disable @typescript-eslint/no-invalid-this */
if (this.prefix) {
args.unshift(this.prefix);

View File

@ -62,7 +62,7 @@ export class MSC3089Branch {
}
private get roomId(): string {
return this.indexEvent.getRoomId();
return this.indexEvent.getRoomId()!;
}
/**
@ -223,7 +223,7 @@ export class MSC3089Branch {
do {
childEvent = timelineEvents.find(e => e.replacingEventId() === parentEvent.getId());
if (childEvent) {
const branch = this.directory.getFile(childEvent.getId());
const branch = this.directory.getFile(childEvent.getId()!);
if (branch) {
fileHistory.push(branch);
parentEvent = childEvent;

View File

@ -73,7 +73,7 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
}
public get beaconInfoId(): string {
return this.rootEvent.getId();
return this.rootEvent.getId()!;
}
public get beaconInfoOwner(): string {

View File

@ -95,8 +95,8 @@ export class EventContext {
* @param {boolean} backwards true to set the pagination token for going
* backwards in time
*/
public setPaginateToken(token: string, backwards = false): void {
this.paginateTokens[backwards ? Direction.Backward : Direction.Forward] = token;
public setPaginateToken(token?: string, backwards = false): void {
this.paginateTokens[backwards ? Direction.Backward : Direction.Forward] = token ?? null;
}
/**

View File

@ -459,7 +459,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
let lastEventWasNew = false;
for (let i = 0; i < events.length; i++) {
const event = events[i];
const eventId = event.getId();
const eventId = event.getId()!;
const existingTimeline = this._eventIdToTimeline.get(eventId);
@ -612,7 +612,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
}
}
const timeline = this._eventIdToTimeline.get(event.getId());
const timeline = this._eventIdToTimeline.get(event.getId()!);
if (timeline) {
if (duplicateStrategy === DuplicateStrategy.Replace) {
debuglog("EventTimelineSet.addLiveEvent: replacing duplicate event " + event.getId());
@ -702,7 +702,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
);
}
const eventId = event.getId();
const eventId = event.getId()!;
timeline.addEvent(event, {
toStartOfTimeline,
roomState,

View File

@ -74,7 +74,7 @@ export class EventTimeline {
// check this to avoid overriding non-sentinel members by sentinel ones
// when adding the event to a filtered timeline
if (!event.sender?.events?.member) {
event.sender = stateContext.getSentinelMember(event.getSender());
event.sender = stateContext.getSentinelMember(event.getSender()!);
}
if (!event.target?.events?.member && event.getType() === EventType.RoomMember) {
event.target = stateContext.getSentinelMember(event.getStateKey()!);

View File

@ -71,7 +71,7 @@ export interface IEvent {
type: string;
content: IContent;
sender: string;
room_id: string;
room_id?: string;
origin_server_ts: number;
txn_id?: string;
state_key?: string;
@ -392,7 +392,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
* @return {string} The event ID, e.g. <code>$143350589368169JsLZx:localhost
* </code>
*/
public getId(): string {
public getId(): string | undefined {
return this.event.event_id;
}
@ -400,7 +400,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
* Get the user_id for this event.
* @return {string} The user ID, e.g. <code>@alice:matrix.org</code>
*/
public getSender(): string {
public getSender(): string | undefined {
return this.event.sender || this.event.user_id; // v2 / v1
}
@ -521,7 +521,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
return !!threadDetails || (this.getThread()?.id === this.getId());
}
public get replyEventId(): string {
public get replyEventId(): string | undefined {
// We're prefer ev.getContent() over ev.getWireContent() to make sure
// we grab the latest edit with potentially new relations. But we also
// can't just rely on ev.getContent() by itself because historically we
@ -748,12 +748,14 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
// original sending device if it wasn't us.
const wireContent = this.getWireContent();
const recipients = [{
userId, deviceId: '*',
userId,
deviceId: '*',
}];
const sender = this.getSender();
if (sender !== userId) {
recipients.push({
userId: sender, deviceId: wireContent.device_id,
userId: sender!,
deviceId: wireContent.device_id,
});
}
return recipients;
@ -1387,7 +1389,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
return new Date(ts);
}
} else if (this._replacingEvent) {
return this._replacingEvent.getDate();
return this._replacingEvent.getDate() ?? undefined;
}
}

View File

@ -21,7 +21,7 @@ export const MAIN_ROOM_TIMELINE = "main";
export function synthesizeReceipt(userId: string, event: MatrixEvent, receiptType: ReceiptType): MatrixEvent {
return new MatrixEvent({
content: {
[event.getId()]: {
[event.getId()!]: {
[receiptType]: {
[userId]: {
ts: event.getTs(),
@ -241,7 +241,7 @@ export abstract class ReadReceipt<
* an empty list.
*/
public getReceiptsForEvent(event: MatrixEvent): CachedReceipt[] {
return this.receiptCacheByEventId[event.getId()] || [];
return this.receiptCacheByEventId[event.getId()!] || [];
}
public abstract addReceipt(event: MatrixEvent, synthetic: boolean): void;

View File

@ -26,7 +26,7 @@ export class RelatedRelations {
}
public getRelations(): MatrixEvent[] {
return this.relations.reduce((c, p) => [...c, ...p.getRelations()], []);
return this.relations.reduce<MatrixEvent[]>((c, p) => [...c, ...p.getRelations()], []);
}
public on<T extends RelationsEvent>(ev: T, fn: Listener<RelationsEvent, EventHandlerMap, T>) {

View File

@ -74,7 +74,7 @@ export class RelationsContainer {
* @param {MatrixEvent} event The event to check as relation target.
*/
public aggregateParentEvent(event: MatrixEvent): void {
const relationsForEvent = this.relations.get(event.getId());
const relationsForEvent = this.relations.get(event.getId()!);
if (!relationsForEvent) return;
for (const relationsWithRelType of relationsForEvent.values()) {

View File

@ -76,7 +76,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
* The new relation event to be added.
*/
public async addEvent(event: MatrixEvent) {
if (this.relationEventIds.has(event.getId())) {
if (this.relationEventIds.has(event.getId()!)) {
return;
}
@ -101,7 +101,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
}
this.relations.add(event);
this.relationEventIds.add(event.getId());
this.relationEventIds.add(event.getId()!);
if (this.relationType === RelationType.Annotation) {
this.addAnnotationToAggregation(event);
@ -206,7 +206,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
return bEvents.size - aEvents.size;
});
const sender = event.getSender();
const sender = event.getSender()!;
let eventsFromSender = this.annotationsBySender[sender];
if (!eventsFromSender) {
eventsFromSender = this.annotationsBySender[sender] = new Set();
@ -231,7 +231,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
});
}
const sender = event.getSender();
const sender = event.getSender()!;
const eventsFromSender = this.annotationsBySender[sender];
if (eventsFromSender) {
eventsFromSender.delete(event);

View File

@ -18,6 +18,8 @@ limitations under the License.
* @module models/room
*/
import { Optional } from "matrix-events-sdk";
import {
EventTimelineSet,
DuplicateStrategy,
@ -210,7 +212,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
// read by megolm via getter; boolean value - null indicates "use global value"
private blacklistUnverifiedDevices?: boolean;
private selfMembership?: string;
private summaryHeroes: string[] = null;
private summaryHeroes: string[] | null = null;
// flags to stop logspam about missing m.room.create events
private getTypeWarning = false;
private getVersionWarning = false;
@ -238,25 +240,25 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
/**
* The room summary.
*/
public summary: RoomSummary = null;
public summary: RoomSummary | null = null;
// legacy fields
/**
* The live event timeline for this room, with the oldest event at index 0.
* Present for backwards compatibility - prefer getLiveTimeline().getEvents()
*/
public timeline: MatrixEvent[];
public timeline!: MatrixEvent[];
/**
* oldState The state of the room at the time of the oldest
* event in the live timeline. Present for backwards compatibility -
* prefer getLiveTimeline().getState(EventTimeline.BACKWARDS).
*/
public oldState: RoomState;
public oldState!: RoomState;
/**
* currentState The state of the room at the time of the
* newest event in the timeline. Present for backwards compatibility -
* prefer getLiveTimeline().getState(EventTimeline.FORWARDS).
*/
public currentState: RoomState;
public currentState!: RoomState;
public readonly relations = new RelationsContainer(this.client, this);
/**
@ -592,7 +594,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
* @throws If <code>opts.pendingEventOrdering</code> was not 'detached'
*/
public getPendingEvents(): MatrixEvent[] {
if (this.opts.pendingEventOrdering !== PendingEventOrdering.Detached) {
if (!this.pendingEventList) {
throw new Error(
"Cannot call getPendingEvents with pendingEventOrdering == " +
this.opts.pendingEventOrdering);
@ -608,7 +610,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
* @return {boolean} True if an element was removed.
*/
public removePendingEvent(eventId: string): boolean {
if (this.opts.pendingEventOrdering !== PendingEventOrdering.Detached) {
if (!this.pendingEventList) {
throw new Error(
"Cannot call removePendingEvent with pendingEventOrdering == " +
this.opts.pendingEventOrdering);
@ -634,11 +636,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
* @return {boolean}
*/
public hasPendingEvent(eventId: string): boolean {
if (this.opts.pendingEventOrdering !== PendingEventOrdering.Detached) {
return false;
}
return this.pendingEventList.some(event => event.getId() === eventId);
return this.pendingEventList?.some(event => event.getId() === eventId) ?? false;
}
/**
@ -648,11 +646,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
* @return {MatrixEvent}
*/
public getPendingEvent(eventId: string): MatrixEvent | null {
if (this.opts.pendingEventOrdering !== PendingEventOrdering.Detached) {
return null;
}
return this.pendingEventList.find(event => event.getId() === eventId) ?? null;
return this.pendingEventList?.find(event => event.getId() === eventId) ?? null;
}
/**
@ -693,17 +687,16 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
* @return {string} user id of the inviter
*/
public getDMInviter(): string | undefined {
if (this.myUserId) {
const me = this.getMember(this.myUserId);
if (me) {
return me.getDMInviter();
}
const me = this.getMember(this.myUserId);
if (me) {
return me.getDMInviter();
}
if (this.selfMembership === "invite") {
// fall back to summary information
const memberCount = this.getInvitedAndJoinedMemberCount();
if (memberCount == 2 && this.summaryHeroes.length) {
return this.summaryHeroes[0];
if (memberCount === 2) {
return this.summaryHeroes?.[0];
}
}
}
@ -720,11 +713,8 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
return inviterId;
}
}
// remember, we're assuming this room is a DM,
// so returning the first member we find should be fine
const hasHeroes = Array.isArray(this.summaryHeroes) &&
this.summaryHeroes.length;
if (hasHeroes) {
// Remember, we're assuming this room is a DM, so returning the first member we find should be fine
if (Array.isArray(this.summaryHeroes) && this.summaryHeroes.length) {
return this.summaryHeroes[0];
}
const members = this.currentState.getMembers();
@ -743,10 +733,9 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
if (memberCount > 2) {
return;
}
const hasHeroes = Array.isArray(this.summaryHeroes) &&
this.summaryHeroes.length;
const hasHeroes = Array.isArray(this.summaryHeroes) && this.summaryHeroes.length;
if (hasHeroes) {
const availableMember = this.summaryHeroes.map((userId) => {
const availableMember = this.summaryHeroes!.map((userId) => {
return this.getMember(userId);
}).find((member) => !!member);
if (availableMember) {
@ -767,7 +756,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
// if all else fails, try falling back to a user,
// and create a one-off member for it
if (hasHeroes) {
const availableUser = this.summaryHeroes.map((userId) => {
const availableUser = this.summaryHeroes!.map((userId) => {
return this.client.getUser(userId);
}).find((user) => !!user);
if (availableUser) {
@ -934,7 +923,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
// Get the main TimelineSet
const timelineSet = this.getUnfilteredTimelineSet();
let newTimeline: EventTimeline;
let newTimeline: Optional<EventTimeline>;
// If there isn't any event in the timeline, let's go fetch the latest
// event and construct a timeline from it.
//
@ -965,7 +954,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
// we reset everything. The `timelineSet` we pass in needs to be empty
// in order for this function to call `/context` and generate a new
// timeline.
newTimeline = await this.client.getEventTimeline(timelineSet, mostRecentEventInTimeline.getId());
newTimeline = await this.client.getEventTimeline(timelineSet, mostRecentEventInTimeline.getId()!);
}
// If a racing `/sync` beat us to creating a new timeline, use that
@ -982,11 +971,11 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
// of using the `/context` historical token (ex. `t12-13_0_0_0_0_0_0_0_0`)
// so that it matches the next response from `/sync` and we can properly
// continue the timeline.
newTimeline.setPaginationToken(forwardPaginationToken, EventTimeline.FORWARDS);
newTimeline!.setPaginationToken(forwardPaginationToken, EventTimeline.FORWARDS);
// Set our new fresh timeline as the live timeline to continue syncing
// forwards and back paginating from.
timelineSet.setLiveTimeline(newTimeline);
timelineSet.setLiveTimeline(newTimeline!);
// Fixup `this.oldstate` so that `scrollback` has the pagination tokens
// available
this.fixUpLegacyTimelineFields();
@ -1020,7 +1009,8 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
public resetLiveTimeline(backPaginationToken: string | null, forwardPaginationToken: string | null): void {
for (let i = 0; i < this.timelineSets.length; i++) {
this.timelineSets[i].resetLiveTimeline(
backPaginationToken, forwardPaginationToken,
backPaginationToken ?? undefined,
forwardPaginationToken ?? undefined,
);
}
@ -1130,7 +1120,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
* @return {?module:models/event-timeline~EventTimeline} timeline containing
* the given event, or null if unknown
*/
public getTimelineForEvent(eventId: string): EventTimeline {
public getTimelineForEvent(eventId: string): EventTimeline | null {
const event = this.findEventById(eventId);
const thread = this.findThreadForEvent(event);
if (thread) {
@ -1712,8 +1702,8 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
this.currentState,
toStartOfTimeline,
);
if (!this.getThread(rootEvent.getId())) {
this.createThread(rootEvent.getId(), rootEvent, [], toStartOfTimeline);
if (!this.getThread(rootEvent.getId()!)) {
this.createThread(rootEvent.getId()!, rootEvent, [], toStartOfTimeline);
}
}
}
@ -1757,14 +1747,14 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
* is only meant as a short term patch
*/
const threadAMetadata = eventA
.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name)!;
const threadBMetadata = eventB
.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name)!;
return threadAMetadata.latest_event.origin_server_ts -
threadBMetadata.latest_event.origin_server_ts;
});
let latestMyThreadsRootEvent: MatrixEvent;
let latestMyThreadsRootEvent: MatrixEvent | undefined;
const roomState = this.getLiveTimeline().getState(EventTimeline.FORWARDS);
for (const rootEvent of threadRoots) {
this.threadsTimelineSets[0].addLiveEvent(rootEvent, {
@ -1870,7 +1860,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
}
// A thread root is always shown in both timelines
if (event.isThreadRoot || roots?.has(event.getId())) {
if (event.isThreadRoot || roots?.has(event.getId()!)) {
return {
shouldLiveInRoom: true,
shouldLiveInThread: true,
@ -1887,7 +1877,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
};
}
const parentEventId = event.getAssociatedId();
const parentEventId = event.getAssociatedId()!;
const parentEvent = this.findEventById(parentEventId) ?? events?.find(e => e.getId() === parentEventId);
// Treat relations and redactions as extensions of their parents so evaluate parentEvent instead
@ -1896,7 +1886,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
}
// Edge case where we know the event is a relation but don't have the parentEvent
if (roots?.has(event.relationEventId)) {
if (roots?.has(event.relationEventId!)) {
return {
shouldLiveInRoom: true,
shouldLiveInThread: true,
@ -1940,10 +1930,10 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
const eventsByThread: { [threadId: string]: MatrixEvent[] } = {};
for (const event of events) {
const { threadId, shouldLiveInThread } = this.eventShouldLiveIn(event);
if (shouldLiveInThread && !eventsByThread[threadId]) {
eventsByThread[threadId] = [];
if (shouldLiveInThread && !eventsByThread[threadId!]) {
eventsByThread[threadId!] = [];
}
eventsByThread[threadId]?.push(event);
eventsByThread[threadId!]?.push(event);
}
Object.entries(eventsByThread).map(([threadId, threadEvents]) => (
@ -1958,7 +1948,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
toStartOfTimeline: boolean,
): Thread {
if (rootEvent) {
const relatedEvents = this.relations.getAllChildEventsForEvent(rootEvent.getId());
const relatedEvents = this.relations.getAllChildEventsForEvent(rootEvent.getId()!);
if (relatedEvents?.length) {
// Include all relations of the root event, given it'll be visible in both timelines,
// except `m.replace` as that will already be applied atop the event using `MatrixEvent::makeReplaced`
@ -2270,14 +2260,14 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
* @private
*/
public handleRemoteEcho(remoteEvent: MatrixEvent, localEvent: MatrixEvent): void {
const oldEventId = localEvent.getId();
const newEventId = remoteEvent.getId();
const oldEventId = localEvent.getId()!;
const newEventId = remoteEvent.getId()!;
const oldStatus = localEvent.status;
logger.debug(`Got remote echo for event ${oldEventId} -> ${newEventId} old status ${oldStatus}`);
// no longer pending
delete this.txnToEvent[remoteEvent.getUnsigned().transaction_id];
delete this.txnToEvent[remoteEvent.getUnsigned().transaction_id!];
// if it's in the pending list, remove it
if (this.pendingEventList) {
@ -2289,7 +2279,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
localEvent.handleRemoteEcho(remoteEvent.event);
const { shouldLiveInRoom, threadId } = this.eventShouldLiveIn(remoteEvent);
const thread = this.getThread(threadId);
const thread = threadId ? this.getThread(threadId) : null;
thread?.timelineSet.handleRemoteEcho(localEvent, oldEventId, newEventId);
if (shouldLiveInRoom) {
@ -2345,7 +2335,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
remoteEvent.setUnsigned(unsigned);
// the remote event is _already_ in the timeline, so we need to remove it so
// we can convert the local event into the final event.
this.removeEvent(remoteEvent.getId());
this.removeEvent(remoteEvent.getId()!);
this.handleRemoteEcho(remoteEvent, event);
}
return;
@ -2353,17 +2343,15 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
}
const oldStatus = event.status;
const oldEventId = event.getId();
const oldEventId = event.getId()!;
if (!oldStatus) {
throw new Error("updatePendingEventStatus called on an event which is " +
"not a local echo.");
throw new Error("updatePendingEventStatus called on an event which is not a local echo.");
}
const allowed = ALLOWED_TRANSITIONS[oldStatus];
if (!allowed || allowed.indexOf(newStatus) < 0) {
throw new Error("Invalid EventStatus transition " + oldStatus + "->" +
newStatus);
if (!allowed?.includes(newStatus)) {
throw new Error(`Invalid EventStatus transition ${oldStatus}->${newStatus}`);
}
event.setStatus(newStatus);
@ -2964,7 +2952,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
}).map((m) => m.name);
}
let oldName: string;
let oldName: string | undefined;
if (leftNames.length) {
oldName = this.roomNameGenerator({
type: RoomNameType.Generated,
@ -3060,8 +3048,8 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
throw new Error("expected a visibility change event");
}
const relation = event.getRelation();
const originalEventId = relation.event_id;
const visibilityEventsOnOriginalEvent = this.visibilityEvents.get(originalEventId);
const originalEventId = relation?.event_id;
const visibilityEventsOnOriginalEvent = this.visibilityEvents.get(originalEventId!);
if (!visibilityEventsOnOriginalEvent) {
// No visibility changes on the original event.
// In particular, this change event was not recorded,
@ -3079,13 +3067,13 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
// If we removed the latest visibility change event, propagate changes.
if (index === visibilityEventsOnOriginalEvent.length) {
const originalEvent = this.findEventById(originalEventId);
const originalEvent = this.findEventById(originalEventId!);
if (!originalEvent) {
return;
}
if (index === 0) {
// We have just removed the only visibility change event.
this.visibilityEvents.delete(originalEventId);
this.visibilityEvents.delete(originalEventId!);
originalEvent.applyVisibilityEvent();
} else {
const newEvent = visibilityEventsOnOriginalEvent[visibilityEventsOnOriginalEvent.length - 1];
@ -3110,7 +3098,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
* change event.
*/
private applyPendingVisibilityEvents(event: MatrixEvent): void {
const visibilityEvents = this.visibilityEvents.get(event.getId());
const visibilityEvents = this.visibilityEvents.get(event.getId()!);
if (!visibilityEvents || visibilityEvents.length == 0) {
// No pending visibility change in store.
return;

View File

@ -222,7 +222,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
}
private addEventToTimeline(event: MatrixEvent, toStartOfTimeline: boolean): void {
if (!this.findEventById(event.getId())) {
if (!this.findEventById(event.getId()!)) {
this.timelineSet.addEventToTimeline(
event,
this.liveTimeline,
@ -305,7 +305,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
this._currentUserParticipated = !!bundledRelationship.current_user_participated;
const event = new MatrixEvent({
room_id: this.rootEvent.getRoomId(),
room_id: this.room.roomId,
...bundledRelationship.latest_event,
});
this.setEventMetadata(event);
@ -322,7 +322,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
private async fetchEditsWhereNeeded(...events: MatrixEvent[]): Promise<unknown> {
return Promise.all(events.filter(e => e.isEncrypted()).map((event: MatrixEvent) => {
if (event.isRelation()) return; // skip - relations don't get edits
return this.client.relations(this.roomId, event.getId(), RelationType.Replace, event.getType(), {
return this.client.relations(this.roomId, event.getId()!, RelationType.Replace, event.getType(), {
limit: 1,
}).then(relations => {
if (relations.events.length) {

View File

@ -302,7 +302,7 @@ export class PushProcessor {
// Note that this should not be the current state of the room but the state at
// the point the event is in the DAG. Unfortunately the js-sdk does not store
// this.
return room.currentState.mayTriggerNotifOfType(notifLevelKey, ev.getSender());
return room.currentState.mayTriggerNotifOfType(notifLevelKey, ev.getSender()!);
}
private eventFulfillsRoomMemberCountCondition(cond: IRoomMemberCountCondition, ev: MatrixEvent): boolean {

View File

@ -208,7 +208,7 @@ export class MSC3906Rendezvous {
await this.send({
type: PayloadType.Finish,
outcome: Outcome.Verified,
verifying_device_id: this.client.getDeviceId(),
verifying_device_id: this.client.getDeviceId()!,
verifying_device_key: this.client.getDeviceEd25519Key()!,
master_key: masterPublicKey,
});

View File

@ -418,7 +418,7 @@ export class SlidingSyncSdk {
// this room, then timeline_limit: 50).
const knownEvents = new Set<string>();
room.getLiveTimeline().getEvents().forEach((e) => {
knownEvents.add(e.getId());
knownEvents.add(e.getId()!);
});
// all unknown events BEFORE a known event must be scrollback e.g:
// D E <-- what we know
@ -433,7 +433,7 @@ export class SlidingSyncSdk {
let seenKnownEvent = false;
for (let i = timelineEvents.length-1; i >= 0; i--) {
const recvEvent = timelineEvents[i];
if (knownEvents.has(recvEvent.getId())) {
if (knownEvents.has(recvEvent.getId()!)) {
seenKnownEvent = true;
continue; // don't include this event, it's a dupe
}

View File

@ -24,6 +24,7 @@ import { IMinimalEvent, IRooms, ISyncResponse } from "../sync-accumulator";
import { IStartClientOpts } from "../client";
import { IStateEventWithRoomId } from "../@types/search";
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage";
import { EventEmitterEvents } from "../models/typed-event-emitter";
export interface ISavedSync {
nextBatch: string;
@ -39,7 +40,7 @@ export interface IStore {
// XXX: The indexeddb store exposes a non-standard emitter for the "degraded" event
// for when it falls back to being a memory store due to errors.
on?: (event: string, handler: (...args: any[]) => void) => void;
on?: (event: EventEmitterEvents | "degraded", handler: (...args: any[]) => void) => void;
/** @return {Promise<boolean>} whether or not the database was newly created in this session. */
isNewlyCreated(): Promise<boolean>;
@ -231,7 +232,7 @@ export interface IStore {
clearOutOfBandMembers(roomId: string): Promise<void>;
getClientOptions(): Promise<IStartClientOpts>;
getClientOptions(): Promise<IStartClientOpts | undefined>;
storeClientOptions(options: IStartClientOpts): Promise<void>;

View File

@ -15,7 +15,7 @@ limitations under the License.
*/
import { ISavedSync } from "./index";
import { IEvent, IStartClientOpts, IStateEventWithRoomId, ISyncResponse } from "../matrix";
import { IEvent, IStateEventWithRoomId, IStoredClientOpts, ISyncResponse } from "../matrix";
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage";
export interface IIndexedDBBackend {
@ -30,8 +30,8 @@ export interface IIndexedDBBackend {
setOutOfBandMembers(roomId: string, membershipEvents: IStateEventWithRoomId[]): Promise<void>;
clearOutOfBandMembers(roomId: string): Promise<void>;
getUserPresenceEvents(): Promise<UserTuple[]>;
getClientOptions(): Promise<IStartClientOpts>;
storeClientOptions(options: IStartClientOpts): Promise<void>;
getClientOptions(): Promise<IStoredClientOpts | undefined>;
storeClientOptions(options: IStoredClientOpts): Promise<void>;
saveToDeviceBatches(batches: ToDeviceBatchWithTxnId[]): Promise<void>;
getOldestToDeviceBatch(): Promise<IndexedToDeviceBatch | null>;
removeToDeviceBatch(id: number): Promise<void>;

View File

@ -18,7 +18,7 @@ import { IMinimalEvent, ISyncData, ISyncResponse, SyncAccumulator } from "../syn
import * as utils from "../utils";
import * as IndexedDBHelpers from "../indexeddb-helpers";
import { logger } from '../logger';
import { IStartClientOpts, IStateEventWithRoomId } from "../matrix";
import { IStateEventWithRoomId, IStoredClientOpts } from "../matrix";
import { ISavedSync } from "./index";
import { IIndexedDBBackend, UserTuple } from "./indexeddb-backend";
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage";
@ -538,7 +538,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
});
}
public getClientOptions(): Promise<IStartClientOpts> {
public getClientOptions(): Promise<IStoredClientOpts | undefined> {
return Promise.resolve().then(() => {
const txn = this.db!.transaction(["client_options"], "readonly");
const store = txn.objectStore("client_options");
@ -548,7 +548,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
});
}
public async storeClientOptions(options: IStartClientOpts): Promise<void> {
public async storeClientOptions(options: IStoredClientOpts): Promise<void> {
const txn = this.db!.transaction(["client_options"], "readwrite");
const store = txn.objectStore("client_options");
store.put({

View File

@ -17,7 +17,7 @@ limitations under the License.
import { logger } from "../logger";
import { defer, IDeferred } from "../utils";
import { ISavedSync } from "./index";
import { IStartClientOpts } from "../client";
import { IStoredClientOpts } from "../client";
import { IStateEventWithRoomId, ISyncResponse } from "../matrix";
import { IIndexedDBBackend, UserTuple } from "./indexeddb-backend";
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage";
@ -118,11 +118,11 @@ export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend {
return this.doCmd('clearOutOfBandMembers', [roomId]);
}
public getClientOptions(): Promise<IStartClientOpts> {
public getClientOptions(): Promise<IStoredClientOpts | undefined> {
return this.doCmd('getClientOptions');
}
public storeClientOptions(options: IStartClientOpts): Promise<void> {
public storeClientOptions(options: IStoredClientOpts): Promise<void> {
return this.doCmd('storeClientOptions', [options]);
}

View File

@ -28,6 +28,7 @@ import { ISyncResponse } from "../sync-accumulator";
import { TypedEventEmitter } from "../models/typed-event-emitter";
import { IStateEventWithRoomId } from "../@types/search";
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage";
import { IStoredClientOpts } from "../client";
/**
* This is an internal module. See {@link IndexedDBStore} for the public class.
@ -269,11 +270,11 @@ export class IndexedDBStore extends MemoryStore {
return this.backend.clearOutOfBandMembers(roomId);
}, "clearOutOfBandMembers");
public getClientOptions = this.degradable((): Promise<object> => {
public getClientOptions = this.degradable((): Promise<IStoredClientOpts | undefined> => {
return this.backend.getClientOptions();
}, "getClientOptions");
public storeClientOptions = this.degradable((options: object): Promise<void> => {
public storeClientOptions = this.degradable((options: IStoredClientOpts): Promise<void> => {
super.storeClientOptions(options);
return this.backend.storeClientOptions(options);
}, "storeClientOptions");

View File

@ -31,6 +31,7 @@ import { RoomSummary } from "../models/room-summary";
import { ISyncResponse } from "../sync-accumulator";
import { IStateEventWithRoomId } from "../@types/search";
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage";
import { IStoredClientOpts } from "../client";
function isValidFilterId(filterId?: string | number | null): boolean {
const isValidStr = typeof filterId === "string" &&
@ -64,7 +65,7 @@ export class MemoryStore implements IStore {
protected readonly localStorage?: Storage;
private oobMembers: Record<string, IStateEventWithRoomId[]> = {}; // roomId: [member events]
private pendingEvents: { [roomId: string]: Partial<IEvent>[] } = {};
private clientOptions = {};
private clientOptions?: IStoredClientOpts;
private pendingToDeviceBatches: IndexedToDeviceBatch[] = [];
private nextToDeviceBatchId = 0;
@ -169,7 +170,7 @@ export class MemoryStore implements IStore {
*/
public getRoomSummaries(): RoomSummary[] {
return Object.values(this.rooms).map(function(room) {
return room.summary;
return room.summary!;
});
}
@ -412,11 +413,11 @@ export class MemoryStore implements IStore {
return Promise.resolve();
}
public getClientOptions(): Promise<object> {
public getClientOptions(): Promise<IStoredClientOpts | undefined> {
return Promise.resolve(this.clientOptions);
}
public storeClientOptions(options: object): Promise<void> {
public storeClientOptions(options: IStoredClientOpts): Promise<void> {
this.clientOptions = Object.assign({}, options);
return Promise.resolve();
}

View File

@ -29,6 +29,7 @@ import { RoomSummary } from "../models/room-summary";
import { ISyncResponse } from "../sync-accumulator";
import { IStateEventWithRoomId } from "../@types/search";
import { IndexedToDeviceBatch, ToDeviceBatch } from "../models/ToDeviceMessage";
import { IStoredClientOpts } from "../client";
/**
* Construct a stub store. This does no-ops on most store methods.
@ -256,11 +257,11 @@ export class StubStore implements IStore {
return Promise.resolve();
}
public getClientOptions(): Promise<object> {
return Promise.resolve({});
public getClientOptions(): Promise<IStoredClientOpts | undefined> {
return Promise.resolve(undefined);
}
public storeClientOptions(options: object): Promise<void> {
public storeClientOptions(options: IStoredClientOpts): Promise<void> {
return Promise.resolve();
}

View File

@ -1081,11 +1081,11 @@ export class SyncApi {
if (Array.isArray(data.presence?.events)) {
data.presence!.events.map(client.getEventMapper()).forEach(
function(presenceEvent) {
let user = client.store.getUser(presenceEvent.getSender());
let user = client.store.getUser(presenceEvent.getSender()!);
if (user) {
user.setPresenceEvent(presenceEvent);
} else {
user = createNewUser(client, presenceEvent.getSender());
user = createNewUser(client, presenceEvent.getSender()!);
user.setPresenceEvent(presenceEvent);
client.store.storeUser(user);
}
@ -1097,7 +1097,7 @@ export class SyncApi {
if (Array.isArray(data.account_data?.events)) {
const events = data.account_data.events.map(client.getEventMapper());
const prevEventsMap = events.reduce((m, c) => {
m[c.getType()] = client.store.getAccountData(c.getType());
m[c.getType()!] = client.store.getAccountData(c.getType());
return m;
}, {});
client.store.storeAccountDataEvents(events);
@ -1111,7 +1111,7 @@ export class SyncApi {
const rules = accountDataEvent.getContent<IPushRules>();
client.pushRules = PushProcessor.rewriteDefaultRules(rules);
}
const prevEvent = prevEventsMap[accountDataEvent.getType()];
const prevEvent = prevEventsMap[accountDataEvent.getType()!];
client.emit(ClientEvent.AccountData, accountDataEvent, prevEvent);
return accountDataEvent;
},
@ -1330,10 +1330,9 @@ export class SyncApi {
// will stop us linking the empty timeline into the chain).
//
for (let i = events.length - 1; i >= 0; i--) {
const eventId = events[i].getId();
const eventId = events[i].getId()!;
if (room.getTimelineForEvent(eventId)) {
debuglog("Already have event " + eventId + " in limited " +
"sync - not resetting");
debuglog(`Already have event ${eventId} in limited sync - not resetting`);
limited = false;
// we might still be missing some of the events before i;

View File

@ -355,7 +355,9 @@ export function globToRegexp(glob: string, extended = false): string {
const replacements: ([RegExp, string | ((substring: string, ...args: any[]) => string) ])[] = [
[/\\\*/g, '.*'],
[/\?/g, '.'],
!extended && [
];
if (!extended) {
replacements.push([
/\\\[(!|)(.*)\\]/g,
(_match: string, neg: string, pat: string) => [
'[',
@ -363,8 +365,8 @@ export function globToRegexp(glob: string, extended = false): string {
pat.replace(/\\-/, '-'),
']',
].join(''),
],
];
]);
}
return replacements.reduce(
// https://github.com/microsoft/TypeScript/issues/30134
(pat, args) => args ? pat.replace(args[0], args[1] as any) : pat,
@ -372,8 +374,11 @@ export function globToRegexp(glob: string, extended = false): string {
);
}
export function ensureNoTrailingSlash(url: string): string {
if (url && url.endsWith("/")) {
export function ensureNoTrailingSlash(url: string): string;
export function ensureNoTrailingSlash(url: undefined): undefined;
export function ensureNoTrailingSlash(url?: string): string | undefined;
export function ensureNoTrailingSlash(url?: string): string | undefined {
if (url?.endsWith("/")) {
return url.slice(0, -1);
} else {
return url;

View File

@ -2134,7 +2134,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
this.direction = CallDirection.Outbound;
// XXX Find a better way to do this
this.client.callEventHandler.calls.set(this.callId, this);
this.client.callEventHandler!.calls.set(this.callId, this);
// make sure we have valid turn creds. Unless something's gone wrong, it should
// poll and keep the credentials valid so this should be instant.

View File

@ -161,7 +161,7 @@ export class CallEventHandler {
logger.info("Current turn creds expire in " + timeUntilTurnCresExpire + " ms");
call = createNewMatrixCall(
this.client,
event.getRoomId(),
event.getRoomId()!,
{ forceTURN: this.client.forceTURN },
) ?? undefined;
if (!call) {
@ -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()) ?? undefined;
call = createNewMatrixCall(this.client, event.getRoomId()!) ?? undefined;
if (call) {
call.callId = content.call_id;
call.initWithHangup(event);

View File

@ -65,14 +65,14 @@ export class MediaHandler {
if (this.userMediaStreams.length === 0) return;
const callMediaStreamParams: Map<string, { audio: boolean, video: boolean }> = new Map();
for (const call of this.client.callEventHandler.calls.values()) {
for (const call of this.client.callEventHandler!.calls.values()) {
callMediaStreamParams.set(call.callId, {
audio: call.hasLocalUserMediaAudioTrack,
video: call.hasLocalUserMediaVideoTrack,
});
}
for (const call of this.client.callEventHandler.calls.values()) {
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)!;