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

View File

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

View File

@ -222,9 +222,9 @@ describe.each([
["IndexedDBCryptoStore", ["IndexedDBCryptoStore",
() => new IndexedDBCryptoStore(global.indexedDB, "tests")], () => new IndexedDBCryptoStore(global.indexedDB, "tests")],
["LocalStorageCryptoStore", ["LocalStorageCryptoStore",
() => new IndexedDBCryptoStore(undefined, "tests")], () => new IndexedDBCryptoStore(undefined!, "tests")],
["MemoryCryptoStore", () => { ["MemoryCryptoStore", () => {
const store = new IndexedDBCryptoStore(undefined, "tests"); const store = new IndexedDBCryptoStore(undefined!, "tests");
// @ts-ignore set private properties // @ts-ignore set private properties
store._backend = new MemoryCryptoStore(); store._backend = new MemoryCryptoStore();
// @ts-ignore // @ts-ignore
@ -255,6 +255,6 @@ describe.each([
expect(nokey).toBeNull(); expect(nokey).toBeNull();
const key = await getCrossSigningKeyCache!("self_signing", ""); 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, "@bob:example.com", BOB_DEVICES,
); );
aliceClient.crypto!.deviceList.downloadKeys = async function(userIds) { aliceClient.crypto!.deviceList.downloadKeys = async function(userIds) {
// @ts-ignore short-circuiting private method
return this.getDevicesFromStore(userIds); return this.getDevicesFromStore(userIds);
}; };

View File

@ -437,6 +437,7 @@ describe("Secrets", function() {
return [keyId, secretStorageKeys[keyId]]; return [keyId, secretStorageKeys[keyId]];
} }
} }
return null;
}, },
}, },
}, },
@ -571,6 +572,7 @@ describe("Secrets", function() {
return [keyId, secretStorageKeys[keyId]]; 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( await ownRequest.channel.handleEvent(
makeRemoteEcho(event), makeRemoteEcho(event),
ownRequest, ownRequest,

View File

@ -45,7 +45,7 @@ describe('EventTimelineSet', () => {
it('should return the related events', () => { it('should return the related events', () => {
eventTimelineSet.relations.aggregateChildEvent(messageEvent); eventTimelineSet.relations.aggregateChildEvent(messageEvent);
const relations = eventTimelineSet.relations.getChildEventsForEvent( const relations = eventTimelineSet.relations.getChildEventsForEvent(
messageEvent.getId(), messageEvent.getId()!,
"m.in_reply_to", "m.in_reply_to",
EventType.RoomMessage, EventType.RoomMessage,
); );
@ -193,7 +193,7 @@ describe('EventTimelineSet', () => {
it('should not return the related events', () => { it('should not return the related events', () => {
eventTimelineSet.relations.aggregateChildEvent(messageEvent); eventTimelineSet.relations.aggregateChildEvent(messageEvent);
const relations = eventTimelineSet.relations.getChildEventsForEvent( const relations = eventTimelineSet.relations.getChildEventsForEvent(
messageEvent.getId(), messageEvent.getId()!,
"m.in_reply_to", "m.in_reply_to",
EventType.RoomMessage, EventType.RoomMessage,
); );
@ -236,7 +236,7 @@ describe('EventTimelineSet', () => {
"m.relates_to": { "m.relates_to": {
"event_id": root.getId(), "event_id": root.getId(),
"m.in_reply_to": { "m.in_reply_to": {
"event_id": root.getId(), "event_id": root.getId()!,
}, },
"rel_type": "m.thread", "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", () => { 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); const eventTimelineSet = new EventTimelineSet(room, {}, client, thread);
messageEvent.setThread(thread); messageEvent.setThread(thread);
expect(eventTimelineSet.canContain(messageEvent)).toBeTruthy(); 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", () => { 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); const eventTimelineSet = new EventTimelineSet(room, {}, client, thread);
messageEvent.setThread(thread); messageEvent.setThread(thread);
const event = mkThreadResponse(messageEvent); const event = mkThreadResponse(messageEvent);
@ -310,7 +310,7 @@ describe('EventTimelineSet', () => {
content: { body: "test" }, content: { body: "test" },
event_id: "!test1:server", 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); expect(eventTimelineSet.getLiveTimeline().getEvents()).toContain(roomMessageEvent);
const roomFilteredEvent = new MatrixEvent({ const roomFilteredEvent = new MatrixEvent({
@ -318,7 +318,7 @@ describe('EventTimelineSet', () => {
content: { body: "test" }, content: { body: "test" },
event_id: "!test2:server", 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); expect(eventTimelineSet.getLiveTimeline().getEvents()).not.toContain(roomFilteredEvent);
}); });
}); });

View File

@ -341,11 +341,11 @@ describe("EventTimeline", function() {
timeline.addEvent(events[1], { toStartOfTimeline: false }); timeline.addEvent(events[1], { toStartOfTimeline: false });
expect(timeline.getEvents().length).toEqual(2); 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(ev).toBe(events[0]);
expect(timeline.getEvents().length).toEqual(1); expect(timeline.getEvents().length).toEqual(1);
ev = timeline.removeEvent(events[1].getId()); ev = timeline.removeEvent(events[1].getId()!);
expect(ev).toBe(events[1]); expect(ev).toBe(events[1]);
expect(timeline.getEvents().length).toEqual(0); expect(timeline.getEvents().length).toEqual(0);
}); });
@ -357,11 +357,11 @@ describe("EventTimeline", function() {
expect(timeline.getEvents().length).toEqual(3); expect(timeline.getEvents().length).toEqual(3);
expect(timeline.getBaseIndex()).toEqual(1); expect(timeline.getBaseIndex()).toEqual(1);
timeline.removeEvent(events[2].getId()); timeline.removeEvent(events[2].getId()!);
expect(timeline.getEvents().length).toEqual(2); expect(timeline.getEvents().length).toEqual(2);
expect(timeline.getBaseIndex()).toEqual(1); expect(timeline.getBaseIndex()).toEqual(1);
timeline.removeEvent(events[1].getId()); timeline.removeEvent(events[1].getId()!);
expect(timeline.getEvents().length).toEqual(1); expect(timeline.getEvents().length).toEqual(1);
expect(timeline.getBaseIndex()).toEqual(0); expect(timeline.getBaseIndex()).toEqual(0);
}); });
@ -372,7 +372,7 @@ describe("EventTimeline", function() {
it("should not make baseIndex assplode when removing the last event", it("should not make baseIndex assplode when removing the last event",
function() { function() {
timeline.addEvent(events[0], { toStartOfTimeline: true }); timeline.addEvent(events[0], { toStartOfTimeline: true });
timeline.removeEvent(events[0].getId()); timeline.removeEvent(events[0].getId()!);
const initialIndex = timeline.getBaseIndex(); const initialIndex = timeline.getBaseIndex();
timeline.addEvent(events[1], { toStartOfTimeline: false }); timeline.addEvent(events[1], { toStartOfTimeline: false });
timeline.addEvent(events[2], { 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); } as MatrixEvent);
const events = [await branch.getFileEvent(), await branch2.getFileEvent(), { const events = [await branch.getFileEvent(), await branch2.getFileEvent(), {
replacingEventId: (): string => null, replacingEventId: (): string | undefined => undefined,
getId: () => "$unknown", getId: () => "$unknown",
}]; }];
staticRoom.getLiveTimeline = () => ({ getEvents: () => events }) as EventTimeline; staticRoom.getLiveTimeline = () => ({ getEvents: () => events }) as EventTimeline;

View File

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

View File

@ -263,7 +263,7 @@ describe('Beacon', () => {
roomId, roomId,
); );
// less than the original event // 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); beacon.update(oldUpdateEvent);
// didnt update // didnt update

View File

@ -115,8 +115,53 @@ describe('MatrixEvent', () => {
}); });
const prom = emitPromise(ev, MatrixEventEvent.VisibilityChange); 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; 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); 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); 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 // flush, as per comment in first test
await flushPromises(); await flushPromises();
@ -164,7 +164,7 @@ describe.each([
], ],
}); });
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0); 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 // Asserting that another request is never made is obviously
// a bit tricky - we just flush the queue what should hopefully // a bit tricky - we just flush the queue what should hopefully
@ -200,7 +200,7 @@ describe.each([
], ],
}); });
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0); await flushAndRunTimersUntil(() => httpBackend.requests.length > 0);
expect(httpBackend.flushSync(null, 1)).toEqual(1); expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
await flushPromises(); await flushPromises();
logger.info("Advancing clock to just before expected retry time..."); logger.info("Advancing clock to just before expected retry time...");
@ -215,7 +215,7 @@ describe.each([
jest.advanceTimersByTime(2000); jest.advanceTimersByTime(2000);
await flushPromises(); await flushPromises();
expect(httpBackend.flushSync(null, 1)).toEqual(1); expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
}); });
it("retries on retryImmediately()", async function() { it("retries on retryImmediately()", async function() {
@ -223,7 +223,7 @@ describe.each([
versions: ["r0.0.1"], 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( httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/", "PUT", "/sendToDevice/org.example.foo/",
@ -239,13 +239,13 @@ describe.each([
FAKE_MSG, FAKE_MSG,
], ],
}); });
expect(await httpBackend.flush(null, 1, 1)).toEqual(1); expect(await httpBackend.flush(undefined, 1, 1)).toEqual(1);
await flushPromises(); await flushPromises();
client.retryImmediately(); client.retryImmediately();
// longer timeout here to try & avoid flakiness // 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() { it("retries on when client is started", async function() {
@ -269,13 +269,13 @@ describe.each([
FAKE_MSG, FAKE_MSG,
], ],
}); });
expect(await httpBackend.flush(null, 1, 1)).toEqual(1); expect(await httpBackend.flush(undefined, 1, 1)).toEqual(1);
await flushPromises(); await flushPromises();
client.stopClient(); client.stopClient();
await Promise.all([client.startClient(), httpBackend.flush("/_matrix/client/versions", 1, 20)]); 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() { it("retries when a message is retried", async function() {
@ -283,7 +283,7 @@ describe.each([
versions: ["r0.0.1"], 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( httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/", "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(); await flushPromises();
const dummyEvent = new MatrixEvent({ const dummyEvent = new MatrixEvent({
@ -311,7 +311,7 @@ describe.each([
} as unknown as Room; } as unknown as Room;
client.resendEvent(dummyEvent, mockRoom); 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() { 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", { "POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
$roomId: ROOM_ID, $roomId: ROOM_ID,
$receiptType: ReceiptType.Read, $receiptType: ReceiptType.Read,
$eventId: threadEvent.getId(), $eventId: threadEvent.getId()!,
}), }),
).check((request) => { ).check((request) => {
expect(request.data.thread_id).toEqual(THREAD_ID); expect(request.data.thread_id).toEqual(THREAD_ID);
@ -115,7 +115,7 @@ describe("Read receipt", () => {
"POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", { "POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
$roomId: ROOM_ID, $roomId: ROOM_ID,
$receiptType: ReceiptType.Read, $receiptType: ReceiptType.Read,
$eventId: roomEvent.getId(), $eventId: roomEvent.getId()!,
}), }),
).check((request) => { ).check((request) => {
expect(request.data.thread_id).toEqual(MAIN_ROOM_TIMELINE); expect(request.data.thread_id).toEqual(MAIN_ROOM_TIMELINE);
@ -133,7 +133,7 @@ describe("Read receipt", () => {
"POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", { "POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
$roomId: ROOM_ID, $roomId: ROOM_ID,
$receiptType: ReceiptType.Read, $receiptType: ReceiptType.Read,
$eventId: threadEvent.getId(), $eventId: threadEvent.getId()!,
}), }),
).check((request) => { ).check((request) => {
expect(request.data.thread_id).toBeUndefined(); expect(request.data.thread_id).toBeUndefined();
@ -151,7 +151,7 @@ describe("Read receipt", () => {
"POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", { "POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
$roomId: ROOM_ID, $roomId: ROOM_ID,
$receiptType: ReceiptType.Read, $receiptType: ReceiptType.Read,
$eventId: threadEvent.getId(), $eventId: threadEvent.getId()!,
}), }),
).check((request) => { ).check((request) => {
expect(request.data).toEqual({}); 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"; import * as callbacks from "../../src/realtime-callbacks";
let wallTime = 1234567890; let wallTime = 1234567890;
@ -37,7 +53,7 @@ describe("realtime-callbacks", function() {
it("should set 'this' to the global object", function() { it("should set 'this' to the global object", function() {
let passed = false; 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).toBe(global); // eslint-disable-line @typescript-eslint/no-invalid-this
expect(this.console).toBeTruthy(); // eslint-disable-line @typescript-eslint/no-invalid-this expect(this.console).toBeTruthy(); // eslint-disable-line @typescript-eslint/no-invalid-this
passed = true; passed = true;

View File

@ -22,7 +22,7 @@ import { TestClient } from "../TestClient";
describe("Relations", function() { describe("Relations", function() {
it("should deduplicate annotations", 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); const relations = new Relations("m.annotation", "m.reaction", room);
// Create an instance of an annotation // Create an instance of an annotation
@ -99,7 +99,7 @@ describe("Relations", function() {
// Add the target event first, then the relation event // 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 => { const relationsCreated = new Promise(resolve => {
targetEvent.once(MatrixEventEvent.RelationsCreated, resolve); targetEvent.once(MatrixEventEvent.RelationsCreated, resolve);
}); });
@ -113,7 +113,7 @@ describe("Relations", function() {
// Add the relation event first, then the target event // 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 => { const relationsCreated = new Promise(resolve => {
targetEvent.once(MatrixEventEvent.RelationsCreated, 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 () => { 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 timelineSet1 = new EventTimelineSet(room);
const timelineSet2 = new EventTimelineSet(room); const timelineSet2 = new EventTimelineSet(room);
expect(room.relations).toBe(timelineSet1.relations); expect(room.relations).toBe(timelineSet1.relations);
@ -136,7 +136,7 @@ describe("Relations", function() {
it("should ignore m.replace for state events", async () => { it("should ignore m.replace for state events", async () => {
const userId = "@bob:example.com"; 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); const relations = new Relations("m.replace", "m.room.topic", room);
// Create an instance of a state event with rel_type m.replace // 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) { state.on(RoomStateEvent.Members, function(ev, st, mem) {
expect(ev).toEqual(memberEvents[emitCount]); expect(ev).toEqual(memberEvents[emitCount]);
expect(st).toEqual(state); expect(st).toEqual(state);
expect(mem).toEqual(state.getMember(ev.getSender())); expect(mem).toEqual(state.getMember(ev.getSender()!));
emitCount += 1; emitCount += 1;
}); });
state.setStateEvents(memberEvents); state.setStateEvents(memberEvents);

View File

@ -24,7 +24,7 @@ import {
DuplicateStrategy, DuplicateStrategy,
EventStatus, EventStatus,
EventTimelineSet, EventTimelineSet,
EventType, EventType, IStateEventWithRoomId,
JoinRule, JoinRule,
MatrixEvent, MatrixEvent,
MatrixEventEvent, MatrixEventEvent,
@ -66,7 +66,7 @@ describe("Room", function() {
"body": "Reply :: " + Math.random(), "body": "Reply :: " + Math.random(),
"m.relates_to": { "m.relates_to": {
"m.in_reply_to": { "m.in_reply_to": {
"event_id": target.getId(), "event_id": target.getId()!,
}, },
}, },
}, },
@ -84,7 +84,7 @@ describe("Room", function() {
}, },
"m.relates_to": { "m.relates_to": {
rel_type: RelationType.Replace, rel_type: RelationType.Replace,
event_id: target.getId(), event_id: target.getId()!,
}, },
}, },
}, room.client); }, room.client);
@ -97,9 +97,9 @@ describe("Room", function() {
content: { content: {
"body": "Thread response :: " + Math.random(), "body": "Thread response :: " + Math.random(),
"m.relates_to": { "m.relates_to": {
"event_id": root.getId(), "event_id": root.getId()!,
"m.in_reply_to": { "m.in_reply_to": {
"event_id": root.getId(), "event_id": root.getId()!,
}, },
"rel_type": "m.thread", "rel_type": "m.thread",
}, },
@ -114,7 +114,7 @@ describe("Room", function() {
content: { content: {
"m.relates_to": { "m.relates_to": {
"rel_type": RelationType.Annotation, "rel_type": RelationType.Annotation,
"event_id": target.getId(), "event_id": target.getId()!,
"key": Math.random().toString(), "key": Math.random().toString(),
}, },
}, },
@ -125,7 +125,7 @@ describe("Room", function() {
type: EventType.RoomRedaction, type: EventType.RoomRedaction,
user: userA, user: userA,
room: roomId, room: roomId,
redacts: target.getId(), redacts: target.getId()!,
content: {}, content: {},
}, room.client); }, room.client);
@ -722,13 +722,13 @@ describe("Room", function() {
it("should handle events in the same timeline", function() { it("should handle events in the same timeline", function() {
room.addLiveEvents(events); room.addLiveEvents(events);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId(), expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!,
events[1].getId())) events[1].getId()))
.toBeLessThan(0); .toBeLessThan(0);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[2].getId(), expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[2].getId()!,
events[1].getId())) events[1].getId()))
.toBeGreaterThan(0); .toBeGreaterThan(0);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId(), expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!,
events[1].getId())) events[1].getId()))
.toEqual(0); .toEqual(0);
}); });
@ -741,10 +741,10 @@ describe("Room", function() {
room.addEventsToTimeline([events[0]], false, oldTimeline); room.addEventsToTimeline([events[0]], false, oldTimeline);
room.addLiveEvents([events[1]]); room.addLiveEvents([events[1]]);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId(), expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!,
events[1].getId())) events[1].getId()))
.toBeLessThan(0); .toBeLessThan(0);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId(), expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!,
events[0].getId())) events[0].getId()))
.toBeGreaterThan(0); .toBeGreaterThan(0);
}); });
@ -755,10 +755,10 @@ describe("Room", function() {
room.addEventsToTimeline([events[0]], false, oldTimeline); room.addEventsToTimeline([events[0]], false, oldTimeline);
room.addLiveEvents([events[1]]); room.addLiveEvents([events[1]]);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId(), expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!,
events[1].getId())) events[1].getId()))
.toBe(null); .toBe(null);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId(), expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!,
events[0].getId())) events[0].getId()))
.toBe(null); .toBe(null);
}); });
@ -767,13 +767,13 @@ describe("Room", function() {
room.addLiveEvents(events); room.addLiveEvents(events);
expect(room.getUnfilteredTimelineSet() expect(room.getUnfilteredTimelineSet()
.compareEventOrdering(events[0].getId(), "xxx")) .compareEventOrdering(events[0].getId()!, "xxx"))
.toBe(null); .toBe(null);
expect(room.getUnfilteredTimelineSet() expect(room.getUnfilteredTimelineSet()
.compareEventOrdering("xxx", events[0].getId())) .compareEventOrdering("xxx", events[0].getId()))
.toBe(null); .toBe(null);
expect(room.getUnfilteredTimelineSet() expect(room.getUnfilteredTimelineSet()
.compareEventOrdering(events[0].getId(), events[0].getId())) .compareEventOrdering(events[0].getId()!, events[0].getId()))
.toBe(0); .toBe(0);
}); });
}); });
@ -1228,7 +1228,7 @@ describe("Room", function() {
it("should store the receipt so it can be obtained via getReceiptsForEvent", function() { it("should store the receipt so it can be obtained via getReceiptsForEvent", function() {
const ts = 13787898424; const ts = 13787898424;
room.addReceipt(mkReceipt(roomId, [ room.addReceipt(mkReceipt(roomId, [
mkRecord(eventToAck.getId(), "m.read", userB, ts), mkRecord(eventToAck.getId()!, "m.read", userB, ts),
])); ]));
expect(room.getReceiptsForEvent(eventToAck)).toEqual([{ expect(room.getReceiptsForEvent(eventToAck)).toEqual([{
type: "m.read", type: "m.read",
@ -1247,7 +1247,7 @@ describe("Room", function() {
const ts = 13787898424; const ts = 13787898424;
const receiptEvent = mkReceipt(roomId, [ const receiptEvent = mkReceipt(roomId, [
mkRecord(eventToAck.getId(), "m.read", userB, ts), mkRecord(eventToAck.getId()!, "m.read", userB, ts),
]); ]);
room.addReceipt(receiptEvent); room.addReceipt(receiptEvent);
@ -1261,11 +1261,11 @@ describe("Room", function() {
}); });
const ts = 13787898424; const ts = 13787898424;
room.addReceipt(mkReceipt(roomId, [ room.addReceipt(mkReceipt(roomId, [
mkRecord(eventToAck.getId(), "m.read", userB, ts), mkRecord(eventToAck.getId()!, "m.read", userB, ts),
])); ]));
const ts2 = 13787899999; const ts2 = 13787899999;
room.addReceipt(mkReceipt(roomId, [ 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(eventToAck)).toEqual([]);
expect(room.getReceiptsForEvent(nextEventToAck)).toEqual([{ expect(room.getReceiptsForEvent(nextEventToAck)).toEqual([{
@ -1280,9 +1280,9 @@ describe("Room", function() {
it("should persist multiple receipts for a single event ID", function() { it("should persist multiple receipts for a single event ID", function() {
const ts = 13787898424; const ts = 13787898424;
room.addReceipt(mkReceipt(roomId, [ room.addReceipt(mkReceipt(roomId, [
mkRecord(eventToAck.getId(), "m.read", userB, ts), mkRecord(eventToAck.getId()!, "m.read", userB, ts),
mkRecord(eventToAck.getId(), "m.read", userC, ts), mkRecord(eventToAck.getId()!, "m.read", userC, ts),
mkRecord(eventToAck.getId(), "m.read", userD, ts), mkRecord(eventToAck.getId()!, "m.read", userD, ts),
])); ]));
expect(room.getUsersReadUpTo(eventToAck)).toEqual( expect(room.getUsersReadUpTo(eventToAck)).toEqual(
[userB, userC, userD], [userB, userC, userD],
@ -1300,9 +1300,9 @@ describe("Room", function() {
}); });
const ts = 13787898424; const ts = 13787898424;
room.addReceipt(mkReceipt(roomId, [ room.addReceipt(mkReceipt(roomId, [
mkRecord(eventToAck.getId(), "m.read", userB, ts), mkRecord(eventToAck.getId()!, "m.read", userB, ts),
mkRecord(eventTwo.getId(), "m.read", userC, ts), mkRecord(eventTwo.getId()!, "m.read", userC, ts),
mkRecord(eventThree.getId(), "m.read", userD, ts), mkRecord(eventThree.getId()!, "m.read", userD, ts),
])); ]));
expect(room.getUsersReadUpTo(eventToAck)).toEqual([userB]); expect(room.getUsersReadUpTo(eventToAck)).toEqual([userB]);
expect(room.getUsersReadUpTo(eventTwo)).toEqual([userC]); expect(room.getUsersReadUpTo(eventTwo)).toEqual([userC]);
@ -1311,9 +1311,9 @@ describe("Room", function() {
it("should persist multiple receipts for a single user ID", function() { it("should persist multiple receipts for a single user ID", function() {
room.addReceipt(mkReceipt(roomId, [ room.addReceipt(mkReceipt(roomId, [
mkRecord(eventToAck.getId(), "m.delivered", userB, 13787898424), mkRecord(eventToAck.getId()!, "m.delivered", userB, 13787898424),
mkRecord(eventToAck.getId(), "m.read", userB, 22222222), mkRecord(eventToAck.getId()!, "m.read", userB, 22222222),
mkRecord(eventToAck.getId(), "m.seen", userB, 33333333), mkRecord(eventToAck.getId()!, "m.seen", userB, 33333333),
])); ]));
expect(room.getReceiptsForEvent(eventToAck)).toEqual([ expect(room.getReceiptsForEvent(eventToAck)).toEqual([
{ {
@ -1361,19 +1361,19 @@ describe("Room", function() {
// check it initialises correctly // check it initialises correctly
room.addReceipt(mkReceipt(roomId, [ 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()); expect(room.getEventReadUpTo(userB)).toEqual(events[0].getId());
// 2>0, so it should move forward // 2>0, so it should move forward
room.addReceipt(mkReceipt(roomId, [ 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()); expect(room.getEventReadUpTo(userB)).toEqual(events[2].getId());
// 1<2, so it should stay put // 1<2, so it should stay put
room.addReceipt(mkReceipt(roomId, [ 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)).toEqual(events[2].getId());
}); });
@ -1399,13 +1399,13 @@ describe("Room", function() {
// check it initialises correctly // check it initialises correctly
room.addReceipt(mkReceipt(roomId, [ 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()); expect(room.getEventReadUpTo(userB)).toEqual(events[0].getId());
// 2>0, so it should move forward // 2>0, so it should move forward
room.addReceipt(mkReceipt(roomId, [ room.addReceipt(mkReceipt(roomId, [
mkRecord(events[2].getId(), "m.read", userB, ts), mkRecord(events[2].getId()!, "m.read", userB, ts),
]), true); ]), true);
expect(room.getEventReadUpTo(userB)).toEqual(events[2].getId()); expect(room.getEventReadUpTo(userB)).toEqual(events[2].getId());
expect(room.getReceiptsForEvent(events[2])).toEqual([ expect(room.getReceiptsForEvent(events[2])).toEqual([
@ -1414,7 +1414,7 @@ describe("Room", function() {
// 1<2, so it should stay put // 1<2, so it should stay put
room.addReceipt(mkReceipt(roomId, [ 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)).toEqual(events[2].getId());
expect(room.getEventReadUpTo(userB, true)).toEqual(events[1].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() { it("should return user IDs read up to the given event", function() {
const ts = 13787898424; const ts = 13787898424;
room.addReceipt(mkReceipt(roomId, [ room.addReceipt(mkReceipt(roomId, [
mkRecord(eventToAck.getId(), "m.read", userB, ts), mkRecord(eventToAck.getId()!, "m.read", userB, ts),
])); ]));
expect(room.getUsersReadUpTo(eventToAck)).toEqual([userB]); expect(room.getUsersReadUpTo(eventToAck)).toEqual([userB]);
}); });
@ -1438,9 +1438,9 @@ describe("Room", function() {
it("should acknowledge if an event has been read", function() { it("should acknowledge if an event has been read", function() {
const ts = 13787898424; const ts = 13787898424;
room.addReceipt(mkReceipt(roomId, [ 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() { it("return false for an unknown event", function() {
expect(room.hasUserReadEvent(userB, "unknown_event")).toEqual(false); expect(room.hasUserReadEvent(userB, "unknown_event")).toEqual(false);
@ -1556,7 +1556,7 @@ describe("Room", function() {
user: userA, user: userA,
type: EventType.RoomRedaction, type: EventType.RoomRedaction,
content: {}, content: {},
redacts: eventA.getId(), redacts: eventA.getId()!,
event: true, event: true,
}); });
redactA.status = EventStatus.SENDING; redactA.status = EventStatus.SENDING;
@ -1609,7 +1609,7 @@ describe("Room", function() {
}); });
it("should remove cancelled events from the timeline", 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({ const eventA = utils.mkMessage({
room: roomId, user: userA, event: true, room: roomId, user: userA, event: true,
}); });
@ -1643,7 +1643,7 @@ describe("Room", function() {
}); });
describe("loadMembersIfNeeded", function() { describe("loadMembersIfNeeded", function() {
function createClientMock(serverResponse, storageResponse = null) { function createClientMock(serverResponse, storageResponse: MatrixEvent[] | Error | null = null) {
return { return {
getEventMapper: function() { getEventMapper: function() {
// events should already be MatrixEvents // events should already be MatrixEvents
@ -1664,7 +1664,7 @@ describe("Room", function() {
}), }),
store: { store: {
storageResponse, storageResponse,
storedMembers: null, storedMembers: [] as IStateEventWithRoomId[] | null,
getOutOfBandMembers: function() { getOutOfBandMembers: function() {
if (this.storageResponse instanceof Error) { if (this.storageResponse instanceof Error) {
return Promise.reject(this.storageResponse); return Promise.reject(this.storageResponse);
@ -1693,11 +1693,11 @@ describe("Room", function() {
it("should load members from server on first call", async function() { it("should load members from server on first call", async function() {
const client = createClientMock([memberEvent]); 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(); await room.loadMembersIfNeeded();
const memberA = room.getMember("@user_a:bar"); const memberA = room.getMember("@user_a:bar")!;
expect(memberA.name).toEqual("User A"); expect(memberA.name).toEqual("User A");
const storedMembers = client.store.storedMembers; const storedMembers = client.store.storedMembers!;
expect(storedMembers.length).toEqual(1); expect(storedMembers.length).toEqual(1);
expect(storedMembers[0].event_id).toEqual(memberEvent.getId()); expect(storedMembers[0].event_id).toEqual(memberEvent.getId());
}); });
@ -1711,17 +1711,17 @@ describe("Room", function() {
name: "Ms A", name: "Ms A",
}); });
const client = createClientMock([memberEvent2], [memberEvent]); 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(); await room.loadMembersIfNeeded();
const memberA = room.getMember("@user_a:bar"); const memberA = room.getMember("@user_a:bar")!;
expect(memberA.name).toEqual("User A"); expect(memberA.name).toEqual("User A");
}); });
it("should allow retry on error", async function() { it("should allow retry on error", async function() {
const client = createClientMock(new Error("server says no")); 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; let hasThrown = false;
try { try {
await room.loadMembersIfNeeded(); await room.loadMembersIfNeeded();
@ -1740,27 +1740,68 @@ describe("Room", function() {
describe("getMyMembership", function() { describe("getMyMembership", function() {
it("should return synced membership if membership isn't available yet", it("should return synced membership if membership isn't available yet",
function() { function() {
const room = new Room(roomId, null, userA); const room = new Room(roomId, null!, userA);
room.updateMyMembership(JoinRule.Invite); room.updateMyMembership(JoinRule.Invite);
expect(room.getMyMembership()).toEqual(JoinRule.Invite); expect(room.getMyMembership()).toEqual(JoinRule.Invite);
}); });
it("should emit a Room.myMembership event on a change", it("should emit a Room.myMembership event on a change", function() {
function() { const room = new Room(roomId, null!, userA);
const room = new Room(roomId, null, userA); const events: {
const events = []; membership: string;
room.on(RoomEvent.MyMembership, (_room, membership, oldMembership) => { oldMembership?: string;
events.push({ membership, oldMembership }); }[] = [];
}); room.on(RoomEvent.MyMembership, (_room, membership, oldMembership) => {
room.updateMyMembership(JoinRule.Invite); events.push({ membership, oldMembership });
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" });
}); });
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() { 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() { describe("maySendMessage", function() {
it("should return false if synced membership not join", function() { it("should return false if synced membership not join", function() {
const room = new Room(roomId, { isRoomEncrypted: () => false } as any, userA); 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", () => { 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 threadReaction2 = mkReaction(threadRoot);
const threadReaction2Redaction = mkRedaction(threadReaction2); const threadReaction2Redaction = mkRedaction(threadReaction2);
const roots = new Set([threadRoot.getId()]); const roots = new Set([threadRoot.getId()!]);
const events = [ const events = [
randomMessage, randomMessage,
threadRoot, threadRoot,
@ -2377,7 +2448,7 @@ describe("Room", function() {
const threadReaction2 = mkReaction(threadResponse1); const threadReaction2 = mkReaction(threadResponse1);
const threadReaction2Redaction = mkRedaction(threadReaction2); const threadReaction2Redaction = mkRedaction(threadReaction2);
const roots = new Set([threadRoot.getId()]); const roots = new Set([threadRoot.getId()!]);
const events = [threadRoot, threadResponse1, threadReaction1, threadReaction2, threadReaction2Redaction]; const events = [threadRoot, threadResponse1, threadReaction1, threadReaction2, threadReaction2Redaction];
expect(room.eventShouldLiveIn(threadReaction1, events, roots).shouldLiveInRoom).toBeFalsy(); expect(room.eventShouldLiveIn(threadReaction1, events, roots).shouldLiveInRoom).toBeFalsy();
@ -2399,7 +2470,7 @@ describe("Room", function() {
const reaction2 = mkReaction(reply1); const reaction2 = mkReaction(reply1);
const reaction2Redaction = mkRedaction(reply1); const reaction2Redaction = mkRedaction(reply1);
const roots = new Set([threadRoot.getId()]); const roots = new Set([threadRoot.getId()!]);
const events = [ const events = [
threadRoot, threadRoot,
threadResponse1, threadResponse1,
@ -2425,7 +2496,7 @@ describe("Room", function() {
const reply1 = mkReply(threadRoot); const reply1 = mkReply(threadRoot);
const reply2 = mkReply(reply1); const reply2 = mkReply(reply1);
const roots = new Set([threadRoot.getId()]); const roots = new Set([threadRoot.getId()!]);
const events = [ const events = [
threadRoot, threadRoot,
threadResponse1, threadResponse1,
@ -2459,7 +2530,7 @@ describe("Room", function() {
expect(thread.rootEvent).toBe(threadRoot); expect(thread.rootEvent).toBe(threadRoot);
const rootRelations = thread.timelineSet.relations.getChildEventsForEvent( const rootRelations = thread.timelineSet.relations.getChildEventsForEvent(
threadRoot.getId(), threadRoot.getId()!,
RelationType.Annotation, RelationType.Annotation,
EventType.Reaction, EventType.Reaction,
)!.getSortedAnnotationsByKey(); )!.getSortedAnnotationsByKey();
@ -2469,7 +2540,7 @@ describe("Room", function() {
expect(rootRelations![0][1].has(rootReaction)).toBeTruthy(); expect(rootRelations![0][1].has(rootReaction)).toBeTruthy();
const responseRelations = thread.timelineSet.relations.getChildEventsForEvent( const responseRelations = thread.timelineSet.relations.getChildEventsForEvent(
threadResponse.getId(), threadResponse.getId()!,
RelationType.Annotation, RelationType.Annotation,
EventType.Reaction, EventType.Reaction,
)!.getSortedAnnotationsByKey(); )!.getSortedAnnotationsByKey();
@ -2744,7 +2815,7 @@ describe("Room", function() {
expect(pendingEvents[1].isBeingDecrypted()).toBeFalsy(); expect(pendingEvents[1].isBeingDecrypted()).toBeFalsy();
expect(pendingEvents[1].isEncrypted()).toBeTruthy(); expect(pendingEvents[1].isEncrypted()).toBeTruthy();
for (const ev of pendingEvents) { 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 eventD = utils.mkMessage({ user: "@b:bar", room: roomId, event: true });
const buckets = {}; const buckets = {};
buckets[eventA.getId()] = "queue_A"; buckets[eventA.getId()!] = "queue_A";
buckets[eventD.getId()] = "queue_A"; buckets[eventD.getId()!] = "queue_A";
buckets[eventB.getId()] = "queue_B"; buckets[eventB.getId()!] = "queue_B";
buckets[eventC.getId()] = "queue_B"; buckets[eventC.getId()!] = "queue_B";
retryFn = function() { retryFn = function() {
return 0; 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 if (!reEmittersByEvent) return; // We were never re-emitting these events in the first place
for (const eventName of eventNames) { for (const eventName of eventNames) {
source.off(eventName, reEmittersByEvent.get(eventName)); source.off(eventName, reEmittersByEvent.get(eventName)!);
reEmittersByEvent.delete(eventName); reEmittersByEvent.delete(eventName);
} }

View File

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

View File

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

View File

@ -44,8 +44,8 @@ function publicKeyFromKeyInfo(keyInfo: ICrossSigningKey): string {
} }
export interface ICacheCallbacks { export interface ICacheCallbacks {
getCrossSigningKeyCache?(type: string, expectedPublicKey?: string): Promise<Uint8Array>; getCrossSigningKeyCache?(type: string, expectedPublicKey?: string): Promise<Uint8Array | null>;
storeCrossSigningKeyCache?(type: string, key: Uint8Array): Promise<void>; storeCrossSigningKeyCache?(type: string, key?: Uint8Array): Promise<void>;
} }
export interface ICrossSigningInfo { export interface ICrossSigningInfo {
@ -169,7 +169,9 @@ export class CrossSigningInfo {
* with, or null if it is not present or not encrypted with a trusted * with, or null if it is not present or not encrypted with a trusted
* key * 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) // check what SSSS keys have encrypted the master key (if any)
const stored = await secretStorage.isStored("m.cross_signing.master") || {}; const stored = await secretStorage.isStored("m.cross_signing.master") || {};
// then check which of those SSSS keys have also encrypted the SSK and USK // 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( public static async storeInSecretStorage(
keys: Map<string, Uint8Array>, keys: Map<string, Uint8Array>,
secretStorage: SecretStorage, secretStorage: SecretStorage<undefined>,
): Promise<void> { ): Promise<void> {
for (const [type, privateKey] of keys) { for (const [type, privateKey] of keys) {
const encodedKey = encodeBase64(privateKey); const encodedKey = encodeBase64(privateKey);
@ -433,10 +435,9 @@ export class CrossSigningInfo {
// if everything checks out, then save the keys // if everything checks out, then save the keys
if (keys.master) { if (keys.master) {
this.keys.master = keys.master; this.keys.master = keys.master;
// if the master key is set, then the old self-signing and // if the master key is set, then the old self-signing and user-signing keys are obsolete
// user-signing keys are obsolete delete this.keys["self_signing"];
this.keys.self_signing = null; delete this.keys["user_signing"];
this.keys.user_signing = null;
} }
if (keys.self_signing) { if (keys.self_signing) {
this.keys.self_signing = keys.self_signing; this.keys.self_signing = keys.self_signing;
@ -723,7 +724,7 @@ export function createCryptoStoreCacheCallbacks(store: CryptoStore, olmDevice: O
}, },
storeCrossSigningKeyCache: async function( storeCrossSigningKeyCache: async function(
type: keyof SecretStorePrivateKeys, type: keyof SecretStorePrivateKeys,
key: Uint8Array, key?: Uint8Array,
): Promise<void> { ): Promise<void> {
if (!(key instanceof Uint8Array)) { if (!(key instanceof Uint8Array)) {
throw new Error( throw new Error(

View File

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

View File

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

View File

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

View File

@ -75,10 +75,26 @@ export enum RoomKeyRequestState {
CancellationPendingAndWillResend, 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 { export class OutgoingRoomKeyRequestManager {
// handle for the delayed call to sendOutgoingRoomKeyRequests. Non-null // handle for the delayed call to sendOutgoingRoomKeyRequests. Non-null
// if the callback has been set, or if it is still running. // 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 // sanity check to ensure that we don't end up with two concurrent runs
// of sendOutgoingRoomKeyRequests // of sendOutgoingRoomKeyRequests
@ -369,43 +385,42 @@ export class OutgoingRoomKeyRequestManager {
// look for and send any queued requests. Runs itself recursively until // 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 // there are no more requests, or there is an error (in which case, the
// timer will be restarted before the promise resolves). // timer will be restarted before the promise resolves).
private sendOutgoingRoomKeyRequests(): Promise<void> { private async sendOutgoingRoomKeyRequests(): Promise<void> {
if (!this.clientRunning) { if (!this.clientRunning) {
this.sendOutgoingRoomKeyRequestsTimer = null; this.sendOutgoingRoomKeyRequestsTimer = undefined;
return Promise.resolve(); return;
} }
return this.cryptoStore.getOutgoingRoomKeyRequestByState([ const req = await this.cryptoStore.getOutgoingRoomKeyRequestByState([
RoomKeyRequestState.CancellationPending, RoomKeyRequestState.CancellationPending,
RoomKeyRequestState.CancellationPendingAndWillResend, RoomKeyRequestState.CancellationPendingAndWillResend,
RoomKeyRequestState.Unsent, RoomKeyRequestState.Unsent,
]).then((req: OutgoingRoomKeyRequest) => { ]);
if (!req) {
this.sendOutgoingRoomKeyRequestsTimer = null;
return;
}
let prom; if (!req) {
this.sendOutgoingRoomKeyRequestsTimer = undefined;
return;
}
try {
switch (req.state) { switch (req.state) {
case RoomKeyRequestState.Unsent: case RoomKeyRequestState.Unsent:
prom = this.sendOutgoingRoomKeyRequest(req); await this.sendOutgoingRoomKeyRequest(req);
break; break;
case RoomKeyRequestState.CancellationPending: case RoomKeyRequestState.CancellationPending:
prom = this.sendOutgoingRoomKeyRequestCancellation(req); await this.sendOutgoingRoomKeyRequestCancellation(req);
break; break;
case RoomKeyRequestState.CancellationPendingAndWillResend: case RoomKeyRequestState.CancellationPendingAndWillResend:
prom = this.sendOutgoingRoomKeyRequestCancellation(req, true); await this.sendOutgoingRoomKeyRequestCancellation(req, true);
break; break;
} }
return prom.then(() => { // go around the loop again
// go around the loop again return this.sendOutgoingRoomKeyRequests();
return this.sendOutgoingRoomKeyRequests(); } catch (e) {
}).catch((e) => { logger.error("Error sending room key request; will retry later.", e);
logger.error("Error sending room key request; will retry later.", e); this.sendOutgoingRoomKeyRequestsTimer = undefined;
this.sendOutgoingRoomKeyRequestsTimer = null; }
});
});
} }
// given a RoomKeyRequest, send it and update the request record // given a RoomKeyRequest, send it and update the request record
@ -416,16 +431,14 @@ export class OutgoingRoomKeyRequestManager {
`(id ${req.requestId})`, `(id ${req.requestId})`,
); );
const requestMessage = { const requestMessage: RequestMessage = {
action: "request", action: "request",
requesting_device_id: this.deviceId, requesting_device_id: this.deviceId,
request_id: req.requestId, request_id: req.requestId,
body: req.requestBody, body: req.requestBody,
}; };
return this.sendMessageToDevices( return this.sendMessageToDevices(requestMessage, req.recipients, req.requestTxnId || req.requestId).then(() => {
requestMessage, req.recipients, req.requestTxnId || req.requestId,
).then(() => {
return this.cryptoStore.updateOutgoingRoomKeyRequest( return this.cryptoStore.updateOutgoingRoomKeyRequest(
req.requestId, RoomKeyRequestState.Unsent, req.requestId, RoomKeyRequestState.Unsent,
{ state: RoomKeyRequestState.Sent }, { state: RoomKeyRequestState.Sent },
@ -443,7 +456,7 @@ export class OutgoingRoomKeyRequestManager {
`(cancellation id ${req.cancellationTxnId})`, `(cancellation id ${req.cancellationTxnId})`,
); );
const requestMessage = { const requestMessage: RequestMessage = {
action: "request_cancellation", action: "request_cancellation",
requesting_device_id: this.deviceId, requesting_device_id: this.deviceId,
request_id: req.requestId, request_id: req.requestId,
@ -467,7 +480,11 @@ export class OutgoingRoomKeyRequestManager {
} }
// send a RoomKeyRequest to a list of recipients // 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>>> = {}; const contentMap: Record<string, Record<string, Record<string, any>>> = {};
for (const recip of recipients) { for (const recip of recipients) {
if (!contentMap[recip.userId]) { 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 // we assume that the request is for megolm keys, which are identified by
// room id and session id // room id and session id
return requestBody.room_id + " / " + requestBody.session_id; return requestBody.room_id + " / " + requestBody.session_id;
} }
function stringifyRecipientList(recipients) { function stringifyRecipientList(recipients: IRoomKeyRequestRecipient[]): string {
return '[' return `[${recipients.map((r) => `${r.userId}:${r.deviceId}`).join(",")}]`;
+ 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) // Object of roomId -> room e2e info object (body of the m.room.encryption event)
private roomEncryption: Record<string, IRoomEncryption> = {}; private roomEncryption: Record<string, IRoomEncryption> = {};
constructor(private readonly cryptoStore: CryptoStore) {} constructor(private readonly cryptoStore?: CryptoStore) {}
public async init(): Promise<void> { public async init(): Promise<void> {
await this.cryptoStore.doTxn( await this.cryptoStore!.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => { 'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
this.cryptoStore.getEndToEndRooms(txn, (result) => { this.cryptoStore!.getEndToEndRooms(txn, (result) => {
this.roomEncryption = result; this.roomEncryption = result;
}); });
}, },
@ -63,9 +63,9 @@ export class RoomList {
// as it prevents the Crypto::setRoomEncryption from calling // as it prevents the Crypto::setRoomEncryption from calling
// this twice for consecutive m.room.encryption events // this twice for consecutive m.room.encryption events
this.roomEncryption[roomId] = roomInfo; this.roomEncryption[roomId] = roomInfo;
await this.cryptoStore.doTxn( await this.cryptoStore!.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => { '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 { encodeBase64 } from './olmlib';
import { randomString } from '../randomstring'; import { randomString } from '../randomstring';
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from './aes'; 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 { ClientEventHandlerMap, MatrixClient } from "../client";
import { IAddSecretStorageKeyOpts, ISecretStorageKeyInfo } from './api'; import { IAddSecretStorageKeyOpts, ISecretStorageKeyInfo } from './api';
import { TypedEventEmitter } from '../models/typed-event-emitter'; 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"; 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> { export interface IAccountDataClient extends TypedEventEmitter<ClientEvent.AccountData, ClientEventHandlerMap> {
// Subset of MatrixClient (which also uses any for the event content) // Subset of MatrixClient (which also uses any for the event content)
getAccountDataFromServer: <T extends {[k: string]: any}>(eventType: string) => Promise<T>; 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<{}>; setAccountData: (eventType: string, content: any) => Promise<{}>;
} }
interface ISecretRequestInternal { interface ISecretRequestInternal {
name: string; name: string;
devices: string[]; devices: string[];
resolve: (reason: string) => void; deferred: IDeferred<string>;
reject: (error: Error) => void;
} }
interface IDecryptors { interface IDecryptors {
@ -66,7 +66,7 @@ interface ISecretInfo {
* Implements Secure Secret Storage and Sharing (MSC1946) * Implements Secure Secret Storage and Sharing (MSC1946)
* @module crypto/SecretStorage * @module crypto/SecretStorage
*/ */
export class SecretStorage { export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
private requests = new Map<string, ISecretRequestInternal>(); private requests = new Map<string, ISecretRequestInternal>();
// In it's pure javascript days, this was relying on some proper Javascript-style // In it's pure javascript days, this was relying on some proper Javascript-style
@ -80,7 +80,7 @@ export class SecretStorage {
constructor( constructor(
private readonly accountDataAdapter: IAccountDataClient, private readonly accountDataAdapter: IAccountDataClient,
private readonly cryptoCallbacks: ICryptoCallbacks, private readonly cryptoCallbacks: ICryptoCallbacks,
private readonly baseApis?: MatrixClient, private readonly baseApis: B,
) {} ) {}
public async getDefaultKeyId(): Promise<string | null> { public async getDefaultKeyId(): Promise<string | null> {
@ -129,13 +129,11 @@ export class SecretStorage {
*/ */
public async addKey( public async addKey(
algorithm: string, algorithm: string,
opts: IAddSecretStorageKeyOpts, opts: IAddSecretStorageKeyOpts = {},
keyId?: string, keyId?: string,
): Promise<SecretStorageKeyObject> { ): Promise<SecretStorageKeyObject> {
const keyInfo = { algorithm } as ISecretStorageKeyInfo; const keyInfo = { algorithm } as ISecretStorageKeyInfo;
if (!opts) opts = {} as IAddSecretStorageKeyOpts;
if (opts.name) { if (opts.name) {
keyInfo.name = 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} name the name of the secret to request
* @param {string[]} devices the devices to request the secret from * @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(); const requestId = this.baseApis.makeTxnId();
let resolve: (s: string) => void; const deferred = defer<string>();
let reject: (e: Error) => void; this.requests.set(requestId, { name, devices, deferred });
const promise = new Promise<string>((res, rej) => {
resolve = res;
reject = rej;
});
this.requests.set(requestId, {
name,
devices,
resolve,
reject,
});
const cancel = (reason: string) => { const cancel = (reason: string) => {
// send cancellation event // send cancellation event
@ -404,12 +392,12 @@ export class SecretStorage {
toDevice[device] = cancelData; toDevice[device] = cancelData;
} }
this.baseApis.sendToDevice("m.secret.request", { 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 // and reject the promise so that anyone waiting on it will be
// notified // notified
reject(new Error(reason || "Cancelled")); deferred.reject(new Error(reason || "Cancelled"));
}; };
// send request to devices // send request to devices
@ -425,22 +413,23 @@ export class SecretStorage {
} }
logger.info(`Request secret ${name} from ${devices}, id ${requestId}`); logger.info(`Request secret ${name} from ${devices}, id ${requestId}`);
this.baseApis.sendToDevice("m.secret.request", { this.baseApis.sendToDevice("m.secret.request", {
[this.baseApis.getUserId()]: toDevice, [this.baseApis.getUserId()!]: toDevice,
}); });
return { return {
requestId, requestId,
promise, promise: deferred.promise,
cancel, cancel,
}; };
} }
public async onRequestReceived(event: MatrixEvent): Promise<void> { public async onRequestReceived(this: SecretStorage<MatrixClient>, event: MatrixEvent): Promise<void> {
const sender = event.getSender(); const sender = event.getSender();
const content = event.getContent(); const content = event.getContent();
if (sender !== this.baseApis.getUserId() if (sender !== this.baseApis.getUserId()
|| !(content.name && content.action || !(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 // ignore requests from anyone else, for now
return; return;
} }
@ -498,25 +487,25 @@ export class SecretStorage {
}; };
const encryptedContent = { const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM, algorithm: olmlib.OLM_ALGORITHM,
sender_key: this.baseApis.crypto.olmDevice.deviceCurve25519Key, sender_key: this.baseApis.crypto!.olmDevice.deviceCurve25519Key,
ciphertext: {}, ciphertext: {},
}; };
await olmlib.ensureOlmSessionsForDevices( await olmlib.ensureOlmSessionsForDevices(
this.baseApis.crypto.olmDevice, this.baseApis.crypto!.olmDevice,
this.baseApis, this.baseApis,
{ {
[sender]: [ [sender]: [
this.baseApis.getStoredDevice(sender, deviceId), this.baseApis.getStoredDevice(sender, deviceId)!,
], ],
}, },
); );
await olmlib.encryptMessageForDevice( await olmlib.encryptMessageForDevice(
encryptedContent.ciphertext, encryptedContent.ciphertext,
this.baseApis.getUserId(), this.baseApis.getUserId()!,
this.baseApis.deviceId, this.baseApis.deviceId!,
this.baseApis.crypto.olmDevice, this.baseApis.crypto!.olmDevice,
sender, sender,
this.baseApis.getStoredDevice(sender, deviceId), this.baseApis.getStoredDevice(sender, deviceId)!,
payload, payload,
); );
const contentMap = { 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()) { if (event.getSender() !== this.baseApis.getUserId()) {
// we shouldn't be receiving secrets from anyone else, so ignore // we shouldn't be receiving secrets from anyone else, so ignore
// because someone could be trying to send us bogus data // because someone could be trying to send us bogus data
@ -547,7 +536,7 @@ export class SecretStorage {
const content = event.getContent(); const content = event.getContent();
const senderKeyUser = this.baseApis.crypto.deviceList.getUserByIdentityKey( const senderKeyUser = this.baseApis.crypto!.deviceList.getUserByIdentityKey(
olmlib.OLM_ALGORITHM, olmlib.OLM_ALGORITHM,
event.getSenderKey() || "", event.getSenderKey() || "",
); );
@ -561,9 +550,9 @@ export class SecretStorage {
if (requestControl) { if (requestControl) {
// make sure that the device that sent it is one of the devices that // make sure that the device that sent it is one of the devices that
// we requested from // we requested from
const deviceInfo = this.baseApis.crypto.deviceList.getDeviceByIdentityKey( const deviceInfo = this.baseApis.crypto!.deviceList.getDeviceByIdentityKey(
olmlib.OLM_ALGORITHM, olmlib.OLM_ALGORITHM,
event.getSenderKey(), event.getSenderKey()!,
); );
if (!deviceInfo) { if (!deviceInfo) {
logger.log( logger.log(
@ -578,7 +567,7 @@ export class SecretStorage {
// unsure that the sender is trusted. In theory, this check is // unsure that the sender is trusted. In theory, this check is
// unnecessary since we only accept secret shares from devices that // unnecessary since we only accept secret shares from devices that
// we requested from, but it doesn't hurt. // 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()) { if (!deviceTrust.isVerified()) {
logger.log("secret share from unverified device"); logger.log("secret share from unverified device");
return; return;
@ -588,7 +577,7 @@ export class SecretStorage {
`Successfully received secret ${requestControl.name} ` + `Successfully received secret ${requestControl.name} ` +
`from ${deviceInfo.deviceId}`, `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>(); 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 * map of registered encryption algorithm classes. Map from string to {@link
@ -52,7 +52,7 @@ export interface IParams {
crypto: Crypto; crypto: Crypto;
olmDevice: OlmDevice; olmDevice: OlmDevice;
baseApis: MatrixClient; baseApis: MatrixClient;
roomId: string; roomId?: string;
config: IRoomEncryption & object; config: IRoomEncryption & object;
} }
@ -76,7 +76,7 @@ export abstract class EncryptionAlgorithm {
protected readonly crypto: Crypto; protected readonly crypto: Crypto;
protected readonly olmDevice: OlmDevice; protected readonly olmDevice: OlmDevice;
protected readonly baseApis: MatrixClient; protected readonly baseApis: MatrixClient;
protected readonly roomId: string; protected readonly roomId?: string;
constructor(params: IParams) { constructor(params: IParams) {
this.userId = params.userId; this.userId = params.userId;
@ -148,7 +148,7 @@ export abstract class DecryptionAlgorithm {
protected readonly crypto: Crypto; protected readonly crypto: Crypto;
protected readonly olmDevice: OlmDevice; protected readonly olmDevice: OlmDevice;
protected readonly baseApis: MatrixClient; protected readonly baseApis: MatrixClient;
protected readonly roomId: string; protected readonly roomId?: string;
constructor(params: DecryptionClassParams) { constructor(params: DecryptionClassParams) {
this.userId = params.userId; this.userId = params.userId;
@ -296,11 +296,11 @@ export class UnknownDeviceError extends Error {
* module:crypto/algorithms/base.DecryptionAlgorithm|DecryptionAlgorithm} * module:crypto/algorithms/base.DecryptionAlgorithm|DecryptionAlgorithm}
* implementation * implementation
*/ */
export function registerAlgorithm( export function registerAlgorithm<P extends IParams = IParams>(
algorithm: string, algorithm: string,
encryptor: new (params: IParams) => EncryptionAlgorithm, encryptor: new (params: P) => EncryptionAlgorithm,
decryptor: new (params: DecryptionClassParams) => DecryptionAlgorithm, decryptor: new (params: DecryptionClassParams<P>) => DecryptionAlgorithm,
): void { ): void {
ENCRYPTION_CLASSES.set(algorithm, encryptor); ENCRYPTION_CLASSES.set(algorithm, encryptor as new (params: IParams) => EncryptionAlgorithm);
DECRYPTION_CLASSES.set(algorithm, decryptor); 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 * as olmlib from "../olmlib";
import { import {
DecryptionAlgorithm, DecryptionAlgorithm,
DecryptionClassParams,
DecryptionError, DecryptionError,
EncryptionAlgorithm, EncryptionAlgorithm,
IParams, IParams,
@ -251,8 +252,11 @@ class MegolmEncryption extends EncryptionAlgorithm {
startTime: number; startTime: number;
}; };
constructor(params: IParams) { protected readonly roomId: string;
constructor(params: IParams & Required<Pick<IParams, "roomId">>) {
super(params); super(params);
this.roomId = params.roomId;
this.sessionRotationPeriodMsgs = params.config?.rotation_period_msgs ?? 100; this.sessionRotationPeriodMsgs = params.config?.rotation_period_msgs ?? 100;
this.sessionRotationPeriodMs = params.config?.rotation_period_ms ?? 7 * 24 * 3600 * 1000; 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. // this gets stubbed out by the unit tests.
private olmlib = olmlib; private olmlib = olmlib;
protected readonly roomId: string;
constructor(params: DecryptionClassParams<IParams & Required<Pick<IParams, "roomId">>>) {
super(params);
this.roomId = params.roomId;
}
/** /**
* @inheritdoc * @inheritdoc
* *
@ -1264,7 +1275,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
try { try {
res = await this.olmDevice.decryptGroupMessage( res = await this.olmDevice.decryptGroupMessage(
event.getRoomId()!, content.sender_key, content.session_id, content.ciphertext, event.getRoomId()!, content.sender_key, content.session_id, content.ciphertext,
event.getId(), event.getTs(), event.getId()!, event.getTs(),
); );
} catch (e) { } catch (e) {
if ((<Error>e).name === "DecryptionError") { if ((<Error>e).name === "DecryptionError") {
@ -1464,7 +1475,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
return; return;
} }
const outgoingRequests = deviceInfo ? await this.crypto.cryptoStore.getOutgoingRoomKeyRequestsByTarget( 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) => ( const weRequested = outgoingRequests.some((req) => (
req.requestBody.room_id === content.room_id && req.requestBody.session_id === content.session_id 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 // that room later
if (!room) { if (!room) {
const parkedData = { const parkedData = {
senderId: event.getSender(), senderId: event.getSender()!,
senderKey: content.sender_key, senderKey: content.sender_key,
sessionId: content.session_id, sessionId: content.session_id,
sessionKey: content.session_key, sessionKey: content.session_key,
@ -1544,7 +1555,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
olmlib.OLM_ALGORITHM, olmlib.OLM_ALGORITHM,
senderKey, senderKey,
) ?? undefined; ) ?? undefined;
const deviceTrust = this.crypto.checkDeviceInfoTrust(event.getSender(), sendingDevice); const deviceTrust = this.crypto.checkDeviceInfoTrust(event.getSender()!, sendingDevice);
if (fromUs && !deviceTrust.isVerified()) { if (fromUs && !deviceTrust.isVerified()) {
return; return;
@ -1608,7 +1619,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
const senderKey = content.sender_key; const senderKey = content.sender_key;
if (content.code === "m.no_olm") { if (content.code === "m.no_olm") {
const sender = event.getSender(); const sender = event.getSender()!;
logger.warn( logger.warn(
`${sender}:${senderKey} was unable to establish an olm session with us`, `${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 // assume that the device logged out. Some event handlers, such as
// secret sharing, may be more strict and reject events that come from // secret sharing, may be more strict and reject events that come from
// unknown devices. // unknown devices.
await this.crypto.deviceList.downloadKeys([event.getSender()], false); await this.crypto.deviceList.downloadKeys([event.getSender()!], false);
const senderKeyUser = this.crypto.deviceList.getUserByIdentityKey( const senderKeyUser = this.crypto.deviceList.getUserByIdentityKey(
olmlib.OLM_ALGORITHM, olmlib.OLM_ALGORITHM,
deviceKey, deviceKey,
@ -250,7 +250,7 @@ class OlmDecryption extends DecryptionAlgorithm {
throw new DecryptionError( throw new DecryptionError(
"OLM_FORWARDED_MESSAGE", "OLM_FORWARDED_MESSAGE",
"Message forwarded from " + payload.sender, { "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 { export interface IAddSecretStorageKeyOpts {
pubkey: string; pubkey?: string;
passphrase?: IPassphraseInfo; passphrase?: IPassphraseInfo;
name?: string; name?: string;
key: Uint8Array; key?: Uint8Array;
} }
export interface IImportOpts { export interface IImportOpts {

View File

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

View File

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

View File

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

View File

@ -678,7 +678,7 @@ export class Backend implements CryptoStore {
senderCurve25519Key, sessionId, session: sessionData, senderCurve25519Key, sessionId, session: sessionData,
}); });
addReq.onerror = (ev) => { addReq.onerror = (ev) => {
if (addReq.error.name === 'ConstraintError') { if (addReq.error?.name === 'ConstraintError') {
// This stops the error from triggering the txn's onerror // This stops the error from triggering the txn's onerror
ev.stopPropagation(); ev.stopPropagation();
// ...and this stops it from aborting the transaction // ...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"); const timeoutException = new Error("Verification timed out");
export class SwitchStartEventError extends Error { export class SwitchStartEventError extends Error {
constructor(public readonly startEvent: MatrixEvent) { constructor(public readonly startEvent: MatrixEvent | null) {
super(); super();
} }
} }
@ -96,7 +96,7 @@ export class VerificationBase<
public readonly baseApis: MatrixClient, public readonly baseApis: MatrixClient,
public readonly userId: string, public readonly userId: string,
public readonly deviceId: string, public readonly deviceId: string,
public startEvent: MatrixEvent, public startEvent: MatrixEvent | null,
public readonly request: VerificationRequest, public readonly request: VerificationRequest,
) { ) {
super(); super();

View File

@ -23,7 +23,7 @@ limitations under the License.
import { MatrixEvent } from "../../models/event"; import { MatrixEvent } from "../../models/event";
import { EventType } from '../../@types/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); const content = Object.assign({}, { code, reason }, extraData);
return new MatrixEvent({ return new MatrixEvent({
type: EventType.KeyVerificationCancel, type: EventType.KeyVerificationCancel,

View File

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

View File

@ -233,10 +233,10 @@ type EventHandlerMap = {
* @extends {module:crypto/verification/Base} * @extends {module:crypto/verification/Base}
*/ */
export class SAS extends Base<SasEvent, EventHandlerMap> { export class SAS extends Base<SasEvent, EventHandlerMap> {
private waitingForAccept: boolean; private waitingForAccept?: boolean;
public ourSASPubKey: string; public ourSASPubKey?: string;
public theirSASPubKey: string; public theirSASPubKey?: string;
public sasEvent: ISasEvent; public sasEvent?: ISasEvent;
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
public static get NAME(): string { public static get NAME(): string {
@ -279,7 +279,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
return false; return false;
} }
const content = event.getContent(); 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>> { private async sendStart(): Promise<Record<string, any>> {
@ -400,7 +400,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
private async doRespondVerification(): Promise<void> { private async doRespondVerification(): Promise<void> {
// as m.related_to is not included in the encrypted content in e2e rooms, // as m.related_to is not included in the encrypted content in e2e rooms,
// we need to make sure it is added // 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, // Note: we intersect using our pre-made lists, rather than the sets,
// so that the result will be in our order of preference. Then // 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 { export interface IVerificationChannel {
request?: VerificationRequest; request?: VerificationRequest;
readonly userId: string; readonly userId?: string;
readonly roomId?: string; readonly roomId?: string;
readonly deviceId?: string; readonly deviceId?: string;
readonly transactionId: string; readonly transactionId?: string;
readonly receiveStartFromOtherDevices?: boolean; readonly receiveStartFromOtherDevices?: boolean;
getTimestamp(event: MatrixEvent): number; getTimestamp(event: MatrixEvent): number;
send(type: string, uncompletedContent: Record<string, any>): Promise<void>; 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. * Uses the event id of the initial m.key.verification.request event as a transaction id.
*/ */
export class InRoomChannel implements IVerificationChannel { 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. * @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( constructor(
private readonly client: MatrixClient, private readonly client: MatrixClient,
public readonly roomId: string, 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 */ /** The transaction id generated/used by this verification channel */
public get transactionId(): string { public get transactionId(): string | undefined {
return this.requestEventId; 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); const type = InRoomChannel.getEventType(event);
if (type !== REQUEST_TYPE) { if (type !== REQUEST_TYPE) {
return; return;
@ -103,12 +103,12 @@ export class InRoomChannel implements IVerificationChannel {
* @param {MatrixEvent} event the event * @param {MatrixEvent} event the event
* @returns {string} the transaction id * @returns {string} the transaction id
*/ */
public static getTransactionId(event: MatrixEvent): string { public static getTransactionId(event: MatrixEvent): string | undefined {
if (InRoomChannel.getEventType(event) === REQUEST_TYPE) { if (InRoomChannel.getEventType(event) === REQUEST_TYPE) {
return event.getId(); return event.getId();
} else { } else {
const relation = event.getRelation(); const relation = event.getRelation();
if (relation && relation.rel_type === M_REFERENCE) { if (relation?.rel_type === M_REFERENCE) {
return relation.event_id; 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 * @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. * @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 // prevent processing the same event multiple times, as under
// some circumstances Room.timeline can get emitted twice for the same event // some circumstances Room.timeline can get emitted twice for the same event
if (request.hasEventId(event.getId())) { if (request.hasEventId(event.getId()!)) {
return; return;
} }
const type = InRoomChannel.getEventType(event); const type = InRoomChannel.getEventType(event);
@ -198,7 +198,7 @@ export class InRoomChannel implements IVerificationChannel {
return; return;
} }
// set userId if not set already // set userId if not set already
if (this.userId === null) { if (!this.userId) {
const userId = InRoomChannel.getOtherPartyUserId(event, this.client); const userId = InRoomChannel.getOtherPartyUserId(event, this.client);
if (userId) { if (userId) {
this.userId = userId; this.userId = userId;
@ -207,14 +207,13 @@ export class InRoomChannel implements IVerificationChannel {
// ignore events not sent by us or the other party // ignore events not sent by us or the other party
const ownUserId = this.client.getUserId(); const ownUserId = this.client.getUserId();
const sender = event.getSender(); const sender = event.getSender();
if (this.userId !== null) { if (this.userId) {
if (sender !== ownUserId && sender !== this.userId) { if (sender !== ownUserId && sender !== this.userId) {
logger.log(`InRoomChannel: ignoring verification event from ` + logger.log(`InRoomChannel: ignoring verification event from non-participating sender ${sender}`);
`non-participating sender ${sender}`);
return; return;
} }
} }
if (this.requestEventId === null) { if (!this.requestEventId) {
this.requestEventId = InRoomChannel.getTransactionId(event); this.requestEventId = InRoomChannel.getTransactionId(event);
} }
@ -236,7 +235,7 @@ export class InRoomChannel implements IVerificationChannel {
// ensure m.related_to is included in e2ee rooms // ensure m.related_to is included in e2ee rooms
// as the field is excluded from encryption // as the field is excluded from encryption
const content = Object.assign({}, event.getContent()); const content = Object.assign({}, event.getContent());
content[M_RELATES_TO] = event.getRelation(); content[M_RELATES_TO] = event.getRelation()!;
return content; return content;
} }
@ -307,17 +306,17 @@ export class InRoomChannel implements IVerificationChannel {
export class InRoomRequests implements IRequestsMap { export class InRoomRequests implements IRequestsMap {
private requestsByRoomId = new Map<string, Map<string, VerificationRequest>>(); private requestsByRoomId = new Map<string, Map<string, VerificationRequest>>();
public getRequest(event: MatrixEvent): VerificationRequest { public getRequest(event: MatrixEvent): VerificationRequest | undefined {
const roomId = event.getRoomId(); const roomId = event.getRoomId()!;
const txnId = InRoomChannel.getTransactionId(event); const txnId = InRoomChannel.getTransactionId(event)!;
return this.getRequestByTxnId(roomId, txnId); return this.getRequestByTxnId(roomId, txnId);
} }
public getRequestByChannel(channel: InRoomChannel): VerificationRequest { public getRequestByChannel(channel: InRoomChannel): VerificationRequest | undefined {
return this.getRequestByTxnId(channel.roomId, channel.transactionId); 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); const requestsByTxnId = this.requestsByRoomId.get(roomId);
if (requestsByTxnId) { if (requestsByTxnId) {
return requestsByTxnId.get(txnId); return requestsByTxnId.get(txnId);
@ -325,11 +324,11 @@ export class InRoomRequests implements IRequestsMap {
} }
public setRequest(event: MatrixEvent, request: VerificationRequest): void { 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 { 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 { private doSetRequest(roomId: string, txnId: string, request: VerificationRequest): void {
@ -342,17 +341,17 @@ export class InRoomRequests implements IRequestsMap {
} }
public removeRequest(event: MatrixEvent): void { public removeRequest(event: MatrixEvent): void {
const roomId = event.getRoomId(); const roomId = event.getRoomId()!;
const requestsByTxnId = this.requestsByRoomId.get(roomId); const requestsByTxnId = this.requestsByRoomId.get(roomId);
if (requestsByTxnId) { if (requestsByTxnId) {
requestsByTxnId.delete(InRoomChannel.getTransactionId(event)); requestsByTxnId.delete(InRoomChannel.getTransactionId(event)!);
if (requestsByTxnId.size === 0) { if (requestsByTxnId.size === 0) {
this.requestsByRoomId.delete(roomId); this.requestsByRoomId.delete(roomId);
} }
} }
} }
public findRequestInProgress(roomId: string): VerificationRequest { public findRequestInProgress(roomId: string): VerificationRequest | undefined {
const requestsByTxnId = this.requestsByRoomId.get(roomId); const requestsByTxnId = this.requestsByRoomId.get(roomId);
if (requestsByTxnId) { if (requestsByTxnId) {
for (const request of requestsByTxnId.values()) { for (const request of requestsByTxnId.values()) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -95,8 +95,8 @@ export class EventContext {
* @param {boolean} backwards true to set the pagination token for going * @param {boolean} backwards true to set the pagination token for going
* backwards in time * backwards in time
*/ */
public setPaginateToken(token: string, backwards = false): void { public setPaginateToken(token?: string, backwards = false): void {
this.paginateTokens[backwards ? Direction.Backward : Direction.Forward] = token; 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; let lastEventWasNew = false;
for (let i = 0; i < events.length; i++) { for (let i = 0; i < events.length; i++) {
const event = events[i]; const event = events[i];
const eventId = event.getId(); const eventId = event.getId()!;
const existingTimeline = this._eventIdToTimeline.get(eventId); 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 (timeline) {
if (duplicateStrategy === DuplicateStrategy.Replace) { if (duplicateStrategy === DuplicateStrategy.Replace) {
debuglog("EventTimelineSet.addLiveEvent: replacing duplicate event " + event.getId()); 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, { timeline.addEvent(event, {
toStartOfTimeline, toStartOfTimeline,
roomState, roomState,

View File

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

View File

@ -71,7 +71,7 @@ export interface IEvent {
type: string; type: string;
content: IContent; content: IContent;
sender: string; sender: string;
room_id: string; room_id?: string;
origin_server_ts: number; origin_server_ts: number;
txn_id?: string; txn_id?: string;
state_key?: 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 * @return {string} The event ID, e.g. <code>$143350589368169JsLZx:localhost
* </code> * </code>
*/ */
public getId(): string { public getId(): string | undefined {
return this.event.event_id; return this.event.event_id;
} }
@ -400,7 +400,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
* Get the user_id for this event. * Get the user_id for this event.
* @return {string} The user ID, e.g. <code>@alice:matrix.org</code> * @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 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()); 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're prefer ev.getContent() over ev.getWireContent() to make sure
// we grab the latest edit with potentially new relations. But we also // we grab the latest edit with potentially new relations. But we also
// can't just rely on ev.getContent() by itself because historically we // 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. // original sending device if it wasn't us.
const wireContent = this.getWireContent(); const wireContent = this.getWireContent();
const recipients = [{ const recipients = [{
userId, deviceId: '*', userId,
deviceId: '*',
}]; }];
const sender = this.getSender(); const sender = this.getSender();
if (sender !== userId) { if (sender !== userId) {
recipients.push({ recipients.push({
userId: sender, deviceId: wireContent.device_id, userId: sender!,
deviceId: wireContent.device_id,
}); });
} }
return recipients; return recipients;
@ -1387,7 +1389,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
return new Date(ts); return new Date(ts);
} }
} else if (this._replacingEvent) { } 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 { export function synthesizeReceipt(userId: string, event: MatrixEvent, receiptType: ReceiptType): MatrixEvent {
return new MatrixEvent({ return new MatrixEvent({
content: { content: {
[event.getId()]: { [event.getId()!]: {
[receiptType]: { [receiptType]: {
[userId]: { [userId]: {
ts: event.getTs(), ts: event.getTs(),
@ -241,7 +241,7 @@ export abstract class ReadReceipt<
* an empty list. * an empty list.
*/ */
public getReceiptsForEvent(event: MatrixEvent): CachedReceipt[] { public getReceiptsForEvent(event: MatrixEvent): CachedReceipt[] {
return this.receiptCacheByEventId[event.getId()] || []; return this.receiptCacheByEventId[event.getId()!] || [];
} }
public abstract addReceipt(event: MatrixEvent, synthetic: boolean): void; public abstract addReceipt(event: MatrixEvent, synthetic: boolean): void;

View File

@ -26,7 +26,7 @@ export class RelatedRelations {
} }
public getRelations(): MatrixEvent[] { 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>) { 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. * @param {MatrixEvent} event The event to check as relation target.
*/ */
public aggregateParentEvent(event: MatrixEvent): void { public aggregateParentEvent(event: MatrixEvent): void {
const relationsForEvent = this.relations.get(event.getId()); const relationsForEvent = this.relations.get(event.getId()!);
if (!relationsForEvent) return; if (!relationsForEvent) return;
for (const relationsWithRelType of relationsForEvent.values()) { 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. * The new relation event to be added.
*/ */
public async addEvent(event: MatrixEvent) { public async addEvent(event: MatrixEvent) {
if (this.relationEventIds.has(event.getId())) { if (this.relationEventIds.has(event.getId()!)) {
return; return;
} }
@ -101,7 +101,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
} }
this.relations.add(event); this.relations.add(event);
this.relationEventIds.add(event.getId()); this.relationEventIds.add(event.getId()!);
if (this.relationType === RelationType.Annotation) { if (this.relationType === RelationType.Annotation) {
this.addAnnotationToAggregation(event); this.addAnnotationToAggregation(event);
@ -206,7 +206,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
return bEvents.size - aEvents.size; return bEvents.size - aEvents.size;
}); });
const sender = event.getSender(); const sender = event.getSender()!;
let eventsFromSender = this.annotationsBySender[sender]; let eventsFromSender = this.annotationsBySender[sender];
if (!eventsFromSender) { if (!eventsFromSender) {
eventsFromSender = this.annotationsBySender[sender] = new Set(); 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]; const eventsFromSender = this.annotationsBySender[sender];
if (eventsFromSender) { if (eventsFromSender) {
eventsFromSender.delete(event); eventsFromSender.delete(event);

View File

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

View File

@ -222,7 +222,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
} }
private addEventToTimeline(event: MatrixEvent, toStartOfTimeline: boolean): void { private addEventToTimeline(event: MatrixEvent, toStartOfTimeline: boolean): void {
if (!this.findEventById(event.getId())) { if (!this.findEventById(event.getId()!)) {
this.timelineSet.addEventToTimeline( this.timelineSet.addEventToTimeline(
event, event,
this.liveTimeline, this.liveTimeline,
@ -305,7 +305,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
this._currentUserParticipated = !!bundledRelationship.current_user_participated; this._currentUserParticipated = !!bundledRelationship.current_user_participated;
const event = new MatrixEvent({ const event = new MatrixEvent({
room_id: this.rootEvent.getRoomId(), room_id: this.room.roomId,
...bundledRelationship.latest_event, ...bundledRelationship.latest_event,
}); });
this.setEventMetadata(event); this.setEventMetadata(event);
@ -322,7 +322,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
private async fetchEditsWhereNeeded(...events: MatrixEvent[]): Promise<unknown> { private async fetchEditsWhereNeeded(...events: MatrixEvent[]): Promise<unknown> {
return Promise.all(events.filter(e => e.isEncrypted()).map((event: MatrixEvent) => { return Promise.all(events.filter(e => e.isEncrypted()).map((event: MatrixEvent) => {
if (event.isRelation()) return; // skip - relations don't get edits 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, limit: 1,
}).then(relations => { }).then(relations => {
if (relations.events.length) { 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 // 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 // the point the event is in the DAG. Unfortunately the js-sdk does not store
// this. // this.
return room.currentState.mayTriggerNotifOfType(notifLevelKey, ev.getSender()); return room.currentState.mayTriggerNotifOfType(notifLevelKey, ev.getSender()!);
} }
private eventFulfillsRoomMemberCountCondition(cond: IRoomMemberCountCondition, ev: MatrixEvent): boolean { private eventFulfillsRoomMemberCountCondition(cond: IRoomMemberCountCondition, ev: MatrixEvent): boolean {

View File

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

View File

@ -418,7 +418,7 @@ export class SlidingSyncSdk {
// this room, then timeline_limit: 50). // this room, then timeline_limit: 50).
const knownEvents = new Set<string>(); const knownEvents = new Set<string>();
room.getLiveTimeline().getEvents().forEach((e) => { 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: // all unknown events BEFORE a known event must be scrollback e.g:
// D E <-- what we know // D E <-- what we know
@ -433,7 +433,7 @@ export class SlidingSyncSdk {
let seenKnownEvent = false; let seenKnownEvent = false;
for (let i = timelineEvents.length-1; i >= 0; i--) { for (let i = timelineEvents.length-1; i >= 0; i--) {
const recvEvent = timelineEvents[i]; const recvEvent = timelineEvents[i];
if (knownEvents.has(recvEvent.getId())) { if (knownEvents.has(recvEvent.getId()!)) {
seenKnownEvent = true; seenKnownEvent = true;
continue; // don't include this event, it's a dupe 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 { IStartClientOpts } from "../client";
import { IStateEventWithRoomId } from "../@types/search"; import { IStateEventWithRoomId } from "../@types/search";
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage"; import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage";
import { EventEmitterEvents } from "../models/typed-event-emitter";
export interface ISavedSync { export interface ISavedSync {
nextBatch: string; nextBatch: string;
@ -39,7 +40,7 @@ export interface IStore {
// XXX: The indexeddb store exposes a non-standard emitter for the "degraded" event // 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. // 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. */ /** @return {Promise<boolean>} whether or not the database was newly created in this session. */
isNewlyCreated(): Promise<boolean>; isNewlyCreated(): Promise<boolean>;
@ -231,7 +232,7 @@ export interface IStore {
clearOutOfBandMembers(roomId: string): Promise<void>; clearOutOfBandMembers(roomId: string): Promise<void>;
getClientOptions(): Promise<IStartClientOpts>; getClientOptions(): Promise<IStartClientOpts | undefined>;
storeClientOptions(options: IStartClientOpts): Promise<void>; storeClientOptions(options: IStartClientOpts): Promise<void>;

View File

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import { ISavedSync } from "./index"; import { ISavedSync } from "./index";
import { IEvent, IStartClientOpts, IStateEventWithRoomId, ISyncResponse } from "../matrix"; import { IEvent, IStateEventWithRoomId, IStoredClientOpts, ISyncResponse } from "../matrix";
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage"; import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage";
export interface IIndexedDBBackend { export interface IIndexedDBBackend {
@ -30,8 +30,8 @@ export interface IIndexedDBBackend {
setOutOfBandMembers(roomId: string, membershipEvents: IStateEventWithRoomId[]): Promise<void>; setOutOfBandMembers(roomId: string, membershipEvents: IStateEventWithRoomId[]): Promise<void>;
clearOutOfBandMembers(roomId: string): Promise<void>; clearOutOfBandMembers(roomId: string): Promise<void>;
getUserPresenceEvents(): Promise<UserTuple[]>; getUserPresenceEvents(): Promise<UserTuple[]>;
getClientOptions(): Promise<IStartClientOpts>; getClientOptions(): Promise<IStoredClientOpts | undefined>;
storeClientOptions(options: IStartClientOpts): Promise<void>; storeClientOptions(options: IStoredClientOpts): Promise<void>;
saveToDeviceBatches(batches: ToDeviceBatchWithTxnId[]): Promise<void>; saveToDeviceBatches(batches: ToDeviceBatchWithTxnId[]): Promise<void>;
getOldestToDeviceBatch(): Promise<IndexedToDeviceBatch | null>; getOldestToDeviceBatch(): Promise<IndexedToDeviceBatch | null>;
removeToDeviceBatch(id: number): Promise<void>; 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 utils from "../utils";
import * as IndexedDBHelpers from "../indexeddb-helpers"; import * as IndexedDBHelpers from "../indexeddb-helpers";
import { logger } from '../logger'; import { logger } from '../logger';
import { IStartClientOpts, IStateEventWithRoomId } from "../matrix"; import { IStateEventWithRoomId, IStoredClientOpts } from "../matrix";
import { ISavedSync } from "./index"; import { ISavedSync } from "./index";
import { IIndexedDBBackend, UserTuple } from "./indexeddb-backend"; import { IIndexedDBBackend, UserTuple } from "./indexeddb-backend";
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage"; 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(() => { return Promise.resolve().then(() => {
const txn = this.db!.transaction(["client_options"], "readonly"); const txn = this.db!.transaction(["client_options"], "readonly");
const store = txn.objectStore("client_options"); 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 txn = this.db!.transaction(["client_options"], "readwrite");
const store = txn.objectStore("client_options"); const store = txn.objectStore("client_options");
store.put({ store.put({

View File

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

View File

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

View File

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

View File

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

View File

@ -1081,11 +1081,11 @@ export class SyncApi {
if (Array.isArray(data.presence?.events)) { if (Array.isArray(data.presence?.events)) {
data.presence!.events.map(client.getEventMapper()).forEach( data.presence!.events.map(client.getEventMapper()).forEach(
function(presenceEvent) { function(presenceEvent) {
let user = client.store.getUser(presenceEvent.getSender()); let user = client.store.getUser(presenceEvent.getSender()!);
if (user) { if (user) {
user.setPresenceEvent(presenceEvent); user.setPresenceEvent(presenceEvent);
} else { } else {
user = createNewUser(client, presenceEvent.getSender()); user = createNewUser(client, presenceEvent.getSender()!);
user.setPresenceEvent(presenceEvent); user.setPresenceEvent(presenceEvent);
client.store.storeUser(user); client.store.storeUser(user);
} }
@ -1097,7 +1097,7 @@ export class SyncApi {
if (Array.isArray(data.account_data?.events)) { if (Array.isArray(data.account_data?.events)) {
const events = data.account_data.events.map(client.getEventMapper()); const events = data.account_data.events.map(client.getEventMapper());
const prevEventsMap = events.reduce((m, c) => { const prevEventsMap = events.reduce((m, c) => {
m[c.getType()] = client.store.getAccountData(c.getType()); m[c.getType()!] = client.store.getAccountData(c.getType());
return m; return m;
}, {}); }, {});
client.store.storeAccountDataEvents(events); client.store.storeAccountDataEvents(events);
@ -1111,7 +1111,7 @@ export class SyncApi {
const rules = accountDataEvent.getContent<IPushRules>(); const rules = accountDataEvent.getContent<IPushRules>();
client.pushRules = PushProcessor.rewriteDefaultRules(rules); client.pushRules = PushProcessor.rewriteDefaultRules(rules);
} }
const prevEvent = prevEventsMap[accountDataEvent.getType()]; const prevEvent = prevEventsMap[accountDataEvent.getType()!];
client.emit(ClientEvent.AccountData, accountDataEvent, prevEvent); client.emit(ClientEvent.AccountData, accountDataEvent, prevEvent);
return accountDataEvent; return accountDataEvent;
}, },
@ -1330,10 +1330,9 @@ export class SyncApi {
// will stop us linking the empty timeline into the chain). // will stop us linking the empty timeline into the chain).
// //
for (let i = events.length - 1; i >= 0; i--) { for (let i = events.length - 1; i >= 0; i--) {
const eventId = events[i].getId(); const eventId = events[i].getId()!;
if (room.getTimelineForEvent(eventId)) { if (room.getTimelineForEvent(eventId)) {
debuglog("Already have event " + eventId + " in limited " + debuglog(`Already have event ${eventId} in limited sync - not resetting`);
"sync - not resetting");
limited = false; limited = false;
// we might still be missing some of the events before i; // 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) ])[] = [ const replacements: ([RegExp, string | ((substring: string, ...args: any[]) => string) ])[] = [
[/\\\*/g, '.*'], [/\\\*/g, '.*'],
[/\?/g, '.'], [/\?/g, '.'],
!extended && [ ];
if (!extended) {
replacements.push([
/\\\[(!|)(.*)\\]/g, /\\\[(!|)(.*)\\]/g,
(_match: string, neg: string, pat: string) => [ (_match: string, neg: string, pat: string) => [
'[', '[',
@ -363,8 +365,8 @@ export function globToRegexp(glob: string, extended = false): string {
pat.replace(/\\-/, '-'), pat.replace(/\\-/, '-'),
']', ']',
].join(''), ].join(''),
], ]);
]; }
return replacements.reduce( return replacements.reduce(
// https://github.com/microsoft/TypeScript/issues/30134 // https://github.com/microsoft/TypeScript/issues/30134
(pat, args) => args ? pat.replace(args[0], args[1] as any) : pat, (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 { export function ensureNoTrailingSlash(url: string): string;
if (url && url.endsWith("/")) { 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); return url.slice(0, -1);
} else { } else {
return url; return url;

View File

@ -2134,7 +2134,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
this.direction = CallDirection.Outbound; this.direction = CallDirection.Outbound;
// XXX Find a better way to do this // 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 // 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. // 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"); logger.info("Current turn creds expire in " + timeUntilTurnCresExpire + " ms");
call = createNewMatrixCall( call = createNewMatrixCall(
this.client, this.client,
event.getRoomId(), event.getRoomId()!,
{ forceTURN: this.client.forceTURN }, { forceTURN: this.client.forceTURN },
) ?? undefined; ) ?? undefined;
if (!call) { if (!call) {
@ -250,7 +250,7 @@ export class CallEventHandler {
// if not live, store the fact that the call has ended because // if not live, store the fact that the call has ended because
// we're probably getting events backwards so // we're probably getting events backwards so
// the hangup will come before the invite // the hangup will come before the invite
call = createNewMatrixCall(this.client, event.getRoomId()) ?? undefined; call = createNewMatrixCall(this.client, event.getRoomId()!) ?? undefined;
if (call) { if (call) {
call.callId = content.call_id; call.callId = content.call_id;
call.initWithHangup(event); call.initWithHangup(event);

View File

@ -65,14 +65,14 @@ export class MediaHandler {
if (this.userMediaStreams.length === 0) return; if (this.userMediaStreams.length === 0) return;
const callMediaStreamParams: Map<string, { audio: boolean, video: boolean }> = new Map(); 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, { callMediaStreamParams.set(call.callId, {
audio: call.hasLocalUserMediaAudioTrack, audio: call.hasLocalUserMediaAudioTrack,
video: call.hasLocalUserMediaVideoTrack, 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; if (call.state === CallState.Ended || !callMediaStreamParams.has(call.callId)) continue;
const { audio, video } = callMediaStreamParams.get(call.callId)!; const { audio, video } = callMediaStreamParams.get(call.callId)!;