You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-31 15:24:23 +03:00
Apply more strict typescript around the codebase (#2778)
* Apply more strict typescript around the codebase * Fix tests * Revert strict mode commit * Iterate strict * Iterate * Iterate strict * Iterate * Fix tests * Iterate * Iterate strict * Add tests * Iterate * Iterate * Fix tests * Fix tests * Strict types be strict * Fix types * detectOpenHandles * Strict * Fix client not stopping * Add sync peeking tests * Make test happier * More strict * Iterate * Stabilise * Moar strictness * Improve coverage * Fix types * Fix types * Improve types further * Fix types * Improve typing of NamespacedValue * Fix types
This commit is contained in:
committed by
GitHub
parent
fdbbd9bca4
commit
867a0ca7ee
@ -38,8 +38,8 @@ import { IKeysUploadResponse, IUploadKeysRequest } from '../src/client';
|
||||
export class TestClient {
|
||||
public readonly httpBackend: MockHttpBackend;
|
||||
public readonly client: MatrixClient;
|
||||
public deviceKeys: IDeviceKeys;
|
||||
public oneTimeKeys: Record<string, IOneTimeKey>;
|
||||
public deviceKeys?: IDeviceKeys | null;
|
||||
public oneTimeKeys?: Record<string, IOneTimeKey>;
|
||||
|
||||
constructor(
|
||||
public readonly userId?: string,
|
||||
@ -123,7 +123,7 @@ export class TestClient {
|
||||
|
||||
logger.log(this + ': received device keys');
|
||||
// we expect this to happen before any one-time keys are uploaded.
|
||||
expect(Object.keys(this.oneTimeKeys).length).toEqual(0);
|
||||
expect(Object.keys(this.oneTimeKeys!).length).toEqual(0);
|
||||
|
||||
this.deviceKeys = content.device_keys;
|
||||
return { one_time_key_counts: { signed_curve25519: 0 } };
|
||||
@ -138,9 +138,9 @@ export class TestClient {
|
||||
* @returns {Promise} for the one-time keys
|
||||
*/
|
||||
public awaitOneTimeKeyUpload(): Promise<Record<string, IOneTimeKey>> {
|
||||
if (Object.keys(this.oneTimeKeys).length != 0) {
|
||||
if (Object.keys(this.oneTimeKeys!).length != 0) {
|
||||
// already got one-time keys
|
||||
return Promise.resolve(this.oneTimeKeys);
|
||||
return Promise.resolve(this.oneTimeKeys!);
|
||||
}
|
||||
|
||||
this.httpBackend.when("POST", "/keys/upload")
|
||||
@ -148,7 +148,7 @@ export class TestClient {
|
||||
expect(content.device_keys).toBe(undefined);
|
||||
expect(content.one_time_keys).toBe(undefined);
|
||||
return { one_time_key_counts: {
|
||||
signed_curve25519: Object.keys(this.oneTimeKeys).length,
|
||||
signed_curve25519: Object.keys(this.oneTimeKeys!).length,
|
||||
} };
|
||||
});
|
||||
|
||||
@ -158,17 +158,17 @@ export class TestClient {
|
||||
expect(content.one_time_keys).toBeTruthy();
|
||||
expect(content.one_time_keys).not.toEqual({});
|
||||
logger.log('%s: received %i one-time keys', this,
|
||||
Object.keys(content.one_time_keys).length);
|
||||
Object.keys(content.one_time_keys!).length);
|
||||
this.oneTimeKeys = content.one_time_keys;
|
||||
return { one_time_key_counts: {
|
||||
signed_curve25519: Object.keys(this.oneTimeKeys).length,
|
||||
signed_curve25519: Object.keys(this.oneTimeKeys!).length,
|
||||
} };
|
||||
});
|
||||
|
||||
// this can take ages
|
||||
return this.httpBackend.flush('/keys/upload', 2, 1000).then((flushed) => {
|
||||
expect(flushed).toEqual(2);
|
||||
return this.oneTimeKeys;
|
||||
return this.oneTimeKeys!;
|
||||
});
|
||||
}
|
||||
|
||||
@ -183,7 +183,7 @@ export class TestClient {
|
||||
this.httpBackend.when('POST', '/keys/query').respond<IDownloadKeyResult>(
|
||||
200, (_path, content) => {
|
||||
Object.keys(response.device_keys).forEach((userId) => {
|
||||
expect(content.device_keys[userId]).toEqual([]);
|
||||
expect(content.device_keys![userId]).toEqual([]);
|
||||
});
|
||||
return response;
|
||||
});
|
||||
@ -206,7 +206,7 @@ export class TestClient {
|
||||
*/
|
||||
public getDeviceKey(): string {
|
||||
const keyId = 'curve25519:' + this.deviceId;
|
||||
return this.deviceKeys.keys[keyId];
|
||||
return this.deviceKeys!.keys[keyId];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -216,7 +216,7 @@ export class TestClient {
|
||||
*/
|
||||
public getSigningKey(): string {
|
||||
const keyId = 'ed25519:' + this.deviceId;
|
||||
return this.deviceKeys.keys[keyId];
|
||||
return this.deviceKeys!.keys[keyId];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -237,6 +237,6 @@ export class TestClient {
|
||||
}
|
||||
|
||||
public getUserId(): string {
|
||||
return this.userId;
|
||||
return this.userId!;
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ async function bobUploadsDeviceKeys(): Promise<void> {
|
||||
bobTestClient.client.uploadKeys(),
|
||||
bobTestClient.httpBackend.flushAllExpected(),
|
||||
]);
|
||||
expect(Object.keys(bobTestClient.deviceKeys).length).not.toEqual(0);
|
||||
expect(Object.keys(bobTestClient.deviceKeys!).length).not.toEqual(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -99,7 +99,7 @@ async function expectAliClaimKeys(): Promise<void> {
|
||||
expect(claimType).toEqual("signed_curve25519");
|
||||
let keyId = '';
|
||||
for (keyId in keys) {
|
||||
if (bobTestClient.oneTimeKeys.hasOwnProperty(keyId)) {
|
||||
if (bobTestClient.oneTimeKeys!.hasOwnProperty(keyId)) {
|
||||
if (keyId.indexOf(claimType + ":") === 0) {
|
||||
break;
|
||||
}
|
||||
@ -137,7 +137,7 @@ async function aliDownloadsKeys(): Promise<void> {
|
||||
// @ts-ignore - protected
|
||||
aliTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
||||
const devices = data!.devices[bobUserId]!;
|
||||
expect(devices[bobDeviceId].keys).toEqual(bobTestClient.deviceKeys.keys);
|
||||
expect(devices[bobDeviceId].keys).toEqual(bobTestClient.deviceKeys!.keys);
|
||||
expect(devices[bobDeviceId].verified).
|
||||
toBe(DeviceInfo.DeviceVerification.UNVERIFIED);
|
||||
});
|
||||
@ -223,7 +223,7 @@ async function expectBobSendMessageRequest(): Promise<OlmPayload> {
|
||||
const content = await expectSendMessageRequest(bobTestClient.httpBackend);
|
||||
bobMessages.push(content);
|
||||
const aliKeyId = "curve25519:" + aliDeviceId;
|
||||
const aliDeviceCurve25519Key = aliTestClient.deviceKeys.keys[aliKeyId];
|
||||
const aliDeviceCurve25519Key = aliTestClient.deviceKeys!.keys[aliKeyId];
|
||||
expect(Object.keys(content.ciphertext)).toEqual([aliDeviceCurve25519Key]);
|
||||
const ciphertext = content.ciphertext[aliDeviceCurve25519Key];
|
||||
expect(ciphertext).toBeTruthy();
|
||||
@ -393,7 +393,7 @@ describe("MatrixClient crypto", () => {
|
||||
it("Ali gets keys with an invalid signature", async () => {
|
||||
await bobUploadsDeviceKeys();
|
||||
// tamper bob's keys
|
||||
const bobDeviceKeys = bobTestClient.deviceKeys;
|
||||
const bobDeviceKeys = bobTestClient.deviceKeys!;
|
||||
expect(bobDeviceKeys.keys["curve25519:" + bobDeviceId]).toBeTruthy();
|
||||
bobDeviceKeys.keys["curve25519:" + bobDeviceId] += "abc";
|
||||
await Promise.all([
|
||||
@ -479,7 +479,7 @@ describe("MatrixClient crypto", () => {
|
||||
await bobTestClient.start();
|
||||
const keys = await bobTestClient.awaitOneTimeKeyUpload();
|
||||
expect(Object.keys(keys).length).toEqual(5);
|
||||
expect(Object.keys(bobTestClient.deviceKeys).length).not.toEqual(0);
|
||||
expect(Object.keys(bobTestClient.deviceKeys!).length).not.toEqual(0);
|
||||
});
|
||||
|
||||
it("Ali sends a message", async () => {
|
||||
|
@ -1047,7 +1047,7 @@ describe("MatrixClient event timelines", function() {
|
||||
response = {
|
||||
chunk: [THREAD_ROOT],
|
||||
state: [],
|
||||
next_batch: RANDOM_TOKEN,
|
||||
next_batch: RANDOM_TOKEN as string | null,
|
||||
},
|
||||
): ExpectedHttpRequest {
|
||||
const request = httpBackend.when("GET", encodeUri("/_matrix/client/r0/rooms/$roomId/threads", {
|
||||
|
@ -139,7 +139,7 @@ describe("MatrixClient", function() {
|
||||
const r = client!.cancelUpload(prom);
|
||||
expect(r).toBe(true);
|
||||
await expect(prom).rejects.toThrow("Aborted");
|
||||
expect(client.getCurrentUploads()).toHaveLength(0);
|
||||
expect(client!.getCurrentUploads()).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
@ -178,7 +178,7 @@ describe("MatrixClient", function() {
|
||||
expect(request.data.third_party_signed).toEqual(signature);
|
||||
}).respond(200, { room_id: roomId });
|
||||
|
||||
const prom = client.joinRoom(roomId, {
|
||||
const prom = client!.joinRoom(roomId, {
|
||||
inviteSignUrl,
|
||||
viaServers,
|
||||
});
|
||||
@ -1164,18 +1164,18 @@ describe("MatrixClient", function() {
|
||||
|
||||
describe("logout", () => {
|
||||
it("should abort pending requests when called with stopClient=true", async () => {
|
||||
httpBackend.when("POST", "/logout").respond(200, {});
|
||||
httpBackend!.when("POST", "/logout").respond(200, {});
|
||||
const fn = jest.fn();
|
||||
client.http.request(Method.Get, "/test").catch(fn);
|
||||
client.logout(true);
|
||||
await httpBackend.flush(undefined);
|
||||
client!.http.request(Method.Get, "/test").catch(fn);
|
||||
client!.logout(true);
|
||||
await httpBackend!.flush(undefined);
|
||||
expect(fn).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("sendHtmlEmote", () => {
|
||||
it("should send valid html emote", async () => {
|
||||
httpBackend.when("PUT", "/send").check(req => {
|
||||
httpBackend!.when("PUT", "/send").check(req => {
|
||||
expect(req.data).toStrictEqual({
|
||||
"msgtype": "m.emote",
|
||||
"body": "Body",
|
||||
@ -1184,15 +1184,15 @@ describe("MatrixClient", function() {
|
||||
"org.matrix.msc1767.message": expect.anything(),
|
||||
});
|
||||
}).respond(200, { event_id: "$foobar" });
|
||||
const prom = client.sendHtmlEmote("!room:server", "Body", "<h1>Body</h1>");
|
||||
await httpBackend.flush(undefined);
|
||||
const prom = client!.sendHtmlEmote("!room:server", "Body", "<h1>Body</h1>");
|
||||
await httpBackend!.flush(undefined);
|
||||
await expect(prom).resolves.toStrictEqual({ event_id: "$foobar" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("sendHtmlMessage", () => {
|
||||
it("should send valid html message", async () => {
|
||||
httpBackend.when("PUT", "/send").check(req => {
|
||||
httpBackend!.when("PUT", "/send").check(req => {
|
||||
expect(req.data).toStrictEqual({
|
||||
"msgtype": "m.text",
|
||||
"body": "Body",
|
||||
@ -1201,24 +1201,24 @@ describe("MatrixClient", function() {
|
||||
"org.matrix.msc1767.message": expect.anything(),
|
||||
});
|
||||
}).respond(200, { event_id: "$foobar" });
|
||||
const prom = client.sendHtmlMessage("!room:server", "Body", "<h1>Body</h1>");
|
||||
await httpBackend.flush(undefined);
|
||||
const prom = client!.sendHtmlMessage("!room:server", "Body", "<h1>Body</h1>");
|
||||
await httpBackend!.flush(undefined);
|
||||
await expect(prom).resolves.toStrictEqual({ event_id: "$foobar" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("forget", () => {
|
||||
it("should remove from store by default", async () => {
|
||||
const room = new Room("!roomId:server", client, userId);
|
||||
client.store.storeRoom(room);
|
||||
expect(client.store.getRooms()).toContain(room);
|
||||
const room = new Room("!roomId:server", client!, userId);
|
||||
client!.store.storeRoom(room);
|
||||
expect(client!.store.getRooms()).toContain(room);
|
||||
|
||||
httpBackend.when("POST", "/forget").respond(200, {});
|
||||
httpBackend!.when("POST", "/forget").respond(200, {});
|
||||
await Promise.all([
|
||||
client.forget(room.roomId),
|
||||
httpBackend.flushAllExpected(),
|
||||
client!.forget(room.roomId),
|
||||
httpBackend!.flushAllExpected(),
|
||||
]);
|
||||
expect(client.store.getRooms()).not.toContain(room);
|
||||
expect(client!.store.getRooms()).not.toContain(room);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1306,8 +1306,8 @@ describe("MatrixClient", function() {
|
||||
const resp = await prom;
|
||||
expect(resp.access_token).toBe(token);
|
||||
expect(resp.user_id).toBe(userId);
|
||||
expect(client.getUserId()).toBe(userId);
|
||||
expect(client.http.opts.accessToken).toBe(token);
|
||||
expect(client!.getUserId()).toBe(userId);
|
||||
expect(client!.http.opts.accessToken).toBe(token);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1541,6 +1541,67 @@ describe("MatrixClient syncing", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("peek", () => {
|
||||
beforeEach(() => {
|
||||
httpBackend!.expectedRequests = [];
|
||||
});
|
||||
|
||||
it("should return a room based on the room initialSync API", async () => {
|
||||
httpBackend!.when("GET", `/rooms/${encodeURIComponent(roomOne)}/initialSync`).respond(200, {
|
||||
room_id: roomOne,
|
||||
membership: "leave",
|
||||
messages: {
|
||||
start: "start",
|
||||
end: "end",
|
||||
chunk: [{
|
||||
content: { body: "Message 1" },
|
||||
type: "m.room.message",
|
||||
event_id: "$eventId1",
|
||||
sender: userA,
|
||||
origin_server_ts: 12313525,
|
||||
room_id: roomOne,
|
||||
}, {
|
||||
content: { body: "Message 2" },
|
||||
type: "m.room.message",
|
||||
event_id: "$eventId2",
|
||||
sender: userB,
|
||||
origin_server_ts: 12315625,
|
||||
room_id: roomOne,
|
||||
}],
|
||||
},
|
||||
state: [{
|
||||
content: { name: "Room Name" },
|
||||
type: "m.room.name",
|
||||
event_id: "$eventId",
|
||||
sender: userA,
|
||||
origin_server_ts: 12314525,
|
||||
state_key: "",
|
||||
room_id: roomOne,
|
||||
}],
|
||||
presence: [{
|
||||
content: {},
|
||||
type: "m.presence",
|
||||
sender: userA,
|
||||
}],
|
||||
});
|
||||
httpBackend!.when("GET", "/events").respond(200, { chunk: [] });
|
||||
|
||||
const prom = client!.peekInRoom(roomOne);
|
||||
await httpBackend!.flushAllExpected();
|
||||
const room = await prom;
|
||||
|
||||
expect(room.roomId).toBe(roomOne);
|
||||
expect(room.getMyMembership()).toBe("leave");
|
||||
expect(room.name).toBe("Room Name");
|
||||
expect(room.currentState.getStateEvents("m.room.name", "").getId()).toBe("$eventId");
|
||||
expect(room.timeline[0].getContent().body).toBe("Message 1");
|
||||
expect(room.timeline[1].getContent().body).toBe("Message 2");
|
||||
client?.stopPeeking();
|
||||
httpBackend!.when("GET", "/events").respond(200, { chunk: [] });
|
||||
await httpBackend!.flushAllExpected();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* waits for the MatrixClient to emit one or more 'sync' events.
|
||||
*
|
||||
|
@ -1160,11 +1160,11 @@ describe("megolm", () => {
|
||||
"algorithm": 'm.megolm.v1.aes-sha2',
|
||||
"room_id": ROOM_ID,
|
||||
"sender_key": content.sender_key,
|
||||
"sender_claimed_ed25519_key": groupSessionKey.sender_claimed_ed25519_key,
|
||||
"sender_claimed_ed25519_key": groupSessionKey!.sender_claimed_ed25519_key,
|
||||
"session_id": content.session_id,
|
||||
"session_key": groupSessionKey.key,
|
||||
"chain_index": groupSessionKey.chain_index,
|
||||
"forwarding_curve25519_key_chain": groupSessionKey.forwarding_curve25519_key_chain,
|
||||
"session_key": groupSessionKey!.key,
|
||||
"chain_index": groupSessionKey!.chain_index,
|
||||
"forwarding_curve25519_key_chain": groupSessionKey!.forwarding_curve25519_key_chain,
|
||||
"org.matrix.msc3061.shared_history": true,
|
||||
},
|
||||
plaintype: 'm.forwarded_room_key',
|
||||
@ -1298,11 +1298,11 @@ describe("megolm", () => {
|
||||
"algorithm": 'm.megolm.v1.aes-sha2',
|
||||
"room_id": ROOM_ID,
|
||||
"sender_key": content.sender_key,
|
||||
"sender_claimed_ed25519_key": groupSessionKey.sender_claimed_ed25519_key,
|
||||
"sender_claimed_ed25519_key": groupSessionKey!.sender_claimed_ed25519_key,
|
||||
"session_id": content.session_id,
|
||||
"session_key": groupSessionKey.key,
|
||||
"chain_index": groupSessionKey.chain_index,
|
||||
"forwarding_curve25519_key_chain": groupSessionKey.forwarding_curve25519_key_chain,
|
||||
"session_key": groupSessionKey!.key,
|
||||
"chain_index": groupSessionKey!.chain_index,
|
||||
"forwarding_curve25519_key_chain": groupSessionKey!.forwarding_curve25519_key_chain,
|
||||
"org.matrix.msc3061.shared_history": true,
|
||||
},
|
||||
plaintype: 'm.forwarded_room_key',
|
||||
|
@ -468,7 +468,7 @@ describe("SlidingSyncSdk", () => {
|
||||
it("emits SyncState.Reconnecting when < FAILED_SYNC_ERROR_THRESHOLD & SyncState.Error when over", async () => {
|
||||
mockSlidingSync!.emit(
|
||||
SlidingSyncEvent.Lifecycle, SlidingSyncState.Complete,
|
||||
{ pos: "h", lists: [], rooms: {}, extensions: {} }, null,
|
||||
{ pos: "h", lists: [], rooms: {}, extensions: {} },
|
||||
);
|
||||
expect(sdk!.getSyncState()).toEqual(SyncState.Syncing);
|
||||
|
||||
@ -490,7 +490,6 @@ describe("SlidingSyncSdk", () => {
|
||||
SlidingSyncEvent.Lifecycle,
|
||||
SlidingSyncState.Complete,
|
||||
{ pos: "i", lists: [], rooms: {}, extensions: {} },
|
||||
null,
|
||||
);
|
||||
expect(sdk!.getSyncState()).toEqual(SyncState.Syncing);
|
||||
});
|
||||
|
@ -82,7 +82,7 @@ describe("SlidingSync", () => {
|
||||
|
||||
it("should reset the connection on HTTP 400 and send everything again", async () => {
|
||||
// seed the connection with some lists, extensions and subscriptions to verify they are sent again
|
||||
slidingSync = new SlidingSync(proxyBaseUrl, [], {}, client, 1);
|
||||
slidingSync = new SlidingSync(proxyBaseUrl, [], {}, client!, 1);
|
||||
const roomId = "!sub:localhost";
|
||||
const subInfo = {
|
||||
timeline_limit: 42,
|
||||
@ -108,7 +108,7 @@ describe("SlidingSync", () => {
|
||||
|
||||
// expect everything to be sent
|
||||
let txnId;
|
||||
httpBackend.when("POST", syncUrl).check(function(req) {
|
||||
httpBackend!.when("POST", syncUrl).check(function(req) {
|
||||
const body = req.data;
|
||||
logger.debug("got ", body);
|
||||
expect(body.room_subscriptions).toEqual({
|
||||
@ -117,7 +117,7 @@ describe("SlidingSync", () => {
|
||||
expect(body.lists[0]).toEqual(listInfo);
|
||||
expect(body.extensions).toBeTruthy();
|
||||
expect(body.extensions["custom_extension"]).toEqual({ initial: true });
|
||||
expect(req.queryParams["pos"]).toBeUndefined();
|
||||
expect(req.queryParams!["pos"]).toBeUndefined();
|
||||
txnId = body.txn_id;
|
||||
}).respond(200, function() {
|
||||
return {
|
||||
@ -127,10 +127,10 @@ describe("SlidingSync", () => {
|
||||
txn_id: txnId,
|
||||
};
|
||||
});
|
||||
await httpBackend.flushAllExpected();
|
||||
await httpBackend!.flushAllExpected();
|
||||
|
||||
// expect nothing but ranges and non-initial extensions to be sent
|
||||
httpBackend.when("POST", syncUrl).check(function(req) {
|
||||
httpBackend!.when("POST", syncUrl).check(function(req) {
|
||||
const body = req.data;
|
||||
logger.debug("got ", body);
|
||||
expect(body.room_subscriptions).toBeFalsy();
|
||||
@ -139,7 +139,7 @@ describe("SlidingSync", () => {
|
||||
});
|
||||
expect(body.extensions).toBeTruthy();
|
||||
expect(body.extensions["custom_extension"]).toEqual({ initial: false });
|
||||
expect(req.queryParams["pos"]).toEqual("11");
|
||||
expect(req.queryParams!["pos"]).toEqual("11");
|
||||
}).respond(200, function() {
|
||||
return {
|
||||
pos: "12",
|
||||
@ -147,19 +147,19 @@ describe("SlidingSync", () => {
|
||||
extensions: {},
|
||||
};
|
||||
});
|
||||
await httpBackend.flushAllExpected();
|
||||
await httpBackend!.flushAllExpected();
|
||||
|
||||
// now we expire the session
|
||||
httpBackend.when("POST", syncUrl).respond(400, function() {
|
||||
httpBackend!.when("POST", syncUrl).respond(400, function() {
|
||||
logger.debug("sending session expired 400");
|
||||
return {
|
||||
error: "HTTP 400 : session expired",
|
||||
};
|
||||
});
|
||||
await httpBackend.flushAllExpected();
|
||||
await httpBackend!.flushAllExpected();
|
||||
|
||||
// ...and everything should be sent again
|
||||
httpBackend.when("POST", syncUrl).check(function(req) {
|
||||
httpBackend!.when("POST", syncUrl).check(function(req) {
|
||||
const body = req.data;
|
||||
logger.debug("got ", body);
|
||||
expect(body.room_subscriptions).toEqual({
|
||||
@ -168,7 +168,7 @@ describe("SlidingSync", () => {
|
||||
expect(body.lists[0]).toEqual(listInfo);
|
||||
expect(body.extensions).toBeTruthy();
|
||||
expect(body.extensions["custom_extension"]).toEqual({ initial: true });
|
||||
expect(req.queryParams["pos"]).toBeUndefined();
|
||||
expect(req.queryParams!["pos"]).toBeUndefined();
|
||||
}).respond(200, function() {
|
||||
return {
|
||||
pos: "1",
|
||||
@ -176,7 +176,7 @@ describe("SlidingSync", () => {
|
||||
extensions: {},
|
||||
};
|
||||
});
|
||||
await httpBackend.flushAllExpected();
|
||||
await httpBackend!.flushAllExpected();
|
||||
slidingSync.stop();
|
||||
});
|
||||
});
|
||||
@ -415,7 +415,7 @@ describe("SlidingSync", () => {
|
||||
expect(slidingSync.getList(0)).toBeDefined();
|
||||
expect(slidingSync.getList(5)).toBeNull();
|
||||
expect(slidingSync.getListData(5)).toBeNull();
|
||||
const syncData = slidingSync.getListData(0);
|
||||
const syncData = slidingSync.getListData(0)!;
|
||||
expect(syncData.joinedCount).toEqual(500); // from previous test
|
||||
expect(syncData.roomIndexToRoomId).toEqual({
|
||||
0: roomA,
|
||||
@ -665,7 +665,7 @@ describe("SlidingSync", () => {
|
||||
0: roomB,
|
||||
1: roomC,
|
||||
};
|
||||
expect(slidingSync.getListData(0).roomIndexToRoomId).toEqual(indexToRoomId);
|
||||
expect(slidingSync.getListData(0)!.roomIndexToRoomId).toEqual(indexToRoomId);
|
||||
httpBackend!.when("POST", syncUrl).respond(200, {
|
||||
pos: "f",
|
||||
// currently the list is [B,C] so we will insert D then immediately delete it
|
||||
@ -703,7 +703,7 @@ describe("SlidingSync", () => {
|
||||
});
|
||||
|
||||
it("should handle deletions correctly", async () => {
|
||||
expect(slidingSync.getListData(0).roomIndexToRoomId).toEqual({
|
||||
expect(slidingSync.getListData(0)!.roomIndexToRoomId).toEqual({
|
||||
0: roomB,
|
||||
1: roomC,
|
||||
});
|
||||
@ -739,7 +739,7 @@ describe("SlidingSync", () => {
|
||||
});
|
||||
|
||||
it("should handle insertions correctly", async () => {
|
||||
expect(slidingSync.getListData(0).roomIndexToRoomId).toEqual({
|
||||
expect(slidingSync.getListData(0)!.roomIndexToRoomId).toEqual({
|
||||
0: roomC,
|
||||
});
|
||||
httpBackend!.when("POST", syncUrl).respond(200, {
|
||||
|
@ -135,7 +135,7 @@ export class MockMediaDeviceInfo {
|
||||
|
||||
export class MockMediaHandler {
|
||||
getUserMediaStream(audio: boolean, video: boolean) {
|
||||
const tracks = [];
|
||||
const tracks: MockMediaStreamTrack[] = [];
|
||||
if (audio) tracks.push(new MockMediaStreamTrack("audio_track", "audio"));
|
||||
if (video) tracks.push(new MockMediaStreamTrack("video_track", "video"));
|
||||
|
||||
|
@ -32,7 +32,7 @@ describe("NamespacedValue", () => {
|
||||
});
|
||||
|
||||
it("should have a falsey unstable if needed", () => {
|
||||
const ns = new NamespacedValue("stable", null);
|
||||
const ns = new NamespacedValue("stable");
|
||||
expect(ns.name).toBe(ns.stable);
|
||||
expect(ns.altName).toBeFalsy();
|
||||
expect(ns.names).toEqual([ns.stable]);
|
||||
@ -41,17 +41,17 @@ describe("NamespacedValue", () => {
|
||||
it("should match against either stable or unstable", () => {
|
||||
const ns = new NamespacedValue("stable", "unstable");
|
||||
expect(ns.matches("no")).toBe(false);
|
||||
expect(ns.matches(ns.stable)).toBe(true);
|
||||
expect(ns.matches(ns.unstable)).toBe(true);
|
||||
expect(ns.matches(ns.stable!)).toBe(true);
|
||||
expect(ns.matches(ns.unstable!)).toBe(true);
|
||||
});
|
||||
|
||||
it("should not permit falsey values for both parts", () => {
|
||||
try {
|
||||
new UnstableValue(null, null);
|
||||
new UnstableValue(null!, null!);
|
||||
// noinspection ExceptionCaughtLocallyJS
|
||||
throw new Error("Failed to fail");
|
||||
} catch (e) {
|
||||
expect(e.message).toBe("One of stable or unstable values must be supplied");
|
||||
expect((<Error>e).message).toBe("One of stable or unstable values must be supplied");
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -65,7 +65,7 @@ describe("UnstableValue", () => {
|
||||
});
|
||||
|
||||
it("should return unstable if there is no stable", () => {
|
||||
const ns = new UnstableValue(null, "unstable");
|
||||
const ns = new UnstableValue(null!, "unstable");
|
||||
expect(ns.name).toBe(ns.unstable);
|
||||
expect(ns.altName).toBeFalsy();
|
||||
expect(ns.names).toEqual([ns.unstable]);
|
||||
@ -73,11 +73,11 @@ describe("UnstableValue", () => {
|
||||
|
||||
it("should not permit falsey unstable values", () => {
|
||||
try {
|
||||
new UnstableValue("stable", null);
|
||||
new UnstableValue("stable", null!);
|
||||
// noinspection ExceptionCaughtLocallyJS
|
||||
throw new Error("Failed to fail");
|
||||
} catch (e) {
|
||||
expect(e.message).toBe("Unstable value must be supplied");
|
||||
expect((<Error>e).message).toBe("Unstable value must be supplied");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -678,7 +678,7 @@ describe("AutoDiscovery", function() {
|
||||
|
||||
it("should return FAIL_PROMPT for connection errors", () => {
|
||||
const httpBackend = getHttpBackend();
|
||||
httpBackend.when("GET", "/.well-known/matrix/client").fail(0, undefined);
|
||||
httpBackend.when("GET", "/.well-known/matrix/client").fail(0, undefined!);
|
||||
return Promise.all([
|
||||
httpBackend.flushAllExpected(),
|
||||
AutoDiscovery.findClientConfig("example.org").then((conf) => {
|
||||
|
@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { getHttpUriForMxc } from "../../src/content-repo";
|
||||
|
||||
describe("ContentRepo", function() {
|
||||
|
@ -2,6 +2,7 @@ import '../olm-loader';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
import type { PkDecryption, PkSigning } from "@matrix-org/olm";
|
||||
import { MatrixClient } from "../../src/client";
|
||||
import { Crypto } from "../../src/crypto";
|
||||
import { MemoryCryptoStore } from "../../src/crypto/store/memory-crypto-store";
|
||||
@ -32,7 +33,7 @@ function awaitEvent(emitter, event) {
|
||||
async function keyshareEventForEvent(client, event, index): Promise<MatrixEvent> {
|
||||
const roomId = event.getRoomId();
|
||||
const eventContent = event.getWireContent();
|
||||
const key = await client.crypto.olmDevice.getInboundGroupSessionKey(
|
||||
const key = await client.crypto!.olmDevice.getInboundGroupSessionKey(
|
||||
roomId,
|
||||
eventContent.sender_key,
|
||||
eventContent.session_id,
|
||||
@ -68,10 +69,10 @@ async function keyshareEventForEvent(client, event, index): Promise<MatrixEvent>
|
||||
function roomKeyEventForEvent(client: MatrixClient, event: MatrixEvent): MatrixEvent {
|
||||
const roomId = event.getRoomId();
|
||||
const eventContent = event.getWireContent();
|
||||
const key = client.crypto.olmDevice.getOutboundGroupSessionKey(eventContent.session_id);
|
||||
const key = client.crypto!.olmDevice.getOutboundGroupSessionKey(eventContent.session_id);
|
||||
const ksEvent = new MatrixEvent({
|
||||
type: "m.room_key",
|
||||
sender: client.getUserId(),
|
||||
sender: client.getUserId()!,
|
||||
content: {
|
||||
"algorithm": olmlib.MEGOLM_ALGORITHM,
|
||||
"room_id": roomId,
|
||||
@ -146,7 +147,7 @@ describe("Crypto", function() {
|
||||
'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI';
|
||||
device.keys["ed25519:FLIBBLE"] =
|
||||
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
|
||||
client.crypto.deviceList.getDeviceByIdentityKey = () => device;
|
||||
client.crypto!.deviceList.getDeviceByIdentityKey = () => device;
|
||||
|
||||
encryptionInfo = client.getEventEncryptionInfo(event);
|
||||
expect(encryptionInfo.encrypted).toBeTruthy();
|
||||
@ -334,7 +335,7 @@ describe("Crypto", function() {
|
||||
await Promise.all(events.map(async (event) => {
|
||||
// alice encrypts each event, and then bob tries to decrypt
|
||||
// them without any keys, so that they'll be in pending
|
||||
await aliceClient.crypto.encryptEvent(event, aliceRoom);
|
||||
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
|
||||
// remove keys from the event
|
||||
// @ts-ignore private properties
|
||||
event.clearEvent = undefined;
|
||||
@ -343,17 +344,17 @@ describe("Crypto", function() {
|
||||
// @ts-ignore private properties
|
||||
event.claimedEd25519Key = null;
|
||||
try {
|
||||
await bobClient.crypto.decryptEvent(event);
|
||||
await bobClient.crypto!.decryptEvent(event);
|
||||
} catch (e) {
|
||||
// we expect this to fail because we don't have the
|
||||
// decryption keys yet
|
||||
}
|
||||
}));
|
||||
|
||||
const device = new DeviceInfo(aliceClient.deviceId);
|
||||
bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
|
||||
const device = new DeviceInfo(aliceClient.deviceId!);
|
||||
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
|
||||
|
||||
const bobDecryptor = bobClient.crypto.getRoomDecryptor(
|
||||
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
|
||||
roomId, olmlib.MEGOLM_ALGORITHM,
|
||||
);
|
||||
|
||||
@ -365,14 +366,14 @@ describe("Crypto", function() {
|
||||
// the first message can't be decrypted yet, but the second one
|
||||
// can
|
||||
let ksEvent = await keyshareEventForEvent(aliceClient, events[1], 1);
|
||||
bobClient.crypto.deviceList.downloadKeys = () => Promise.resolve({});
|
||||
bobClient.crypto.deviceList.getUserByIdentityKey = () => "@alice:example.com";
|
||||
bobClient.crypto!.deviceList.downloadKeys = () => Promise.resolve({});
|
||||
bobClient.crypto!.deviceList.getUserByIdentityKey = () => "@alice:example.com";
|
||||
await bobDecryptor.onRoomKeyEvent(ksEvent);
|
||||
await decryptEventsPromise;
|
||||
expect(events[0].getContent().msgtype).toBe("m.bad.encrypted");
|
||||
expect(events[1].getContent().msgtype).not.toBe("m.bad.encrypted");
|
||||
|
||||
const cryptoStore = bobClient.crypto.cryptoStore;
|
||||
const cryptoStore = bobClient.crypto!.cryptoStore;
|
||||
const eventContent = events[0].getWireContent();
|
||||
const senderKey = eventContent.sender_key;
|
||||
const sessionId = eventContent.session_id;
|
||||
@ -437,7 +438,7 @@ describe("Crypto", function() {
|
||||
});
|
||||
// alice encrypts each event, and then bob tries to decrypt
|
||||
// them without any keys, so that they'll be in pending
|
||||
await aliceClient.crypto.encryptEvent(event, aliceRoom);
|
||||
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
|
||||
// remove keys from the event
|
||||
// @ts-ignore private property
|
||||
event.clearEvent = undefined;
|
||||
@ -446,24 +447,24 @@ describe("Crypto", function() {
|
||||
// @ts-ignore private property
|
||||
event.claimedEd25519Key = null;
|
||||
try {
|
||||
await bobClient.crypto.decryptEvent(event);
|
||||
await bobClient.crypto!.decryptEvent(event);
|
||||
} catch (e) {
|
||||
// we expect this to fail because we don't have the
|
||||
// decryption keys yet
|
||||
}
|
||||
|
||||
const device = new DeviceInfo(aliceClient.deviceId);
|
||||
bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
|
||||
const device = new DeviceInfo(aliceClient.deviceId!);
|
||||
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
|
||||
|
||||
const bobDecryptor = bobClient.crypto.getRoomDecryptor(
|
||||
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
|
||||
roomId, olmlib.MEGOLM_ALGORITHM,
|
||||
);
|
||||
|
||||
const ksEvent = await keyshareEventForEvent(aliceClient, event, 1);
|
||||
ksEvent.getContent().sender_key = undefined; // test
|
||||
bobClient.crypto.olmDevice.addInboundGroupSession = jest.fn();
|
||||
bobClient.crypto!.olmDevice.addInboundGroupSession = jest.fn();
|
||||
await bobDecryptor.onRoomKeyEvent(ksEvent);
|
||||
expect(bobClient.crypto.olmDevice.addInboundGroupSession).not.toHaveBeenCalled();
|
||||
expect(bobClient.crypto!.olmDevice.addInboundGroupSession).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("creates a new keyshare request if we request a keyshare", async function() {
|
||||
@ -479,7 +480,7 @@ describe("Crypto", function() {
|
||||
},
|
||||
});
|
||||
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
|
||||
const cryptoStore = aliceClient.crypto.cryptoStore;
|
||||
const cryptoStore = aliceClient.crypto!.cryptoStore;
|
||||
const roomKeyRequestBody = {
|
||||
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||
room_id: "!someroom",
|
||||
@ -514,7 +515,7 @@ describe("Crypto", function() {
|
||||
// let the client set up enough for that to happen, so gut-wrench a bit
|
||||
// to force it to send now.
|
||||
// @ts-ignore
|
||||
aliceClient.crypto.outgoingRoomKeyRequestManager.sendQueuedRequests();
|
||||
aliceClient.crypto!.outgoingRoomKeyRequestManager.sendQueuedRequests();
|
||||
jest.runAllTimers();
|
||||
await Promise.resolve();
|
||||
expect(aliceSendToDevice).toBeCalledTimes(1);
|
||||
@ -571,7 +572,7 @@ describe("Crypto", function() {
|
||||
await Promise.all(events.map(async (event) => {
|
||||
// alice encrypts each event, and then bob tries to decrypt
|
||||
// them without any keys, so that they'll be in pending
|
||||
await aliceClient.crypto.encryptEvent(event, aliceRoom);
|
||||
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
|
||||
// remove keys from the event
|
||||
// @ts-ignore private properties
|
||||
event.clearEvent = undefined;
|
||||
@ -580,18 +581,18 @@ describe("Crypto", function() {
|
||||
// @ts-ignore private properties
|
||||
event.claimedEd25519Key = null;
|
||||
try {
|
||||
await bobClient.crypto.decryptEvent(event);
|
||||
await bobClient.crypto!.decryptEvent(event);
|
||||
} catch (e) {
|
||||
// we expect this to fail because we don't have the
|
||||
// decryption keys yet
|
||||
}
|
||||
}));
|
||||
|
||||
const device = new DeviceInfo(aliceClient.deviceId);
|
||||
bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
|
||||
bobClient.crypto.deviceList.getUserByIdentityKey = () => "@alice:example.com";
|
||||
const device = new DeviceInfo(aliceClient.deviceId!);
|
||||
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
|
||||
bobClient.crypto!.deviceList.getUserByIdentityKey = () => "@alice:example.com";
|
||||
|
||||
const cryptoStore = bobClient.crypto.cryptoStore;
|
||||
const cryptoStore = bobClient.crypto!.cryptoStore;
|
||||
const eventContent = events[0].getWireContent();
|
||||
const senderKey = eventContent.sender_key;
|
||||
const sessionId = eventContent.session_id;
|
||||
@ -604,11 +605,11 @@ describe("Crypto", function() {
|
||||
const outgoingReq = await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody);
|
||||
expect(outgoingReq).toBeDefined();
|
||||
await cryptoStore.updateOutgoingRoomKeyRequest(
|
||||
outgoingReq.requestId, RoomKeyRequestState.Unsent,
|
||||
outgoingReq!.requestId, RoomKeyRequestState.Unsent,
|
||||
{ state: RoomKeyRequestState.Sent },
|
||||
);
|
||||
|
||||
const bobDecryptor = bobClient.crypto.getRoomDecryptor(
|
||||
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
|
||||
roomId, olmlib.MEGOLM_ALGORITHM,
|
||||
);
|
||||
|
||||
@ -617,7 +618,7 @@ describe("Crypto", function() {
|
||||
}));
|
||||
const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
|
||||
await bobDecryptor.onRoomKeyEvent(ksEvent);
|
||||
const key = await bobClient.crypto.olmDevice.getInboundGroupSessionKey(
|
||||
const key = await bobClient.crypto!.olmDevice.getInboundGroupSessionKey(
|
||||
roomId,
|
||||
events[0].getWireContent().sender_key,
|
||||
events[0].getWireContent().session_id,
|
||||
@ -675,7 +676,7 @@ describe("Crypto", function() {
|
||||
await Promise.all(events.map(async (event) => {
|
||||
// alice encrypts each event, and then bob tries to decrypt
|
||||
// them without any keys, so that they'll be in pending
|
||||
await aliceClient.crypto.encryptEvent(event, aliceRoom);
|
||||
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
|
||||
// remove keys from the event
|
||||
// @ts-ignore private properties
|
||||
event.clearEvent = undefined;
|
||||
@ -684,18 +685,18 @@ describe("Crypto", function() {
|
||||
// @ts-ignore private properties
|
||||
event.claimedEd25519Key = null;
|
||||
try {
|
||||
await bobClient.crypto.decryptEvent(event);
|
||||
await bobClient.crypto!.decryptEvent(event);
|
||||
} catch (e) {
|
||||
// we expect this to fail because we don't have the
|
||||
// decryption keys yet
|
||||
}
|
||||
}));
|
||||
|
||||
const device = new DeviceInfo(claraClient.deviceId);
|
||||
bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
|
||||
bobClient.crypto.deviceList.getUserByIdentityKey = () => "@clara:example.com";
|
||||
const device = new DeviceInfo(claraClient.deviceId!);
|
||||
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
|
||||
bobClient.crypto!.deviceList.getUserByIdentityKey = () => "@clara:example.com";
|
||||
|
||||
const bobDecryptor = bobClient.crypto.getRoomDecryptor(
|
||||
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
|
||||
roomId, olmlib.MEGOLM_ALGORITHM,
|
||||
);
|
||||
|
||||
@ -703,10 +704,10 @@ describe("Crypto", function() {
|
||||
return awaitEvent(ev, "Event.decrypted");
|
||||
}));
|
||||
const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
|
||||
ksEvent.event.sender = claraClient.getUserId(),
|
||||
ksEvent.sender = new RoomMember(roomId, claraClient.getUserId());
|
||||
ksEvent.event.sender = claraClient.getUserId()!;
|
||||
ksEvent.sender = new RoomMember(roomId, claraClient.getUserId()!);
|
||||
await bobDecryptor.onRoomKeyEvent(ksEvent);
|
||||
const key = await bobClient.crypto.olmDevice.getInboundGroupSessionKey(
|
||||
const key = await bobClient.crypto!.olmDevice.getInboundGroupSessionKey(
|
||||
roomId,
|
||||
events[0].getWireContent().sender_key,
|
||||
events[0].getWireContent().session_id,
|
||||
@ -753,7 +754,7 @@ describe("Crypto", function() {
|
||||
await Promise.all(events.map(async (event) => {
|
||||
// alice encrypts each event, and then bob tries to decrypt
|
||||
// them without any keys, so that they'll be in pending
|
||||
await aliceClient.crypto.encryptEvent(event, aliceRoom);
|
||||
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
|
||||
// remove keys from the event
|
||||
// @ts-ignore private properties
|
||||
event.clearEvent = undefined;
|
||||
@ -762,19 +763,19 @@ describe("Crypto", function() {
|
||||
// @ts-ignore private properties
|
||||
event.claimedEd25519Key = null;
|
||||
try {
|
||||
await bobClient.crypto.decryptEvent(event);
|
||||
await bobClient.crypto!.decryptEvent(event);
|
||||
} catch (e) {
|
||||
// we expect this to fail because we don't have the
|
||||
// decryption keys yet
|
||||
}
|
||||
}));
|
||||
|
||||
const device = new DeviceInfo(claraClient.deviceId);
|
||||
const device = new DeviceInfo(claraClient.deviceId!);
|
||||
device.verified = DeviceInfo.DeviceVerification.VERIFIED;
|
||||
bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
|
||||
bobClient.crypto.deviceList.getUserByIdentityKey = () => "@bob:example.com";
|
||||
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
|
||||
bobClient.crypto!.deviceList.getUserByIdentityKey = () => "@bob:example.com";
|
||||
|
||||
const bobDecryptor = bobClient.crypto.getRoomDecryptor(
|
||||
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
|
||||
roomId, olmlib.MEGOLM_ALGORITHM,
|
||||
);
|
||||
|
||||
@ -782,10 +783,10 @@ describe("Crypto", function() {
|
||||
return awaitEvent(ev, "Event.decrypted");
|
||||
}));
|
||||
const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
|
||||
ksEvent.event.sender = bobClient.getUserId(),
|
||||
ksEvent.sender = new RoomMember(roomId, bobClient.getUserId());
|
||||
ksEvent.event.sender = bobClient.getUserId()!;
|
||||
ksEvent.sender = new RoomMember(roomId, bobClient.getUserId()!);
|
||||
await bobDecryptor.onRoomKeyEvent(ksEvent);
|
||||
const key = await bobClient.crypto.olmDevice.getInboundGroupSessionKey(
|
||||
const key = await bobClient.crypto!.olmDevice.getInboundGroupSessionKey(
|
||||
roomId,
|
||||
events[0].getWireContent().sender_key,
|
||||
events[0].getWireContent().session_id,
|
||||
@ -835,7 +836,7 @@ describe("Crypto", function() {
|
||||
await Promise.all(events.map(async (event) => {
|
||||
// alice encrypts each event, and then bob tries to decrypt
|
||||
// them without any keys, so that they'll be in pending
|
||||
await aliceClient.crypto.encryptEvent(event, aliceRoom);
|
||||
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
|
||||
// remove keys from the event
|
||||
// @ts-ignore private properties
|
||||
event.clearEvent = undefined;
|
||||
@ -844,26 +845,26 @@ describe("Crypto", function() {
|
||||
// @ts-ignore private properties
|
||||
event.claimedEd25519Key = null;
|
||||
try {
|
||||
await bobClient.crypto.decryptEvent(event);
|
||||
await bobClient.crypto!.decryptEvent(event);
|
||||
} catch (e) {
|
||||
// we expect this to fail because we don't have the
|
||||
// decryption keys yet
|
||||
}
|
||||
}));
|
||||
|
||||
const device = new DeviceInfo(claraClient.deviceId);
|
||||
bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
|
||||
bobClient.crypto.deviceList.getUserByIdentityKey = () => "@alice:example.com";
|
||||
const device = new DeviceInfo(claraClient.deviceId!);
|
||||
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
|
||||
bobClient.crypto!.deviceList.getUserByIdentityKey = () => "@alice:example.com";
|
||||
|
||||
const bobDecryptor = bobClient.crypto.getRoomDecryptor(
|
||||
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
|
||||
roomId, olmlib.MEGOLM_ALGORITHM,
|
||||
);
|
||||
|
||||
const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
|
||||
ksEvent.event.sender = claraClient.getUserId(),
|
||||
ksEvent.sender = new RoomMember(roomId, claraClient.getUserId());
|
||||
ksEvent.event.sender = claraClient.getUserId()!;
|
||||
ksEvent.sender = new RoomMember(roomId, claraClient.getUserId()!);
|
||||
await bobDecryptor.onRoomKeyEvent(ksEvent);
|
||||
const key = await bobClient.crypto.olmDevice.getInboundGroupSessionKey(
|
||||
const key = await bobClient.crypto!.olmDevice.getInboundGroupSessionKey(
|
||||
roomId,
|
||||
events[0].getWireContent().sender_key,
|
||||
events[0].getWireContent().session_id,
|
||||
@ -904,7 +905,7 @@ describe("Crypto", function() {
|
||||
await Promise.all(events.map(async (event) => {
|
||||
// alice encrypts each event, and then bob tries to decrypt
|
||||
// them without any keys, so that they'll be in pending
|
||||
await aliceClient.crypto.encryptEvent(event, aliceRoom);
|
||||
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
|
||||
// remove keys from the event
|
||||
// @ts-ignore private properties
|
||||
event.clearEvent = undefined;
|
||||
@ -914,11 +915,11 @@ describe("Crypto", function() {
|
||||
event.claimedEd25519Key = null;
|
||||
}));
|
||||
|
||||
const device = new DeviceInfo(aliceClient.deviceId);
|
||||
bobClient.crypto.deviceList.getDeviceByIdentityKey = () => device;
|
||||
bobClient.crypto.deviceList.getUserByIdentityKey = () => "@alice:example.com";
|
||||
const device = new DeviceInfo(aliceClient.deviceId!);
|
||||
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
|
||||
bobClient.crypto!.deviceList.getUserByIdentityKey = () => "@alice:example.com";
|
||||
|
||||
const bobDecryptor = bobClient.crypto.getRoomDecryptor(
|
||||
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
|
||||
roomId, olmlib.MEGOLM_ALGORITHM,
|
||||
);
|
||||
|
||||
@ -926,25 +927,25 @@ describe("Crypto", function() {
|
||||
|
||||
const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
|
||||
await bobDecryptor.onRoomKeyEvent(ksEvent);
|
||||
const bobKey = await bobClient.crypto.olmDevice.getInboundGroupSessionKey(
|
||||
const bobKey = await bobClient.crypto!.olmDevice.getInboundGroupSessionKey(
|
||||
roomId,
|
||||
content.sender_key,
|
||||
content.session_id,
|
||||
);
|
||||
expect(bobKey).toBeNull();
|
||||
|
||||
const aliceKey = await aliceClient.crypto.olmDevice.getInboundGroupSessionKey(
|
||||
const aliceKey = await aliceClient.crypto!.olmDevice.getInboundGroupSessionKey(
|
||||
roomId,
|
||||
content.sender_key,
|
||||
content.session_id,
|
||||
);
|
||||
const parked = await bobClient.crypto.cryptoStore.takeParkedSharedHistory(roomId);
|
||||
const parked = await bobClient.crypto!.cryptoStore.takeParkedSharedHistory(roomId);
|
||||
expect(parked).toEqual([{
|
||||
senderId: aliceClient.getUserId(),
|
||||
senderKey: content.sender_key,
|
||||
sessionId: content.session_id,
|
||||
sessionKey: aliceKey.key,
|
||||
keysClaimed: { ed25519: aliceKey.sender_claimed_ed25519_key },
|
||||
sessionKey: aliceKey!.key,
|
||||
keysClaimed: { ed25519: aliceKey!.sender_claimed_ed25519_key },
|
||||
forwardingCurve25519KeyChain: ["akey"],
|
||||
}]);
|
||||
});
|
||||
@ -956,19 +957,19 @@ describe("Crypto", function() {
|
||||
jest.setTimeout(10000);
|
||||
const client = (new TestClient("@a:example.com", "dev")).client;
|
||||
await client.initCrypto();
|
||||
client.crypto.getSecretStorageKey = jest.fn().mockResolvedValue(null);
|
||||
client.crypto.isCrossSigningReady = async () => false;
|
||||
client.crypto.baseApis.uploadDeviceSigningKeys = jest.fn().mockResolvedValue(null);
|
||||
client.crypto.baseApis.setAccountData = jest.fn().mockResolvedValue(null);
|
||||
client.crypto.baseApis.uploadKeySignatures = jest.fn();
|
||||
client.crypto.baseApis.http.authedRequest = jest.fn();
|
||||
client.crypto!.getSecretStorageKey = jest.fn().mockResolvedValue(null);
|
||||
client.crypto!.isCrossSigningReady = async () => false;
|
||||
client.crypto!.baseApis.uploadDeviceSigningKeys = jest.fn().mockResolvedValue(null);
|
||||
client.crypto!.baseApis.setAccountData = jest.fn().mockResolvedValue(null);
|
||||
client.crypto!.baseApis.uploadKeySignatures = jest.fn();
|
||||
client.crypto!.baseApis.http.authedRequest = jest.fn();
|
||||
const createSecretStorageKey = async () => {
|
||||
return {
|
||||
keyInfo: undefined, // Returning undefined here used to cause a crash
|
||||
privateKey: Uint8Array.of(32, 33),
|
||||
};
|
||||
};
|
||||
await client.crypto.bootstrapSecretStorage({
|
||||
await client.crypto!.bootstrapSecretStorage({
|
||||
createSecretStorageKey,
|
||||
});
|
||||
client.stopClient();
|
||||
@ -995,7 +996,7 @@ describe("Crypto", function() {
|
||||
|
||||
encryptedPayload = {
|
||||
algorithm: "m.olm.v1.curve25519-aes-sha2",
|
||||
sender_key: client.client.crypto.olmDevice.deviceCurve25519Key,
|
||||
sender_key: client.client.crypto!.olmDevice.deviceCurve25519Key,
|
||||
ciphertext: { plaintext: JSON.stringify(payload) },
|
||||
};
|
||||
});
|
||||
@ -1075,4 +1076,50 @@ describe("Crypto", function() {
|
||||
client.httpBackend.verifyNoOutstandingRequests();
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkSecretStoragePrivateKey", () => {
|
||||
let client: TestClient;
|
||||
|
||||
beforeEach(async () => {
|
||||
client = new TestClient("@alice:example.org", "aliceweb");
|
||||
await client.client.initCrypto();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await client.stop();
|
||||
});
|
||||
|
||||
it("should free PkDecryption", () => {
|
||||
const free = jest.fn();
|
||||
jest.spyOn(Olm, "PkDecryption").mockImplementation(() => ({
|
||||
init_with_private_key: jest.fn(),
|
||||
free,
|
||||
}) as unknown as PkDecryption);
|
||||
client.client.checkSecretStoragePrivateKey(new Uint8Array(), "");
|
||||
expect(free).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkCrossSigningPrivateKey", () => {
|
||||
let client: TestClient;
|
||||
|
||||
beforeEach(async () => {
|
||||
client = new TestClient("@alice:example.org", "aliceweb");
|
||||
await client.client.initCrypto();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await client.stop();
|
||||
});
|
||||
|
||||
it("should free PkSigning", () => {
|
||||
const free = jest.fn();
|
||||
jest.spyOn(Olm, "PkSigning").mockImplementation(() => ({
|
||||
init_with_seed: jest.fn(),
|
||||
free,
|
||||
}) as unknown as PkSigning);
|
||||
client.client.checkCrossSigningPrivateKey(new Uint8Array(), "");
|
||||
expect(free).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -247,14 +247,14 @@ describe.each([
|
||||
const olmDevice = new OlmDevice(store);
|
||||
const { getCrossSigningKeyCache, storeCrossSigningKeyCache } =
|
||||
createCryptoStoreCacheCallbacks(store, olmDevice);
|
||||
await storeCrossSigningKeyCache("self_signing", testKey);
|
||||
await storeCrossSigningKeyCache!("self_signing", testKey);
|
||||
|
||||
// If we've not saved anything, don't expect anything
|
||||
// Definitely don't accidentally return the wrong key for the type
|
||||
const nokey = await getCrossSigningKeyCache("self", "");
|
||||
const nokey = await getCrossSigningKeyCache!("self", "");
|
||||
expect(nokey).toBeNull();
|
||||
|
||||
const key = await getCrossSigningKeyCache("self_signing", "");
|
||||
const key = await getCrossSigningKeyCache!("self_signing", "");
|
||||
expect(new Uint8Array(key)).toEqual(testKey);
|
||||
});
|
||||
});
|
||||
|
@ -90,7 +90,7 @@ const signedDeviceList2: IDownloadKeyResult = {
|
||||
describe('DeviceList', function() {
|
||||
let downloadSpy;
|
||||
let cryptoStore;
|
||||
let deviceLists = [];
|
||||
let deviceLists: DeviceList[] = [];
|
||||
|
||||
beforeEach(function() {
|
||||
deviceLists = [];
|
||||
|
@ -32,8 +32,8 @@ import { ClientEvent, MatrixClient, RoomMember } from '../../../../src';
|
||||
import { DeviceInfo, IDevice } from '../../../../src/crypto/deviceinfo';
|
||||
import { DeviceTrustLevel } from '../../../../src/crypto/CrossSigning';
|
||||
|
||||
const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get('m.megolm.v1.aes-sha2');
|
||||
const MegolmEncryption = algorithms.ENCRYPTION_CLASSES.get('m.megolm.v1.aes-sha2');
|
||||
const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get('m.megolm.v1.aes-sha2')!;
|
||||
const MegolmEncryption = algorithms.ENCRYPTION_CLASSES.get('m.megolm.v1.aes-sha2')!;
|
||||
|
||||
const ROOM_ID = '!ROOM:ID';
|
||||
|
||||
@ -331,7 +331,7 @@ describe("MegolmDecryption", function() {
|
||||
},
|
||||
},
|
||||
});
|
||||
mockBaseApis.sendToDevice.mockResolvedValue(undefined);
|
||||
mockBaseApis.sendToDevice.mockResolvedValue({});
|
||||
mockBaseApis.queueToDevice.mockResolvedValue(undefined);
|
||||
|
||||
aliceDeviceInfo = {
|
||||
@ -515,8 +515,8 @@ describe("MegolmDecryption", function() {
|
||||
bobdevice1: {
|
||||
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
|
||||
keys: {
|
||||
"ed25519:Dynabook": bobDevice1.deviceEd25519Key,
|
||||
"curve25519:Dynabook": bobDevice1.deviceCurve25519Key,
|
||||
"ed25519:Dynabook": bobDevice1.deviceEd25519Key!,
|
||||
"curve25519:Dynabook": bobDevice1.deviceCurve25519Key!,
|
||||
},
|
||||
verified: 0,
|
||||
known: false,
|
||||
@ -524,8 +524,8 @@ describe("MegolmDecryption", function() {
|
||||
bobdevice2: {
|
||||
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
|
||||
keys: {
|
||||
"ed25519:Dynabook": bobDevice2.deviceEd25519Key,
|
||||
"curve25519:Dynabook": bobDevice2.deviceCurve25519Key,
|
||||
"ed25519:Dynabook": bobDevice2.deviceEd25519Key!,
|
||||
"curve25519:Dynabook": bobDevice2.deviceCurve25519Key!,
|
||||
},
|
||||
verified: -1,
|
||||
known: false,
|
||||
@ -614,8 +614,8 @@ describe("MegolmDecryption", function() {
|
||||
bobdevice: {
|
||||
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
|
||||
keys: {
|
||||
"ed25519:bobdevice": bobDevice.deviceEd25519Key,
|
||||
"curve25519:bobdevice": bobDevice.deviceCurve25519Key,
|
||||
"ed25519:bobdevice": bobDevice.deviceEd25519Key!,
|
||||
"curve25519:bobdevice": bobDevice.deviceCurve25519Key!,
|
||||
},
|
||||
verified: 0,
|
||||
known: true,
|
||||
@ -718,8 +718,8 @@ describe("MegolmDecryption", function() {
|
||||
device_id: "bobdevice",
|
||||
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
|
||||
keys: {
|
||||
"ed25519:bobdevice": bobDevice.deviceEd25519Key,
|
||||
"curve25519:bobdevice": bobDevice.deviceCurve25519Key,
|
||||
"ed25519:bobdevice": bobDevice.deviceEd25519Key!,
|
||||
"curve25519:bobdevice": bobDevice.deviceCurve25519Key!,
|
||||
},
|
||||
known: true,
|
||||
verified: 1,
|
||||
|
@ -67,13 +67,13 @@ describe("OlmDevice", function() {
|
||||
const sid = await setupSession(aliceOlmDevice, bobOlmDevice);
|
||||
|
||||
const ciphertext = await aliceOlmDevice.encryptMessage(
|
||||
bobOlmDevice.deviceCurve25519Key,
|
||||
bobOlmDevice.deviceCurve25519Key!,
|
||||
sid,
|
||||
"The olm or proteus is an aquatic salamander in the family Proteidae",
|
||||
) as any; // OlmDevice.encryptMessage has incorrect return type
|
||||
|
||||
const result = await bobOlmDevice.createInboundSession(
|
||||
aliceOlmDevice.deviceCurve25519Key,
|
||||
aliceOlmDevice.deviceCurve25519Key!,
|
||||
ciphertext.type,
|
||||
ciphertext.body,
|
||||
);
|
||||
@ -94,7 +94,7 @@ describe("OlmDevice", function() {
|
||||
+ " in the family Proteidae"
|
||||
);
|
||||
const ciphertext = await aliceOlmDevice.encryptMessage(
|
||||
bobOlmDevice.deviceCurve25519Key,
|
||||
bobOlmDevice.deviceCurve25519Key!,
|
||||
sessionId,
|
||||
MESSAGE,
|
||||
) as any; // OlmDevice.encryptMessage has incorrect return type
|
||||
@ -103,7 +103,7 @@ describe("OlmDevice", function() {
|
||||
bobRecreatedOlmDevice.init({ fromExportedDevice: exported });
|
||||
|
||||
const decrypted = await bobRecreatedOlmDevice.createInboundSession(
|
||||
aliceOlmDevice.deviceCurve25519Key,
|
||||
aliceOlmDevice.deviceCurve25519Key!,
|
||||
ciphertext.type,
|
||||
ciphertext.body,
|
||||
);
|
||||
@ -118,7 +118,7 @@ describe("OlmDevice", function() {
|
||||
+ " the olm is entirely aquatic"
|
||||
);
|
||||
const ciphertext2 = await aliceOlmDevice.encryptMessage(
|
||||
bobOlmDevice.deviceCurve25519Key,
|
||||
bobOlmDevice.deviceCurve25519Key!,
|
||||
sessionId,
|
||||
MESSAGE_2,
|
||||
) as any; // OlmDevice.encryptMessage has incorrect return type
|
||||
@ -128,7 +128,7 @@ describe("OlmDevice", function() {
|
||||
|
||||
// Note: "decrypted_2" does not have the same structure as "decrypted"
|
||||
const decrypted2 = await bobRecreatedAgainOlmDevice.decryptMessage(
|
||||
aliceOlmDevice.deviceCurve25519Key,
|
||||
aliceOlmDevice.deviceCurve25519Key!,
|
||||
decrypted.session_id,
|
||||
ciphertext2.type,
|
||||
ciphertext2.body,
|
||||
|
@ -34,7 +34,7 @@ import { MatrixScheduler } from '../../../src';
|
||||
|
||||
const Olm = global.Olm;
|
||||
|
||||
const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get('m.megolm.v1.aes-sha2');
|
||||
const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get('m.megolm.v1.aes-sha2')!;
|
||||
|
||||
const ROOM_ID = '!ROOM:ID';
|
||||
|
||||
@ -197,7 +197,7 @@ describe("MegolmBackup", function() {
|
||||
// to tick the clock between the first try and the retry.
|
||||
const realSetTimeout = global.setTimeout;
|
||||
jest.spyOn(global, 'setTimeout').mockImplementation(function(f, n) {
|
||||
return realSetTimeout(f!, n/100);
|
||||
return realSetTimeout(f!, n!/100);
|
||||
});
|
||||
});
|
||||
|
||||
@ -318,7 +318,7 @@ describe("MegolmBackup", function() {
|
||||
resolve();
|
||||
return Promise.resolve({} as T);
|
||||
};
|
||||
client.crypto.backupManager.backupGroupSession(
|
||||
client.crypto!.backupManager.backupGroupSession(
|
||||
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
||||
groupSession.session_id(),
|
||||
);
|
||||
@ -349,7 +349,7 @@ describe("MegolmBackup", function() {
|
||||
|
||||
return client.initCrypto()
|
||||
.then(() => {
|
||||
return client.crypto.storeSessionBackupPrivateKey(new Uint8Array(32));
|
||||
return client.crypto!.storeSessionBackupPrivateKey(new Uint8Array(32));
|
||||
})
|
||||
.then(() => {
|
||||
return cryptoStore.doTxn(
|
||||
@ -401,7 +401,7 @@ describe("MegolmBackup", function() {
|
||||
resolve();
|
||||
return Promise.resolve({} as T);
|
||||
};
|
||||
client.crypto.backupManager.backupGroupSession(
|
||||
client.crypto!.backupManager.backupGroupSession(
|
||||
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
||||
groupSession.session_id(),
|
||||
);
|
||||
@ -449,7 +449,7 @@ describe("MegolmBackup", function() {
|
||||
try {
|
||||
// make sure auth_data is signed by the master key
|
||||
olmlib.pkVerify(
|
||||
(data as Record<string, any>).auth_data, client.getCrossSigningId(), "@alice:bar",
|
||||
(data as Record<string, any>).auth_data, client.getCrossSigningId()!, "@alice:bar",
|
||||
);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
@ -568,7 +568,7 @@ describe("MegolmBackup", function() {
|
||||
);
|
||||
}
|
||||
};
|
||||
return client.crypto.backupManager.backupGroupSession(
|
||||
return client.crypto!.backupManager.backupGroupSession(
|
||||
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
||||
groupSession.session_id(),
|
||||
);
|
||||
@ -699,4 +699,30 @@ describe("MegolmBackup", function() {
|
||||
)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("flagAllGroupSessionsForBackup", () => {
|
||||
it("should return number of sesions needing backup", async () => {
|
||||
const scheduler = [
|
||||
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
|
||||
"setProcessFunction",
|
||||
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {}) as MockedObject<MatrixScheduler>;
|
||||
const store = new StubStore();
|
||||
const client = new MatrixClient({
|
||||
baseUrl: "https://my.home.server",
|
||||
idBaseUrl: "https://identity.server",
|
||||
accessToken: "my.access.token",
|
||||
fetchFn: jest.fn(), // NOP
|
||||
store,
|
||||
scheduler,
|
||||
userId: "@alice:bar",
|
||||
deviceId: "device",
|
||||
cryptoStore,
|
||||
});
|
||||
await client.initCrypto();
|
||||
|
||||
cryptoStore.countSessionsNeedingBackup = jest.fn().mockReturnValue(6);
|
||||
await expect(client.flagAllGroupSessionsForBackup()).resolves.toBe(6);
|
||||
client.stopClient();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -93,8 +93,8 @@ describe("Cross Signing", function() {
|
||||
);
|
||||
alice.uploadDeviceSigningKeys = jest.fn().mockImplementation(async (auth, keys) => {
|
||||
await olmlib.verifySignature(
|
||||
alice.crypto.olmDevice, keys.master_key, "@alice:example.com",
|
||||
"Osborne2", alice.crypto.olmDevice.deviceEd25519Key,
|
||||
alice.crypto!.olmDevice, keys.master_key, "@alice:example.com",
|
||||
"Osborne2", alice.crypto!.olmDevice.deviceEd25519Key!,
|
||||
);
|
||||
});
|
||||
alice.uploadKeySignatures = async () => ({ failures: {} });
|
||||
@ -152,7 +152,7 @@ describe("Cross Signing", function() {
|
||||
authUploadDeviceSigningKeys,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.errcode === "M_FORBIDDEN") {
|
||||
if ((<MatrixError>e).errcode === "M_FORBIDDEN") {
|
||||
bootstrapDidThrow = true;
|
||||
}
|
||||
}
|
||||
@ -169,7 +169,7 @@ describe("Cross Signing", function() {
|
||||
// set Alice's cross-signing key
|
||||
await resetCrossSigningKeys(alice);
|
||||
// Alice downloads Bob's device key
|
||||
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
||||
alice.crypto!.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
||||
keys: {
|
||||
master: {
|
||||
user_id: "@bob:example.com",
|
||||
@ -238,12 +238,12 @@ describe("Cross Signing", function() {
|
||||
alice.uploadKeySignatures = jest.fn().mockImplementation(async (content) => {
|
||||
try {
|
||||
await olmlib.verifySignature(
|
||||
alice.crypto.olmDevice,
|
||||
alice.crypto!.olmDevice,
|
||||
content["@alice:example.com"][
|
||||
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
|
||||
],
|
||||
"@alice:example.com",
|
||||
"Osborne2", alice.crypto.olmDevice.deviceEd25519Key,
|
||||
"Osborne2", alice.crypto!.olmDevice.deviceEd25519Key!,
|
||||
);
|
||||
olmlib.pkVerify(
|
||||
content["@alice:example.com"]["Osborne2"],
|
||||
@ -258,7 +258,7 @@ describe("Cross Signing", function() {
|
||||
});
|
||||
|
||||
// @ts-ignore private property
|
||||
const deviceInfo = alice.crypto.deviceList.devices["@alice:example.com"]
|
||||
const deviceInfo = alice.crypto!.deviceList.devices["@alice:example.com"]
|
||||
.Osborne2;
|
||||
const aliceDevice = {
|
||||
user_id: "@alice:example.com",
|
||||
@ -266,7 +266,7 @@ describe("Cross Signing", function() {
|
||||
keys: deviceInfo.keys,
|
||||
algorithms: deviceInfo.algorithms,
|
||||
};
|
||||
await alice.crypto.signObject(aliceDevice);
|
||||
await alice.crypto!.signObject(aliceDevice);
|
||||
olmlib.pkSign(
|
||||
aliceDevice as ISignedKey,
|
||||
selfSigningKey as unknown as PkSigning,
|
||||
@ -401,7 +401,7 @@ describe("Cross Signing", function() {
|
||||
["ed25519:" + bobMasterPubkey]: sskSig,
|
||||
},
|
||||
};
|
||||
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
||||
alice.crypto!.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
||||
keys: {
|
||||
master: {
|
||||
user_id: "@bob:example.com",
|
||||
@ -435,7 +435,7 @@ describe("Cross Signing", function() {
|
||||
verified: 0,
|
||||
known: false,
|
||||
};
|
||||
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
|
||||
alice.crypto!.deviceList.storeDevicesForUser("@bob:example.com", {
|
||||
Dynabook: bobDevice,
|
||||
});
|
||||
// Bob's device key should be TOFU
|
||||
@ -467,11 +467,11 @@ describe("Cross Signing", function() {
|
||||
const aliceKeys: Record<string, PkSigning> = {};
|
||||
const { client: alice, httpBackend } = await makeTestClient(
|
||||
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
||||
null,
|
||||
undefined,
|
||||
aliceKeys,
|
||||
);
|
||||
alice.crypto.deviceList.startTrackingDeviceList("@bob:example.com");
|
||||
alice.crypto.deviceList.stopTrackingAllDeviceLists = () => {};
|
||||
alice.crypto!.deviceList.startTrackingDeviceList("@bob:example.com");
|
||||
alice.crypto!.deviceList.stopTrackingAllDeviceLists = () => {};
|
||||
alice.uploadDeviceSigningKeys = async () => ({});
|
||||
alice.uploadKeySignatures = async () => ({ failures: {} });
|
||||
|
||||
@ -486,7 +486,7 @@ describe("Cross Signing", function() {
|
||||
]);
|
||||
|
||||
const keyChangePromise = new Promise<void>((resolve, reject) => {
|
||||
alice.crypto.deviceList.once(CryptoEvent.UserCrossSigningUpdated, (userId) => {
|
||||
alice.crypto!.deviceList.once(CryptoEvent.UserCrossSigningUpdated, (userId) => {
|
||||
if (userId === "@bob:example.com") {
|
||||
resolve();
|
||||
}
|
||||
@ -494,7 +494,7 @@ describe("Cross Signing", function() {
|
||||
});
|
||||
|
||||
// @ts-ignore private property
|
||||
const deviceInfo = alice.crypto.deviceList.devices["@alice:example.com"]
|
||||
const deviceInfo = alice.crypto!.deviceList.devices["@alice:example.com"]
|
||||
.Osborne2;
|
||||
const aliceDevice = {
|
||||
user_id: "@alice:example.com",
|
||||
@ -502,7 +502,7 @@ describe("Cross Signing", function() {
|
||||
keys: deviceInfo.keys,
|
||||
algorithms: deviceInfo.algorithms,
|
||||
};
|
||||
await alice.crypto.signObject(aliceDevice);
|
||||
await alice.crypto!.signObject(aliceDevice);
|
||||
|
||||
const bobOlmAccount = new global.Olm.Account();
|
||||
bobOlmAccount.create();
|
||||
@ -667,7 +667,7 @@ describe("Cross Signing", function() {
|
||||
["ed25519:" + bobMasterPubkey]: sskSig,
|
||||
},
|
||||
};
|
||||
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
||||
alice.crypto!.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
||||
keys: {
|
||||
master: {
|
||||
user_id: "@bob:example.com",
|
||||
@ -690,7 +690,7 @@ describe("Cross Signing", function() {
|
||||
"ed25519:Dynabook": "someOtherPubkey",
|
||||
},
|
||||
};
|
||||
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
|
||||
alice.crypto!.deviceList.storeDevicesForUser("@bob:example.com", {
|
||||
Dynabook: bobDevice as unknown as IDevice,
|
||||
});
|
||||
// Bob's device key should be untrusted
|
||||
@ -735,7 +735,7 @@ describe("Cross Signing", function() {
|
||||
["ed25519:" + bobMasterPubkey]: sskSig,
|
||||
},
|
||||
};
|
||||
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
||||
alice.crypto!.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
||||
keys: {
|
||||
master: {
|
||||
user_id: "@bob:example.com",
|
||||
@ -770,7 +770,7 @@ describe("Cross Signing", function() {
|
||||
},
|
||||
},
|
||||
};
|
||||
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
|
||||
alice.crypto!.deviceList.storeDevicesForUser("@bob:example.com", {
|
||||
Dynabook: bobDevice,
|
||||
});
|
||||
// Alice verifies Bob's SSK
|
||||
@ -802,7 +802,7 @@ describe("Cross Signing", function() {
|
||||
["ed25519:" + bobMasterPubkey2]: sskSig2,
|
||||
},
|
||||
};
|
||||
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
||||
alice.crypto!.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
||||
keys: {
|
||||
master: {
|
||||
user_id: "@bob:example.com",
|
||||
@ -838,8 +838,8 @@ describe("Cross Signing", function() {
|
||||
|
||||
// Alice gets new signature for device
|
||||
const sig2 = bobSigning2.sign(bobDeviceString);
|
||||
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey2] = sig2;
|
||||
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
|
||||
bobDevice.signatures!["@bob:example.com"]["ed25519:" + bobPubkey2] = sig2;
|
||||
alice.crypto!.deviceList.storeDevicesForUser("@bob:example.com", {
|
||||
Dynabook: bobDevice,
|
||||
});
|
||||
|
||||
@ -876,20 +876,20 @@ describe("Cross Signing", function() {
|
||||
bob.uploadKeySignatures = async () => ({ failures: {} });
|
||||
// set Bob's cross-signing key
|
||||
await resetCrossSigningKeys(bob);
|
||||
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
|
||||
alice.crypto!.deviceList.storeDevicesForUser("@bob:example.com", {
|
||||
Dynabook: {
|
||||
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
|
||||
keys: {
|
||||
"curve25519:Dynabook": bob.crypto.olmDevice.deviceCurve25519Key,
|
||||
"ed25519:Dynabook": bob.crypto.olmDevice.deviceEd25519Key,
|
||||
"curve25519:Dynabook": bob.crypto!.olmDevice.deviceCurve25519Key!,
|
||||
"ed25519:Dynabook": bob.crypto!.olmDevice.deviceEd25519Key!,
|
||||
},
|
||||
verified: 1,
|
||||
known: true,
|
||||
},
|
||||
});
|
||||
alice.crypto.deviceList.storeCrossSigningForUser(
|
||||
alice.crypto!.deviceList.storeCrossSigningForUser(
|
||||
"@bob:example.com",
|
||||
bob.crypto.crossSigningInfo.toStorage(),
|
||||
bob.crypto!.crossSigningInfo.toStorage(),
|
||||
);
|
||||
|
||||
alice.uploadDeviceSigningKeys = async () => ({});
|
||||
@ -909,8 +909,8 @@ describe("Cross Signing", function() {
|
||||
expect(bobTrust.isTofu()).toBeTruthy();
|
||||
|
||||
// "forget" that Bob is trusted
|
||||
delete alice.crypto.deviceList.crossSigningInfo["@bob:example.com"]
|
||||
.keys.master.signatures["@alice:example.com"];
|
||||
delete alice.crypto!.deviceList.crossSigningInfo["@bob:example.com"]
|
||||
.keys.master.signatures!["@alice:example.com"];
|
||||
|
||||
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
|
||||
expect(bobTrust2.isCrossSigningVerified()).toBeFalsy();
|
||||
@ -919,9 +919,9 @@ describe("Cross Signing", function() {
|
||||
upgradePromise = new Promise((resolve) => {
|
||||
upgradeResolveFunc = resolve;
|
||||
});
|
||||
alice.crypto.deviceList.emit(CryptoEvent.UserCrossSigningUpdated, "@bob:example.com");
|
||||
alice.crypto!.deviceList.emit(CryptoEvent.UserCrossSigningUpdated, "@bob:example.com");
|
||||
await new Promise((resolve) => {
|
||||
alice.crypto.on(CryptoEvent.UserTrustStatusChanged, resolve);
|
||||
alice.crypto!.on(CryptoEvent.UserTrustStatusChanged, resolve);
|
||||
});
|
||||
await upgradePromise;
|
||||
|
||||
@ -963,7 +963,7 @@ describe("Cross Signing", function() {
|
||||
};
|
||||
|
||||
// Alice's device downloads the keys, but doesn't trust them yet
|
||||
alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", {
|
||||
alice.crypto!.deviceList.storeCrossSigningForUser("@alice:example.com", {
|
||||
keys: {
|
||||
master: {
|
||||
user_id: "@alice:example.com",
|
||||
@ -999,7 +999,7 @@ describe("Cross Signing", function() {
|
||||
["ed25519:" + alicePubkey]: sig,
|
||||
},
|
||||
} };
|
||||
alice.crypto.deviceList.storeDevicesForUser("@alice:example.com", {
|
||||
alice.crypto!.deviceList.storeDevicesForUser("@alice:example.com", {
|
||||
[aliceDeviceId]: aliceCrossSignedDevice,
|
||||
});
|
||||
|
||||
@ -1042,7 +1042,7 @@ describe("Cross Signing", function() {
|
||||
};
|
||||
|
||||
// Alice's device downloads the keys
|
||||
alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", {
|
||||
alice.crypto!.deviceList.storeCrossSigningForUser("@alice:example.com", {
|
||||
keys: {
|
||||
master: {
|
||||
user_id: "@alice:example.com",
|
||||
@ -1067,11 +1067,65 @@ describe("Cross Signing", function() {
|
||||
"ed25519:Dynabook": "someOtherPubkey",
|
||||
},
|
||||
};
|
||||
alice.crypto.deviceList.storeDevicesForUser("@alice:example.com", {
|
||||
alice.crypto!.deviceList.storeDevicesForUser("@alice:example.com", {
|
||||
[deviceId]: aliceNotCrossSignedDevice,
|
||||
});
|
||||
|
||||
expect(alice.checkIfOwnDeviceCrossSigned(deviceId)).toBeFalsy();
|
||||
alice.stopClient();
|
||||
});
|
||||
|
||||
it("checkIfOwnDeviceCrossSigned should sanely handle unknown devices", async () => {
|
||||
const { client: alice } = await makeTestClient(
|
||||
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
||||
);
|
||||
alice.uploadDeviceSigningKeys = async () => ({});
|
||||
alice.uploadKeySignatures = async () => ({ failures: {} });
|
||||
|
||||
// Generate Alice's SSK etc
|
||||
const aliceMasterSigning = new global.Olm.PkSigning();
|
||||
const aliceMasterPrivkey = aliceMasterSigning.generate_seed();
|
||||
const aliceMasterPubkey = aliceMasterSigning.init_with_seed(aliceMasterPrivkey);
|
||||
const aliceSigning = new global.Olm.PkSigning();
|
||||
const alicePrivkey = aliceSigning.generate_seed();
|
||||
const alicePubkey = aliceSigning.init_with_seed(alicePrivkey);
|
||||
const aliceSSK: ICrossSigningKey = {
|
||||
user_id: "@alice:example.com",
|
||||
usage: ["self_signing"],
|
||||
keys: {
|
||||
["ed25519:" + alicePubkey]: alicePubkey,
|
||||
},
|
||||
};
|
||||
const sskSig = aliceMasterSigning.sign(anotherjson.stringify(aliceSSK));
|
||||
aliceSSK.signatures = {
|
||||
"@alice:example.com": {
|
||||
["ed25519:" + aliceMasterPubkey]: sskSig,
|
||||
},
|
||||
};
|
||||
|
||||
// Alice's device downloads the keys
|
||||
alice.crypto!.deviceList.storeCrossSigningForUser("@alice:example.com", {
|
||||
keys: {
|
||||
master: {
|
||||
user_id: "@alice:example.com",
|
||||
usage: ["master"],
|
||||
keys: {
|
||||
["ed25519:" + aliceMasterPubkey]: aliceMasterPubkey,
|
||||
},
|
||||
},
|
||||
self_signing: aliceSSK,
|
||||
},
|
||||
firstUse: true,
|
||||
crossSigningVerifiedBefore: false,
|
||||
});
|
||||
|
||||
expect(alice.checkIfOwnDeviceCrossSigned("notadevice")).toBeFalsy();
|
||||
alice.stopClient();
|
||||
});
|
||||
|
||||
it("checkIfOwnDeviceCrossSigned should sanely handle unknown users", async () => {
|
||||
const { client: alice } = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" });
|
||||
expect(alice.checkIfOwnDeviceCrossSigned("notadevice")).toBeFalsy();
|
||||
alice.stopClient();
|
||||
});
|
||||
});
|
||||
|
@ -39,7 +39,7 @@ export async function createSecretStorageKey(): Promise<IRecoveryKey> {
|
||||
decryption.free();
|
||||
return {
|
||||
// `pubkey` not used anymore with symmetric 4S
|
||||
keyInfo: { pubkey: storagePublicKey, key: undefined },
|
||||
keyInfo: { pubkey: storagePublicKey, key: undefined! },
|
||||
privateKey: storagePrivateKey,
|
||||
};
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ describe.each([
|
||||
await store.getOutgoingRoomKeyRequestByState([RoomKeyRequestState.Sent]);
|
||||
expect(r).not.toBeNull();
|
||||
expect(r).not.toBeUndefined();
|
||||
expect(r.state).toEqual(RoomKeyRequestState.Sent);
|
||||
expect(r!.state).toEqual(RoomKeyRequestState.Sent);
|
||||
expect(requests).toContainEqual(r);
|
||||
});
|
||||
});
|
||||
|
@ -21,9 +21,9 @@ import { MatrixEvent } from "../../../src/models/event";
|
||||
import { TestClient } from '../../TestClient';
|
||||
import { makeTestClients } from './verification/util';
|
||||
import { encryptAES } from "../../../src/crypto/aes";
|
||||
import { resetCrossSigningKeys, createSecretStorageKey } from "./crypto-utils";
|
||||
import { createSecretStorageKey, resetCrossSigningKeys } from "./crypto-utils";
|
||||
import { logger } from '../../../src/logger';
|
||||
import { ICreateClientOpts } from '../../../src/client';
|
||||
import { ClientEvent, ICreateClientOpts } from '../../../src/client';
|
||||
import { ISecretStorageKeyInfo } from '../../../src/crypto/api';
|
||||
import { DeviceInfo } from '../../../src/crypto/deviceinfo';
|
||||
|
||||
@ -41,7 +41,7 @@ async function makeTestClient(userInfo: { userId: string, deviceId: string}, opt
|
||||
await client.initCrypto();
|
||||
|
||||
// No need to download keys for these tests
|
||||
jest.spyOn(client.crypto, 'downloadKeys').mockResolvedValue({});
|
||||
jest.spyOn(client.crypto!, 'downloadKeys').mockResolvedValue({});
|
||||
|
||||
return client;
|
||||
}
|
||||
@ -93,11 +93,11 @@ describe("Secrets", function() {
|
||||
},
|
||||
},
|
||||
);
|
||||
alice.crypto.crossSigningInfo.setKeys({
|
||||
alice.crypto!.crossSigningInfo.setKeys({
|
||||
master: signingkeyInfo,
|
||||
});
|
||||
|
||||
const secretStorage = alice.crypto.secretStorage;
|
||||
const secretStorage = alice.crypto!.secretStorage;
|
||||
|
||||
jest.spyOn(alice, 'setAccountData').mockImplementation(
|
||||
async function(eventType, contents) {
|
||||
@ -113,7 +113,7 @@ describe("Secrets", function() {
|
||||
const keyAccountData = {
|
||||
algorithm: SECRET_STORAGE_ALGORITHM_V1_AES,
|
||||
};
|
||||
await alice.crypto.crossSigningInfo.signObject(keyAccountData, 'master');
|
||||
await alice.crypto!.crossSigningInfo.signObject(keyAccountData, 'master');
|
||||
|
||||
alice.store.storeAccountDataEvents([
|
||||
new MatrixEvent({
|
||||
@ -200,7 +200,7 @@ describe("Secrets", function() {
|
||||
await alice.storeSecret("foo", "bar");
|
||||
|
||||
const accountData = alice.getAccountData('foo');
|
||||
expect(accountData.getContent().encrypted).toBeTruthy();
|
||||
expect(accountData!.getContent().encrypted).toBeTruthy();
|
||||
alice.stopClient();
|
||||
});
|
||||
|
||||
@ -233,29 +233,29 @@ describe("Secrets", function() {
|
||||
},
|
||||
);
|
||||
|
||||
const vaxDevice = vax.client.crypto.olmDevice;
|
||||
const osborne2Device = osborne2.client.crypto.olmDevice;
|
||||
const secretStorage = osborne2.client.crypto.secretStorage;
|
||||
const vaxDevice = vax.client.crypto!.olmDevice;
|
||||
const osborne2Device = osborne2.client.crypto!.olmDevice;
|
||||
const secretStorage = osborne2.client.crypto!.secretStorage;
|
||||
|
||||
osborne2.client.crypto.deviceList.storeDevicesForUser("@alice:example.com", {
|
||||
osborne2.client.crypto!.deviceList.storeDevicesForUser("@alice:example.com", {
|
||||
"VAX": {
|
||||
known: false,
|
||||
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
|
||||
keys: {
|
||||
"ed25519:VAX": vaxDevice.deviceEd25519Key,
|
||||
"curve25519:VAX": vaxDevice.deviceCurve25519Key,
|
||||
"ed25519:VAX": vaxDevice.deviceEd25519Key!,
|
||||
"curve25519:VAX": vaxDevice.deviceCurve25519Key!,
|
||||
},
|
||||
verified: DeviceInfo.DeviceVerification.VERIFIED,
|
||||
},
|
||||
});
|
||||
vax.client.crypto.deviceList.storeDevicesForUser("@alice:example.com", {
|
||||
vax.client.crypto!.deviceList.storeDevicesForUser("@alice:example.com", {
|
||||
"Osborne2": {
|
||||
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
|
||||
verified: 0,
|
||||
known: false,
|
||||
keys: {
|
||||
"ed25519:Osborne2": osborne2Device.deviceEd25519Key,
|
||||
"curve25519:Osborne2": osborne2Device.deviceCurve25519Key,
|
||||
"ed25519:Osborne2": osborne2Device.deviceEd25519Key!,
|
||||
"curve25519:Osborne2": osborne2Device.deviceCurve25519Key!,
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -264,13 +264,13 @@ describe("Secrets", function() {
|
||||
const otks = (await osborne2Device.getOneTimeKeys()).curve25519;
|
||||
await osborne2Device.markKeysAsPublished();
|
||||
|
||||
await vax.client.crypto.olmDevice.createOutboundSession(
|
||||
osborne2Device.deviceCurve25519Key,
|
||||
await vax.client.crypto!.olmDevice.createOutboundSession(
|
||||
osborne2Device.deviceCurve25519Key!,
|
||||
Object.values(otks)[0],
|
||||
);
|
||||
|
||||
osborne2.client.crypto.deviceList.downloadKeys = () => Promise.resolve({});
|
||||
osborne2.client.crypto.deviceList.getUserByIdentityKey = () => "@alice:example.com";
|
||||
osborne2.client.crypto!.deviceList.downloadKeys = () => Promise.resolve({});
|
||||
osborne2.client.crypto!.deviceList.getUserByIdentityKey = () => "@alice:example.com";
|
||||
|
||||
const request = await secretStorage.request("foo", ["VAX"]);
|
||||
await request.promise; // return value not used
|
||||
@ -328,7 +328,7 @@ describe("Secrets", function() {
|
||||
this.store.storeAccountDataEvents([
|
||||
event,
|
||||
]);
|
||||
this.emit("accountData", event);
|
||||
this.emit(ClientEvent.AccountData, event);
|
||||
return {};
|
||||
};
|
||||
|
||||
@ -339,8 +339,8 @@ describe("Secrets", function() {
|
||||
createSecretStorageKey,
|
||||
});
|
||||
|
||||
const crossSigning = bob.crypto.crossSigningInfo;
|
||||
const secretStorage = bob.crypto.secretStorage;
|
||||
const crossSigning = bob.crypto!.crossSigningInfo;
|
||||
const secretStorage = bob.crypto!.secretStorage;
|
||||
|
||||
expect(crossSigning.getId()).toBeTruthy();
|
||||
expect(await crossSigning.isStoredInSecretStorage(secretStorage))
|
||||
@ -486,7 +486,7 @@ describe("Secrets", function() {
|
||||
},
|
||||
}),
|
||||
]);
|
||||
alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", {
|
||||
alice.crypto!.deviceList.storeCrossSigningForUser("@alice:example.com", {
|
||||
firstUse: false,
|
||||
crossSigningVerifiedBefore: false,
|
||||
keys: {
|
||||
@ -528,16 +528,15 @@ describe("Secrets", function() {
|
||||
content: data,
|
||||
});
|
||||
alice.store.storeAccountDataEvents([event]);
|
||||
this.emit("accountData", event);
|
||||
this.emit(ClientEvent.AccountData, event);
|
||||
return {};
|
||||
};
|
||||
|
||||
await alice.bootstrapSecretStorage({});
|
||||
|
||||
expect(alice.getAccountData("m.secret_storage.default_key").getContent())
|
||||
expect(alice.getAccountData("m.secret_storage.default_key")!.getContent())
|
||||
.toEqual({ key: "key_id" });
|
||||
const keyInfo = alice.getAccountData("m.secret_storage.key.key_id")
|
||||
.getContent() as ISecretStorageKeyInfo;
|
||||
const keyInfo = alice.getAccountData("m.secret_storage.key.key_id")!.getContent<ISecretStorageKeyInfo>();
|
||||
expect(keyInfo.algorithm)
|
||||
.toEqual("m.secret_storage.v1.aes-hmac-sha2");
|
||||
expect(keyInfo.passphrase).toEqual({
|
||||
@ -630,7 +629,7 @@ describe("Secrets", function() {
|
||||
},
|
||||
}),
|
||||
]);
|
||||
alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", {
|
||||
alice.crypto!.deviceList.storeCrossSigningForUser("@alice:example.com", {
|
||||
firstUse: false,
|
||||
crossSigningVerifiedBefore: false,
|
||||
keys: {
|
||||
@ -672,14 +671,13 @@ describe("Secrets", function() {
|
||||
content: data,
|
||||
});
|
||||
alice.store.storeAccountDataEvents([event]);
|
||||
this.emit("accountData", event);
|
||||
this.emit(ClientEvent.AccountData, event);
|
||||
return {};
|
||||
};
|
||||
|
||||
await alice.bootstrapSecretStorage({});
|
||||
|
||||
const backupKey = alice.getAccountData("m.megolm_backup.v1")
|
||||
.getContent();
|
||||
const backupKey = alice.getAccountData("m.megolm_backup.v1")!.getContent();
|
||||
expect(backupKey.encrypted).toHaveProperty("key_id");
|
||||
expect(await alice.getSecret("m.megolm_backup.v1"))
|
||||
.toEqual("ey0GB1kB6jhOWgwiBUMIWg==");
|
||||
|
@ -49,7 +49,7 @@ describe("verification request integration tests with crypto layer", function()
|
||||
verificationMethods: [verificationMethods.SAS],
|
||||
},
|
||||
);
|
||||
alice.client.crypto.deviceList.getRawStoredDevicesForUser = function() {
|
||||
alice.client.crypto!.deviceList.getRawStoredDevicesForUser = function() {
|
||||
return {
|
||||
Dynabook: {
|
||||
algorithms: [],
|
||||
|
@ -17,16 +17,17 @@ limitations under the License.
|
||||
import "../../../olm-loader";
|
||||
import { makeTestClients, setupWebcrypto, teardownWebcrypto } from './util';
|
||||
import { MatrixEvent } from "../../../../src/models/event";
|
||||
import { SAS } from "../../../../src/crypto/verification/SAS";
|
||||
import { ISasEvent, SAS, SasEvent } from "../../../../src/crypto/verification/SAS";
|
||||
import { DeviceInfo } from "../../../../src/crypto/deviceinfo";
|
||||
import { CryptoEvent, verificationMethods } from "../../../../src/crypto";
|
||||
import * as olmlib from "../../../../src/crypto/olmlib";
|
||||
import { logger } from "../../../../src/logger";
|
||||
import { resetCrossSigningKeys } from "../crypto-utils";
|
||||
import { VerificationBase } from "../../../../src/crypto/verification/Base";
|
||||
import { VerificationBase as Verification, VerificationBase } from "../../../../src/crypto/verification/Base";
|
||||
import { IVerificationChannel } from "../../../../src/crypto/verification/request/Channel";
|
||||
import { MatrixClient } from "../../../../src";
|
||||
import { VerificationRequest } from "../../../../src/crypto/verification/request/VerificationRequest";
|
||||
import { TestClient } from "../../../TestClient";
|
||||
|
||||
const Olm = global.Olm;
|
||||
|
||||
@ -75,13 +76,13 @@ describe("SAS verification", function() {
|
||||
});
|
||||
|
||||
describe("verification", () => {
|
||||
let alice;
|
||||
let bob;
|
||||
let aliceSasEvent;
|
||||
let bobSasEvent;
|
||||
let aliceVerifier;
|
||||
let bobPromise;
|
||||
let clearTestClientTimeouts;
|
||||
let alice: TestClient;
|
||||
let bob: TestClient;
|
||||
let aliceSasEvent: ISasEvent | null;
|
||||
let bobSasEvent: ISasEvent | null;
|
||||
let aliceVerifier: Verification<any, any>;
|
||||
let bobPromise: Promise<VerificationBase<any, any>>;
|
||||
let clearTestClientTimeouts: () => void;
|
||||
|
||||
beforeEach(async () => {
|
||||
[[alice, bob], clearTestClientTimeouts] = await makeTestClients(
|
||||
@ -94,8 +95,8 @@ describe("SAS verification", function() {
|
||||
},
|
||||
);
|
||||
|
||||
const aliceDevice = alice.client.crypto.olmDevice;
|
||||
const bobDevice = bob.client.crypto.olmDevice;
|
||||
const aliceDevice = alice.client.crypto!.olmDevice;
|
||||
const bobDevice = bob.client.crypto!.olmDevice;
|
||||
|
||||
ALICE_DEVICES = {
|
||||
Osborne2: {
|
||||
@ -121,26 +122,26 @@ describe("SAS verification", function() {
|
||||
},
|
||||
};
|
||||
|
||||
alice.client.crypto.deviceList.storeDevicesForUser(
|
||||
alice.client.crypto!.deviceList.storeDevicesForUser(
|
||||
"@bob:example.com", BOB_DEVICES,
|
||||
);
|
||||
alice.client.downloadKeys = () => {
|
||||
return Promise.resolve();
|
||||
return Promise.resolve({});
|
||||
};
|
||||
|
||||
bob.client.crypto.deviceList.storeDevicesForUser(
|
||||
bob.client.crypto!.deviceList.storeDevicesForUser(
|
||||
"@alice:example.com", ALICE_DEVICES,
|
||||
);
|
||||
bob.client.downloadKeys = () => {
|
||||
return Promise.resolve();
|
||||
return Promise.resolve({});
|
||||
};
|
||||
|
||||
aliceSasEvent = null;
|
||||
bobSasEvent = null;
|
||||
|
||||
bobPromise = new Promise((resolve, reject) => {
|
||||
bob.client.on("crypto.verification.request", request => {
|
||||
request.verifier.on("show_sas", (e) => {
|
||||
bobPromise = new Promise<VerificationBase<any, any>>((resolve, reject) => {
|
||||
bob.client.on(CryptoEvent.VerificationRequest, request => {
|
||||
request.verifier!.on("show_sas", (e) => {
|
||||
if (!e.sas.emoji || !e.sas.decimal) {
|
||||
e.cancel();
|
||||
} else if (!aliceSasEvent) {
|
||||
@ -156,14 +157,14 @@ describe("SAS verification", function() {
|
||||
}
|
||||
}
|
||||
});
|
||||
resolve(request.verifier);
|
||||
resolve(request.verifier!);
|
||||
});
|
||||
});
|
||||
|
||||
aliceVerifier = alice.client.beginKeyVerification(
|
||||
verificationMethods.SAS, bob.client.getUserId(), bob.deviceId,
|
||||
verificationMethods.SAS, bob.client.getUserId()!, bob.deviceId!,
|
||||
);
|
||||
aliceVerifier.on("show_sas", (e) => {
|
||||
aliceVerifier.on(SasEvent.ShowSas, (e) => {
|
||||
if (!e.sas.emoji || !e.sas.decimal) {
|
||||
e.cancel();
|
||||
} else if (!bobSasEvent) {
|
||||
@ -195,9 +196,9 @@ describe("SAS verification", function() {
|
||||
const origSendToDevice = bob.client.sendToDevice.bind(bob.client);
|
||||
bob.client.sendToDevice = function(type, map) {
|
||||
if (type === "m.key.verification.accept") {
|
||||
macMethod = map[alice.client.getUserId()][alice.client.deviceId]
|
||||
macMethod = map[alice.client.getUserId()!][alice.client.deviceId!]
|
||||
.message_authentication_code;
|
||||
keyAgreement = map[alice.client.getUserId()][alice.client.deviceId]
|
||||
keyAgreement = map[alice.client.getUserId()!][alice.client.deviceId!]
|
||||
.key_agreement_protocol;
|
||||
}
|
||||
return origSendToDevice(type, map);
|
||||
@ -219,8 +220,8 @@ describe("SAS verification", function() {
|
||||
await Promise.all([
|
||||
aliceVerifier.verify(),
|
||||
bobPromise.then((verifier) => verifier.verify()),
|
||||
alice.httpBackend.flush(),
|
||||
bob.httpBackend.flush(),
|
||||
alice.httpBackend.flush(undefined),
|
||||
bob.httpBackend.flush(undefined),
|
||||
]);
|
||||
|
||||
// make sure that it uses the preferred method
|
||||
@ -230,10 +231,10 @@ describe("SAS verification", function() {
|
||||
// make sure Alice and Bob verified each other
|
||||
const bobDevice
|
||||
= await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
|
||||
expect(bobDevice.isVerified()).toBeTruthy();
|
||||
expect(bobDevice?.isVerified()).toBeTruthy();
|
||||
const aliceDevice
|
||||
= await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
|
||||
expect(aliceDevice.isVerified()).toBeTruthy();
|
||||
expect(aliceDevice?.isVerified()).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should be able to verify using the old base64", async () => {
|
||||
@ -248,7 +249,7 @@ describe("SAS verification", function() {
|
||||
// has, since it is the same object. If this does not
|
||||
// happen, the verification will fail due to a hash
|
||||
// commitment mismatch.
|
||||
map[bob.client.getUserId()][bob.client.deviceId]
|
||||
map[bob.client.getUserId()!][bob.client.deviceId!]
|
||||
.message_authentication_codes = ['hkdf-hmac-sha256'];
|
||||
}
|
||||
return aliceOrigSendToDevice(type, map);
|
||||
@ -256,7 +257,7 @@ describe("SAS verification", function() {
|
||||
const bobOrigSendToDevice = bob.client.sendToDevice.bind(bob.client);
|
||||
bob.client.sendToDevice = (type, map) => {
|
||||
if (type === "m.key.verification.accept") {
|
||||
macMethod = map[alice.client.getUserId()][alice.client.deviceId]
|
||||
macMethod = map[alice.client.getUserId()!][alice.client.deviceId!]
|
||||
.message_authentication_code;
|
||||
}
|
||||
return bobOrigSendToDevice(type, map);
|
||||
@ -278,18 +279,18 @@ describe("SAS verification", function() {
|
||||
await Promise.all([
|
||||
aliceVerifier.verify(),
|
||||
bobPromise.then((verifier) => verifier.verify()),
|
||||
alice.httpBackend.flush(),
|
||||
bob.httpBackend.flush(),
|
||||
alice.httpBackend.flush(undefined),
|
||||
bob.httpBackend.flush(undefined),
|
||||
]);
|
||||
|
||||
expect(macMethod).toBe("hkdf-hmac-sha256");
|
||||
|
||||
const bobDevice
|
||||
= await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
|
||||
expect(bobDevice.isVerified()).toBeTruthy();
|
||||
expect(bobDevice!.isVerified()).toBeTruthy();
|
||||
const aliceDevice
|
||||
= await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
|
||||
expect(aliceDevice.isVerified()).toBeTruthy();
|
||||
expect(aliceDevice!.isVerified()).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should be able to verify using the old MAC", async () => {
|
||||
@ -304,7 +305,7 @@ describe("SAS verification", function() {
|
||||
// has, since it is the same object. If this does not
|
||||
// happen, the verification will fail due to a hash
|
||||
// commitment mismatch.
|
||||
map[bob.client.getUserId()][bob.client.deviceId]
|
||||
map[bob.client.getUserId()!][bob.client.deviceId!]
|
||||
.message_authentication_codes = ['hmac-sha256'];
|
||||
}
|
||||
return aliceOrigSendToDevice(type, map);
|
||||
@ -312,7 +313,7 @@ describe("SAS verification", function() {
|
||||
const bobOrigSendToDevice = bob.client.sendToDevice.bind(bob.client);
|
||||
bob.client.sendToDevice = (type, map) => {
|
||||
if (type === "m.key.verification.accept") {
|
||||
macMethod = map[alice.client.getUserId()][alice.client.deviceId]
|
||||
macMethod = map[alice.client.getUserId()!][alice.client.deviceId!]
|
||||
.message_authentication_code;
|
||||
}
|
||||
return bobOrigSendToDevice(type, map);
|
||||
@ -334,18 +335,18 @@ describe("SAS verification", function() {
|
||||
await Promise.all([
|
||||
aliceVerifier.verify(),
|
||||
bobPromise.then((verifier) => verifier.verify()),
|
||||
alice.httpBackend.flush(),
|
||||
bob.httpBackend.flush(),
|
||||
alice.httpBackend.flush(undefined),
|
||||
bob.httpBackend.flush(undefined),
|
||||
]);
|
||||
|
||||
expect(macMethod).toBe("hmac-sha256");
|
||||
|
||||
const bobDevice
|
||||
= await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
|
||||
expect(bobDevice.isVerified()).toBeTruthy();
|
||||
expect(bobDevice?.isVerified()).toBeTruthy();
|
||||
const aliceDevice
|
||||
= await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
|
||||
expect(aliceDevice.isVerified()).toBeTruthy();
|
||||
expect(aliceDevice?.isVerified()).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should verify a cross-signing key", async () => {
|
||||
@ -361,9 +362,11 @@ describe("SAS verification", function() {
|
||||
|
||||
await resetCrossSigningKeys(bob.client);
|
||||
|
||||
bob.client.crypto.deviceList.storeCrossSigningForUser(
|
||||
bob.client.crypto!.deviceList.storeCrossSigningForUser(
|
||||
"@alice:example.com", {
|
||||
keys: alice.client.crypto.crossSigningInfo.keys,
|
||||
keys: alice.client.crypto!.crossSigningInfo.keys,
|
||||
crossSigningVerifiedBefore: false,
|
||||
firstUse: true,
|
||||
},
|
||||
);
|
||||
|
||||
@ -415,10 +418,10 @@ describe("SAS verification", function() {
|
||||
|
||||
const bobPromise = new Promise<VerificationBase<any, any>>((resolve, reject) => {
|
||||
bob.client.on(CryptoEvent.VerificationRequest, request => {
|
||||
request.verifier.on("show_sas", (e) => {
|
||||
request.verifier!.on("show_sas", (e) => {
|
||||
e.mismatch();
|
||||
});
|
||||
resolve(request.verifier);
|
||||
resolve(request.verifier!);
|
||||
});
|
||||
});
|
||||
|
||||
@ -464,7 +467,7 @@ describe("SAS verification", function() {
|
||||
},
|
||||
);
|
||||
|
||||
alice.client.crypto.setDeviceVerification = jest.fn();
|
||||
alice.client.crypto!.setDeviceVerification = jest.fn();
|
||||
alice.client.getDeviceEd25519Key = () => {
|
||||
return "alice+base64+ed25519+key";
|
||||
};
|
||||
@ -482,7 +485,7 @@ describe("SAS verification", function() {
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
bob.client.crypto.setDeviceVerification = jest.fn();
|
||||
bob.client.crypto!.setDeviceVerification = jest.fn();
|
||||
bob.client.getStoredDevice = () => {
|
||||
return DeviceInfo.fromStorage(
|
||||
{
|
||||
@ -565,7 +568,7 @@ describe("SAS verification", function() {
|
||||
]);
|
||||
|
||||
// make sure Alice and Bob verified each other
|
||||
expect(alice.client.crypto.setDeviceVerification)
|
||||
expect(alice.client.crypto!.setDeviceVerification)
|
||||
.toHaveBeenCalledWith(
|
||||
bob.client.getUserId(),
|
||||
bob.client.deviceId,
|
||||
@ -574,7 +577,7 @@ describe("SAS verification", function() {
|
||||
null,
|
||||
{ "ed25519:Dynabook": "bob+base64+ed25519+key" },
|
||||
);
|
||||
expect(bob.client.crypto.setDeviceVerification)
|
||||
expect(bob.client.crypto!.setDeviceVerification)
|
||||
.toHaveBeenCalledWith(
|
||||
alice.client.getUserId(),
|
||||
alice.client.deviceId,
|
||||
|
@ -41,7 +41,7 @@ export async function makeTestClients(userInfos, options): Promise<[TestClient[]
|
||||
});
|
||||
const client = clientMap[userId][deviceId];
|
||||
const decryptionPromise = event.isEncrypted() ?
|
||||
event.attemptDecryption(client.crypto) :
|
||||
event.attemptDecryption(client.crypto!) :
|
||||
Promise.resolve();
|
||||
|
||||
decryptionPromise.then(
|
||||
|
@ -32,7 +32,7 @@ describe("eventMapperFor", function() {
|
||||
fetchFn: function() {} as any, // NOP
|
||||
store: {
|
||||
getRoom(roomId: string): Room | null {
|
||||
return rooms.find(r => r.roomId === roomId);
|
||||
return rooms.find(r => r.roomId === roomId) ?? null;
|
||||
},
|
||||
} as IStore,
|
||||
scheduler: {
|
||||
|
@ -50,8 +50,8 @@ describe('EventTimelineSet', () => {
|
||||
EventType.RoomMessage,
|
||||
);
|
||||
expect(relations).toBeDefined();
|
||||
expect(relations.getRelations().length).toBe(1);
|
||||
expect(relations.getRelations()[0].getId()).toBe(replyEvent.getId());
|
||||
expect(relations!.getRelations().length).toBe(1);
|
||||
expect(relations!.getRelations()[0].getId()).toBe(replyEvent.getId());
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -21,7 +21,7 @@ describe("EventTimeline", function() {
|
||||
const getTimeline = (): EventTimeline => {
|
||||
const room = new Room(roomId, mockClient, userA);
|
||||
const timelineSet = new EventTimelineSet(room);
|
||||
jest.spyOn(timelineSet.room, 'getUnfilteredTimelineSet').mockReturnValue(timelineSet);
|
||||
jest.spyOn(room, 'getUnfilteredTimelineSet').mockReturnValue(timelineSet);
|
||||
|
||||
return new EventTimeline(timelineSet);
|
||||
};
|
||||
|
@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { UNREAD_THREAD_NOTIFICATIONS } from "../../src/@types/sync";
|
||||
import { Filter, IFilterDefinition } from "../../src/filter";
|
||||
import { mkEvent } from "../test-utils/test-utils";
|
||||
|
@ -36,7 +36,7 @@ import { ReceiptType } from "../../src/@types/read_receipts";
|
||||
import * as testUtils from "../test-utils/test-utils";
|
||||
import { makeBeaconInfoContent } from "../../src/content-helpers";
|
||||
import { M_BEACON_INFO } from "../../src/@types/beacon";
|
||||
import { ContentHelpers, EventTimeline, Room } from "../../src";
|
||||
import { ContentHelpers, EventTimeline, MatrixError, Room } from "../../src";
|
||||
import { supportsMatrixCall } from "../../src/webrtc/call";
|
||||
import { makeBeaconEvent } from "../test-utils/beacon";
|
||||
import {
|
||||
@ -88,21 +88,22 @@ describe("MatrixClient", function() {
|
||||
data: SYNC_DATA,
|
||||
};
|
||||
|
||||
let httpLookups = [
|
||||
// items are objects which look like:
|
||||
// {
|
||||
// method: "GET",
|
||||
// path: "/initialSync",
|
||||
// data: {},
|
||||
// error: { errcode: M_FORBIDDEN } // if present will reject promise,
|
||||
// expectBody: {} // additional expects on the body
|
||||
// expectQueryParams: {} // additional expects on query params
|
||||
// thenCall: function(){} // function to call *AFTER* returning response.
|
||||
// }
|
||||
// items are popped off when processed and block if no items left.
|
||||
];
|
||||
// items are popped off when processed and block if no items left.
|
||||
let httpLookups: {
|
||||
method: string;
|
||||
path: string;
|
||||
data?: object;
|
||||
error?: object;
|
||||
expectBody?: object;
|
||||
expectQueryParams?: object;
|
||||
thenCall?: Function;
|
||||
}[] = [];
|
||||
let acceptKeepalives: boolean;
|
||||
let pendingLookup = null;
|
||||
let pendingLookup: {
|
||||
promise: Promise<any>;
|
||||
method: string;
|
||||
path: string;
|
||||
} | null = null;
|
||||
function httpReq(method, path, qp, data, prefix) {
|
||||
if (path === KEEP_ALIVE_PATH && acceptKeepalives) {
|
||||
return Promise.resolve({
|
||||
@ -144,7 +145,7 @@ describe("MatrixClient", function() {
|
||||
}
|
||||
if (next.expectQueryParams) {
|
||||
Object.keys(next.expectQueryParams).forEach(function(k) {
|
||||
expect(qp[k]).toEqual(next.expectQueryParams[k]);
|
||||
expect(qp[k]).toEqual(next.expectQueryParams![k]);
|
||||
});
|
||||
}
|
||||
|
||||
@ -155,9 +156,9 @@ describe("MatrixClient", function() {
|
||||
if (next.error) {
|
||||
// eslint-disable-next-line
|
||||
return Promise.reject({
|
||||
errcode: next.error.errcode,
|
||||
httpStatus: next.error.httpStatus,
|
||||
name: next.error.errcode,
|
||||
errcode: (<MatrixError>next.error).errcode,
|
||||
httpStatus: (<MatrixError>next.error).httpStatus,
|
||||
name: (<MatrixError>next.error).errcode,
|
||||
message: "Expected testing error",
|
||||
data: next.error,
|
||||
});
|
||||
@ -254,7 +255,7 @@ describe("MatrixClient", function() {
|
||||
type: UNSTABLE_MSC3088_PURPOSE.unstable,
|
||||
state_key: UNSTABLE_MSC3089_TREE_SUBTYPE.unstable,
|
||||
content: {
|
||||
[UNSTABLE_MSC3088_ENABLED.unstable]: true,
|
||||
[UNSTABLE_MSC3088_ENABLED.unstable!]: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -299,7 +300,7 @@ describe("MatrixClient", function() {
|
||||
expect(stateKey).toEqual(UNSTABLE_MSC3089_TREE_SUBTYPE.unstable);
|
||||
return new MatrixEvent({
|
||||
content: {
|
||||
[UNSTABLE_MSC3088_ENABLED.unstable]: true,
|
||||
[UNSTABLE_MSC3088_ENABLED.unstable!]: true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
@ -359,7 +360,7 @@ describe("MatrixClient", function() {
|
||||
expect(stateKey).toEqual(UNSTABLE_MSC3089_TREE_SUBTYPE.unstable);
|
||||
return new MatrixEvent({
|
||||
content: {
|
||||
[UNSTABLE_MSC3088_ENABLED.unstable]: true,
|
||||
[UNSTABLE_MSC3088_ENABLED.unstable!]: true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
@ -393,7 +394,7 @@ describe("MatrixClient", function() {
|
||||
expect(stateKey).toEqual(UNSTABLE_MSC3089_TREE_SUBTYPE.unstable);
|
||||
return new MatrixEvent({
|
||||
content: {
|
||||
[UNSTABLE_MSC3088_ENABLED.unstable]: false,
|
||||
[UNSTABLE_MSC3088_ENABLED.unstable!]: false,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
@ -599,14 +600,14 @@ describe("MatrixClient", function() {
|
||||
}
|
||||
|
||||
it("should transition null -> PREPARED after the first /sync", function(done) {
|
||||
const expectedStates = [];
|
||||
const expectedStates: [string, string | null][] = [];
|
||||
expectedStates.push(["PREPARED", null]);
|
||||
client.on("sync", syncChecker(expectedStates, done));
|
||||
client.startClient();
|
||||
});
|
||||
|
||||
it("should transition null -> ERROR after a failed /filter", function(done) {
|
||||
const expectedStates = [];
|
||||
const expectedStates: [string, string | null][] = [];
|
||||
httpLookups = [];
|
||||
httpLookups.push(PUSH_RULES_RESPONSE);
|
||||
httpLookups.push({
|
||||
@ -620,36 +621,35 @@ describe("MatrixClient", function() {
|
||||
// Disabled because now `startClient` makes a legit call to `/versions`
|
||||
// And those tests are really unhappy about it... Not possible to figure
|
||||
// out what a good resolution would look like
|
||||
xit("should transition ERROR -> CATCHUP after /sync if prev failed",
|
||||
function(done) {
|
||||
const expectedStates = [];
|
||||
acceptKeepalives = false;
|
||||
httpLookups = [];
|
||||
httpLookups.push(PUSH_RULES_RESPONSE);
|
||||
httpLookups.push(FILTER_RESPONSE);
|
||||
httpLookups.push({
|
||||
method: "GET", path: "/sync", error: { errcode: "NOPE_NOPE_NOPE" },
|
||||
});
|
||||
httpLookups.push({
|
||||
method: "GET", path: KEEP_ALIVE_PATH,
|
||||
error: { errcode: "KEEPALIVE_FAIL" },
|
||||
});
|
||||
httpLookups.push({
|
||||
method: "GET", path: KEEP_ALIVE_PATH, data: {},
|
||||
});
|
||||
httpLookups.push({
|
||||
method: "GET", path: "/sync", data: SYNC_DATA,
|
||||
});
|
||||
|
||||
expectedStates.push(["RECONNECTING", null]);
|
||||
expectedStates.push(["ERROR", "RECONNECTING"]);
|
||||
expectedStates.push(["CATCHUP", "ERROR"]);
|
||||
client.on("sync", syncChecker(expectedStates, done));
|
||||
client.startClient();
|
||||
xit("should transition ERROR -> CATCHUP after /sync if prev failed", function(done) {
|
||||
const expectedStates: [string, string | null][] = [];
|
||||
acceptKeepalives = false;
|
||||
httpLookups = [];
|
||||
httpLookups.push(PUSH_RULES_RESPONSE);
|
||||
httpLookups.push(FILTER_RESPONSE);
|
||||
httpLookups.push({
|
||||
method: "GET", path: "/sync", error: { errcode: "NOPE_NOPE_NOPE" },
|
||||
});
|
||||
httpLookups.push({
|
||||
method: "GET", path: KEEP_ALIVE_PATH,
|
||||
error: { errcode: "KEEPALIVE_FAIL" },
|
||||
});
|
||||
httpLookups.push({
|
||||
method: "GET", path: KEEP_ALIVE_PATH, data: {},
|
||||
});
|
||||
httpLookups.push({
|
||||
method: "GET", path: "/sync", data: SYNC_DATA,
|
||||
});
|
||||
|
||||
expectedStates.push(["RECONNECTING", null]);
|
||||
expectedStates.push(["ERROR", "RECONNECTING"]);
|
||||
expectedStates.push(["CATCHUP", "ERROR"]);
|
||||
client.on("sync", syncChecker(expectedStates, done));
|
||||
client.startClient();
|
||||
});
|
||||
|
||||
it("should transition PREPARED -> SYNCING after /sync", function(done) {
|
||||
const expectedStates = [];
|
||||
const expectedStates: [string, string | null][] = [];
|
||||
expectedStates.push(["PREPARED", null]);
|
||||
expectedStates.push(["SYNCING", "PREPARED"]);
|
||||
client.on("sync", syncChecker(expectedStates, done));
|
||||
@ -658,7 +658,7 @@ describe("MatrixClient", function() {
|
||||
|
||||
xit("should transition SYNCING -> ERROR after a failed /sync", function(done) {
|
||||
acceptKeepalives = false;
|
||||
const expectedStates = [];
|
||||
const expectedStates: [string, string | null][] = [];
|
||||
httpLookups.push({
|
||||
method: "GET", path: "/sync", error: { errcode: "NONONONONO" },
|
||||
});
|
||||
@ -675,37 +675,35 @@ describe("MatrixClient", function() {
|
||||
client.startClient();
|
||||
});
|
||||
|
||||
xit("should transition ERROR -> SYNCING after /sync if prev failed",
|
||||
function(done) {
|
||||
const expectedStates = [];
|
||||
httpLookups.push({
|
||||
method: "GET", path: "/sync", error: { errcode: "NONONONONO" },
|
||||
});
|
||||
httpLookups.push(SYNC_RESPONSE);
|
||||
|
||||
expectedStates.push(["PREPARED", null]);
|
||||
expectedStates.push(["SYNCING", "PREPARED"]);
|
||||
expectedStates.push(["ERROR", "SYNCING"]);
|
||||
client.on("sync", syncChecker(expectedStates, done));
|
||||
client.startClient();
|
||||
xit("should transition ERROR -> SYNCING after /sync if prev failed", function(done) {
|
||||
const expectedStates: [string, string | null][] = [];
|
||||
httpLookups.push({
|
||||
method: "GET", path: "/sync", error: { errcode: "NONONONONO" },
|
||||
});
|
||||
httpLookups.push(SYNC_RESPONSE);
|
||||
|
||||
it("should transition SYNCING -> SYNCING on subsequent /sync successes",
|
||||
function(done) {
|
||||
const expectedStates = [];
|
||||
httpLookups.push(SYNC_RESPONSE);
|
||||
httpLookups.push(SYNC_RESPONSE);
|
||||
expectedStates.push(["PREPARED", null]);
|
||||
expectedStates.push(["SYNCING", "PREPARED"]);
|
||||
expectedStates.push(["ERROR", "SYNCING"]);
|
||||
client.on("sync", syncChecker(expectedStates, done));
|
||||
client.startClient();
|
||||
});
|
||||
|
||||
expectedStates.push(["PREPARED", null]);
|
||||
expectedStates.push(["SYNCING", "PREPARED"]);
|
||||
expectedStates.push(["SYNCING", "SYNCING"]);
|
||||
client.on("sync", syncChecker(expectedStates, done));
|
||||
client.startClient();
|
||||
});
|
||||
it("should transition SYNCING -> SYNCING on subsequent /sync successes", function(done) {
|
||||
const expectedStates: [string, string | null][] = [];
|
||||
httpLookups.push(SYNC_RESPONSE);
|
||||
httpLookups.push(SYNC_RESPONSE);
|
||||
|
||||
expectedStates.push(["PREPARED", null]);
|
||||
expectedStates.push(["SYNCING", "PREPARED"]);
|
||||
expectedStates.push(["SYNCING", "SYNCING"]);
|
||||
client.on("sync", syncChecker(expectedStates, done));
|
||||
client.startClient();
|
||||
});
|
||||
|
||||
xit("should transition ERROR -> ERROR if keepalive keeps failing", function(done) {
|
||||
acceptKeepalives = false;
|
||||
const expectedStates = [];
|
||||
const expectedStates: [string, string | null][] = [];
|
||||
httpLookups.push({
|
||||
method: "GET", path: "/sync", error: { errcode: "NONONONONO" },
|
||||
});
|
||||
|
@ -209,32 +209,32 @@ describe('NotificationService', function() {
|
||||
msgtype: "m.text",
|
||||
},
|
||||
});
|
||||
matrixClient.pushRules = PushProcessor.rewriteDefaultRules(matrixClient.pushRules);
|
||||
matrixClient.pushRules = PushProcessor.rewriteDefaultRules(matrixClient.pushRules!);
|
||||
pushProcessor = new PushProcessor(matrixClient);
|
||||
});
|
||||
|
||||
// User IDs
|
||||
|
||||
it('should bing on a user ID.', function() {
|
||||
testEvent.event.content.body = "Hello @ali:matrix.org, how are you?";
|
||||
testEvent.event.content!.body = "Hello @ali:matrix.org, how are you?";
|
||||
const actions = pushProcessor.actionsForEvent(testEvent);
|
||||
expect(actions.tweaks.highlight).toEqual(true);
|
||||
});
|
||||
|
||||
it('should bing on a partial user ID with an @.', function() {
|
||||
testEvent.event.content.body = "Hello @ali, how are you?";
|
||||
testEvent.event.content!.body = "Hello @ali, how are you?";
|
||||
const actions = pushProcessor.actionsForEvent(testEvent);
|
||||
expect(actions.tweaks.highlight).toEqual(true);
|
||||
});
|
||||
|
||||
it('should bing on a partial user ID without @.', function() {
|
||||
testEvent.event.content.body = "Hello ali, how are you?";
|
||||
testEvent.event.content!.body = "Hello ali, how are you?";
|
||||
const actions = pushProcessor.actionsForEvent(testEvent);
|
||||
expect(actions.tweaks.highlight).toEqual(true);
|
||||
});
|
||||
|
||||
it('should bing on a case-insensitive user ID.', function() {
|
||||
testEvent.event.content.body = "Hello @AlI:matrix.org, how are you?";
|
||||
testEvent.event.content!.body = "Hello @AlI:matrix.org, how are you?";
|
||||
const actions = pushProcessor.actionsForEvent(testEvent);
|
||||
expect(actions.tweaks.highlight).toEqual(true);
|
||||
});
|
||||
@ -242,13 +242,13 @@ describe('NotificationService', function() {
|
||||
// Display names
|
||||
|
||||
it('should bing on a display name.', function() {
|
||||
testEvent.event.content.body = "Hello Alice M, how are you?";
|
||||
testEvent.event.content!.body = "Hello Alice M, how are you?";
|
||||
const actions = pushProcessor.actionsForEvent(testEvent);
|
||||
expect(actions.tweaks.highlight).toEqual(true);
|
||||
});
|
||||
|
||||
it('should bing on a case-insensitive display name.', function() {
|
||||
testEvent.event.content.body = "Hello ALICE M, how are you?";
|
||||
testEvent.event.content!.body = "Hello ALICE M, how are you?";
|
||||
const actions = pushProcessor.actionsForEvent(testEvent);
|
||||
expect(actions.tweaks.highlight).toEqual(true);
|
||||
});
|
||||
@ -256,43 +256,43 @@ describe('NotificationService', function() {
|
||||
// Bing words
|
||||
|
||||
it('should bing on a bing word.', function() {
|
||||
testEvent.event.content.body = "I really like coffee";
|
||||
testEvent.event.content!.body = "I really like coffee";
|
||||
const actions = pushProcessor.actionsForEvent(testEvent);
|
||||
expect(actions.tweaks.highlight).toEqual(true);
|
||||
});
|
||||
|
||||
it('should bing on case-insensitive bing words.', function() {
|
||||
testEvent.event.content.body = "Coffee is great";
|
||||
testEvent.event.content!.body = "Coffee is great";
|
||||
const actions = pushProcessor.actionsForEvent(testEvent);
|
||||
expect(actions.tweaks.highlight).toEqual(true);
|
||||
});
|
||||
|
||||
it('should bing on wildcard (.*) bing words.', function() {
|
||||
testEvent.event.content.body = "It was foomahbar I think.";
|
||||
testEvent.event.content!.body = "It was foomahbar I think.";
|
||||
const actions = pushProcessor.actionsForEvent(testEvent);
|
||||
expect(actions.tweaks.highlight).toEqual(true);
|
||||
});
|
||||
|
||||
it('should bing on character group ([abc]) bing words.', function() {
|
||||
testEvent.event.content.body = "Ping!";
|
||||
testEvent.event.content!.body = "Ping!";
|
||||
let actions = pushProcessor.actionsForEvent(testEvent);
|
||||
expect(actions.tweaks.highlight).toEqual(true);
|
||||
testEvent.event.content.body = "Pong!";
|
||||
testEvent.event.content!.body = "Pong!";
|
||||
actions = pushProcessor.actionsForEvent(testEvent);
|
||||
expect(actions.tweaks.highlight).toEqual(true);
|
||||
});
|
||||
|
||||
it('should bing on character range ([a-z]) bing words.', function() {
|
||||
testEvent.event.content.body = "I ate 6 pies";
|
||||
testEvent.event.content!.body = "I ate 6 pies";
|
||||
const actions = pushProcessor.actionsForEvent(testEvent);
|
||||
expect(actions.tweaks.highlight).toEqual(true);
|
||||
});
|
||||
|
||||
it('should bing on character negation ([!a]) bing words.', function() {
|
||||
testEvent.event.content.body = "boke";
|
||||
testEvent.event.content!.body = "boke";
|
||||
let actions = pushProcessor.actionsForEvent(testEvent);
|
||||
expect(actions.tweaks.highlight).toEqual(true);
|
||||
testEvent.event.content.body = "bake";
|
||||
testEvent.event.content!.body = "bake";
|
||||
actions = pushProcessor.actionsForEvent(testEvent);
|
||||
expect(actions.tweaks.highlight).toEqual(false);
|
||||
});
|
||||
@ -316,7 +316,7 @@ describe('NotificationService', function() {
|
||||
// invalid
|
||||
|
||||
it('should gracefully handle bad input.', function() {
|
||||
testEvent.event.content.body = { "foo": "bar" };
|
||||
testEvent.event.content!.body = { "foo": "bar" };
|
||||
const actions = pushProcessor.actionsForEvent(testEvent);
|
||||
expect(actions.tweaks.highlight).toEqual(false);
|
||||
});
|
||||
|
@ -18,6 +18,7 @@ import { EventTimelineSet } from "../../src/models/event-timeline-set";
|
||||
import { MatrixEvent, MatrixEventEvent } from "../../src/models/event";
|
||||
import { Room } from "../../src/models/room";
|
||||
import { Relations } from "../../src/models/relations";
|
||||
import { TestClient } from "../TestClient";
|
||||
|
||||
describe("Relations", function() {
|
||||
it("should deduplicate annotations", function() {
|
||||
@ -43,7 +44,7 @@ describe("Relations", function() {
|
||||
// Add the event once and check results
|
||||
{
|
||||
relations.addEvent(eventA);
|
||||
const annotationsByKey = relations.getSortedAnnotationsByKey();
|
||||
const annotationsByKey = relations.getSortedAnnotationsByKey()!;
|
||||
expect(annotationsByKey.length).toEqual(1);
|
||||
const [key, events] = annotationsByKey[0];
|
||||
expect(key).toEqual("👍️");
|
||||
@ -53,7 +54,7 @@ describe("Relations", function() {
|
||||
// Add the event again and expect the same
|
||||
{
|
||||
relations.addEvent(eventA);
|
||||
const annotationsByKey = relations.getSortedAnnotationsByKey();
|
||||
const annotationsByKey = relations.getSortedAnnotationsByKey()!;
|
||||
expect(annotationsByKey.length).toEqual(1);
|
||||
const [key, events] = annotationsByKey[0];
|
||||
expect(key).toEqual("👍️");
|
||||
@ -66,7 +67,7 @@ describe("Relations", function() {
|
||||
// Add the event again and expect the same
|
||||
{
|
||||
relations.addEvent(eventB);
|
||||
const annotationsByKey = relations.getSortedAnnotationsByKey();
|
||||
const annotationsByKey = relations.getSortedAnnotationsByKey()!;
|
||||
expect(annotationsByKey.length).toEqual(1);
|
||||
const [key, events] = annotationsByKey[0];
|
||||
expect(key).toEqual("👍️");
|
||||
@ -179,4 +180,28 @@ describe("Relations", function() {
|
||||
expect(badlyEditedTopic.replacingEvent()).toBe(null);
|
||||
expect(badlyEditedTopic.getContent().topic).toBe("topic");
|
||||
});
|
||||
|
||||
it("getSortedAnnotationsByKey should return null for non-annotation relations", async () => {
|
||||
const userId = "@user:server";
|
||||
const room = new Room("room123", new TestClient(userId).client, userId);
|
||||
const relations = new Relations("m.replace", "m.room.message", room);
|
||||
|
||||
// Create an instance of an annotation
|
||||
const eventData = {
|
||||
"sender": "@bob:example.com",
|
||||
"type": "m.room.message",
|
||||
"event_id": "$cZ1biX33ENJqIm00ks0W_hgiO_6CHrsAc3ZQrnLeNTw",
|
||||
"room_id": "!pzVjCQSoQPpXQeHpmK:example.com",
|
||||
"content": {
|
||||
"m.relates_to": {
|
||||
"event_id": "$2s4yYpEkVQrPglSCSqB_m6E8vDhWsg0yFNyOJdVIb_o",
|
||||
"rel_type": "m.replace",
|
||||
},
|
||||
},
|
||||
};
|
||||
const eventA = new MatrixEvent(eventData);
|
||||
|
||||
relations.addEvent(eventA);
|
||||
expect(relations.getSortedAnnotationsByKey()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
@ -601,7 +601,7 @@ describe("Room", function() {
|
||||
});
|
||||
|
||||
const resetTimelineTests = function(timelineSupport) {
|
||||
let events = null;
|
||||
let events: MatrixEvent[];
|
||||
|
||||
beforeEach(function() {
|
||||
room = new Room(roomId, new TestClient(userA).client, userA, { timelineSupport: timelineSupport });
|
||||
@ -1732,7 +1732,7 @@ describe("Room", function() {
|
||||
|
||||
client.members.mockReturnValue({ chunk: [memberEvent] });
|
||||
await room.loadMembersIfNeeded();
|
||||
const memberA = room.getMember("@user_a:bar");
|
||||
const memberA = room.getMember("@user_a:bar")!;
|
||||
expect(memberA.name).toEqual("User A");
|
||||
});
|
||||
});
|
||||
@ -2455,28 +2455,28 @@ describe("Room", function() {
|
||||
|
||||
room.addLiveEvents(events);
|
||||
|
||||
const thread = threadRoot.getThread();
|
||||
const thread = threadRoot.getThread()!;
|
||||
expect(thread.rootEvent).toBe(threadRoot);
|
||||
|
||||
const rootRelations = thread.timelineSet.relations.getChildEventsForEvent(
|
||||
threadRoot.getId(),
|
||||
RelationType.Annotation,
|
||||
EventType.Reaction,
|
||||
).getSortedAnnotationsByKey();
|
||||
)!.getSortedAnnotationsByKey();
|
||||
expect(rootRelations).toHaveLength(1);
|
||||
expect(rootRelations[0][0]).toEqual(rootReaction.getRelation().key);
|
||||
expect(rootRelations[0][1].size).toEqual(1);
|
||||
expect(rootRelations[0][1].has(rootReaction)).toBeTruthy();
|
||||
expect(rootRelations![0][0]).toEqual(rootReaction.getRelation()!.key);
|
||||
expect(rootRelations![0][1].size).toEqual(1);
|
||||
expect(rootRelations![0][1].has(rootReaction)).toBeTruthy();
|
||||
|
||||
const responseRelations = thread.timelineSet.relations.getChildEventsForEvent(
|
||||
threadResponse.getId(),
|
||||
RelationType.Annotation,
|
||||
EventType.Reaction,
|
||||
).getSortedAnnotationsByKey();
|
||||
)!.getSortedAnnotationsByKey();
|
||||
expect(responseRelations).toHaveLength(1);
|
||||
expect(responseRelations[0][0]).toEqual(threadReaction.getRelation().key);
|
||||
expect(responseRelations[0][1].size).toEqual(1);
|
||||
expect(responseRelations[0][1].has(threadReaction)).toBeTruthy();
|
||||
expect(responseRelations![0][0]).toEqual(threadReaction.getRelation()!.key);
|
||||
expect(responseRelations![0][1].size).toEqual(1);
|
||||
expect(responseRelations![0][1].has(threadReaction)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -37,7 +37,7 @@ const mockClient = {
|
||||
function createTimeline(numEvents = 3, baseIndex = 1): EventTimeline {
|
||||
const room = new Room(ROOM_ID, mockClient, USER_ID);
|
||||
const timelineSet = new EventTimelineSet(room);
|
||||
jest.spyOn(timelineSet.room, 'getUnfilteredTimelineSet').mockReturnValue(timelineSet);
|
||||
jest.spyOn(room, 'getUnfilteredTimelineSet').mockReturnValue(timelineSet);
|
||||
|
||||
const timeline = new EventTimeline(timelineSet);
|
||||
|
||||
@ -170,7 +170,7 @@ describe("TimelineWindow", function() {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockClient.getEventTimeline.mockResolvedValue(undefined);
|
||||
mockClient.paginateEventTimeline.mockReturnValue(undefined);
|
||||
mockClient.paginateEventTimeline.mockResolvedValue(false);
|
||||
});
|
||||
|
||||
describe("load", function() {
|
||||
|
@ -26,17 +26,19 @@ import {
|
||||
MockRTCPeerConnection,
|
||||
} from "../../test-utils/webrtc";
|
||||
import { CallFeed } from "../../../src/webrtc/callFeed";
|
||||
import { EventType, MatrixClient } from "../../../src";
|
||||
import { MediaHandler } from "../../../src/webrtc/mediaHandler";
|
||||
|
||||
const startVoiceCall = async (client: TestClient, call: MatrixCall): Promise<void> => {
|
||||
const callPromise = call.placeVoiceCall();
|
||||
await client.httpBackend.flush("");
|
||||
await client.httpBackend!.flush("");
|
||||
await callPromise;
|
||||
|
||||
call.getOpponentMember = jest.fn().mockReturnValue({ userId: "@bob:bar.uk" });
|
||||
};
|
||||
|
||||
describe('Call', function() {
|
||||
let client;
|
||||
let client: TestClient;
|
||||
let call;
|
||||
let prevNavigator;
|
||||
let prevDocument;
|
||||
@ -71,10 +73,10 @@ describe('Call', function() {
|
||||
client = new TestClient("@alice:foo", "somedevice", "token", undefined, {});
|
||||
// We just stub out sendEvent: we're not interested in testing the client's
|
||||
// event sending code here
|
||||
client.client.sendEvent = () => {};
|
||||
client.client.mediaHandler = new MockMediaHandler;
|
||||
client.client.getMediaHandler = () => client.client.mediaHandler;
|
||||
client.httpBackend.when("GET", "/voip/turnServer").respond(200, {});
|
||||
client.client.sendEvent = (() => {}) as unknown as MatrixClient["sendEvent"];
|
||||
client.client["mediaHandler"] = new MockMediaHandler as unknown as MediaHandler;
|
||||
client.client.getMediaHandler = () => client.client["mediaHandler"]!;
|
||||
client.httpBackend!.when("GET", "/voip/turnServer").respond(200, {});
|
||||
call = new MatrixCall({
|
||||
client: client.client,
|
||||
roomId: '!foo:bar',
|
||||
@ -237,7 +239,7 @@ describe('Call', function() {
|
||||
|
||||
expect(identChangedCallback).toHaveBeenCalled();
|
||||
|
||||
const ident = call.getRemoteAssertedIdentity();
|
||||
const ident = call.getRemoteAssertedIdentity()!;
|
||||
expect(ident.id).toEqual("@steve:example.com");
|
||||
expect(ident.displayName).toEqual("Steve Gibbons");
|
||||
|
||||
@ -306,19 +308,19 @@ describe('Call', function() {
|
||||
});
|
||||
|
||||
it("should fallback to answering with no video", async () => {
|
||||
await client.httpBackend.flush();
|
||||
await client.httpBackend!.flush(undefined);
|
||||
|
||||
call.shouldAnswerWithMediaType = (wantedValue: boolean) => wantedValue;
|
||||
client.client.mediaHandler.getUserMediaStream = jest.fn().mockRejectedValue("reject");
|
||||
client.client["mediaHandler"].getUserMediaStream = jest.fn().mockRejectedValue("reject");
|
||||
|
||||
await call.answer(true, true);
|
||||
|
||||
expect(client.client.mediaHandler.getUserMediaStream).toHaveBeenNthCalledWith(1, true, true);
|
||||
expect(client.client.mediaHandler.getUserMediaStream).toHaveBeenNthCalledWith(2, true, false);
|
||||
expect(client.client["mediaHandler"].getUserMediaStream).toHaveBeenNthCalledWith(1, true, true);
|
||||
expect(client.client["mediaHandler"].getUserMediaStream).toHaveBeenNthCalledWith(2, true, false);
|
||||
});
|
||||
|
||||
it("should handle mid-call device changes", async () => {
|
||||
client.client.mediaHandler.getUserMediaStream = jest.fn().mockReturnValue(
|
||||
client.client["mediaHandler"].getUserMediaStream = jest.fn().mockReturnValue(
|
||||
new MockMediaStream(
|
||||
"stream", [
|
||||
new MockMediaStreamTrack("audio_track", "audio"),
|
||||
@ -424,7 +426,7 @@ describe('Call', function() {
|
||||
|
||||
it("should choose opponent member", async () => {
|
||||
const callPromise = call.placeVoiceCall();
|
||||
await client.httpBackend.flush();
|
||||
await client.httpBackend!.flush(undefined);
|
||||
await callPromise;
|
||||
|
||||
const opponentMember = {
|
||||
@ -480,7 +482,7 @@ describe('Call', function() {
|
||||
|
||||
it("should correctly generate local SDPStreamMetadata", async () => {
|
||||
const callPromise = call.placeCallWithCallFeeds([new CallFeed({
|
||||
client,
|
||||
client: client.client,
|
||||
// @ts-ignore Mock
|
||||
stream: new MockMediaStream("local_stream1", [new MockMediaStreamTrack("track_id", "audio")]),
|
||||
roomId: call.roomId,
|
||||
@ -489,7 +491,7 @@ describe('Call', function() {
|
||||
audioMuted: false,
|
||||
videoMuted: false,
|
||||
})]);
|
||||
await client.httpBackend.flush();
|
||||
await client.httpBackend!.flush(undefined);
|
||||
await callPromise;
|
||||
call.getOpponentMember = jest.fn().mockReturnValue({ userId: "@bob:bar.uk" });
|
||||
|
||||
@ -521,7 +523,7 @@ describe('Call', function() {
|
||||
|
||||
const callPromise = call.placeCallWithCallFeeds([
|
||||
new CallFeed({
|
||||
client,
|
||||
client: client.client,
|
||||
userId: client.getUserId(),
|
||||
// @ts-ignore Mock
|
||||
stream: localUsermediaStream,
|
||||
@ -531,7 +533,7 @@ describe('Call', function() {
|
||||
videoMuted: false,
|
||||
}),
|
||||
new CallFeed({
|
||||
client,
|
||||
client: client.client,
|
||||
userId: client.getUserId(),
|
||||
// @ts-ignore Mock
|
||||
stream: localScreensharingStream,
|
||||
@ -541,7 +543,7 @@ describe('Call', function() {
|
||||
videoMuted: false,
|
||||
}),
|
||||
]);
|
||||
await client.httpBackend.flush();
|
||||
await client.httpBackend!.flush(undefined);
|
||||
await callPromise;
|
||||
call.getOpponentMember = jest.fn().mockReturnValue({ userId: "@bob:bar.uk" });
|
||||
|
||||
@ -586,14 +588,14 @@ describe('Call', function() {
|
||||
getLocalAge: () => null,
|
||||
});
|
||||
call.feeds.push(new CallFeed({
|
||||
client,
|
||||
client: client.client,
|
||||
userId: "remote_user_id",
|
||||
// @ts-ignore Mock
|
||||
stream: new MockMediaStream("remote_stream_id", [new MockMediaStreamTrack("remote_tack_id")]),
|
||||
id: "remote_feed_id",
|
||||
purpose: SDPStreamMetadataPurpose.Usermedia,
|
||||
}));
|
||||
await client.httpBackend.flush();
|
||||
await client.httpBackend!.flush(undefined);
|
||||
await callPromise;
|
||||
|
||||
const callHangupCallback = jest.fn();
|
||||
@ -664,10 +666,10 @@ describe('Call', function() {
|
||||
});
|
||||
|
||||
it("should return false if window or document are undefined", () => {
|
||||
global.window = undefined;
|
||||
global.window = undefined!;
|
||||
expect(supportsMatrixCall()).toBe(false);
|
||||
global.window = prevWindow;
|
||||
global.document = undefined;
|
||||
global.document = undefined!;
|
||||
expect(supportsMatrixCall()).toBe(false);
|
||||
});
|
||||
|
||||
@ -685,9 +687,9 @@ describe('Call', function() {
|
||||
it("should return false if RTCPeerConnection & RTCSessionDescription " +
|
||||
"& RTCIceCandidate & mediaDevices are unavailable",
|
||||
() => {
|
||||
global.window.RTCPeerConnection = undefined;
|
||||
global.window.RTCSessionDescription = undefined;
|
||||
global.window.RTCIceCandidate = undefined;
|
||||
global.window.RTCPeerConnection = undefined!;
|
||||
global.window.RTCSessionDescription = undefined!;
|
||||
global.window.RTCIceCandidate = undefined!;
|
||||
// @ts-ignore - writing to a read-only property as we are simulating faulty browsers
|
||||
global.navigator.mediaDevices = undefined;
|
||||
expect(supportsMatrixCall()).toBe(false);
|
||||
@ -752,4 +754,17 @@ describe('Call', function() {
|
||||
expect(call.pushLocalFeed).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("transferToCall", () => {
|
||||
it("should send the required events", async () => {
|
||||
const targetCall = new MatrixCall({ client: client.client });
|
||||
const sendEvent = jest.spyOn(client.client, "sendEvent");
|
||||
await call.transferToCall(targetCall);
|
||||
|
||||
const newCallId = (sendEvent.mock.calls[0][2] as any)!.await_call;
|
||||
expect(sendEvent).toHaveBeenCalledWith(call.roomId, EventType.CallReplaces, expect.objectContaining({
|
||||
create_call: newCallId,
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -58,7 +58,7 @@ describe("callEventHandler", () => {
|
||||
client.on(CallEventHandlerEvent.Incoming, incomingCallEmitted);
|
||||
|
||||
client.getSyncState = jest.fn().mockReturnValue(SyncState.Syncing);
|
||||
client.emit(ClientEvent.Sync, SyncState.Syncing);
|
||||
client.emit(ClientEvent.Sync, SyncState.Syncing, null);
|
||||
|
||||
expect(incomingCallEmitted).not.toHaveBeenCalled();
|
||||
});
|
||||
|
@ -23,7 +23,10 @@ import { Optional } from "matrix-events-sdk/lib/types";
|
||||
export class NamespacedValue<S extends string, U extends string> {
|
||||
// Stable is optional, but one of the two parameters is required, hence the weird-looking types.
|
||||
// Goal is to to have developers explicitly say there is no stable value (if applicable).
|
||||
public constructor(public readonly stable: S | null | undefined, public readonly unstable?: U) {
|
||||
public constructor(stable: S, unstable: U);
|
||||
public constructor(stable: S, unstable?: U);
|
||||
public constructor(stable: null | undefined, unstable: U);
|
||||
public constructor(public readonly stable?: S | null, public readonly unstable?: U) {
|
||||
if (!this.unstable && !this.stable) {
|
||||
throw new Error("One of stable or unstable values must be supplied");
|
||||
}
|
||||
@ -33,10 +36,10 @@ export class NamespacedValue<S extends string, U extends string> {
|
||||
if (this.stable) {
|
||||
return this.stable;
|
||||
}
|
||||
return this.unstable;
|
||||
return this.unstable!;
|
||||
}
|
||||
|
||||
public get altName(): U | S | null {
|
||||
public get altName(): U | S | null | undefined {
|
||||
if (!this.stable) {
|
||||
return null;
|
||||
}
|
||||
@ -57,7 +60,7 @@ export class NamespacedValue<S extends string, U extends string> {
|
||||
// this desperately wants https://github.com/microsoft/TypeScript/pull/26349 at the top level of the class
|
||||
// so we can instantiate `NamespacedValue<string, _, _>` as a default type for that namespace.
|
||||
public findIn<T>(obj: any): Optional<T> {
|
||||
let val: T;
|
||||
let val: T | undefined = undefined;
|
||||
if (this.name) {
|
||||
val = obj?.[this.name];
|
||||
}
|
||||
@ -91,7 +94,7 @@ export class ServerControlledNamespacedValue<S extends string, U extends string>
|
||||
if (this.stable && !this.preferUnstable) {
|
||||
return this.stable;
|
||||
}
|
||||
return this.unstable;
|
||||
return this.unstable!;
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,10 +112,10 @@ export class UnstableValue<S extends string, U extends string> extends Namespace
|
||||
}
|
||||
|
||||
public get name(): U {
|
||||
return this.unstable;
|
||||
return this.unstable!;
|
||||
}
|
||||
|
||||
public get altName(): S {
|
||||
return this.stable;
|
||||
return this.stable!;
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { logger } from "./logger";
|
||||
import { MatrixClient } from "./matrix";
|
||||
import { MatrixError, MatrixClient } from "./matrix";
|
||||
import { IndexedToDeviceBatch, ToDeviceBatch, ToDeviceBatchWithTxnId, ToDevicePayload } from "./models/ToDeviceMessage";
|
||||
import { MatrixScheduler } from "./scheduler";
|
||||
|
||||
@ -72,7 +72,7 @@ export class ToDeviceMessageQueue {
|
||||
logger.debug("Attempting to send queued to-device messages");
|
||||
|
||||
this.sending = true;
|
||||
let headBatch: IndexedToDeviceBatch;
|
||||
let headBatch: IndexedToDeviceBatch | null;
|
||||
try {
|
||||
while (this.running) {
|
||||
headBatch = await this.client.store.getOldestToDeviceBatch();
|
||||
@ -90,11 +90,11 @@ export class ToDeviceMessageQueue {
|
||||
++this.retryAttempts;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
// eslint-disable-next-line new-cap
|
||||
const retryDelay = MatrixScheduler.RETRY_BACKOFF_RATELIMIT(null, this.retryAttempts, e);
|
||||
const retryDelay = MatrixScheduler.RETRY_BACKOFF_RATELIMIT(null, this.retryAttempts, <MatrixError>e);
|
||||
if (retryDelay === -1) {
|
||||
// the scheduler function doesn't differentiate between fatal errors and just getting
|
||||
// bored and giving up for now
|
||||
if (Math.floor(e.httpStatus / 100) === 4) {
|
||||
if (Math.floor((<MatrixError>e).httpStatus! / 100) === 4) {
|
||||
logger.error("Fatal error when sending to-device message - dropping to-device batch!", e);
|
||||
await this.client.store.removeToDeviceBatch(headBatch!.id);
|
||||
} else {
|
||||
|
@ -347,7 +347,7 @@ export class AutoDiscovery {
|
||||
* @returns {Promise<object>} Resolves to the domain's client config. Can
|
||||
* be an empty object.
|
||||
*/
|
||||
public static async getRawClientConfig(domain: string): Promise<IClientWellKnown> {
|
||||
public static async getRawClientConfig(domain?: string): Promise<IClientWellKnown> {
|
||||
if (!domain || typeof(domain) !== "string" || domain.length === 0) {
|
||||
throw new Error("'domain' must be a string of non-zero length");
|
||||
}
|
||||
|
278
src/client.ts
278
src/client.ts
@ -434,7 +434,7 @@ export interface IStartClientOpts {
|
||||
}
|
||||
|
||||
export interface IStoredClientOpts extends IStartClientOpts {
|
||||
crypto: Crypto;
|
||||
crypto?: Crypto;
|
||||
canResetEntireTimeline: ResetTimelineCallback;
|
||||
}
|
||||
|
||||
@ -697,41 +697,31 @@ export interface IMyDevice {
|
||||
[UNSTABLE_MSC3852_LAST_SEEN_UA.unstable]?: string;
|
||||
}
|
||||
|
||||
export interface Keys {
|
||||
keys: { [keyId: string]: string };
|
||||
usage: string[];
|
||||
user_id: string;
|
||||
}
|
||||
|
||||
export interface SigningKeys extends Keys {
|
||||
signatures: ISignatures;
|
||||
}
|
||||
|
||||
export interface DeviceKeys {
|
||||
[deviceId: string]: IDeviceKeys & {
|
||||
unsigned?: {
|
||||
device_display_name: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface IDownloadKeyResult {
|
||||
failures: { [serverName: string]: object };
|
||||
device_keys: {
|
||||
[userId: string]: {
|
||||
[deviceId: string]: IDeviceKeys & {
|
||||
unsigned?: {
|
||||
device_display_name: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
device_keys: { [userId: string]: DeviceKeys };
|
||||
// the following three fields were added in 1.1
|
||||
master_keys?: {
|
||||
[userId: string]: {
|
||||
keys: { [keyId: string]: string };
|
||||
usage: string[];
|
||||
user_id: string;
|
||||
};
|
||||
};
|
||||
self_signing_keys?: {
|
||||
[userId: string]: {
|
||||
keys: { [keyId: string]: string };
|
||||
signatures: ISignatures;
|
||||
usage: string[];
|
||||
user_id: string;
|
||||
};
|
||||
};
|
||||
user_signing_keys?: {
|
||||
[userId: string]: {
|
||||
keys: { [keyId: string]: string };
|
||||
signatures: ISignatures;
|
||||
usage: string[];
|
||||
user_id: string;
|
||||
};
|
||||
};
|
||||
master_keys?: { [userId: string]: Keys };
|
||||
self_signing_keys?: { [userId: string]: SigningKeys };
|
||||
user_signing_keys?: { [userId: string]: SigningKeys };
|
||||
}
|
||||
|
||||
export interface IClaimOTKsResult {
|
||||
@ -886,7 +876,7 @@ export type EmittedEvents = ClientEvent
|
||||
| BeaconEvent;
|
||||
|
||||
export type ClientEventHandlerMap = {
|
||||
[ClientEvent.Sync]: (state: SyncState, lastState?: SyncState, data?: ISyncStateData) => void;
|
||||
[ClientEvent.Sync]: (state: SyncState, lastState: SyncState | null, data?: ISyncStateData) => void;
|
||||
[ClientEvent.Event]: (event: MatrixEvent) => void;
|
||||
[ClientEvent.ToDeviceEvent]: (event: MatrixEvent) => void;
|
||||
[ClientEvent.AccountData]: (event: MatrixEvent, lastEvent?: MatrixEvent) => void;
|
||||
@ -943,22 +933,22 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
// We don't technically support this usage, but have reasons to do this.
|
||||
|
||||
protected canSupportVoip = false;
|
||||
protected peekSync: SyncApi = null;
|
||||
protected peekSync: SyncApi | null = null;
|
||||
protected isGuestAccount = false;
|
||||
protected ongoingScrollbacks: {[roomId: string]: {promise?: Promise<Room>, errorTs?: number}} = {};
|
||||
protected notifTimelineSet: EventTimelineSet = null;
|
||||
protected notifTimelineSet: EventTimelineSet | null = null;
|
||||
protected cryptoStore: CryptoStore;
|
||||
protected verificationMethods: VerificationMethod[];
|
||||
protected fallbackICEServerAllowed = false;
|
||||
protected roomList: RoomList;
|
||||
protected syncApi: SlidingSyncSdk | SyncApi;
|
||||
protected syncApi?: SlidingSyncSdk | SyncApi;
|
||||
public roomNameGenerator?: ICreateClientOpts["roomNameGenerator"];
|
||||
public pushRules: IPushRules;
|
||||
protected syncLeftRoomsPromise: Promise<Room[]>;
|
||||
public pushRules?: IPushRules;
|
||||
protected syncLeftRoomsPromise?: Promise<Room[]>;
|
||||
protected syncedLeftRooms = false;
|
||||
protected clientOpts: IStoredClientOpts;
|
||||
protected clientWellKnownIntervalID: ReturnType<typeof setInterval>;
|
||||
protected canResetTimelineCallback: ResetTimelineCallback;
|
||||
protected clientOpts?: IStoredClientOpts;
|
||||
protected clientWellKnownIntervalID?: ReturnType<typeof setInterval>;
|
||||
protected canResetTimelineCallback?: ResetTimelineCallback;
|
||||
|
||||
public canSupport = new Map<Feature, ServerSupport>();
|
||||
|
||||
@ -967,7 +957,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
|
||||
// Promise to a response of the server's /versions response
|
||||
// TODO: This should expire: https://github.com/matrix-org/matrix-js-sdk/issues/1020
|
||||
protected serverVersionsPromise: Promise<IServerVersions>;
|
||||
protected serverVersionsPromise?: Promise<IServerVersions>;
|
||||
|
||||
public cachedCapabilities: {
|
||||
capabilities: ICapabilities;
|
||||
@ -1242,7 +1232,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
this.clientRunning = false;
|
||||
|
||||
this.syncApi?.stop();
|
||||
this.syncApi = null;
|
||||
this.syncApi = undefined;
|
||||
|
||||
this.peekSync?.stopPeeking();
|
||||
|
||||
@ -1268,7 +1258,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* to the new device ID if the dehydration was successful.
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public async rehydrateDevice(): Promise<string> {
|
||||
public async rehydrateDevice(): Promise<string | undefined> {
|
||||
if (this.crypto) {
|
||||
throw new Error("Cannot rehydrate device after crypto is initialized");
|
||||
}
|
||||
@ -1317,7 +1307,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
},
|
||||
);
|
||||
|
||||
if (rehydrateResult.success === true) {
|
||||
if (rehydrateResult.success) {
|
||||
this.deviceId = getDeviceResult.device_id;
|
||||
logger.info("using dehydrated device");
|
||||
const pickleKey = this.pickleKey || "DEFAULT_KEY";
|
||||
@ -1395,7 +1385,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
key: Uint8Array,
|
||||
keyInfo: IDehydratedDeviceKeyInfo,
|
||||
deviceDisplayName?: string,
|
||||
): Promise<string> {
|
||||
): Promise<string | undefined> {
|
||||
if (!this.crypto) {
|
||||
logger.warn('not dehydrating device if crypto is not enabled');
|
||||
return;
|
||||
@ -1404,7 +1394,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
return this.crypto.dehydrationManager.dehydrateDevice();
|
||||
}
|
||||
|
||||
public async exportDevice(): Promise<IExportedDevice> {
|
||||
public async exportDevice(): Promise<IExportedDevice | undefined> {
|
||||
if (!this.crypto) {
|
||||
logger.warn('not exporting device if crypto is not enabled');
|
||||
return;
|
||||
@ -1452,7 +1442,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* Get the domain for this client's MXID
|
||||
* @return {?string} Domain of this MXID
|
||||
*/
|
||||
public getDomain(): string {
|
||||
public getDomain(): string | null {
|
||||
if (this.credentials && this.credentials.userId) {
|
||||
return this.credentials.userId.replace(/^.*?:/, '');
|
||||
}
|
||||
@ -1527,11 +1517,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {?SyncState} the sync state, which may be null.
|
||||
* @see module:client~MatrixClient#event:"sync"
|
||||
*/
|
||||
public getSyncState(): SyncState {
|
||||
if (!this.syncApi) {
|
||||
return null;
|
||||
}
|
||||
return this.syncApi.getSyncState();
|
||||
public getSyncState(): SyncState | null {
|
||||
return this.syncApi?.getSyncState() ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1600,7 +1587,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
public retryImmediately(): boolean {
|
||||
// don't await for this promise: we just want to kick it off
|
||||
this.toDeviceMessageQueue.sendQueue();
|
||||
return this.syncApi.retryImmediately();
|
||||
return this.syncApi?.retryImmediately() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1608,7 +1595,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @return {EventTimelineSet} the globl notification EventTimelineSet
|
||||
*/
|
||||
public getNotifTimelineSet(): EventTimelineSet {
|
||||
public getNotifTimelineSet(): EventTimelineSet | null {
|
||||
return this.notifTimelineSet;
|
||||
}
|
||||
|
||||
@ -1759,9 +1746,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {?string} base64-encoded ed25519 key. Null if crypto is
|
||||
* disabled.
|
||||
*/
|
||||
public getDeviceEd25519Key(): string {
|
||||
if (!this.crypto) return null;
|
||||
return this.crypto.getDeviceEd25519Key();
|
||||
public getDeviceEd25519Key(): string | null {
|
||||
return this.crypto?.getDeviceEd25519Key() ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1770,9 +1756,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {?string} base64-encoded curve25519 key. Null if crypto is
|
||||
* disabled.
|
||||
*/
|
||||
public getDeviceCurve25519Key(): string {
|
||||
if (!this.crypto) return null;
|
||||
return this.crypto.getDeviceCurve25519Key();
|
||||
public getDeviceCurve25519Key(): string | null {
|
||||
return this.crypto?.getDeviceCurve25519Key() ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1900,9 +1885,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
private async setDeviceVerification(
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
verified: boolean,
|
||||
blocked: boolean,
|
||||
known: boolean,
|
||||
verified?: boolean | null,
|
||||
blocked?: boolean | null,
|
||||
known?: boolean | null,
|
||||
): Promise<void> {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
@ -2058,7 +2043,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @returns {string} the key ID
|
||||
*/
|
||||
public getCrossSigningId(type: CrossSigningKey | string = CrossSigningKey.Master): string {
|
||||
public getCrossSigningId(type: CrossSigningKey | string = CrossSigningKey.Master): string | null {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
@ -2074,7 +2059,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @returns {CrossSigningInfo} the cross signing information for the user.
|
||||
*/
|
||||
public getStoredCrossSigningForUser(userId: string): CrossSigningInfo {
|
||||
public getStoredCrossSigningForUser(userId: string): CrossSigningInfo | null {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
@ -2408,7 +2393,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*
|
||||
* @return {string} the contents of the secret
|
||||
*/
|
||||
public getSecret(name: string): Promise<string> {
|
||||
public getSecret(name: string): Promise<string | undefined> {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
@ -2656,7 +2641,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* trust information (as returned by isKeyBackupTrusted)
|
||||
* in trustInfo.
|
||||
*/
|
||||
public checkKeyBackup(): Promise<IKeyBackupCheck> {
|
||||
public checkKeyBackup(): Promise<IKeyBackupCheck | null> {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
return this.crypto.backupManager.checkKeyBackup();
|
||||
}
|
||||
|
||||
@ -2672,7 +2660,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
{ prefix: ClientPrefix.V3 },
|
||||
);
|
||||
} catch (e) {
|
||||
if (e.errcode === 'M_NOT_FOUND') {
|
||||
if ((<MatrixError>e).errcode === 'M_NOT_FOUND') {
|
||||
return null;
|
||||
} else {
|
||||
throw e;
|
||||
@ -2693,6 +2681,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* }
|
||||
*/
|
||||
public isKeyBackupTrusted(info: IKeyBackupInfo): Promise<TrustInfo> {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
return this.crypto.backupManager.isKeyBackupTrusted(info);
|
||||
}
|
||||
|
||||
@ -2701,7 +2692,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* the server, otherwise false. If we haven't completed a successful check
|
||||
* of key backup status yet, returns null.
|
||||
*/
|
||||
public getKeyBackupEnabled(): boolean {
|
||||
public getKeyBackupEnabled(): boolean | null {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
@ -2750,7 +2741,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*/
|
||||
// TODO: Verify types
|
||||
public async prepareKeyBackupVersion(
|
||||
password?: string,
|
||||
password?: string | Uint8Array | null,
|
||||
opts: IKeyBackupPrepareOpts = { secureSecretStorage: false },
|
||||
): Promise<Pick<IPreparedKeyBackupVersion, "algorithm" | "auth_data" | "recovery_key">> {
|
||||
if (!this.crypto) {
|
||||
@ -3053,17 +3044,20 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
targetSessionId?: string,
|
||||
opts?: IKeyBackupRestoreOpts,
|
||||
): Promise<IKeyBackupRestoreResult> {
|
||||
if (!this.crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
const storedKey = await this.getSecret("m.megolm_backup.v1");
|
||||
|
||||
// ensure that the key is in the right format. If not, fix the key and
|
||||
// store the fixed version
|
||||
const fixedKey = fixBackupKey(storedKey);
|
||||
if (fixedKey) {
|
||||
const [keyId] = await this.crypto.getSecretStorageKey();
|
||||
await this.storeSecret("m.megolm_backup.v1", fixedKey, [keyId]);
|
||||
const keys = await this.crypto.getSecretStorageKey();
|
||||
await this.storeSecret("m.megolm_backup.v1", fixedKey, [keys![0]]);
|
||||
}
|
||||
|
||||
const privKey = decodeBase64(fixedKey || storedKey);
|
||||
const privKey = decodeBase64(fixedKey || storedKey!);
|
||||
return this.restoreKeyBackup(
|
||||
privKey, targetRoomId, targetSessionId, backupInfo, opts,
|
||||
);
|
||||
@ -3406,7 +3400,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*/
|
||||
public setAccountData(eventType: EventType | string, content: IContent): Promise<{}> {
|
||||
const path = utils.encodeUri("/user/$userId/account_data/$type", {
|
||||
$userId: this.credentials.userId,
|
||||
$userId: this.credentials.userId!,
|
||||
$type: eventType,
|
||||
});
|
||||
return retryNetworkOperation(5, () => {
|
||||
@ -3442,7 +3436,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
return event.getContent<T>();
|
||||
}
|
||||
const path = utils.encodeUri("/user/$userId/account_data/$type", {
|
||||
$userId: this.credentials.userId,
|
||||
$userId: this.credentials.userId!,
|
||||
$type: eventType,
|
||||
});
|
||||
try {
|
||||
@ -3506,7 +3500,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
|
||||
const room = this.getRoom(roomIdOrAlias);
|
||||
if (room?.hasMembershipState(this.credentials.userId, "join")) {
|
||||
if (room?.hasMembershipState(this.credentials.userId!, "join")) {
|
||||
return Promise.resolve(room);
|
||||
}
|
||||
|
||||
@ -3621,7 +3615,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*/
|
||||
public getRoomTags(roomId: string): Promise<ITagsResponse> {
|
||||
const path = utils.encodeUri("/user/$userId/rooms/$roomId/tags", {
|
||||
$userId: this.credentials.userId,
|
||||
$userId: this.credentials.userId!,
|
||||
$roomId: roomId,
|
||||
});
|
||||
return this.http.authedRequest(Method.Get, path);
|
||||
@ -3636,7 +3630,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*/
|
||||
public setRoomTag(roomId: string, tagName: string, metadata: ITagMetadata): Promise<{}> {
|
||||
const path = utils.encodeUri("/user/$userId/rooms/$roomId/tags/$tag", {
|
||||
$userId: this.credentials.userId,
|
||||
$userId: this.credentials.userId!,
|
||||
$roomId: roomId,
|
||||
$tag: tagName,
|
||||
});
|
||||
@ -3651,7 +3645,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*/
|
||||
public deleteRoomTag(roomId: string, tagName: string): Promise<{}> {
|
||||
const path = utils.encodeUri("/user/$userId/rooms/$roomId/tags/$tag", {
|
||||
$userId: this.credentials.userId,
|
||||
$userId: this.credentials.userId!,
|
||||
$roomId: roomId,
|
||||
$tag: tagName,
|
||||
});
|
||||
@ -3671,7 +3665,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
content: Record<string, any>,
|
||||
): Promise<{}> {
|
||||
const path = utils.encodeUri("/user/$userId/rooms/$roomId/account_data/$type", {
|
||||
$userId: this.credentials.userId,
|
||||
$userId: this.credentials.userId!,
|
||||
$roomId: roomId,
|
||||
$type: eventType,
|
||||
});
|
||||
@ -3734,8 +3728,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
roomId: string,
|
||||
beaconInfoContent: MBeaconInfoEventContent,
|
||||
) {
|
||||
const userId = this.getUserId();
|
||||
return this.sendStateEvent(roomId, M_BEACON_INFO.name, beaconInfoContent, userId);
|
||||
return this.sendStateEvent(roomId, M_BEACON_INFO.name, beaconInfoContent, this.getUserId()!);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3827,7 +3820,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}));
|
||||
|
||||
const room = this.getRoom(roomId);
|
||||
const thread = room?.getThread(threadId);
|
||||
const thread = threadId ? room?.getThread(threadId) : undefined;
|
||||
if (thread) {
|
||||
localEvent.setThread(thread);
|
||||
}
|
||||
@ -3847,8 +3840,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
// this event does get sent, we have the correct event_id
|
||||
const targetId = localEvent.getAssociatedId();
|
||||
if (targetId?.startsWith("~")) {
|
||||
const target = room.getPendingEvents().find(e => e.getId() === targetId);
|
||||
target.once(MatrixEventEvent.LocalEventIdReplaced, () => {
|
||||
const target = room?.getPendingEvents().find(e => e.getId() === targetId);
|
||||
target?.once(MatrixEventEvent.LocalEventIdReplaced, () => {
|
||||
localEvent.updateAssociatedId(target.getId());
|
||||
});
|
||||
}
|
||||
@ -3879,13 +3872,13 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @returns {Promise} returns a promise which resolves with the result of the send request
|
||||
* @private
|
||||
*/
|
||||
private encryptAndSendEvent(room: Room, event: MatrixEvent): Promise<ISendEventResponse> {
|
||||
private encryptAndSendEvent(room: Room | null, event: MatrixEvent): Promise<ISendEventResponse> {
|
||||
let cancelled = false;
|
||||
// Add an extra Promise.resolve() to turn synchronous exceptions into promise rejections,
|
||||
// so that we can handle synchronous and asynchronous exceptions with the
|
||||
// same code path.
|
||||
return Promise.resolve().then(() => {
|
||||
const encryptionPromise = this.encryptEventIfNeeded(event, room);
|
||||
const encryptionPromise = this.encryptEventIfNeeded(event, room ?? undefined);
|
||||
if (!encryptionPromise) return null; // doesn't need encryption
|
||||
|
||||
this.pendingEventEncryption.set(event.getId(), encryptionPromise);
|
||||
@ -3900,14 +3893,14 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
});
|
||||
}).then(() => {
|
||||
if (cancelled) return {} as ISendEventResponse;
|
||||
let promise: Promise<ISendEventResponse>;
|
||||
let promise: Promise<ISendEventResponse> | null = null;
|
||||
if (this.scheduler) {
|
||||
// if this returns a promise then the scheduler has control now and will
|
||||
// resolve/reject when it is done. Internally, the scheduler will invoke
|
||||
// processFn which is set to this._sendEventHttpRequest so the same code
|
||||
// path is executed regardless.
|
||||
promise = this.scheduler.queueEvent(event);
|
||||
if (promise && this.scheduler.getQueueForEvent(event).length > 1) {
|
||||
if (promise && this.scheduler.getQueueForEvent(event)!.length > 1) {
|
||||
// event is processed FIFO so if the length is 2 or more we know
|
||||
// this event is stuck behind an earlier event.
|
||||
this.updatePendingEventStatus(room, event, EventStatus.QUEUED);
|
||||
@ -3933,11 +3926,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
// consistent at that point.
|
||||
event.error = err;
|
||||
this.updatePendingEventStatus(room, event, EventStatus.NOT_SENT);
|
||||
// also put the event object on the error: the caller will need this
|
||||
// to resend or cancel the event
|
||||
err.event = event;
|
||||
} catch (e) {
|
||||
logger.error("Exception in error handler!", e.stack || err);
|
||||
logger.error("Exception in error handler!", (<Error>e).stack || err);
|
||||
}
|
||||
if (err instanceof MatrixError) {
|
||||
err.event = event;
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
@ -4128,8 +4121,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
// reasonably large pool of messages to parse.
|
||||
let eventType: string = EventType.RoomMessage;
|
||||
let sendContent: IContent = content as IContent;
|
||||
const makeContentExtensible = (content: IContent = {}, recurse = true): IPartialEvent<object> => {
|
||||
let newEvent: IPartialEvent<object> = null;
|
||||
const makeContentExtensible = (content: IContent = {}, recurse = true): IPartialEvent<object> | undefined => {
|
||||
let newEvent: IPartialEvent<object> | undefined;
|
||||
|
||||
if (content['msgtype'] === MsgType.Text) {
|
||||
newEvent = MessageEvent.from(content['body'], content['formatted_body']).serialize();
|
||||
@ -4818,7 +4811,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
|
||||
const populationResults: { [roomId: string]: Error } = {};
|
||||
const promises = [];
|
||||
const promises: Promise<any>[] = [];
|
||||
|
||||
const doLeave = (roomId: string) => {
|
||||
return this.leave(roomId).then(() => {
|
||||
@ -4907,7 +4900,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
|
||||
private membershipChange(
|
||||
roomId: string,
|
||||
userId: string,
|
||||
userId: string | undefined,
|
||||
membership: string,
|
||||
reason?: string,
|
||||
): Promise<{}> { // API returns an empty object
|
||||
@ -5020,13 +5013,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*/
|
||||
public async setPresence(opts: IPresenceOpts): Promise<void> {
|
||||
const path = utils.encodeUri("/presence/$userId/status", {
|
||||
$userId: this.credentials.userId,
|
||||
$userId: this.credentials.userId!,
|
||||
});
|
||||
|
||||
if (typeof opts === "string") {
|
||||
opts = { presence: opts }; // legacy
|
||||
}
|
||||
|
||||
const validStates = ["offline", "online", "unavailable"];
|
||||
if (validStates.indexOf(opts.presence) === -1) {
|
||||
throw new Error("Bad presence value: " + opts.presence);
|
||||
@ -5086,7 +5075,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
// reduce the required number of events appropriately
|
||||
limit = limit - numAdded;
|
||||
|
||||
const prom = new Promise<Room>((resolve, reject) => {
|
||||
const promise = new Promise<Room>((resolve, reject) => {
|
||||
// wait for a time before doing this request
|
||||
// (which may be 0 in order not to special case the code paths)
|
||||
sleep(timeToWaitMs).then(() => {
|
||||
@ -5114,7 +5103,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
room.oldState.paginationToken = null;
|
||||
}
|
||||
this.store.storeEvents(room, matrixEvents, res.end, true);
|
||||
this.ongoingScrollbacks[room.roomId] = null;
|
||||
delete this.ongoingScrollbacks[room.roomId];
|
||||
resolve(room);
|
||||
}).catch((err) => {
|
||||
this.ongoingScrollbacks[room.roomId] = {
|
||||
@ -5124,13 +5113,10 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
});
|
||||
});
|
||||
|
||||
info = {
|
||||
promise: prom,
|
||||
errorTs: null,
|
||||
};
|
||||
info = { promise };
|
||||
|
||||
this.ongoingScrollbacks[room.roomId] = info;
|
||||
return prom;
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -5181,7 +5167,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
);
|
||||
|
||||
let params: Record<string, string | string[]> | undefined = undefined;
|
||||
if (this.clientOpts.lazyLoadMembers) {
|
||||
if (this.clientOpts?.lazyLoadMembers) {
|
||||
params = { filter: JSON.stringify(Filter.LAZY_LOADING_MESSAGES_FILTER) };
|
||||
}
|
||||
|
||||
@ -5343,7 +5329,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
|
||||
let filter: IRoomEventFilter | null = null;
|
||||
if (this.clientOpts.lazyLoadMembers) {
|
||||
if (this.clientOpts?.lazyLoadMembers) {
|
||||
// create a shallow copy of LAZY_LOADING_MESSAGES_FILTER,
|
||||
// so the timelineFilter doesn't get written into it below
|
||||
filter = Object.assign({}, Filter.LAZY_LOADING_MESSAGES_FILTER);
|
||||
@ -5393,7 +5379,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
|
||||
let filter: IRoomEventFilter | null = null;
|
||||
if (this.clientOpts.lazyLoadMembers) {
|
||||
if (this.clientOpts?.lazyLoadMembers) {
|
||||
// create a shallow copy of LAZY_LOADING_MESSAGES_FILTER,
|
||||
// so the timelineFilter doesn't get written into it below
|
||||
filter = {
|
||||
@ -5441,7 +5427,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*/
|
||||
public paginateEventTimeline(eventTimeline: EventTimeline, opts: IPaginateOpts): Promise<boolean> {
|
||||
const isNotifTimeline = (eventTimeline.getTimelineSet() === this.notifTimelineSet);
|
||||
const room = this.getRoom(eventTimeline.getRoomId());
|
||||
const room = this.getRoom(eventTimeline.getRoomId()!);
|
||||
const isThreadTimeline = eventTimeline.getTimelineSet().isThreadTimeline;
|
||||
|
||||
// TODO: we should implement a backoff (as per scrollback()) to deal more
|
||||
@ -5519,7 +5505,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
|
||||
promise = this.createThreadListMessagesRequest(
|
||||
eventTimeline.getRoomId(),
|
||||
eventTimeline.getRoomId()!,
|
||||
token,
|
||||
opts.limit,
|
||||
dir,
|
||||
@ -5555,7 +5541,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
|
||||
promise = this.createMessagesRequest(
|
||||
eventTimeline.getRoomId(),
|
||||
eventTimeline.getRoomId()!,
|
||||
token,
|
||||
opts.limit,
|
||||
dir,
|
||||
@ -5612,7 +5598,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
// know about /notifications, so we have no choice but to start paginating
|
||||
// from the current point in time. This may well overlap with historical
|
||||
// notifs which are then inserted into the timeline by /sync responses.
|
||||
this.notifTimelineSet.resetLiveTimeline('end', null);
|
||||
this.notifTimelineSet.resetLiveTimeline('end');
|
||||
|
||||
// we could try to paginate a single event at this point in order to get
|
||||
// a more valid pagination token, but it just ends up with an out of order
|
||||
@ -5634,9 +5620,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public peekInRoom(roomId: string): Promise<Room> {
|
||||
if (this.peekSync) {
|
||||
this.peekSync.stopPeeking();
|
||||
}
|
||||
this.peekSync?.stopPeeking();
|
||||
this.peekSync = new SyncApi(this, this.clientOpts);
|
||||
return this.peekSync.peek(roomId);
|
||||
}
|
||||
@ -5943,8 +5927,8 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {Promise} Resolves: result object
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public setRoomMutePushRule(scope: string, roomId: string, mute: boolean): Promise<void> | void {
|
||||
let promise: Promise<unknown>;
|
||||
public setRoomMutePushRule(scope: string, roomId: string, mute: boolean): Promise<void> | undefined {
|
||||
let promise: Promise<unknown> | undefined;
|
||||
let hasDontNotifyRule = false;
|
||||
|
||||
// Get the existing room-kind push rule if any
|
||||
@ -5986,7 +5970,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
if (promise) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// Update this.pushRules when the operation completes
|
||||
promise.then(() => {
|
||||
promise!.then(() => {
|
||||
this.getPushRules().then((result) => {
|
||||
this.pushRules = result;
|
||||
resolve();
|
||||
@ -6173,7 +6157,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
logger.log("Marking success of sync left room request");
|
||||
this.syncedLeftRooms = true; // flip the bit on success
|
||||
}).finally(() => {
|
||||
this.syncLeftRoomsPromise = null; // cleanup ongoing request state
|
||||
this.syncLeftRoomsPromise = undefined; // cleanup ongoing request state
|
||||
});
|
||||
|
||||
return this.syncLeftRoomsPromise;
|
||||
@ -6235,13 +6219,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*/
|
||||
public async getOrCreateFilter(filterName: string, filter: Filter): Promise<string> {
|
||||
const filterId = this.store.getFilterIdByName(filterName);
|
||||
let existingId = undefined;
|
||||
let existingId: string | undefined;
|
||||
|
||||
if (filterId) {
|
||||
// check that the existing filter matches our expectations
|
||||
try {
|
||||
const existingFilter =
|
||||
await this.getFilter(this.credentials.userId, filterId, true);
|
||||
const existingFilter = await this.getFilter(this.credentials.userId!, filterId, true);
|
||||
if (existingFilter) {
|
||||
const oldDef = existingFilter.getDefinition();
|
||||
const newDef = filter.getDefinition();
|
||||
@ -6260,7 +6243,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
// name: "M_UNKNOWN",
|
||||
// message: "No row found",
|
||||
// }
|
||||
if (error.errcode !== "M_UNKNOWN" && error.errcode !== "M_NOT_FOUND") {
|
||||
if ((<MatrixError>error).errcode !== "M_UNKNOWN" && (<MatrixError>error).errcode !== "M_NOT_FOUND") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@ -6413,7 +6396,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
public isSynapseAdministrator(): Promise<boolean> {
|
||||
const path = utils.encodeUri(
|
||||
"/_synapse/admin/v1/users/$userId/admin",
|
||||
{ $userId: this.getUserId() },
|
||||
{ $userId: this.getUserId()! },
|
||||
);
|
||||
return this.http.authedRequest(
|
||||
Method.Get, path, undefined, undefined, { prefix: '' },
|
||||
@ -6452,7 +6435,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
private async fetchClientWellKnown(): Promise<void> {
|
||||
// `getRawClientConfig` does not throw or reject on network errors, instead
|
||||
// it absorbs errors and returns `{}`.
|
||||
this.clientWellKnownPromise = AutoDiscovery.getRawClientConfig(this.getDomain());
|
||||
this.clientWellKnownPromise = AutoDiscovery.getRawClientConfig(this.getDomain() ?? undefined);
|
||||
this.clientWellKnown = await this.clientWellKnownPromise;
|
||||
this.emit(ClientEvent.ClientWellKnown, this.clientWellKnown);
|
||||
}
|
||||
@ -6474,7 +6457,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*/
|
||||
public storeClientOptions(): Promise<void> { // XXX: Intended private, used in code
|
||||
const primTypes = ["boolean", "string", "number"];
|
||||
const serializableOpts = Object.entries(this.clientOpts)
|
||||
const serializableOpts = Object.entries(this.clientOpts!)
|
||||
.filter(([key, value]) => {
|
||||
return primTypes.includes(typeof value);
|
||||
})
|
||||
@ -6530,7 +6513,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
},
|
||||
).catch((e: Error) => {
|
||||
// Need to unset this if it fails, otherwise we'll never retry
|
||||
this.serverVersionsPromise = null;
|
||||
this.serverVersionsPromise = undefined;
|
||||
// but rethrow the exception to anything that was waiting
|
||||
throw e;
|
||||
});
|
||||
@ -6692,7 +6675,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {boolean} Whether or not members are lazy loaded by this client
|
||||
*/
|
||||
public hasLazyLoadMembersEnabled(): boolean {
|
||||
return !!this.clientOpts.lazyLoadMembers;
|
||||
return !!this.clientOpts?.lazyLoadMembers;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -6712,7 +6695,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* Get the callback set via `setCanResetTimelineCallback`.
|
||||
* @return {?Function} The callback or null
|
||||
*/
|
||||
public getCanResetTimelineCallback(): ResetTimelineCallback {
|
||||
public getCanResetTimelineCallback(): ResetTimelineCallback | undefined {
|
||||
return this.canResetTimelineCallback;
|
||||
}
|
||||
|
||||
@ -6734,7 +6717,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
eventType?: EventType | string | null,
|
||||
opts: IRelationsRequestOpts = { dir: Direction.Backward },
|
||||
): Promise<{
|
||||
originalEvent: MatrixEvent;
|
||||
originalEvent?: MatrixEvent;
|
||||
events: MatrixEvent[];
|
||||
nextBatch?: string;
|
||||
prevBatch?: string;
|
||||
@ -6775,7 +6758,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* triggering a user interaction.
|
||||
* @return {object}
|
||||
*/
|
||||
public getCrossSigningCacheCallbacks(): ICacheCallbacks {
|
||||
public getCrossSigningCacheCallbacks(): ICacheCallbacks | undefined {
|
||||
// XXX: Private member access
|
||||
return this.crypto?.crossSigningInfo.getCacheCallbacks();
|
||||
}
|
||||
@ -8032,7 +8015,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public getPushRules(): Promise<IPushRules> {
|
||||
return this.http.authedRequest(Method.Get, "/pushrules/").then((rules: IPushRules) => {
|
||||
return this.http.authedRequest<IPushRules>(Method.Get, "/pushrules/").then((rules: IPushRules) => {
|
||||
return PushProcessor.rewriteDefaultRules(rules);
|
||||
});
|
||||
}
|
||||
@ -8455,8 +8438,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
*/
|
||||
public getIdentityHashDetails(identityAccessToken: string): Promise<any> { // TODO: Types
|
||||
return this.http.idServerRequest(
|
||||
Method.Get, "/hash_details",
|
||||
null, IdentityPrefix.V2, identityAccessToken,
|
||||
Method.Get,
|
||||
"/hash_details",
|
||||
undefined,
|
||||
IdentityPrefix.V2,
|
||||
identityAccessToken,
|
||||
);
|
||||
}
|
||||
|
||||
@ -8530,7 +8516,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
|
||||
if (!response || !response['mappings']) return []; // no results
|
||||
|
||||
const foundAddresses = [/* {address: "plain@example.org", mxid} */];
|
||||
const foundAddresses: { address: string, mxid: string }[] = [];
|
||||
for (const hashed of Object.keys(response['mappings'])) {
|
||||
const mxid = response['mappings'][hashed];
|
||||
const plainAddress = localMapping[hashed];
|
||||
@ -8560,14 +8546,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
public async lookupThreePid(
|
||||
medium: string,
|
||||
address: string,
|
||||
identityAccessToken?: string,
|
||||
identityAccessToken: string,
|
||||
): Promise<any> { // TODO: Types
|
||||
// Note: we're using the V2 API by calling this function, but our
|
||||
// function contract requires a V1 response. We therefore have to
|
||||
// convert it manually.
|
||||
const response = await this.identityHashedLookup(
|
||||
[[address, medium]], identityAccessToken,
|
||||
);
|
||||
const response = await this.identityHashedLookup([[address, medium]], identityAccessToken);
|
||||
const result = response.find(p => p.address === address);
|
||||
if (!result) {
|
||||
return {};
|
||||
@ -8608,7 +8592,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
query.map(p => [p[1], p[0]]), identityAccessToken,
|
||||
);
|
||||
|
||||
const v1results = [];
|
||||
const v1results: [medium: string, address: string, mxid: string][] = [];
|
||||
for (const mapping of response) {
|
||||
const originalQuery = query.find(p => p[1] === mapping.address);
|
||||
if (!originalQuery) {
|
||||
@ -8800,7 +8784,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
$roomId: roomId,
|
||||
});
|
||||
|
||||
const queryParams: Record<string, string | string[]> = {
|
||||
const queryParams: QueryDict = {
|
||||
suggested_only: String(suggestedOnly),
|
||||
max_depth: maxDepth?.toString(),
|
||||
from: fromToken,
|
||||
|
@ -114,10 +114,10 @@ export class CrossSigningInfo {
|
||||
}
|
||||
|
||||
if (expectedPubkey === undefined) {
|
||||
expectedPubkey = this.getId(type);
|
||||
expectedPubkey = this.getId(type)!;
|
||||
}
|
||||
|
||||
function validateKey(key: Uint8Array): [string, PkSigning] {
|
||||
function validateKey(key: Uint8Array | null): [string, PkSigning] | undefined {
|
||||
if (!key) return;
|
||||
const signing = new global.Olm.PkSigning();
|
||||
const gotPubkey = signing.init_with_seed(key);
|
||||
@ -127,7 +127,7 @@ export class CrossSigningInfo {
|
||||
signing.free();
|
||||
}
|
||||
|
||||
let privkey;
|
||||
let privkey: Uint8Array | null = null;
|
||||
if (this.cacheCallbacks.getCrossSigningKeyCache && shouldCache) {
|
||||
privkey = await this.cacheCallbacks.getCrossSigningKeyCache(type, expectedPubkey);
|
||||
}
|
||||
@ -141,7 +141,7 @@ export class CrossSigningInfo {
|
||||
const result = validateKey(privkey);
|
||||
if (result) {
|
||||
if (this.cacheCallbacks.storeCrossSigningKeyCache && shouldCache) {
|
||||
await this.cacheCallbacks.storeCrossSigningKeyCache(type, privkey);
|
||||
await this.cacheCallbacks.storeCrossSigningKeyCache(type, privkey!);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -169,7 +169,7 @@ export class CrossSigningInfo {
|
||||
* with, or null if it is not present or not encrypted with a trusted
|
||||
* key
|
||||
*/
|
||||
public async isStoredInSecretStorage(secretStorage: SecretStorage): Promise<Record<string, object>> {
|
||||
public async isStoredInSecretStorage(secretStorage: SecretStorage): Promise<Record<string, object> | null> {
|
||||
// check what SSSS keys have encrypted the master key (if any)
|
||||
const stored = await secretStorage.isStored("m.cross_signing.master") || {};
|
||||
// then check which of those SSSS keys have also encrypted the SSK and USK
|
||||
@ -213,7 +213,7 @@ export class CrossSigningInfo {
|
||||
* @param {SecretStorage} secretStorage The secret store using account data
|
||||
* @return {Uint8Array} The private key
|
||||
*/
|
||||
public static async getFromSecretStorage(type: string, secretStorage: SecretStorage): Promise<Uint8Array> {
|
||||
public static async getFromSecretStorage(type: string, secretStorage: SecretStorage): Promise<Uint8Array | null> {
|
||||
const encodedKey = await secretStorage.get(`m.cross_signing.${type}`);
|
||||
if (!encodedKey) {
|
||||
return null;
|
||||
@ -233,7 +233,7 @@ export class CrossSigningInfo {
|
||||
if (!cacheCallbacks) return false;
|
||||
const types = type ? [type] : ["master", "self_signing", "user_signing"];
|
||||
for (const t of types) {
|
||||
if (!await cacheCallbacks.getCrossSigningKeyCache(t)) {
|
||||
if (!await cacheCallbacks.getCrossSigningKeyCache?.(t)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -250,7 +250,7 @@ export class CrossSigningInfo {
|
||||
const cacheCallbacks = this.cacheCallbacks;
|
||||
if (!cacheCallbacks) return keys;
|
||||
for (const type of ["master", "self_signing", "user_signing"]) {
|
||||
const privKey = await cacheCallbacks.getCrossSigningKeyCache(type);
|
||||
const privKey = await cacheCallbacks.getCrossSigningKeyCache?.(type);
|
||||
if (!privKey) {
|
||||
continue;
|
||||
}
|
||||
@ -268,7 +268,7 @@ export class CrossSigningInfo {
|
||||
*
|
||||
* @return {string} the ID
|
||||
*/
|
||||
public getId(type = "master"): string {
|
||||
public getId(type = "master"): string | null {
|
||||
if (!this.keys[type]) return null;
|
||||
const keyInfo = this.keys[type];
|
||||
return publicKeyFromKeyInfo(keyInfo);
|
||||
@ -469,7 +469,7 @@ export class CrossSigningInfo {
|
||||
}
|
||||
}
|
||||
|
||||
public async signUser(key: CrossSigningInfo): Promise<ICrossSigningKey> {
|
||||
public async signUser(key: CrossSigningInfo): Promise<ICrossSigningKey | undefined> {
|
||||
if (!this.keys.user_signing) {
|
||||
logger.info("No user signing key: not signing user");
|
||||
return;
|
||||
@ -477,7 +477,7 @@ export class CrossSigningInfo {
|
||||
return this.signObject(key.keys.master, "user_signing");
|
||||
}
|
||||
|
||||
public async signDevice(userId: string, device: DeviceInfo): Promise<ISignedKey> {
|
||||
public async signDevice(userId: string, device: DeviceInfo): Promise<ISignedKey | undefined> {
|
||||
if (userId !== this.userId) {
|
||||
throw new Error(
|
||||
`Trying to sign ${userId}'s device; can only sign our own device`,
|
||||
@ -521,9 +521,9 @@ export class CrossSigningInfo {
|
||||
return new UserTrustLevel(false, false, userCrossSigning.firstUse);
|
||||
}
|
||||
|
||||
let userTrusted;
|
||||
let userTrusted: boolean;
|
||||
const userMaster = userCrossSigning.keys.master;
|
||||
const uskId = this.getId('user_signing');
|
||||
const uskId = this.getId('user_signing')!;
|
||||
try {
|
||||
pkVerify(userMaster, uskId, this.userId);
|
||||
userTrusted = true;
|
||||
@ -567,7 +567,7 @@ export class CrossSigningInfo {
|
||||
const deviceObj = deviceToObject(device, userCrossSigning.userId);
|
||||
try {
|
||||
// if we can verify the user's SSK from their master key...
|
||||
pkVerify(userSSK, userCrossSigning.getId(), userCrossSigning.userId);
|
||||
pkVerify(userSSK, userCrossSigning.getId()!, userCrossSigning.userId);
|
||||
// ...and this device's key from their SSK...
|
||||
pkVerify(deviceObj, publicKeyFromKeyInfo(userSSK), userCrossSigning.userId);
|
||||
// ...then we trust this device as much as far as we trust the user
|
||||
@ -752,7 +752,7 @@ export type KeysDuringVerification = [[string, PkSigning], [string, PkSigning],
|
||||
* @param {string} userId The user ID being verified
|
||||
* @param {string} deviceId The device ID being verified
|
||||
*/
|
||||
export function requestKeysDuringVerification(
|
||||
export async function requestKeysDuringVerification(
|
||||
baseApis: MatrixClient,
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
@ -766,7 +766,7 @@ export function requestKeysDuringVerification(
|
||||
// it. We return here in order to test.
|
||||
return new Promise<KeysDuringVerification | void>((resolve, reject) => {
|
||||
const client = baseApis;
|
||||
const original = client.crypto.crossSigningInfo;
|
||||
const original = client.crypto!.crossSigningInfo;
|
||||
|
||||
// We already have all of the infrastructure we need to validate and
|
||||
// cache cross-signing keys, so instead of replicating that, here we set
|
||||
@ -801,7 +801,7 @@ export function requestKeysDuringVerification(
|
||||
|
||||
// also request and cache the key backup key
|
||||
const backupKeyPromise = (async () => {
|
||||
const cachedKey = await client.crypto.getSessionBackupPrivateKey();
|
||||
const cachedKey = await client.crypto!.getSessionBackupPrivateKey();
|
||||
if (!cachedKey) {
|
||||
logger.info("No cached backup key found. Requesting...");
|
||||
const secretReq = client.requestSecret(
|
||||
@ -811,13 +811,13 @@ export function requestKeysDuringVerification(
|
||||
logger.info("Got key backup key, decoding...");
|
||||
const decodedKey = decodeBase64(base64Key);
|
||||
logger.info("Decoded backup key, storing...");
|
||||
await client.crypto.storeSessionBackupPrivateKey(
|
||||
await client.crypto!.storeSessionBackupPrivateKey(
|
||||
Uint8Array.from(decodedKey),
|
||||
);
|
||||
logger.info("Backup key stored. Starting backup restore...");
|
||||
const backupInfo = await client.getKeyBackupVersion();
|
||||
// no need to await for this - just let it go in the bg
|
||||
client.restoreKeyBackupWithCache(undefined, undefined, backupInfo).then(() => {
|
||||
client.restoreKeyBackupWithCache(undefined, undefined, backupInfo!).then(() => {
|
||||
logger.info("Backup restored.");
|
||||
});
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import { CrossSigningInfo, ICrossSigningInfo } from './CrossSigning';
|
||||
import * as olmlib from './olmlib';
|
||||
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
|
||||
import { chunkPromises, defer, IDeferred, sleep } from '../utils';
|
||||
import { IDownloadKeyResult, MatrixClient } from "../client";
|
||||
import { DeviceKeys, IDownloadKeyResult, Keys, MatrixClient, SigningKeys } from "../client";
|
||||
import { OlmDevice } from "./OlmDevice";
|
||||
import { CryptoStore } from "./store/base";
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter";
|
||||
@ -81,24 +81,24 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
// The 'next_batch' sync token at the point the data was written,
|
||||
// ie. a token representing the point immediately after the
|
||||
// moment represented by the snapshot in the db.
|
||||
private syncToken: string = null;
|
||||
private syncToken: string | null = null;
|
||||
|
||||
private keyDownloadsInProgressByUser: { [userId: string]: Promise<void> } = {};
|
||||
private keyDownloadsInProgressByUser = new Map<string, Promise<void>>;
|
||||
|
||||
// Set whenever changes are made other than setting the sync token
|
||||
private dirty = false;
|
||||
|
||||
// Promise resolved when device data is saved
|
||||
private savePromise: Promise<boolean> = null;
|
||||
private savePromise: Promise<boolean> | null = null;
|
||||
// Function that resolves the save promise
|
||||
private resolveSavePromise: (saved: boolean) => void = null;
|
||||
private resolveSavePromise: ((saved: boolean) => void) | null = null;
|
||||
// The time the save is scheduled for
|
||||
private savePromiseTime: number = null;
|
||||
private savePromiseTime: number | null = null;
|
||||
// The timer used to delay the save
|
||||
private saveTimer: ReturnType<typeof setTimeout> = null;
|
||||
private saveTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
// True if we have fetched data from the server or loaded a non-empty
|
||||
// set of device data from the store
|
||||
private hasFetched: boolean = null;
|
||||
private hasFetched: boolean | null = null;
|
||||
|
||||
private readonly serialiser: DeviceListUpdateSerialiser;
|
||||
|
||||
@ -127,7 +127,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
deviceData.crossSigningInfo || {} : {};
|
||||
this.deviceTrackingStatus = deviceData ?
|
||||
deviceData.trackingStatus : {};
|
||||
this.syncToken = deviceData ? deviceData.syncToken : null;
|
||||
this.syncToken = deviceData?.syncToken ?? null;
|
||||
this.userByIdentityKey = {};
|
||||
for (const user of Object.keys(this.devices)) {
|
||||
const userDevices = this.devices[user];
|
||||
@ -181,7 +181,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
if (this.savePromiseTime && targetTime < this.savePromiseTime) {
|
||||
// There's a save scheduled but for after we would like: cancel
|
||||
// it & schedule one for the time we want
|
||||
clearTimeout(this.saveTimer);
|
||||
clearTimeout(this.saveTimer!);
|
||||
this.saveTimer = null;
|
||||
this.savePromiseTime = null;
|
||||
// (but keep the save promise since whatever called save before
|
||||
@ -216,13 +216,13 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
devices: this.devices,
|
||||
crossSigningInfo: this.crossSigningInfo,
|
||||
trackingStatus: this.deviceTrackingStatus,
|
||||
syncToken: this.syncToken,
|
||||
syncToken: this.syncToken ?? undefined,
|
||||
}, txn);
|
||||
},
|
||||
).then(() => {
|
||||
// The device list is considered dirty until the write completes.
|
||||
this.dirty = false;
|
||||
resolveSavePromise(true);
|
||||
resolveSavePromise?.(true);
|
||||
}, err => {
|
||||
logger.error('Failed to save device tracking data', this.syncToken);
|
||||
logger.error(err);
|
||||
@ -230,7 +230,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
}, delay);
|
||||
}
|
||||
|
||||
return savePromise;
|
||||
return savePromise!;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -238,7 +238,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
*
|
||||
* @return {string} The sync token
|
||||
*/
|
||||
public getSyncToken(): string {
|
||||
public getSyncToken(): string | null {
|
||||
return this.syncToken;
|
||||
}
|
||||
|
||||
@ -272,14 +272,14 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
|
||||
userIds.forEach((u) => {
|
||||
const trackingStatus = this.deviceTrackingStatus[u];
|
||||
if (this.keyDownloadsInProgressByUser[u]) {
|
||||
if (this.keyDownloadsInProgressByUser.has(u)) {
|
||||
// already a key download in progress/queued for this user; its results
|
||||
// will be good enough for us.
|
||||
logger.log(
|
||||
`downloadKeys: already have a download in progress for ` +
|
||||
`${u}: awaiting its result`,
|
||||
);
|
||||
promises.push(this.keyDownloadsInProgressByUser[u]);
|
||||
promises.push(this.keyDownloadsInProgressByUser.get(u)!);
|
||||
} else if (forceDownload || trackingStatus != TrackingStatus.UpToDate) {
|
||||
usersToDownload.push(u);
|
||||
}
|
||||
@ -341,7 +341,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
if (!devs) {
|
||||
return null;
|
||||
}
|
||||
const res = [];
|
||||
const res: DeviceInfo[] = [];
|
||||
for (const deviceId in devs) {
|
||||
if (devs.hasOwnProperty(deviceId)) {
|
||||
res.push(DeviceInfo.fromStorage(devs[deviceId], deviceId));
|
||||
@ -362,7 +362,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
return this.devices[userId];
|
||||
}
|
||||
|
||||
public getStoredCrossSigningForUser(userId: string): CrossSigningInfo {
|
||||
public getStoredCrossSigningForUser(userId: string): CrossSigningInfo | null {
|
||||
if (!this.crossSigningInfo[userId]) return null;
|
||||
|
||||
return CrossSigningInfo.fromStorage(this.crossSigningInfo[userId], userId);
|
||||
@ -382,9 +382,9 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
* @return {module:crypto/deviceinfo?} device, or undefined
|
||||
* if we don't know about this device
|
||||
*/
|
||||
public getStoredDevice(userId: string, deviceId: string): DeviceInfo {
|
||||
public getStoredDevice(userId: string, deviceId: string): DeviceInfo | undefined {
|
||||
const devs = this.devices[userId];
|
||||
if (!devs || !devs[deviceId]) {
|
||||
if (!devs?.[deviceId]) {
|
||||
return undefined;
|
||||
}
|
||||
return DeviceInfo.fromStorage(devs[deviceId], deviceId);
|
||||
@ -398,11 +398,8 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
*
|
||||
* @return {string} user ID
|
||||
*/
|
||||
public getUserByIdentityKey(algorithm: string, senderKey: string): string {
|
||||
if (
|
||||
algorithm !== olmlib.OLM_ALGORITHM &&
|
||||
algorithm !== olmlib.MEGOLM_ALGORITHM
|
||||
) {
|
||||
public getUserByIdentityKey(algorithm: string, senderKey: string): string | null {
|
||||
if (algorithm !== olmlib.OLM_ALGORITHM && algorithm !== olmlib.MEGOLM_ALGORITHM) {
|
||||
// we only deal in olm keys
|
||||
return null;
|
||||
}
|
||||
@ -557,7 +554,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
public refreshOutdatedDeviceLists(): Promise<void> {
|
||||
this.saveIfDirty();
|
||||
|
||||
const usersToDownload = [];
|
||||
const usersToDownload: string[] = [];
|
||||
for (const userId of Object.keys(this.deviceTrackingStatus)) {
|
||||
const stat = this.deviceTrackingStatus[userId];
|
||||
if (stat == TrackingStatus.PendingDownload) {
|
||||
@ -617,7 +614,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const prom = this.serialiser.updateDevicesForUsers(users, this.syncToken).then(() => {
|
||||
const prom = this.serialiser.updateDevicesForUsers(users, this.syncToken!).then(() => {
|
||||
finished(true);
|
||||
}, (e) => {
|
||||
logger.error(
|
||||
@ -628,7 +625,7 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
});
|
||||
|
||||
users.forEach((u) => {
|
||||
this.keyDownloadsInProgressByUser[u] = prom;
|
||||
this.keyDownloadsInProgressByUser.set(u, prom);
|
||||
const stat = this.deviceTrackingStatus[u];
|
||||
if (stat == TrackingStatus.PendingDownload) {
|
||||
this.deviceTrackingStatus[u] = TrackingStatus.DownloadInProgress;
|
||||
@ -643,11 +640,11 @@ export class DeviceList extends TypedEventEmitter<EmittedEvents, CryptoEventHand
|
||||
// we may have queued up another download request for this user
|
||||
// since we started this request. If that happens, we should
|
||||
// ignore the completion of the first one.
|
||||
if (this.keyDownloadsInProgressByUser[u] !== prom) {
|
||||
if (this.keyDownloadsInProgressByUser.get(u) !== prom) {
|
||||
logger.log('Another update in the queue for', u, '- not marking up-to-date');
|
||||
return;
|
||||
}
|
||||
delete this.keyDownloadsInProgressByUser[u];
|
||||
this.keyDownloadsInProgressByUser.delete(u);
|
||||
const stat = this.deviceTrackingStatus[u];
|
||||
if (stat == TrackingStatus.DownloadInProgress) {
|
||||
if (success) {
|
||||
@ -687,9 +684,9 @@ class DeviceListUpdateSerialiser {
|
||||
|
||||
// deferred which is resolved when the queued users are downloaded.
|
||||
// non-null indicates that we have users queued for download.
|
||||
private queuedQueryDeferred: IDeferred<void> = null;
|
||||
private queuedQueryDeferred?: IDeferred<void>;
|
||||
|
||||
private syncToken: string = null; // The sync token we send with the requests
|
||||
private syncToken?: string; // The sync token we send with the requests
|
||||
|
||||
/*
|
||||
* @param {object} baseApis Base API object
|
||||
@ -748,7 +745,7 @@ class DeviceListUpdateSerialiser {
|
||||
const downloadUsers = Object.keys(this.keyDownloadsQueuedByUser);
|
||||
this.keyDownloadsQueuedByUser = {};
|
||||
const deferred = this.queuedQueryDeferred;
|
||||
this.queuedQueryDeferred = null;
|
||||
this.queuedQueryDeferred = undefined;
|
||||
|
||||
logger.log('Starting key download for', downloadUsers);
|
||||
this.downloadInProgress = true;
|
||||
@ -785,9 +782,9 @@ class DeviceListUpdateSerialiser {
|
||||
try {
|
||||
await this.processQueryResponseForUser(
|
||||
userId, dk[userId], {
|
||||
master: masterKeys[userId],
|
||||
self_signing: ssks[userId],
|
||||
user_signing: usks[userId],
|
||||
master: masterKeys?.[userId],
|
||||
self_signing: ssks?.[userId],
|
||||
user_signing: usks?.[userId],
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
@ -800,7 +797,7 @@ class DeviceListUpdateSerialiser {
|
||||
logger.log('Completed key download for ' + downloadUsers);
|
||||
|
||||
this.downloadInProgress = false;
|
||||
deferred.resolve();
|
||||
deferred?.resolve();
|
||||
|
||||
// if we have queued users, fire off another request.
|
||||
if (this.queuedQueryDeferred) {
|
||||
@ -809,19 +806,19 @@ class DeviceListUpdateSerialiser {
|
||||
}, (e) => {
|
||||
logger.warn('Error downloading keys for ' + downloadUsers + ':', e);
|
||||
this.downloadInProgress = false;
|
||||
deferred.reject(e);
|
||||
deferred?.reject(e);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
return deferred!.promise;
|
||||
}
|
||||
|
||||
private async processQueryResponseForUser(
|
||||
userId: string,
|
||||
dkResponse: IDownloadKeyResult["device_keys"]["user_id"],
|
||||
dkResponse: DeviceKeys,
|
||||
crossSigningResponse: {
|
||||
master: IDownloadKeyResult["master_keys"]["user_id"];
|
||||
self_signing: IDownloadKeyResult["master_keys"]["user_id"]; // eslint-disable-line camelcase
|
||||
user_signing: IDownloadKeyResult["user_signing_keys"]["user_id"]; // eslint-disable-line camelcase
|
||||
master?: Keys;
|
||||
self_signing?: SigningKeys;
|
||||
user_signing?: SigningKeys;
|
||||
},
|
||||
): Promise<void> {
|
||||
logger.log('got device keys for ' + userId + ':', dkResponse);
|
||||
@ -840,7 +837,7 @@ class DeviceListUpdateSerialiser {
|
||||
|
||||
await updateStoredDeviceKeysForUser(
|
||||
this.olmDevice, userId, userStore, dkResponse || {},
|
||||
this.baseApis.getUserId(), this.baseApis.deviceId,
|
||||
this.baseApis.getUserId()!, this.baseApis.deviceId!,
|
||||
);
|
||||
|
||||
// put the updates into the object that will be returned as our results
|
||||
|
@ -15,9 +15,8 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { Account, InboundGroupSession, OutboundGroupSession, Session, Utility } from "@matrix-org/olm";
|
||||
import { Logger } from "loglevel";
|
||||
|
||||
import { logger } from '../logger';
|
||||
import { logger, PrefixedLogger } from '../logger';
|
||||
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
|
||||
import * as algorithms from './algorithms';
|
||||
import { CryptoStore, IProblem, ISessionInfo, IWithheld } from "./store/base";
|
||||
@ -121,9 +120,9 @@ interface IInboundGroupSessionKey {
|
||||
chain_index: number;
|
||||
key: string;
|
||||
forwarding_curve25519_key_chain: string[];
|
||||
sender_claimed_ed25519_key: string;
|
||||
sender_claimed_ed25519_key: string | null;
|
||||
shared_history: boolean;
|
||||
untrusted: boolean;
|
||||
untrusted?: boolean;
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
@ -145,9 +144,9 @@ export class OlmDevice {
|
||||
public pickleKey = "DEFAULT_KEY"; // set by consumers
|
||||
|
||||
// don't know these until we load the account from storage in init()
|
||||
public deviceCurve25519Key: string = null;
|
||||
public deviceEd25519Key: string = null;
|
||||
private maxOneTimeKeys: number = null;
|
||||
public deviceCurve25519Key: string | null = null;
|
||||
public deviceEd25519Key: string | null = null;
|
||||
private maxOneTimeKeys: number | null = null;
|
||||
|
||||
// we don't bother stashing outboundgroupsessions in the cryptoStore -
|
||||
// instead we keep them here.
|
||||
@ -266,8 +265,8 @@ export class OlmDevice {
|
||||
lastReceivedMessageTs: session.lastReceivedMessageTs,
|
||||
};
|
||||
this.cryptoStore.storeEndToEndSession(
|
||||
deviceKey,
|
||||
sessionId,
|
||||
deviceKey!,
|
||||
sessionId!,
|
||||
sessionInfo,
|
||||
txn,
|
||||
);
|
||||
@ -358,7 +357,7 @@ export class OlmDevice {
|
||||
// is not exactly the same thing you get in method _getSession
|
||||
// see documentation of IndexedDBCryptoStore.getAllEndToEndSessions
|
||||
this.cryptoStore.getAllEndToEndSessions(txn, (pickledSession) => {
|
||||
result.sessions.push(pickledSession);
|
||||
result.sessions!.push(pickledSession!);
|
||||
});
|
||||
},
|
||||
);
|
||||
@ -384,8 +383,8 @@ export class OlmDevice {
|
||||
func: (unpickledSessionInfo: IUnpickledSessionInfo) => void,
|
||||
): void {
|
||||
this.cryptoStore.getEndToEndSession(
|
||||
deviceKey, sessionId, txn, (sessionInfo: ISessionInfo) => {
|
||||
this.unpickleSession(sessionInfo, func);
|
||||
deviceKey, sessionId, txn, (sessionInfo: ISessionInfo | null) => {
|
||||
this.unpickleSession(sessionInfo!, func);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -405,7 +404,7 @@ export class OlmDevice {
|
||||
): void {
|
||||
const session = new global.Olm.Session();
|
||||
try {
|
||||
session.unpickle(this.pickleKey, sessionInfo.session);
|
||||
session.unpickle(this.pickleKey, sessionInfo.session!);
|
||||
const unpickledSessInfo: IUnpickledSessionInfo = Object.assign({}, sessionInfo, { session });
|
||||
|
||||
func(unpickledSessInfo);
|
||||
@ -491,7 +490,7 @@ export class OlmDevice {
|
||||
* @return {number} number of keys
|
||||
*/
|
||||
public maxNumberOfOneTimeKeys(): number {
|
||||
return this.maxOneTimeKeys;
|
||||
return this.maxOneTimeKeys ?? -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -554,7 +553,7 @@ export class OlmDevice {
|
||||
});
|
||||
},
|
||||
);
|
||||
return result;
|
||||
return result!;
|
||||
}
|
||||
|
||||
public async forgetOldFallbackKey(): Promise<void> {
|
||||
@ -607,7 +606,7 @@ export class OlmDevice {
|
||||
},
|
||||
logger.withPrefix("[createOutboundSession]"),
|
||||
);
|
||||
return newSessionId;
|
||||
return newSessionId!;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -668,7 +667,7 @@ export class OlmDevice {
|
||||
logger.withPrefix("[createInboundSession]"),
|
||||
);
|
||||
|
||||
return result;
|
||||
return result!;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -703,7 +702,7 @@ export class OlmDevice {
|
||||
log,
|
||||
);
|
||||
|
||||
return sessionIds;
|
||||
return sessionIds!;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -714,13 +713,13 @@ export class OlmDevice {
|
||||
* @param {boolean} nowait Don't wait for an in-progress session to complete.
|
||||
* This should only be set to true of the calling function is the function
|
||||
* that marked the session as being in-progress.
|
||||
* @param {Logger} [log] A possibly customised log
|
||||
* @param {PrefixedLogger} [log] A possibly customised log
|
||||
* @return {Promise<?string>} session id, or null if no established session
|
||||
*/
|
||||
public async getSessionIdForDevice(
|
||||
theirDeviceIdentityKey: string,
|
||||
nowait = false,
|
||||
log?: Logger,
|
||||
log?: PrefixedLogger,
|
||||
): Promise<string | null> {
|
||||
const sessionInfos = await this.getSessionInfoForDevice(theirDeviceIdentityKey, nowait, log);
|
||||
|
||||
@ -780,7 +779,11 @@ export class OlmDevice {
|
||||
// return an empty result
|
||||
}
|
||||
}
|
||||
const info = [];
|
||||
const info: {
|
||||
lastReceivedMessageTs: number;
|
||||
hasReceivedMessage: boolean;
|
||||
sessionId: string;
|
||||
}[] = [];
|
||||
|
||||
await this.cryptoStore.doTxn(
|
||||
'readonly', [IndexedDBCryptoStore.STORE_SESSIONS],
|
||||
@ -790,9 +793,9 @@ export class OlmDevice {
|
||||
for (const sessionId of sessionIds) {
|
||||
this.unpickleSession(sessions[sessionId], (sessInfo: IUnpickledSessionInfo) => {
|
||||
info.push({
|
||||
lastReceivedMessageTs: sessInfo.lastReceivedMessageTs,
|
||||
lastReceivedMessageTs: sessInfo.lastReceivedMessageTs!,
|
||||
hasReceivedMessage: sessInfo.session.has_received_message(),
|
||||
sessionId: sessionId,
|
||||
sessionId,
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -801,7 +804,7 @@ export class OlmDevice {
|
||||
log,
|
||||
);
|
||||
|
||||
return info;
|
||||
return info!;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -916,7 +919,7 @@ export class OlmDevice {
|
||||
await this.cryptoStore.storeEndToEndSessionProblem(deviceKey, type, fixed);
|
||||
}
|
||||
|
||||
public sessionMayHaveProblems(deviceKey: string, timestamp: number): Promise<IProblem> {
|
||||
public sessionMayHaveProblems(deviceKey: string, timestamp: number): Promise<IProblem | null> {
|
||||
return this.cryptoStore.getEndToEndSessionProblem(deviceKey, timestamp);
|
||||
}
|
||||
|
||||
@ -1056,10 +1059,14 @@ export class OlmDevice {
|
||||
senderKey: string,
|
||||
sessionId: string,
|
||||
txn: unknown,
|
||||
func: (session: InboundGroupSession, data: InboundGroupSessionData, withheld?: IWithheld) => void,
|
||||
func: (
|
||||
session: InboundGroupSession | null,
|
||||
data: InboundGroupSessionData | null,
|
||||
withheld: IWithheld | null,
|
||||
) => void,
|
||||
): void {
|
||||
this.cryptoStore.getEndToEndInboundGroupSession(
|
||||
senderKey, sessionId, txn, (sessionData: InboundGroupSessionData, withheld: IWithheld | null) => {
|
||||
senderKey, sessionId, txn, (sessionData: InboundGroupSessionData | null, withheld: IWithheld | null) => {
|
||||
if (sessionData === null) {
|
||||
func(null, null, withheld);
|
||||
return;
|
||||
@ -1112,94 +1119,94 @@ export class OlmDevice {
|
||||
IndexedDBCryptoStore.STORE_SHARED_HISTORY_INBOUND_GROUP_SESSIONS,
|
||||
], (txn) => {
|
||||
/* if we already have this session, consider updating it */
|
||||
this.getInboundGroupSession(
|
||||
roomId, senderKey, sessionId, txn,
|
||||
(existingSession: InboundGroupSession, existingSessionData: InboundGroupSessionData) => {
|
||||
// new session.
|
||||
const session = new global.Olm.InboundGroupSession();
|
||||
try {
|
||||
if (exportFormat) {
|
||||
session.import_session(sessionKey);
|
||||
} else {
|
||||
session.create(sessionKey);
|
||||
}
|
||||
if (sessionId != session.session_id()) {
|
||||
throw new Error(
|
||||
"Mismatched group session ID from senderKey: " +
|
||||
senderKey,
|
||||
);
|
||||
}
|
||||
|
||||
if (existingSession) {
|
||||
logger.log(
|
||||
"Update for megolm session "
|
||||
+ senderKey + "/" + sessionId,
|
||||
);
|
||||
if (existingSession.first_known_index() <= session.first_known_index()) {
|
||||
if (!existingSessionData.untrusted || extraSessionData.untrusted) {
|
||||
// existing session has less-than-or-equal index
|
||||
// (i.e. can decrypt at least as much), and the
|
||||
// new session's trust does not win over the old
|
||||
// session's trust, so keep it
|
||||
logger.log(`Keeping existing megolm session ${sessionId}`);
|
||||
return;
|
||||
}
|
||||
if (existingSession.first_known_index() < session.first_known_index()) {
|
||||
// We want to upgrade the existing session's trust,
|
||||
// but we can't just use the new session because we'll
|
||||
// lose the lower index. Check that the sessions connect
|
||||
// properly, and then manually set the existing session
|
||||
// as trusted.
|
||||
if (
|
||||
existingSession.export_session(session.first_known_index())
|
||||
=== session.export_session(session.first_known_index())
|
||||
) {
|
||||
logger.info(
|
||||
"Upgrading trust of existing megolm session " +
|
||||
sessionId + " based on newly-received trusted session",
|
||||
);
|
||||
existingSessionData.untrusted = false;
|
||||
this.cryptoStore.storeEndToEndInboundGroupSession(
|
||||
senderKey, sessionId, existingSessionData, txn,
|
||||
);
|
||||
} else {
|
||||
logger.warn(
|
||||
"Newly-received megolm session " + sessionId +
|
||||
" does not match existing session! Keeping existing session",
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// If the sessions have the same index, go ahead and store the new trusted one.
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
"Storing megolm session " + senderKey + "/" + sessionId +
|
||||
" with first index " + session.first_known_index(),
|
||||
);
|
||||
|
||||
const sessionData = Object.assign({}, extraSessionData, {
|
||||
room_id: roomId,
|
||||
session: session.pickle(this.pickleKey),
|
||||
keysClaimed: keysClaimed,
|
||||
forwardingCurve25519KeyChain: forwardingCurve25519KeyChain,
|
||||
});
|
||||
|
||||
this.cryptoStore.storeEndToEndInboundGroupSession(
|
||||
senderKey, sessionId, sessionData, txn,
|
||||
);
|
||||
|
||||
if (!existingSession && extraSessionData.sharedHistory) {
|
||||
this.cryptoStore.addSharedHistoryInboundGroupSession(
|
||||
roomId, senderKey, sessionId, txn,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
session.free();
|
||||
this.getInboundGroupSession(roomId, senderKey, sessionId, txn, (
|
||||
existingSession: InboundGroupSession | null,
|
||||
existingSessionData: InboundGroupSessionData | null,
|
||||
) => {
|
||||
// new session.
|
||||
const session = new global.Olm.InboundGroupSession();
|
||||
try {
|
||||
if (exportFormat) {
|
||||
session.import_session(sessionKey);
|
||||
} else {
|
||||
session.create(sessionKey);
|
||||
}
|
||||
},
|
||||
);
|
||||
if (sessionId != session.session_id()) {
|
||||
throw new Error(
|
||||
"Mismatched group session ID from senderKey: " +
|
||||
senderKey,
|
||||
);
|
||||
}
|
||||
|
||||
if (existingSession) {
|
||||
logger.log(
|
||||
"Update for megolm session "
|
||||
+ senderKey + "/" + sessionId,
|
||||
);
|
||||
if (existingSession.first_known_index() <= session.first_known_index()) {
|
||||
if (!existingSessionData!.untrusted || extraSessionData.untrusted) {
|
||||
// existing session has less-than-or-equal index
|
||||
// (i.e. can decrypt at least as much), and the
|
||||
// new session's trust does not win over the old
|
||||
// session's trust, so keep it
|
||||
logger.log(`Keeping existing megolm session ${sessionId}`);
|
||||
return;
|
||||
}
|
||||
if (existingSession.first_known_index() < session.first_known_index()) {
|
||||
// We want to upgrade the existing session's trust,
|
||||
// but we can't just use the new session because we'll
|
||||
// lose the lower index. Check that the sessions connect
|
||||
// properly, and then manually set the existing session
|
||||
// as trusted.
|
||||
if (
|
||||
existingSession.export_session(session.first_known_index())
|
||||
=== session.export_session(session.first_known_index())
|
||||
) {
|
||||
logger.info(
|
||||
"Upgrading trust of existing megolm session " +
|
||||
sessionId + " based on newly-received trusted session",
|
||||
);
|
||||
existingSessionData!.untrusted = false;
|
||||
this.cryptoStore.storeEndToEndInboundGroupSession(
|
||||
senderKey, sessionId, existingSessionData!, txn,
|
||||
);
|
||||
} else {
|
||||
logger.warn(
|
||||
"Newly-received megolm session " + sessionId +
|
||||
" does not match existing session! Keeping existing session",
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// If the sessions have the same index, go ahead and store the new trusted one.
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
"Storing megolm session " + senderKey + "/" + sessionId +
|
||||
" with first index " + session.first_known_index(),
|
||||
);
|
||||
|
||||
const sessionData = Object.assign({}, extraSessionData, {
|
||||
room_id: roomId,
|
||||
session: session.pickle(this.pickleKey),
|
||||
keysClaimed: keysClaimed,
|
||||
forwardingCurve25519KeyChain: forwardingCurve25519KeyChain,
|
||||
});
|
||||
|
||||
this.cryptoStore.storeEndToEndInboundGroupSession(
|
||||
senderKey, sessionId, sessionData, txn,
|
||||
);
|
||||
|
||||
if (!existingSession && extraSessionData.sharedHistory) {
|
||||
this.cryptoStore.addSharedHistoryInboundGroupSession(
|
||||
roomId, senderKey, sessionId, txn,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
session.free();
|
||||
}
|
||||
});
|
||||
},
|
||||
logger.withPrefix("[addInboundGroupSession]"),
|
||||
);
|
||||
@ -1261,7 +1268,7 @@ export class OlmDevice {
|
||||
eventId: string,
|
||||
timestamp: number,
|
||||
): Promise<IDecryptedGroupMessage | null> {
|
||||
let result: IDecryptedGroupMessage;
|
||||
let result: IDecryptedGroupMessage | null = null;
|
||||
// when the localstorage crypto store is used as an indexeddb backend,
|
||||
// exceptions thrown from within the inner function are not passed through
|
||||
// to the top level, so we store exceptions in a variable and raise them at
|
||||
@ -1275,7 +1282,7 @@ export class OlmDevice {
|
||||
], (txn) => {
|
||||
this.getInboundGroupSession(
|
||||
roomId, senderKey, sessionId, txn, (session, sessionData, withheld) => {
|
||||
if (session === null) {
|
||||
if (session === null || sessionData === null) {
|
||||
if (withheld) {
|
||||
error = new algorithms.DecryptionError(
|
||||
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
|
||||
@ -1292,7 +1299,7 @@ export class OlmDevice {
|
||||
try {
|
||||
res = session.decrypt(body);
|
||||
} catch (e) {
|
||||
if (e && e.message === 'OLM.UNKNOWN_MESSAGE_INDEX' && withheld) {
|
||||
if ((<Error>e)?.message === 'OLM.UNKNOWN_MESSAGE_INDEX' && withheld) {
|
||||
error = new algorithms.DecryptionError(
|
||||
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
|
||||
calculateWithheldMessage(withheld),
|
||||
@ -1301,7 +1308,7 @@ export class OlmDevice {
|
||||
},
|
||||
);
|
||||
} else {
|
||||
error = e;
|
||||
error = <Error>e;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -1350,7 +1357,7 @@ export class OlmDevice {
|
||||
forwardingCurve25519KeyChain: (
|
||||
sessionData.forwardingCurve25519KeyChain || []
|
||||
),
|
||||
untrusted: sessionData.untrusted,
|
||||
untrusted: !!sessionData.untrusted,
|
||||
};
|
||||
},
|
||||
);
|
||||
@ -1358,10 +1365,10 @@ export class OlmDevice {
|
||||
logger.withPrefix("[decryptGroupMessage]"),
|
||||
);
|
||||
|
||||
if (error) {
|
||||
if (error!) {
|
||||
throw error;
|
||||
}
|
||||
return result;
|
||||
return result!;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1404,7 +1411,7 @@ export class OlmDevice {
|
||||
logger.withPrefix("[hasInboundSessionKeys]"),
|
||||
);
|
||||
|
||||
return result;
|
||||
return result!;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1431,8 +1438,8 @@ export class OlmDevice {
|
||||
senderKey: string,
|
||||
sessionId: string,
|
||||
chainIndex?: number,
|
||||
): Promise<IInboundGroupSessionKey> {
|
||||
let result: IInboundGroupSessionKey;
|
||||
): Promise<IInboundGroupSessionKey | null> {
|
||||
let result: IInboundGroupSessionKey | null = null;
|
||||
await this.cryptoStore.doTxn(
|
||||
'readonly', [
|
||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
|
||||
@ -1440,7 +1447,7 @@ export class OlmDevice {
|
||||
], (txn) => {
|
||||
this.getInboundGroupSession(
|
||||
roomId, senderKey, sessionId, txn, (session, sessionData) => {
|
||||
if (session === null) {
|
||||
if (session === null || sessionData === null) {
|
||||
result = null;
|
||||
return;
|
||||
}
|
||||
@ -1520,7 +1527,7 @@ export class OlmDevice {
|
||||
},
|
||||
logger.withPrefix("[getSharedHistoryInboundGroupSessionsForRoom]"),
|
||||
);
|
||||
return result;
|
||||
return result!;
|
||||
}
|
||||
|
||||
// Utilities
|
||||
|
@ -182,7 +182,7 @@ export class SecretStorage {
|
||||
* the form [keyId, keyInfo]. Otherwise, null is returned.
|
||||
* XXX: why is this an array when addKey returns an object?
|
||||
*/
|
||||
public async getKey(keyId: string): Promise<SecretStorageKeyTuple | null> {
|
||||
public async getKey(keyId?: string | null): Promise<SecretStorageKeyTuple | null> {
|
||||
if (!keyId) {
|
||||
keyId = await this.getDefaultKeyId();
|
||||
}
|
||||
@ -237,7 +237,7 @@ export class SecretStorage {
|
||||
* @param {Array} keys The IDs of the keys to use to encrypt the secret
|
||||
* or null/undefined to use the default key.
|
||||
*/
|
||||
public async store(name: string, secret: string, keys?: string[]): Promise<void> {
|
||||
public async store(name: string, secret: string, keys?: string[] | null): Promise<void> {
|
||||
const encrypted: Record<string, IEncryptedPayload> = {};
|
||||
|
||||
if (!keys) {
|
||||
@ -284,7 +284,7 @@ export class SecretStorage {
|
||||
*
|
||||
* @return {string} the contents of the secret
|
||||
*/
|
||||
public async get(name: string): Promise<string> {
|
||||
public async get(name: string): Promise<string | undefined> {
|
||||
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer<ISecretInfo>(name);
|
||||
if (!secretInfo) {
|
||||
return;
|
||||
|
@ -242,7 +242,7 @@ export abstract class DecryptionAlgorithm {
|
||||
export class DecryptionError extends Error {
|
||||
public readonly detailedString: string;
|
||||
|
||||
constructor(public readonly code: string, msg: string, details?: Record<string, string>) {
|
||||
constructor(public readonly code: string, msg: string, details?: Record<string, string | Error>) {
|
||||
super(msg);
|
||||
this.code = code;
|
||||
this.name = 'DecryptionError';
|
||||
@ -250,7 +250,7 @@ export class DecryptionError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
function detailedStringForDecryptionError(err: DecryptionError, details?: Record<string, string>): string {
|
||||
function detailedStringForDecryptionError(err: DecryptionError, details?: Record<string, string | Error>): string {
|
||||
let result = err.name + '[msg: ' + err.message;
|
||||
|
||||
if (details) {
|
||||
@ -272,7 +272,11 @@ function detailedStringForDecryptionError(err: DecryptionError, details?: Record
|
||||
* @extends Error
|
||||
*/
|
||||
export class UnknownDeviceError extends Error {
|
||||
constructor(msg: string, public readonly devices: Record<string, Record<string, object>>) {
|
||||
constructor(
|
||||
msg: string,
|
||||
public readonly devices: Record<string, Record<string, object>>,
|
||||
public event?: MatrixEvent,
|
||||
) {
|
||||
super(msg);
|
||||
this.name = "UnknownDeviceError";
|
||||
this.devices = devices;
|
||||
@ -295,7 +299,7 @@ export class UnknownDeviceError extends Error {
|
||||
export function registerAlgorithm(
|
||||
algorithm: string,
|
||||
encryptor: new (params: IParams) => EncryptionAlgorithm,
|
||||
decryptor: new (params: Omit<IParams, "deviceId">) => DecryptionAlgorithm,
|
||||
decryptor: new (params: DecryptionClassParams) => DecryptionAlgorithm,
|
||||
): void {
|
||||
ENCRYPTION_CLASSES.set(algorithm, encryptor);
|
||||
DECRYPTION_CLASSES.set(algorithm, decryptor);
|
||||
|
@ -40,6 +40,7 @@ import { EventType, MsgType } from '../../@types/event';
|
||||
import { IEventDecryptionResult, IMegolmSessionData, IncomingRoomKeyRequest } from "../index";
|
||||
import { RoomKeyRequestState } from '../OutgoingRoomKeyRequestManager';
|
||||
import { OlmGroupSessionExtraData } from "../../@types/crypto";
|
||||
import { MatrixError } from "../../http-api";
|
||||
|
||||
// determine whether the key can be shared with invitees
|
||||
export function isRoomSharedHistory(room: Room): boolean {
|
||||
@ -492,13 +493,13 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
const key = this.olmDevice.getOutboundGroupSessionKey(sessionId);
|
||||
|
||||
await this.olmDevice.addInboundGroupSession(
|
||||
this.roomId, this.olmDevice.deviceCurve25519Key, [], sessionId,
|
||||
key.key, { ed25519: this.olmDevice.deviceEd25519Key }, false,
|
||||
this.roomId, this.olmDevice.deviceCurve25519Key!, [], sessionId,
|
||||
key.key, { ed25519: this.olmDevice.deviceEd25519Key! }, false,
|
||||
{ sharedHistory },
|
||||
);
|
||||
|
||||
// don't wait for it to complete
|
||||
this.crypto.backupManager.backupGroupSession(this.olmDevice.deviceCurve25519Key, sessionId);
|
||||
this.crypto.backupManager.backupGroupSession(this.olmDevice.deviceCurve25519Key!, sessionId);
|
||||
|
||||
return new OutboundSessionInfo(sessionId, sharedHistory);
|
||||
}
|
||||
@ -929,7 +930,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
room_id: this.roomId,
|
||||
session_id: session.sessionId,
|
||||
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key!,
|
||||
};
|
||||
|
||||
const userDeviceMaps = this.splitDevices(devicesByUser);
|
||||
@ -1259,21 +1260,21 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
// (fixes https://github.com/vector-im/element-web/issues/5001)
|
||||
this.addEventToPendingList(event);
|
||||
|
||||
let res: IDecryptedGroupMessage;
|
||||
let res: IDecryptedGroupMessage | null;
|
||||
try {
|
||||
res = await this.olmDevice.decryptGroupMessage(
|
||||
event.getRoomId(), content.sender_key, content.session_id, content.ciphertext,
|
||||
event.getRoomId()!, content.sender_key, content.session_id, content.ciphertext,
|
||||
event.getId(), event.getTs(),
|
||||
);
|
||||
} catch (e) {
|
||||
if (e.name === "DecryptionError") {
|
||||
if ((<Error>e).name === "DecryptionError") {
|
||||
// re-throw decryption errors as-is
|
||||
throw e;
|
||||
}
|
||||
|
||||
let errorCode = "OLM_DECRYPT_GROUP_MESSAGE_ERROR";
|
||||
|
||||
if (e && e.message === 'OLM.UNKNOWN_MESSAGE_INDEX') {
|
||||
if ((<MatrixError>e)?.message === 'OLM.UNKNOWN_MESSAGE_INDEX') {
|
||||
this.requestKeysForEvent(event);
|
||||
|
||||
errorCode = 'OLM_UNKNOWN_MESSAGE_INDEX';
|
||||
@ -1363,7 +1364,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
const recipients = event.getKeyRequestRecipients(this.userId);
|
||||
|
||||
this.crypto.requestRoomKey({
|
||||
room_id: event.getRoomId(),
|
||||
room_id: event.getRoomId()!,
|
||||
algorithm: wireContent.algorithm,
|
||||
sender_key: wireContent.sender_key,
|
||||
session_id: wireContent.session_id,
|
||||
@ -1384,7 +1385,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
if (!this.pendingEvents.has(senderKey)) {
|
||||
this.pendingEvents.set(senderKey, new Map<string, Set<MatrixEvent>>());
|
||||
}
|
||||
const senderPendingEvents = this.pendingEvents.get(senderKey);
|
||||
const senderPendingEvents = this.pendingEvents.get(senderKey)!;
|
||||
if (!senderPendingEvents.has(sessionId)) {
|
||||
senderPendingEvents.set(sessionId, new Set());
|
||||
}
|
||||
@ -1410,9 +1411,9 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
|
||||
pendingEvents.delete(event);
|
||||
if (pendingEvents.size === 0) {
|
||||
senderPendingEvents.delete(sessionId);
|
||||
senderPendingEvents!.delete(sessionId);
|
||||
}
|
||||
if (senderPendingEvents.size === 0) {
|
||||
if (senderPendingEvents!.size === 0) {
|
||||
this.pendingEvents.delete(senderKey);
|
||||
}
|
||||
}
|
||||
@ -1424,7 +1425,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
*/
|
||||
public async onRoomKeyEvent(event: MatrixEvent): Promise<void> {
|
||||
const content = event.getContent<Partial<IMessage["content"]>>();
|
||||
let senderKey = event.getSenderKey();
|
||||
let senderKey = event.getSenderKey()!;
|
||||
let forwardingKeyChain: string[] = [];
|
||||
let exportFormat = false;
|
||||
let keysClaimed: ReturnType<MatrixEvent["getKeysClaimed"]>;
|
||||
@ -1454,7 +1455,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
olmlib.OLM_ALGORITHM,
|
||||
senderKey,
|
||||
);
|
||||
const senderKeyUser = this.baseApis.crypto.deviceList.getUserByIdentityKey(
|
||||
const senderKeyUser = this.baseApis.crypto!.deviceList.getUserByIdentityKey(
|
||||
olmlib.OLM_ALGORITHM,
|
||||
senderKey,
|
||||
);
|
||||
@ -1533,13 +1534,16 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
await this.crypto.cryptoStore.doTxn(
|
||||
'readwrite',
|
||||
['parked_shared_history'],
|
||||
(txn) => this.crypto.cryptoStore.addParkedSharedHistory(content.room_id, parkedData, txn),
|
||||
(txn) => this.crypto.cryptoStore.addParkedSharedHistory(content.room_id!, parkedData, txn),
|
||||
logger.withPrefix("[addParkedSharedHistory]"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const sendingDevice = this.crypto.deviceList.getDeviceByIdentityKey(olmlib.OLM_ALGORITHM, senderKey);
|
||||
const sendingDevice = this.crypto.deviceList.getDeviceByIdentityKey(
|
||||
olmlib.OLM_ALGORITHM,
|
||||
senderKey,
|
||||
) ?? undefined;
|
||||
const deviceTrust = this.crypto.checkDeviceInfoTrust(event.getSender(), sendingDevice);
|
||||
|
||||
if (fromUs && !deviceTrust.isVerified()) {
|
||||
@ -1698,7 +1702,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
public shareKeysWithDevice(keyRequest: IncomingRoomKeyRequest): void {
|
||||
const userId = keyRequest.userId;
|
||||
const deviceId = keyRequest.deviceId;
|
||||
const deviceInfo = this.crypto.getStoredDevice(userId, deviceId);
|
||||
const deviceInfo = this.crypto.getStoredDevice(userId, deviceId)!;
|
||||
const body = keyRequest.requestBody;
|
||||
|
||||
this.olmlib.ensureOlmSessionsForDevices(
|
||||
@ -1739,7 +1743,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
this.olmDevice,
|
||||
userId,
|
||||
deviceInfo,
|
||||
payload,
|
||||
payload!,
|
||||
).then(() => {
|
||||
const contentMap = {
|
||||
[userId]: {
|
||||
@ -1766,12 +1770,12 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
"algorithm": olmlib.MEGOLM_ALGORITHM,
|
||||
"room_id": roomId,
|
||||
"sender_key": senderKey,
|
||||
"sender_claimed_ed25519_key": key.sender_claimed_ed25519_key,
|
||||
"sender_claimed_ed25519_key": key!.sender_claimed_ed25519_key!,
|
||||
"session_id": sessionId,
|
||||
"session_key": key.key,
|
||||
"chain_index": key.chain_index,
|
||||
"forwarding_curve25519_key_chain": key.forwarding_curve25519_key_chain,
|
||||
"org.matrix.msc3061.shared_history": key.shared_history || false,
|
||||
"session_key": key!.key,
|
||||
"chain_index": key!.chain_index,
|
||||
"forwarding_curve25519_key_chain": key!.forwarding_curve25519_key_chain,
|
||||
"org.matrix.msc3061.shared_history": key!.shared_history || false,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -1901,7 +1905,7 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
for (const deviceInfo of devices) {
|
||||
const encryptedContent: IEncryptedContent = {
|
||||
algorithm: olmlib.OLM_ALGORITHM,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key!,
|
||||
ciphertext: {},
|
||||
};
|
||||
contentMap[userId][deviceInfo.deviceId] = encryptedContent;
|
||||
|
@ -180,14 +180,14 @@ class OlmDecryption extends DecryptionAlgorithm {
|
||||
);
|
||||
}
|
||||
|
||||
if (!(this.olmDevice.deviceCurve25519Key in ciphertext)) {
|
||||
if (!(this.olmDevice.deviceCurve25519Key! in ciphertext)) {
|
||||
throw new DecryptionError(
|
||||
"OLM_NOT_INCLUDED_IN_RECIPIENTS",
|
||||
"Not included in recipients",
|
||||
);
|
||||
}
|
||||
const message = ciphertext[this.olmDevice.deviceCurve25519Key];
|
||||
let payloadString;
|
||||
const message = ciphertext[this.olmDevice.deviceCurve25519Key!];
|
||||
let payloadString: string;
|
||||
|
||||
try {
|
||||
payloadString = await this.decryptMessage(deviceKey, message);
|
||||
@ -196,7 +196,7 @@ class OlmDecryption extends DecryptionAlgorithm {
|
||||
"OLM_BAD_ENCRYPTED_MESSAGE",
|
||||
"Bad Encrypted Message", {
|
||||
sender: deviceKey,
|
||||
err: e,
|
||||
err: e as Error,
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -217,7 +217,7 @@ class OlmDecryption extends DecryptionAlgorithm {
|
||||
"OLM_BAD_RECIPIENT_KEY",
|
||||
"Message not intended for this device", {
|
||||
intended: payload.recipient_keys.ed25519,
|
||||
our_key: this.olmDevice.deviceEd25519Key,
|
||||
our_key: this.olmDevice.deviceEd25519Key!,
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -233,7 +233,7 @@ class OlmDecryption extends DecryptionAlgorithm {
|
||||
olmlib.OLM_ALGORITHM,
|
||||
deviceKey,
|
||||
);
|
||||
if (senderKeyUser !== event.getSender() && senderKeyUser !== undefined) {
|
||||
if (senderKeyUser !== event.getSender() && senderKeyUser != undefined) {
|
||||
throw new DecryptionError(
|
||||
"OLM_BAD_SENDER",
|
||||
"Message claimed to be from " + event.getSender(), {
|
||||
@ -325,13 +325,13 @@ class OlmDecryption extends DecryptionAlgorithm {
|
||||
// session, so it should have worked.
|
||||
throw new Error(
|
||||
"Error decrypting prekey message with existing session id " +
|
||||
sessionId + ": " + e.message,
|
||||
sessionId + ": " + (<Error>e).message,
|
||||
);
|
||||
}
|
||||
|
||||
// otherwise it's probably a message for another session; carry on, but
|
||||
// keep a record of the error
|
||||
decryptionErrors[sessionId] = e.message;
|
||||
decryptionErrors[sessionId] = (<Error>e).message;
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,7 +358,7 @@ class OlmDecryption extends DecryptionAlgorithm {
|
||||
theirDeviceIdentityKey, message.type, message.body,
|
||||
);
|
||||
} catch (e) {
|
||||
decryptionErrors["(new)"] = e.message;
|
||||
decryptionErrors["(new)"] = (<Error>e).message;
|
||||
throw new Error(
|
||||
"Error decrypting prekey message: " +
|
||||
JSON.stringify(decryptionErrors),
|
||||
|
@ -40,6 +40,7 @@ import {
|
||||
import { UnstableValue } from "../NamespacedValue";
|
||||
import { CryptoEvent, IMegolmSessionData } from "./index";
|
||||
import { crypto } from "./crypto";
|
||||
import { HTTPError, MatrixError } from "../http-api";
|
||||
|
||||
const KEY_BACKUP_KEYS_PER_REQUEST = 200;
|
||||
const KEY_BACKUP_CHECK_RATE_LIMIT = 5000; // ms
|
||||
@ -62,7 +63,7 @@ export type TrustInfo = {
|
||||
};
|
||||
|
||||
export interface IKeyBackupCheck {
|
||||
backupInfo: IKeyBackupInfo;
|
||||
backupInfo?: IKeyBackupInfo;
|
||||
trustInfo: TrustInfo;
|
||||
}
|
||||
|
||||
@ -85,9 +86,7 @@ interface BackupAlgorithmClass {
|
||||
init(authData: AuthData, getKey: GetKey): Promise<BackupAlgorithm>;
|
||||
|
||||
// prepare a brand new backup
|
||||
prepare(
|
||||
key: string | Uint8Array | null,
|
||||
): Promise<[Uint8Array, AuthData]>;
|
||||
prepare(key?: string | Uint8Array | null): Promise<[Uint8Array, AuthData]>;
|
||||
|
||||
checkBackupVersion(info: IKeyBackupInfo): void;
|
||||
}
|
||||
@ -221,19 +220,19 @@ export class BackupManager {
|
||||
* one of the user's verified devices, start backing up
|
||||
* to it.
|
||||
*/
|
||||
public async checkAndStart(): Promise<IKeyBackupCheck> {
|
||||
public async checkAndStart(): Promise<IKeyBackupCheck | null> {
|
||||
logger.log("Checking key backup status...");
|
||||
if (this.baseApis.isGuest()) {
|
||||
logger.log("Skipping key backup check since user is guest");
|
||||
this.checkedForBackup = true;
|
||||
return null;
|
||||
}
|
||||
let backupInfo: IKeyBackupInfo;
|
||||
let backupInfo: IKeyBackupInfo | undefined;
|
||||
try {
|
||||
backupInfo = await this.baseApis.getKeyBackupVersion();
|
||||
backupInfo = await this.baseApis.getKeyBackupVersion() ?? undefined;
|
||||
} catch (e) {
|
||||
logger.log("Error checking for active key backup", e);
|
||||
if (e.httpStatus === 404) {
|
||||
if ((<HTTPError>e).httpStatus === 404) {
|
||||
// 404 is returned when the key backup does not exist, so that
|
||||
// counts as successfully checking.
|
||||
this.checkedForBackup = true;
|
||||
@ -245,11 +244,8 @@ export class BackupManager {
|
||||
const trustInfo = await this.isKeyBackupTrusted(backupInfo);
|
||||
|
||||
if (trustInfo.usable && !this.backupInfo) {
|
||||
logger.log(
|
||||
"Found usable key backup v" + backupInfo.version +
|
||||
": enabling key backups",
|
||||
);
|
||||
await this.enableKeyBackup(backupInfo);
|
||||
logger.log(`Found usable key backup v${backupInfo!.version}: enabling key backups`);
|
||||
await this.enableKeyBackup(backupInfo!);
|
||||
} else if (!trustInfo.usable && this.backupInfo) {
|
||||
logger.log("No usable key backup: disabling key backup");
|
||||
this.disableKeyBackup();
|
||||
@ -257,13 +253,11 @@ export class BackupManager {
|
||||
logger.log("No usable key backup: not enabling key backup");
|
||||
} else if (trustInfo.usable && this.backupInfo) {
|
||||
// may not be the same version: if not, we should switch
|
||||
if (backupInfo.version !== this.backupInfo.version) {
|
||||
logger.log(
|
||||
"On backup version " + this.backupInfo.version + " but found " +
|
||||
"version " + backupInfo.version + ": switching.",
|
||||
);
|
||||
if (backupInfo!.version !== this.backupInfo.version) {
|
||||
logger.log(`On backup version ${this.backupInfo.version} but ` +
|
||||
`found version ${backupInfo!.version}: switching.`);
|
||||
this.disableKeyBackup();
|
||||
await this.enableKeyBackup(backupInfo);
|
||||
await this.enableKeyBackup(backupInfo!);
|
||||
// We're now using a new backup, so schedule all the keys we have to be
|
||||
// uploaded to the new backup. This is a bit of a workaround to upload
|
||||
// keys to a new backup in *most* cases, but it won't cover all cases
|
||||
@ -271,7 +265,7 @@ export class BackupManager {
|
||||
// see https://github.com/vector-im/element-web/issues/14833
|
||||
await this.scheduleAllGroupSessionsForBackup();
|
||||
} else {
|
||||
logger.log("Backup version " + backupInfo.version + " still current");
|
||||
logger.log(`Backup version ${backupInfo!.version} still current`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,7 +281,7 @@ export class BackupManager {
|
||||
* trust information (as returned by isKeyBackupTrusted)
|
||||
* in trustInfo.
|
||||
*/
|
||||
public async checkKeyBackup(): Promise<IKeyBackupCheck> {
|
||||
public async checkKeyBackup(): Promise<IKeyBackupCheck | null> {
|
||||
this.checkedForBackup = false;
|
||||
return this.checkAndStart();
|
||||
}
|
||||
@ -325,7 +319,7 @@ export class BackupManager {
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
public async isKeyBackupTrusted(backupInfo: IKeyBackupInfo): Promise<TrustInfo> {
|
||||
public async isKeyBackupTrusted(backupInfo?: IKeyBackupInfo): Promise<TrustInfo> {
|
||||
const ret = {
|
||||
usable: false,
|
||||
trusted_locally: false,
|
||||
@ -342,9 +336,10 @@ export class BackupManager {
|
||||
return ret;
|
||||
}
|
||||
|
||||
const privKey = await this.baseApis.crypto.getSessionBackupPrivateKey();
|
||||
const userId = this.baseApis.getUserId()!;
|
||||
const privKey = await this.baseApis.crypto!.getSessionBackupPrivateKey();
|
||||
if (privKey) {
|
||||
let algorithm;
|
||||
let algorithm: BackupAlgorithm | null = null;
|
||||
try {
|
||||
algorithm = await BackupManager.makeAlgorithm(backupInfo, async () => privKey);
|
||||
|
||||
@ -356,13 +351,11 @@ export class BackupManager {
|
||||
// do nothing -- if we have an error, then we don't mark it as
|
||||
// locally trusted
|
||||
} finally {
|
||||
if (algorithm) {
|
||||
algorithm.free();
|
||||
}
|
||||
algorithm?.free();
|
||||
}
|
||||
}
|
||||
|
||||
const mySigs = backupInfo.auth_data.signatures[this.baseApis.getUserId()] || {};
|
||||
const mySigs = backupInfo.auth_data.signatures[userId] || {};
|
||||
|
||||
for (const keyId of Object.keys(mySigs)) {
|
||||
const keyIdParts = keyId.split(':');
|
||||
@ -375,14 +368,14 @@ export class BackupManager {
|
||||
const sigInfo: SigInfo = { deviceId: keyIdParts[1] };
|
||||
|
||||
// first check to see if it's from our cross-signing key
|
||||
const crossSigningId = this.baseApis.crypto.crossSigningInfo.getId();
|
||||
const crossSigningId = this.baseApis.crypto!.crossSigningInfo.getId();
|
||||
if (crossSigningId === sigInfo.deviceId) {
|
||||
sigInfo.crossSigningId = true;
|
||||
try {
|
||||
await verifySignature(
|
||||
this.baseApis.crypto.olmDevice,
|
||||
this.baseApis.crypto!.olmDevice,
|
||||
backupInfo.auth_data,
|
||||
this.baseApis.getUserId(),
|
||||
userId,
|
||||
sigInfo.deviceId,
|
||||
crossSigningId,
|
||||
);
|
||||
@ -400,17 +393,16 @@ export class BackupManager {
|
||||
// Now look for a sig from a device
|
||||
// At some point this can probably go away and we'll just support
|
||||
// it being signed by the cross-signing master key
|
||||
const device = this.baseApis.crypto.deviceList.getStoredDevice(
|
||||
this.baseApis.getUserId(), sigInfo.deviceId,
|
||||
const device = this.baseApis.crypto!.deviceList.getStoredDevice(userId, sigInfo.deviceId,
|
||||
);
|
||||
if (device) {
|
||||
sigInfo.device = device;
|
||||
sigInfo.deviceTrust = this.baseApis.checkDeviceTrust(this.baseApis.getUserId(), sigInfo.deviceId);
|
||||
sigInfo.deviceTrust = this.baseApis.checkDeviceTrust(userId, sigInfo.deviceId);
|
||||
try {
|
||||
await verifySignature(
|
||||
this.baseApis.crypto.olmDevice,
|
||||
this.baseApis.crypto!.olmDevice,
|
||||
backupInfo.auth_data,
|
||||
this.baseApis.getUserId(),
|
||||
userId,
|
||||
device.deviceId,
|
||||
device.getFingerprint(),
|
||||
);
|
||||
@ -431,12 +423,7 @@ export class BackupManager {
|
||||
}
|
||||
|
||||
ret.usable = ret.sigs.some((s) => {
|
||||
return (
|
||||
s.valid && (
|
||||
(s.device && s.deviceTrust.isVerified()) ||
|
||||
(s.crossSigningId)
|
||||
)
|
||||
);
|
||||
return s.valid && ((s.device && s.deviceTrust?.isVerified()) || (s.crossSigningId));
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
@ -474,17 +461,17 @@ export class BackupManager {
|
||||
} catch (err) {
|
||||
numFailures++;
|
||||
logger.log("Key backup request failed", err);
|
||||
if (err.data) {
|
||||
if ((<MatrixError>err).data) {
|
||||
if (
|
||||
err.data.errcode == 'M_NOT_FOUND' ||
|
||||
err.data.errcode == 'M_WRONG_ROOM_KEYS_VERSION'
|
||||
(<MatrixError>err).data.errcode == 'M_NOT_FOUND' ||
|
||||
(<MatrixError>err).data.errcode == 'M_WRONG_ROOM_KEYS_VERSION'
|
||||
) {
|
||||
// Re-check key backup status on error, so we can be
|
||||
// sure to present the current situation when asked.
|
||||
await this.checkKeyBackup();
|
||||
// Backup version has changed or this backup version
|
||||
// has been deleted
|
||||
this.baseApis.crypto.emit(CryptoEvent.KeyBackupFailed, err.data.errcode);
|
||||
this.baseApis.crypto!.emit(CryptoEvent.KeyBackupFailed, (<MatrixError>err).data.errcode!);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@ -507,50 +494,50 @@ export class BackupManager {
|
||||
* @returns {number} Number of sessions backed up
|
||||
*/
|
||||
public async backupPendingKeys(limit: number): Promise<number> {
|
||||
const sessions = await this.baseApis.crypto.cryptoStore.getSessionsNeedingBackup(limit);
|
||||
const sessions = await this.baseApis.crypto!.cryptoStore.getSessionsNeedingBackup(limit);
|
||||
if (!sessions.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let remaining = await this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup();
|
||||
this.baseApis.crypto.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining);
|
||||
let remaining = await this.baseApis.crypto!.cryptoStore.countSessionsNeedingBackup();
|
||||
this.baseApis.crypto!.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining);
|
||||
|
||||
const rooms: IKeyBackup["rooms"] = {};
|
||||
for (const session of sessions) {
|
||||
const roomId = session.sessionData.room_id;
|
||||
const roomId = session.sessionData!.room_id;
|
||||
if (rooms[roomId] === undefined) {
|
||||
rooms[roomId] = { sessions: {} };
|
||||
}
|
||||
|
||||
const sessionData = this.baseApis.crypto.olmDevice.exportInboundGroupSession(
|
||||
session.senderKey, session.sessionId, session.sessionData,
|
||||
const sessionData = this.baseApis.crypto!.olmDevice.exportInboundGroupSession(
|
||||
session.senderKey, session.sessionId, session.sessionData!,
|
||||
);
|
||||
sessionData.algorithm = MEGOLM_ALGORITHM;
|
||||
|
||||
const forwardedCount =
|
||||
(sessionData.forwarding_curve25519_key_chain || []).length;
|
||||
|
||||
const userId = this.baseApis.crypto.deviceList.getUserByIdentityKey(
|
||||
const userId = this.baseApis.crypto!.deviceList.getUserByIdentityKey(
|
||||
MEGOLM_ALGORITHM, session.senderKey,
|
||||
);
|
||||
const device = this.baseApis.crypto.deviceList.getDeviceByIdentityKey(
|
||||
const device = this.baseApis.crypto!.deviceList.getDeviceByIdentityKey(
|
||||
MEGOLM_ALGORITHM, session.senderKey,
|
||||
);
|
||||
const verified = this.baseApis.crypto.checkDeviceInfoTrust(userId, device).isVerified();
|
||||
) ?? undefined;
|
||||
const verified = this.baseApis.crypto!.checkDeviceInfoTrust(userId!, device).isVerified();
|
||||
|
||||
rooms[roomId]['sessions'][session.sessionId] = {
|
||||
first_message_index: sessionData.first_known_index,
|
||||
forwarded_count: forwardedCount,
|
||||
is_verified: verified,
|
||||
session_data: await this.algorithm.encryptSession(sessionData),
|
||||
session_data: await this.algorithm!.encryptSession(sessionData),
|
||||
};
|
||||
}
|
||||
|
||||
await this.baseApis.sendKeyBackup(undefined, undefined, this.backupInfo.version, { rooms });
|
||||
await this.baseApis.sendKeyBackup(undefined, undefined, this.backupInfo!.version, { rooms });
|
||||
|
||||
await this.baseApis.crypto.cryptoStore.unmarkSessionsNeedingBackup(sessions);
|
||||
remaining = await this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup();
|
||||
this.baseApis.crypto.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining);
|
||||
await this.baseApis.crypto!.cryptoStore.unmarkSessionsNeedingBackup(sessions);
|
||||
remaining = await this.baseApis.crypto!.cryptoStore.countSessionsNeedingBackup();
|
||||
this.baseApis.crypto!.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining);
|
||||
|
||||
return sessions.length;
|
||||
}
|
||||
@ -558,7 +545,7 @@ export class BackupManager {
|
||||
public async backupGroupSession(
|
||||
senderKey: string, sessionId: string,
|
||||
): Promise<void> {
|
||||
await this.baseApis.crypto.cryptoStore.markSessionsNeedingBackup([{
|
||||
await this.baseApis.crypto!.cryptoStore.markSessionsNeedingBackup([{
|
||||
senderKey: senderKey,
|
||||
sessionId: sessionId,
|
||||
}]);
|
||||
@ -590,22 +577,22 @@ export class BackupManager {
|
||||
* (which will be equal to the number of sessions in the store).
|
||||
*/
|
||||
public async flagAllGroupSessionsForBackup(): Promise<number> {
|
||||
await this.baseApis.crypto.cryptoStore.doTxn(
|
||||
await this.baseApis.crypto!.cryptoStore.doTxn(
|
||||
'readwrite',
|
||||
[
|
||||
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
|
||||
IndexedDBCryptoStore.STORE_BACKUP,
|
||||
],
|
||||
(txn) => {
|
||||
this.baseApis.crypto.cryptoStore.getAllEndToEndInboundGroupSessions(txn, (session) => {
|
||||
this.baseApis.crypto!.cryptoStore.getAllEndToEndInboundGroupSessions(txn, (session) => {
|
||||
if (session !== null) {
|
||||
this.baseApis.crypto.cryptoStore.markSessionsNeedingBackup([session], txn);
|
||||
this.baseApis.crypto!.cryptoStore.markSessionsNeedingBackup([session], txn);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const remaining = await this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup();
|
||||
const remaining = await this.baseApis.crypto!.cryptoStore.countSessionsNeedingBackup();
|
||||
this.baseApis.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining);
|
||||
return remaining;
|
||||
}
|
||||
@ -615,7 +602,7 @@ export class BackupManager {
|
||||
* @returns {Promise<int>} Resolves to the number of sessions requiring backup
|
||||
*/
|
||||
public countSessionsNeedingBackup(): Promise<number> {
|
||||
return this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup();
|
||||
return this.baseApis.crypto!.cryptoStore.countSessionsNeedingBackup();
|
||||
}
|
||||
}
|
||||
|
||||
@ -641,7 +628,7 @@ export class Curve25519 implements BackupAlgorithm {
|
||||
}
|
||||
|
||||
public static async prepare(
|
||||
key: string | Uint8Array | null,
|
||||
key?: string | Uint8Array | null,
|
||||
): Promise<[Uint8Array, AuthData]> {
|
||||
const decryption = new global.Olm.PkDecryption();
|
||||
try {
|
||||
@ -741,7 +728,10 @@ function randomBytes(size: number): Uint8Array {
|
||||
return buf;
|
||||
}
|
||||
|
||||
const UNSTABLE_MSC3270_NAME = new UnstableValue(null, "org.matrix.msc3270.v1.aes-hmac-sha2");
|
||||
const UNSTABLE_MSC3270_NAME = new UnstableValue(
|
||||
"m.megolm_backup.v1.aes-hmac-sha2",
|
||||
"org.matrix.msc3270.v1.aes-hmac-sha2",
|
||||
);
|
||||
|
||||
export class Aes256 implements BackupAlgorithm {
|
||||
public static algorithmName = UNSTABLE_MSC3270_NAME.name;
|
||||
@ -769,7 +759,7 @@ export class Aes256 implements BackupAlgorithm {
|
||||
}
|
||||
|
||||
public static async prepare(
|
||||
key: string | Uint8Array | null,
|
||||
key?: string | Uint8Array | null,
|
||||
): Promise<[Uint8Array, AuthData]> {
|
||||
let outKey: Uint8Array;
|
||||
const authData: Partial<IAes256AuthData> = {};
|
||||
|
@ -58,9 +58,9 @@ const oneweek = 7 * 24 * 60 * 60 * 1000;
|
||||
export class DehydrationManager {
|
||||
private inProgress = false;
|
||||
private timeoutId: any;
|
||||
private key: Uint8Array;
|
||||
private keyInfo: {[props: string]: any};
|
||||
private deviceDisplayName: string;
|
||||
private key?: Uint8Array;
|
||||
private keyInfo?: {[props: string]: any};
|
||||
private deviceDisplayName?: string;
|
||||
|
||||
constructor(private readonly crypto: Crypto) {
|
||||
this.getDehydrationKeyFromCache();
|
||||
@ -97,7 +97,7 @@ export class DehydrationManager {
|
||||
/** set the key, and queue periodic dehydration to the server in the background */
|
||||
public async setKeyAndQueueDehydration(
|
||||
key: Uint8Array, keyInfo: {[props: string]: any} = {},
|
||||
deviceDisplayName: string = undefined,
|
||||
deviceDisplayName?: string,
|
||||
): Promise<void> {
|
||||
const matches = await this.setKey(key, keyInfo, deviceDisplayName);
|
||||
if (!matches) {
|
||||
@ -108,8 +108,8 @@ export class DehydrationManager {
|
||||
|
||||
public async setKey(
|
||||
key: Uint8Array, keyInfo: {[props: string]: any} = {},
|
||||
deviceDisplayName: string = undefined,
|
||||
): Promise<boolean> {
|
||||
deviceDisplayName?: string,
|
||||
): Promise<boolean | undefined> {
|
||||
if (!key) {
|
||||
// unsetting the key -- cancel any pending dehydration task
|
||||
if (this.timeoutId) {
|
||||
@ -135,9 +135,9 @@ export class DehydrationManager {
|
||||
// dehydrate a new device. If it's the same, we can keep the same
|
||||
// device. (Assume that keyInfo and deviceDisplayName will be the
|
||||
// same if the key is the same.)
|
||||
let matches: boolean = this.key && key.length == this.key.length;
|
||||
let matches: boolean = !!this.key && key.length == this.key.length;
|
||||
for (let i = 0; matches && i < key.length; i++) {
|
||||
if (key[i] != this.key[i]) {
|
||||
if (key[i] != this.key![i]) {
|
||||
matches = false;
|
||||
}
|
||||
}
|
||||
@ -150,7 +150,7 @@ export class DehydrationManager {
|
||||
}
|
||||
|
||||
/** returns the device id of the newly created dehydrated device */
|
||||
public async dehydrateDevice(): Promise<string> {
|
||||
public async dehydrateDevice(): Promise<string | undefined> {
|
||||
if (this.inProgress) {
|
||||
logger.log("Dehydration already in progress -- not starting new dehydration");
|
||||
return;
|
||||
@ -164,7 +164,7 @@ export class DehydrationManager {
|
||||
const pickleKey = Buffer.from(this.crypto.olmDevice.pickleKey);
|
||||
|
||||
// update the crypto store with the timestamp
|
||||
const key = await encryptAES(encodeBase64(this.key), pickleKey, DEHYDRATION_ALGORITHM);
|
||||
const key = await encryptAES(encodeBase64(this.key!), pickleKey, DEHYDRATION_ALGORITHM);
|
||||
await this.crypto.cryptoStore.doTxn(
|
||||
'readwrite',
|
||||
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||
@ -174,7 +174,7 @@ export class DehydrationManager {
|
||||
{
|
||||
keyInfo: this.keyInfo,
|
||||
key,
|
||||
deviceDisplayName: this.deviceDisplayName,
|
||||
deviceDisplayName: this.deviceDisplayName!,
|
||||
time: Date.now(),
|
||||
},
|
||||
);
|
||||
@ -197,14 +197,14 @@ export class DehydrationManager {
|
||||
account.mark_keys_as_published();
|
||||
|
||||
// dehydrate the account and store it on the server
|
||||
const pickledAccount = account.pickle(new Uint8Array(this.key));
|
||||
const pickledAccount = account.pickle(new Uint8Array(this.key!));
|
||||
|
||||
const deviceData: {[props: string]: any} = {
|
||||
algorithm: DEHYDRATION_ALGORITHM,
|
||||
account: pickledAccount,
|
||||
};
|
||||
if (this.keyInfo.passphrase) {
|
||||
deviceData.passphrase = this.keyInfo.passphrase;
|
||||
if (this.keyInfo!.passphrase) {
|
||||
deviceData.passphrase = this.keyInfo!.passphrase;
|
||||
}
|
||||
|
||||
logger.log("Uploading account to server");
|
||||
|
@ -23,6 +23,7 @@ limitations under the License.
|
||||
|
||||
import anotherjson from "another-json";
|
||||
|
||||
import type { PkDecryption, PkSigning } from "@matrix-org/olm";
|
||||
import { EventType } from "../@types/event";
|
||||
import { TypedReEmitter } from '../ReEmitter';
|
||||
import { logger } from '../logger';
|
||||
@ -274,7 +275,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
|
||||
private trustCrossSignedDevices = true;
|
||||
// the last time we did a check for the number of one-time-keys on the server.
|
||||
private lastOneTimeKeyCheck: number = null;
|
||||
private lastOneTimeKeyCheck: number | null = null;
|
||||
private oneTimeKeyCheckInProgress = false;
|
||||
|
||||
// EncryptionAlgorithm instance for each room
|
||||
@ -317,8 +318,8 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
// processing the response.
|
||||
private sendKeyRequestsImmediately = false;
|
||||
|
||||
private oneTimeKeyCount: number;
|
||||
private needsNewFallback: boolean;
|
||||
private oneTimeKeyCount?: number;
|
||||
private needsNewFallback?: boolean;
|
||||
private fallbackCleanup?: ReturnType<typeof setTimeout>;
|
||||
|
||||
/**
|
||||
@ -399,8 +400,8 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
// store the fixed version
|
||||
const fixedKey = fixBackupKey(storedKey);
|
||||
if (fixedKey) {
|
||||
const [keyId] = await this.getSecretStorageKey();
|
||||
await this.storeSecret("m.megolm_backup.v1", fixedKey, [keyId]);
|
||||
const keys = await this.getSecretStorageKey();
|
||||
await this.storeSecret("m.megolm_backup.v1", fixedKey, [keys![0]]);
|
||||
}
|
||||
|
||||
return olmlib.decodeBase64(fixedKey || storedKey);
|
||||
@ -468,8 +469,8 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
await this.deviceList.load();
|
||||
|
||||
// build our device keys: these will later be uploaded
|
||||
this.deviceKeys["ed25519:" + this.deviceId] = this.olmDevice.deviceEd25519Key;
|
||||
this.deviceKeys["curve25519:" + this.deviceId] = this.olmDevice.deviceCurve25519Key;
|
||||
this.deviceKeys["ed25519:" + this.deviceId] = this.olmDevice.deviceEd25519Key!;
|
||||
this.deviceKeys["curve25519:" + this.deviceId] = this.olmDevice.deviceCurve25519Key!;
|
||||
|
||||
logger.log("Crypto: fetching own devices...");
|
||||
let myDevices = this.deviceList.getRawStoredDevicesForUser(this.userId);
|
||||
@ -547,7 +548,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
!deviceTrust.isLocallyVerified() &&
|
||||
deviceTrust.isCrossSigningVerified()
|
||||
) {
|
||||
const deviceObj = this.deviceList.getStoredDevice(userId, deviceId);
|
||||
const deviceObj = this.deviceList.getStoredDevice(userId, deviceId)!;
|
||||
this.emit(CryptoEvent.DeviceVerificationChanged, userId, deviceId, deviceObj);
|
||||
}
|
||||
}
|
||||
@ -695,9 +696,9 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
builder.addCrossSigningKeys(authUploadDeviceSigningKeys, crossSigningInfo.keys);
|
||||
|
||||
// Cross-sign own device
|
||||
const device = this.deviceList.getStoredDevice(this.userId, this.deviceId);
|
||||
const device = this.deviceList.getStoredDevice(this.userId, this.deviceId)!;
|
||||
const deviceSignature = await crossSigningInfo.signDevice(this.userId, device);
|
||||
builder.addKeySignature(this.userId, this.deviceId, deviceSignature);
|
||||
builder.addKeySignature(this.userId, this.deviceId, deviceSignature!);
|
||||
|
||||
// Sign message key backup with cross-signing master key
|
||||
if (this.backupManager.backupInfo) {
|
||||
@ -838,10 +839,10 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
);
|
||||
|
||||
// the ID of the new SSSS key, if we create one
|
||||
let newKeyId = null;
|
||||
let newKeyId: string | null = null;
|
||||
|
||||
// create a new SSSS key and set it as default
|
||||
const createSSSS = async (opts: IAddSecretStorageKeyOpts, privateKey: Uint8Array) => {
|
||||
const createSSSS = async (opts: IAddSecretStorageKeyOpts, privateKey?: Uint8Array) => {
|
||||
if (privateKey) {
|
||||
opts.key = privateKey;
|
||||
}
|
||||
@ -859,7 +860,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
|
||||
const ensureCanCheckPassphrase = async (keyId: string, keyInfo: ISecretStorageKeyInfo) => {
|
||||
if (!keyInfo.mac) {
|
||||
const key = await this.baseApis.cryptoCallbacks.getSecretStorageKey(
|
||||
const key = await this.baseApis.cryptoCallbacks.getSecretStorageKey?.(
|
||||
{ keys: { [keyId]: keyInfo } }, "",
|
||||
);
|
||||
if (key) {
|
||||
@ -934,7 +935,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
|
||||
// if we have the backup key already cached, use it; otherwise use the
|
||||
// callback to prompt for the key
|
||||
const backupKey = await this.getSessionBackupPrivateKey() || await getKeyBackupPassphrase();
|
||||
const backupKey = await this.getSessionBackupPrivateKey() || await getKeyBackupPassphrase?.();
|
||||
|
||||
// create a new SSSS key and use the backup key as the new SSSS key
|
||||
const opts = {} as IAddSecretStorageKeyOpts;
|
||||
@ -955,7 +956,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
newKeyId = await createSSSS(opts, backupKey);
|
||||
|
||||
// store the backup key in secret storage
|
||||
await secretStorage.store("m.megolm_backup.v1", olmlib.encodeBase64(backupKey), [newKeyId]);
|
||||
await secretStorage.store("m.megolm_backup.v1", olmlib.encodeBase64(backupKey!), [newKeyId]);
|
||||
|
||||
// The backup is trusted because the user provided the private key.
|
||||
// Sign the backup with the cross-signing key so the key backup can
|
||||
@ -1025,8 +1026,9 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
// in secret storage
|
||||
const fixedBackupKey = fixBackupKey(sessionBackupKey);
|
||||
if (fixedBackupKey) {
|
||||
const keyId = newKeyId || oldKeyId;
|
||||
await secretStorage.store("m.megolm_backup.v1",
|
||||
fixedBackupKey, [newKeyId || oldKeyId],
|
||||
fixedBackupKey, keyId ? [keyId] : null,
|
||||
);
|
||||
}
|
||||
const decodedBackupKey = new Uint8Array(olmlib.decodeBase64(
|
||||
@ -1036,7 +1038,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
} else if (this.backupManager.getKeyBackupEnabled()) {
|
||||
// key backup is enabled but we don't have a session backup key in SSSS: see if we have one in
|
||||
// the cache or the user can provide one, and if so, write it to SSSS
|
||||
const backupKey = await this.getSessionBackupPrivateKey() || await getKeyBackupPassphrase();
|
||||
const backupKey = await this.getSessionBackupPrivateKey() || await getKeyBackupPassphrase?.();
|
||||
if (!backupKey) {
|
||||
// This will require user intervention to recover from since we don't have the key
|
||||
// backup key anywhere. The user should probably just set up a new key backup and
|
||||
@ -1061,16 +1063,16 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
public addSecretStorageKey(
|
||||
algorithm: string,
|
||||
opts: IAddSecretStorageKeyOpts,
|
||||
keyID: string,
|
||||
keyID?: string,
|
||||
): Promise<SecretStorageKeyObject> {
|
||||
return this.secretStorage.addKey(algorithm, opts, keyID);
|
||||
}
|
||||
|
||||
public hasSecretStorageKey(keyID: string): Promise<boolean> {
|
||||
public hasSecretStorageKey(keyID?: string): Promise<boolean> {
|
||||
return this.secretStorage.hasKey(keyID);
|
||||
}
|
||||
|
||||
public getSecretStorageKey(keyID?: string): Promise<SecretStorageKeyTuple> {
|
||||
public getSecretStorageKey(keyID?: string): Promise<SecretStorageKeyTuple | null> {
|
||||
return this.secretStorage.getKey(keyID);
|
||||
}
|
||||
|
||||
@ -1078,7 +1080,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
return this.secretStorage.store(name, secret, keys);
|
||||
}
|
||||
|
||||
public getSecret(name: string): Promise<string> {
|
||||
public getSecret(name: string): Promise<string | undefined> {
|
||||
return this.secretStorage.get(name);
|
||||
}
|
||||
|
||||
@ -1115,14 +1117,14 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
* @returns {boolean} true if the key matches, otherwise false
|
||||
*/
|
||||
public checkSecretStoragePrivateKey(privateKey: Uint8Array, expectedPublicKey: string): boolean {
|
||||
let decryption = null;
|
||||
let decryption: PkDecryption | null = null;
|
||||
try {
|
||||
decryption = new global.Olm.PkDecryption();
|
||||
const gotPubkey = decryption.init_with_private_key(privateKey);
|
||||
// make sure it agrees with the given pubkey
|
||||
return gotPubkey === expectedPublicKey;
|
||||
} finally {
|
||||
if (decryption) decryption.free();
|
||||
decryption?.free();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1188,14 +1190,14 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
* @returns {boolean} true if the key matches, otherwise false
|
||||
*/
|
||||
public checkCrossSigningPrivateKey(privateKey: Uint8Array, expectedPublicKey: string): boolean {
|
||||
let signing = null;
|
||||
let signing: PkSigning | null = null;
|
||||
try {
|
||||
signing = new global.Olm.PkSigning();
|
||||
const gotPubkey = signing.init_with_seed(privateKey);
|
||||
// make sure it agrees with the given pubkey
|
||||
return gotPubkey === expectedPublicKey;
|
||||
} finally {
|
||||
if (signing) signing.free();
|
||||
signing?.free();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1209,14 +1211,14 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
logger.info("Starting cross-signing key change post-processing");
|
||||
|
||||
// sign the current device with the new key, and upload to the server
|
||||
const device = this.deviceList.getStoredDevice(this.userId, this.deviceId);
|
||||
const device = this.deviceList.getStoredDevice(this.userId, this.deviceId)!;
|
||||
const signedDevice = await this.crossSigningInfo.signDevice(this.userId, device);
|
||||
logger.info(`Starting background key sig upload for ${this.deviceId}`);
|
||||
|
||||
const upload = ({ shouldEmit = false }) => {
|
||||
return this.baseApis.uploadKeySignatures({
|
||||
[this.userId]: {
|
||||
[this.deviceId]: signedDevice,
|
||||
[this.deviceId]: signedDevice!,
|
||||
},
|
||||
}).then((response) => {
|
||||
const { failures } = response || {};
|
||||
@ -1267,9 +1269,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
if (usersToUpgrade) {
|
||||
for (const userId of usersToUpgrade) {
|
||||
if (userId in users) {
|
||||
await this.baseApis.setDeviceVerified(
|
||||
userId, users[userId].crossSigningInfo.getId(),
|
||||
);
|
||||
await this.baseApis.setDeviceVerified(userId, users[userId].crossSigningInfo.getId()!);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1296,7 +1296,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
private async checkForDeviceVerificationUpgrade(
|
||||
userId: string,
|
||||
crossSigningInfo: CrossSigningInfo,
|
||||
): Promise<IDeviceVerificationUpgrade> {
|
||||
): Promise<IDeviceVerificationUpgrade | undefined> {
|
||||
// only upgrade if this is the first cross-signing key that we've seen for
|
||||
// them, and if their cross-signing key isn't already verified
|
||||
const trustLevel = this.crossSigningInfo.checkUserTrust(crossSigningInfo);
|
||||
@ -1359,7 +1359,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
*
|
||||
* @returns {string} the key ID
|
||||
*/
|
||||
public getCrossSigningId(type: string): string {
|
||||
public getCrossSigningId(type: string): string | null {
|
||||
return this.crossSigningInfo.getId(type);
|
||||
}
|
||||
|
||||
@ -1370,7 +1370,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
*
|
||||
* @returns {CrossSigningInfo} the cross signing information for the user.
|
||||
*/
|
||||
public getStoredCrossSigningForUser(userId: string): CrossSigningInfo {
|
||||
public getStoredCrossSigningForUser(userId: string): CrossSigningInfo | null {
|
||||
return this.deviceList.getStoredCrossSigningForUser(userId);
|
||||
}
|
||||
|
||||
@ -1410,8 +1410,8 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
*
|
||||
* @returns {DeviceTrustLevel}
|
||||
*/
|
||||
public checkDeviceInfoTrust(userId: string, device: DeviceInfo): DeviceTrustLevel {
|
||||
const trustedLocally = !!(device && device.isVerified());
|
||||
public checkDeviceInfoTrust(userId: string, device?: DeviceInfo): DeviceTrustLevel {
|
||||
const trustedLocally = !!device?.isVerified();
|
||||
|
||||
const userCrossSigning = this.deviceList.getStoredCrossSigningForUser(userId);
|
||||
if (device && userCrossSigning) {
|
||||
@ -1436,13 +1436,14 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
*/
|
||||
public checkIfOwnDeviceCrossSigned(deviceId: string): boolean {
|
||||
const device = this.deviceList.getStoredDevice(this.userId, deviceId);
|
||||
if (!device) return false;
|
||||
const userCrossSigning = this.deviceList.getStoredCrossSigningForUser(this.userId);
|
||||
return userCrossSigning.checkDeviceTrust(
|
||||
return userCrossSigning?.checkDeviceTrust(
|
||||
userCrossSigning,
|
||||
device,
|
||||
false,
|
||||
true,
|
||||
).isCrossSigningVerified();
|
||||
).isCrossSigningVerified() ?? false;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1494,7 +1495,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
* Check the copy of our cross-signing key that we have in the device list and
|
||||
* see if we can get the private key. If so, mark it as trusted.
|
||||
*/
|
||||
async checkOwnCrossSigningTrust({
|
||||
public async checkOwnCrossSigningTrust({
|
||||
allowPrivateKeyRequests = false,
|
||||
}: ICheckOwnCrossSigningTrustOpts = {}): Promise<void> {
|
||||
const userId = this.userId;
|
||||
@ -1520,7 +1521,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
return;
|
||||
}
|
||||
|
||||
const seenPubkey = newCrossSigning.getId();
|
||||
const seenPubkey = newCrossSigning.getId()!;
|
||||
const masterChanged = this.crossSigningInfo.getId() !== seenPubkey;
|
||||
const masterExistsNotLocallyCached =
|
||||
newCrossSigning.getId() && !crossSigningPrivateKeys.has("master");
|
||||
@ -1532,18 +1533,16 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
(masterChanged || masterExistsNotLocallyCached)
|
||||
) {
|
||||
logger.info("Attempting to retrieve cross-signing master private key");
|
||||
let signing = null;
|
||||
let signing: PkSigning | null = null;
|
||||
// It's important for control flow that we leave any errors alone for
|
||||
// higher levels to handle so that e.g. cancelling access properly
|
||||
// aborts any larger operation as well.
|
||||
try {
|
||||
const ret = await this.crossSigningInfo.getCrossSigningKey(
|
||||
'master', seenPubkey,
|
||||
);
|
||||
const ret = await this.crossSigningInfo.getCrossSigningKey('master', seenPubkey);
|
||||
signing = ret[1];
|
||||
logger.info("Got cross-signing master private key");
|
||||
} finally {
|
||||
if (signing) signing.free();
|
||||
signing?.free();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1575,22 +1574,20 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
(selfSigningChanged || selfSigningExistsNotLocallyCached)
|
||||
) {
|
||||
logger.info("Attempting to retrieve cross-signing self-signing private key");
|
||||
let signing = null;
|
||||
let signing: PkSigning | null = null;
|
||||
try {
|
||||
const ret = await this.crossSigningInfo.getCrossSigningKey(
|
||||
"self_signing", newCrossSigning.getId("self_signing"),
|
||||
"self_signing", newCrossSigning.getId("self_signing")!,
|
||||
);
|
||||
signing = ret[1];
|
||||
logger.info("Got cross-signing self-signing private key");
|
||||
} finally {
|
||||
if (signing) signing.free();
|
||||
signing?.free();
|
||||
}
|
||||
|
||||
const device = this.deviceList.getStoredDevice(this.userId, this.deviceId);
|
||||
const signedDevice = await this.crossSigningInfo.signDevice(
|
||||
this.userId, device,
|
||||
);
|
||||
keySignatures[this.deviceId] = signedDevice;
|
||||
const device = this.deviceList.getStoredDevice(this.userId, this.deviceId)!;
|
||||
const signedDevice = await this.crossSigningInfo.signDevice(this.userId, device);
|
||||
keySignatures[this.deviceId] = signedDevice!;
|
||||
}
|
||||
if (userSigningChanged) {
|
||||
logger.info("Got new user-signing key", newCrossSigning.getId("user_signing"));
|
||||
@ -1600,26 +1597,26 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
(userSigningChanged || userSigningExistsNotLocallyCached)
|
||||
) {
|
||||
logger.info("Attempting to retrieve cross-signing user-signing private key");
|
||||
let signing = null;
|
||||
let signing: PkSigning | null = null;
|
||||
try {
|
||||
const ret = await this.crossSigningInfo.getCrossSigningKey(
|
||||
"user_signing", newCrossSigning.getId("user_signing"),
|
||||
"user_signing", newCrossSigning.getId("user_signing")!,
|
||||
);
|
||||
signing = ret[1];
|
||||
logger.info("Got cross-signing user-signing private key");
|
||||
} finally {
|
||||
if (signing) signing.free();
|
||||
signing?.free();
|
||||
}
|
||||
}
|
||||
|
||||
if (masterChanged) {
|
||||
const masterKey = this.crossSigningInfo.keys.master;
|
||||
await this.signObject(masterKey);
|
||||
const deviceSig = masterKey.signatures[this.userId]["ed25519:" + this.deviceId];
|
||||
const deviceSig = masterKey.signatures![this.userId]["ed25519:" + this.deviceId];
|
||||
// Include only the _new_ device signature in the upload.
|
||||
// We may have existing signatures from deleted devices, which will cause
|
||||
// the entire upload to fail.
|
||||
keySignatures[this.crossSigningInfo.getId()] = Object.assign(
|
||||
keySignatures[this.crossSigningInfo.getId()!] = Object.assign(
|
||||
{} as ISignedKey,
|
||||
masterKey,
|
||||
{
|
||||
@ -1679,7 +1676,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
*
|
||||
* @param {object} keys The new trusted set of keys
|
||||
*/
|
||||
private async storeTrustedSelfKeys(keys: Record<string, ICrossSigningKey>): Promise<void> {
|
||||
private async storeTrustedSelfKeys(keys: Record<string, ICrossSigningKey> | null): Promise<void> {
|
||||
if (keys) {
|
||||
this.crossSigningInfo.setKeys(keys);
|
||||
} else {
|
||||
@ -1721,9 +1718,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
},
|
||||
});
|
||||
if (usersToUpgrade.includes(userId)) {
|
||||
await this.baseApis.setDeviceVerified(
|
||||
userId, crossSigningInfo.getId(),
|
||||
);
|
||||
await this.baseApis.setDeviceVerified(userId, crossSigningInfo.getId()!);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1771,7 +1766,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
*
|
||||
* @return {string} base64-encoded ed25519 key.
|
||||
*/
|
||||
public getDeviceEd25519Key(): string {
|
||||
public getDeviceEd25519Key(): string | null {
|
||||
return this.olmDevice.deviceEd25519Key;
|
||||
}
|
||||
|
||||
@ -1780,7 +1775,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
*
|
||||
* @return {string} base64-encoded curve25519 key.
|
||||
*/
|
||||
public getDeviceCurve25519Key(): string {
|
||||
public getDeviceCurve25519Key(): string | null {
|
||||
return this.olmDevice.deviceCurve25519Key;
|
||||
}
|
||||
|
||||
@ -1859,11 +1854,11 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
}
|
||||
|
||||
public setNeedsNewFallback(needsNewFallback: boolean) {
|
||||
this.needsNewFallback = !!needsNewFallback;
|
||||
this.needsNewFallback = needsNewFallback;
|
||||
}
|
||||
|
||||
public getNeedsNewFallback(): boolean {
|
||||
return this.needsNewFallback;
|
||||
return !!this.needsNewFallback;
|
||||
}
|
||||
|
||||
// check if it's time to upload one-time keys, and do so if so.
|
||||
@ -1983,10 +1978,10 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
}
|
||||
|
||||
// returns a promise which resolves to the response
|
||||
private async uploadOneTimeKeys() {
|
||||
const promises = [];
|
||||
private async uploadOneTimeKeys(): Promise<IKeysUploadResponse> {
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
let fallbackJson: Record<string, IOneTimeKey>;
|
||||
let fallbackJson: Record<string, IOneTimeKey> | undefined;
|
||||
if (this.getNeedsNewFallback()) {
|
||||
fallbackJson = {};
|
||||
const fallbackKeys = await this.olmDevice.getFallbackKey();
|
||||
@ -2045,7 +2040,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
* module:crypto/deviceinfo|DeviceInfo}.
|
||||
*/
|
||||
public downloadKeys(userIds: string[], forceDownload?: boolean): Promise<DeviceInfoMap> {
|
||||
return this.deviceList.downloadKeys(userIds, forceDownload);
|
||||
return this.deviceList.downloadKeys(userIds, !!forceDownload);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2114,17 +2109,11 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
public async setDeviceVerification(
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
verified?: boolean,
|
||||
blocked?: boolean,
|
||||
known?: boolean,
|
||||
verified: boolean | null = null,
|
||||
blocked: boolean | null = null,
|
||||
known: boolean | null = null,
|
||||
keys?: Record<string, string>,
|
||||
): Promise<DeviceInfo | CrossSigningInfo> {
|
||||
// get rid of any `undefined`s here so we can just check
|
||||
// for null rather than null or undefined
|
||||
if (verified === undefined) verified = null;
|
||||
if (blocked === undefined) blocked = null;
|
||||
if (known === undefined) known = null;
|
||||
|
||||
// Check if the 'device' is actually a cross signing key
|
||||
// The js-sdk's verification treats cross-signing keys as devices
|
||||
// and so uses this method to mark them verified.
|
||||
@ -2240,9 +2229,9 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
if (deviceTrust.isCrossSigningVerified()) {
|
||||
logger.log(`Own device ${deviceId} already cross-signing verified`);
|
||||
} else {
|
||||
device = await this.crossSigningInfo.signDevice(
|
||||
device = (await this.crossSigningInfo.signDevice(
|
||||
userId, DeviceInfo.fromStorage(dev, deviceId),
|
||||
);
|
||||
))!;
|
||||
}
|
||||
|
||||
if (device) {
|
||||
@ -2293,7 +2282,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
return this.requestVerificationWithChannel(userId, channel, this.inRoomVerificationRequests);
|
||||
}
|
||||
|
||||
public requestVerification(userId: string, devices: string[]): Promise<VerificationRequest> {
|
||||
public requestVerification(userId: string, devices?: string[]): Promise<VerificationRequest> {
|
||||
if (!devices) {
|
||||
devices = Object.keys(this.deviceList.getRawStoredDevicesForUser(userId));
|
||||
}
|
||||
@ -2597,7 +2586,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
// because it first stores in memory. We should await the promise only
|
||||
// after all the in-memory state (roomEncryptors and _roomList) has been updated
|
||||
// to avoid races when calling this method multiple times. Hence keep a hold of the promise.
|
||||
let storeConfigPromise: Promise<void> = null;
|
||||
let storeConfigPromise: Promise<void> | null = null;
|
||||
if (!existingConfig) {
|
||||
storeConfigPromise = this.roomList.setRoomEncryption(roomId, config);
|
||||
}
|
||||
@ -2754,7 +2743,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
const total = keys.length;
|
||||
|
||||
function updateProgress() {
|
||||
opts.progressCallback({
|
||||
opts.progressCallback?.({
|
||||
stage: "load_keys",
|
||||
successes,
|
||||
failures,
|
||||
@ -2809,7 +2798,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
* @return {Promise?} Promise which resolves when the event has been
|
||||
* encrypted, or null if nothing was needed
|
||||
*/
|
||||
public async encryptEvent(event: MatrixEvent, room: Room): Promise<void> {
|
||||
public async encryptEvent(event: MatrixEvent, room?: Room): Promise<void> {
|
||||
if (!room) {
|
||||
throw new Error("Cannot send encrypted messages in unknown rooms");
|
||||
}
|
||||
@ -2862,8 +2851,8 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
event.makeEncrypted(
|
||||
"m.room.encrypted",
|
||||
encryptedContent,
|
||||
this.olmDevice.deviceCurve25519Key,
|
||||
this.olmDevice.deviceEd25519Key,
|
||||
this.olmDevice.deviceCurve25519Key!,
|
||||
this.olmDevice.deviceEd25519Key!,
|
||||
);
|
||||
}
|
||||
|
||||
@ -3143,7 +3132,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
const deviceId = deviceInfo.deviceId;
|
||||
const encryptedContent: IEncryptedContent = {
|
||||
algorithm: olmlib.OLM_ALGORITHM,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key!,
|
||||
ciphertext: {},
|
||||
};
|
||||
|
||||
@ -3753,8 +3742,8 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
* unknown
|
||||
*/
|
||||
public getRoomDecryptor(roomId: string, algorithm: string): DecryptionAlgorithm {
|
||||
let decryptors: Map<string, DecryptionAlgorithm>;
|
||||
let alg: DecryptionAlgorithm;
|
||||
let decryptors: Map<string, DecryptionAlgorithm> | undefined;
|
||||
let alg: DecryptionAlgorithm | undefined;
|
||||
|
||||
roomId = roomId || null;
|
||||
if (roomId) {
|
||||
@ -3799,10 +3788,10 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
* @return {array} An array of room decryptors
|
||||
*/
|
||||
private getRoomDecryptors(algorithm: string): DecryptionAlgorithm[] {
|
||||
const decryptors = [];
|
||||
const decryptors: DecryptionAlgorithm[] = [];
|
||||
for (const d of this.roomDecryptors.values()) {
|
||||
if (d.has(algorithm)) {
|
||||
decryptors.push(d.get(algorithm));
|
||||
decryptors.push(d.get(algorithm)!);
|
||||
}
|
||||
}
|
||||
return decryptors;
|
||||
@ -3839,7 +3828,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
* key will be returned. Otherwise null will be returned.
|
||||
*
|
||||
*/
|
||||
export function fixBackupKey(key: string): string | null {
|
||||
export function fixBackupKey(key?: string): string | null {
|
||||
if (typeof key !== "string" || key.indexOf(",") < 0) {
|
||||
return null;
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import anotherjson from "another-json";
|
||||
import { Logger } from "loglevel";
|
||||
|
||||
import type { PkSigning } from "@matrix-org/olm";
|
||||
import { OlmDevice } from "./OlmDevice";
|
||||
@ -56,7 +55,7 @@ export const MEGOLM_BACKUP_ALGORITHM = Algorithm.MegolmBackup;
|
||||
|
||||
export interface IOlmSessionResult {
|
||||
device: DeviceInfo;
|
||||
sessionId?: string;
|
||||
sessionId: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -137,7 +136,7 @@ export async function encryptMessageForDevice(
|
||||
|
||||
interface IExistingOlmSession {
|
||||
device: DeviceInfo;
|
||||
sessionId?: string;
|
||||
sessionId: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -225,19 +224,8 @@ export async function ensureOlmSessionsForDevices(
|
||||
force = false,
|
||||
otkTimeout?: number,
|
||||
failedServers?: string[],
|
||||
log: Logger = logger,
|
||||
log = logger,
|
||||
): Promise<Record<string, Record<string, IOlmSessionResult>>> {
|
||||
if (typeof force === "number") {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore - backwards compatibility
|
||||
log = failedServers;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore - backwards compatibility
|
||||
failedServers = otkTimeout;
|
||||
otkTimeout = force;
|
||||
force = false;
|
||||
}
|
||||
|
||||
const devicesWithoutSession: [string, string][] = [
|
||||
// [userId, deviceId], ...
|
||||
];
|
||||
@ -365,7 +353,7 @@ export async function ensureOlmSessionsForDevices(
|
||||
}
|
||||
|
||||
const deviceRes = userRes[deviceId] || {};
|
||||
let oneTimeKey: IOneTimeKey = null;
|
||||
let oneTimeKey: IOneTimeKey | null = null;
|
||||
for (const keyId in deviceRes) {
|
||||
if (keyId.indexOf(oneTimeKeyAlgorithm + ":") === 0) {
|
||||
oneTimeKey = deviceRes[keyId];
|
||||
@ -388,7 +376,7 @@ export async function ensureOlmSessionsForDevices(
|
||||
olmDevice, oneTimeKey, userId, deviceInfo,
|
||||
).then((sid) => {
|
||||
if (resolveSession[key]) {
|
||||
resolveSession[key](sid);
|
||||
resolveSession[key](sid ?? undefined);
|
||||
}
|
||||
result[userId][deviceId].sessionId = sid;
|
||||
}, (e) => {
|
||||
@ -413,7 +401,7 @@ async function _verifyKeyAndStartSession(
|
||||
oneTimeKey: IOneTimeKey,
|
||||
userId: string,
|
||||
deviceInfo: DeviceInfo,
|
||||
): Promise<string> {
|
||||
): Promise<string | null> {
|
||||
const deviceId = deviceInfo.deviceId;
|
||||
try {
|
||||
await verifySignature(
|
||||
|
@ -90,14 +90,14 @@ export interface CryptoStore {
|
||||
deviceKey: string,
|
||||
sessionId: string,
|
||||
txn: unknown,
|
||||
func: (session: ISessionInfo) => void,
|
||||
func: (session: ISessionInfo | null) => void,
|
||||
): void;
|
||||
getEndToEndSessions(
|
||||
deviceKey: string,
|
||||
txn: unknown,
|
||||
func: (sessions: { [sessionId: string]: ISessionInfo }) => void,
|
||||
): void;
|
||||
getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo) => void): void;
|
||||
getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo | null) => void): void;
|
||||
storeEndToEndSession(deviceKey: string, sessionId: string, sessionInfo: ISessionInfo, txn: unknown): void;
|
||||
storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void>;
|
||||
getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null>;
|
||||
|
@ -307,7 +307,7 @@ export class Backend implements CryptoStore {
|
||||
expectedState: number,
|
||||
updates: Partial<OutgoingRoomKeyRequest>,
|
||||
): Promise<OutgoingRoomKeyRequest | null> {
|
||||
let result: OutgoingRoomKeyRequest = null;
|
||||
let result: OutgoingRoomKeyRequest | null = null;
|
||||
|
||||
function onsuccess(this: IDBRequest<IDBCursorWithValue>) {
|
||||
const cursor = this.result;
|
||||
@ -375,7 +375,7 @@ export class Backend implements CryptoStore {
|
||||
try {
|
||||
func(getReq.result || null);
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
abortWithException(txn, <Error>e);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -395,7 +395,7 @@ export class Backend implements CryptoStore {
|
||||
try {
|
||||
func(getReq.result || null);
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
abortWithException(txn, <Error>e);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -411,7 +411,7 @@ export class Backend implements CryptoStore {
|
||||
try {
|
||||
func(getReq.result || null);
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
abortWithException(txn, <Error>e);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -439,7 +439,7 @@ export class Backend implements CryptoStore {
|
||||
try {
|
||||
func(countReq.result);
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
abortWithException(txn, <Error>e);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -465,7 +465,7 @@ export class Backend implements CryptoStore {
|
||||
try {
|
||||
func(results);
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
abortWithException(txn, <Error>e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -475,7 +475,7 @@ export class Backend implements CryptoStore {
|
||||
deviceKey: string,
|
||||
sessionId: string,
|
||||
txn: IDBTransaction,
|
||||
func: (sessions: { [ sessionId: string ]: ISessionInfo }) => void,
|
||||
func: (session: ISessionInfo | null) => void,
|
||||
): void {
|
||||
const objectStore = txn.objectStore("sessions");
|
||||
const getReq = objectStore.get([deviceKey, sessionId]);
|
||||
@ -490,12 +490,12 @@ export class Backend implements CryptoStore {
|
||||
func(null);
|
||||
}
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
abortWithException(txn, <Error>e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo) => void): void {
|
||||
public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo | null) => void): void {
|
||||
const objectStore = txn.objectStore("sessions");
|
||||
const getReq = objectStore.openCursor();
|
||||
getReq.onsuccess = function() {
|
||||
@ -508,7 +508,7 @@ export class Backend implements CryptoStore {
|
||||
func(null);
|
||||
}
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
abortWithException(txn, <Error>e);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -537,11 +537,11 @@ export class Backend implements CryptoStore {
|
||||
fixed,
|
||||
time: Date.now(),
|
||||
});
|
||||
return promiseifyTxn(txn);
|
||||
await promiseifyTxn(txn);
|
||||
}
|
||||
|
||||
public async getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null> {
|
||||
let result;
|
||||
let result: IProblem | null = null;
|
||||
const txn = this.db.transaction("session_problems", "readwrite");
|
||||
const objectStore = txn.objectStore("session_problems");
|
||||
const index = objectStore.index("deviceKey");
|
||||
@ -604,8 +604,8 @@ export class Backend implements CryptoStore {
|
||||
txn: IDBTransaction,
|
||||
func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void,
|
||||
): void {
|
||||
let session: InboundGroupSessionData | boolean = false;
|
||||
let withheld: IWithheld | boolean = false;
|
||||
let session: InboundGroupSessionData | null | boolean = false;
|
||||
let withheld: IWithheld | null | boolean = false;
|
||||
const objectStore = txn.objectStore("inbound_group_sessions");
|
||||
const getReq = objectStore.get([senderCurve25519Key, sessionId]);
|
||||
getReq.onsuccess = function() {
|
||||
@ -619,7 +619,7 @@ export class Backend implements CryptoStore {
|
||||
func(session as InboundGroupSessionData, withheld as IWithheld);
|
||||
}
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
abortWithException(txn, <Error>e);
|
||||
}
|
||||
};
|
||||
|
||||
@ -636,7 +636,7 @@ export class Backend implements CryptoStore {
|
||||
func(session as InboundGroupSessionData, withheld as IWithheld);
|
||||
}
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
abortWithException(txn, <Error>e);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -654,14 +654,14 @@ export class Backend implements CryptoStore {
|
||||
sessionData: cursor.value.session,
|
||||
});
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
abortWithException(txn, <Error>e);
|
||||
}
|
||||
cursor.continue();
|
||||
} else {
|
||||
try {
|
||||
func(null);
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
abortWithException(txn, <Error>e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -726,7 +726,7 @@ export class Backend implements CryptoStore {
|
||||
try {
|
||||
func(getReq.result || null);
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
abortWithException(txn, <Error>e);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -754,7 +754,7 @@ export class Backend implements CryptoStore {
|
||||
try {
|
||||
func(rooms);
|
||||
} catch (e) {
|
||||
abortWithException(txn, e);
|
||||
abortWithException(txn, <Error>e);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1050,7 +1050,7 @@ function abortWithException(txn: IDBTransaction, e: Error) {
|
||||
}
|
||||
}
|
||||
|
||||
function promiseifyTxn<T>(txn: IDBTransaction): Promise<T> {
|
||||
function promiseifyTxn<T>(txn: IDBTransaction): Promise<T | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
txn.oncomplete = () => {
|
||||
if ((txn as IWrappedIDBTransaction)._mx_abortexception !== undefined) {
|
||||
|
@ -18,7 +18,7 @@ import { logger, PrefixedLogger } from '../../logger';
|
||||
import { LocalStorageCryptoStore } from './localStorage-crypto-store';
|
||||
import { MemoryCryptoStore } from './memory-crypto-store';
|
||||
import * as IndexedDBCryptoStoreBackend from './indexeddb-crypto-store-backend';
|
||||
import { InvalidCryptoStoreError } from '../../errors';
|
||||
import { InvalidCryptoStoreError, InvalidCryptoStoreState } from '../../errors';
|
||||
import * as IndexedDBHelpers from "../../indexeddb-helpers";
|
||||
import {
|
||||
CryptoStore,
|
||||
@ -64,8 +64,8 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
return IndexedDBHelpers.exists(indexedDB, dbName);
|
||||
}
|
||||
|
||||
private backendPromise: Promise<CryptoStore> = null;
|
||||
private backend: CryptoStore = null;
|
||||
private backendPromise?: Promise<CryptoStore>;
|
||||
private backend?: CryptoStore;
|
||||
|
||||
/**
|
||||
* Create a new IndexedDBCryptoStore
|
||||
@ -141,7 +141,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
logger.warn("Crypto DB is too new for us to use!", e);
|
||||
// don't fall back to a different store: the user has crypto data
|
||||
// in this db so we should use it or nothing at all.
|
||||
throw new InvalidCryptoStoreError(InvalidCryptoStoreError.TOO_NEW);
|
||||
throw new InvalidCryptoStoreError(InvalidCryptoStoreState.TooNew);
|
||||
}
|
||||
logger.warn(
|
||||
`unable to connect to indexeddb ${this.dbName}` +
|
||||
@ -213,7 +213,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* same instance as passed in, or the existing one.
|
||||
*/
|
||||
public getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise<OutgoingRoomKeyRequest> {
|
||||
return this.backend.getOrAddOutgoingRoomKeyRequest(request);
|
||||
return this.backend!.getOrAddOutgoingRoomKeyRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -227,7 +227,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* not found
|
||||
*/
|
||||
public getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): Promise<OutgoingRoomKeyRequest | null> {
|
||||
return this.backend.getOutgoingRoomKeyRequest(requestBody);
|
||||
return this.backend!.getOutgoingRoomKeyRequest(requestBody);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -241,7 +241,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* requests in those states, an arbitrary one is chosen.
|
||||
*/
|
||||
public getOutgoingRoomKeyRequestByState(wantedStates: number[]): Promise<OutgoingRoomKeyRequest | null> {
|
||||
return this.backend.getOutgoingRoomKeyRequestByState(wantedStates);
|
||||
return this.backend!.getOutgoingRoomKeyRequestByState(wantedStates);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -252,7 +252,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* @return {Promise<Array<*>>} Returns an array of requests in the given state
|
||||
*/
|
||||
public getAllOutgoingRoomKeyRequestsByState(wantedState: number): Promise<OutgoingRoomKeyRequest[]> {
|
||||
return this.backend.getAllOutgoingRoomKeyRequestsByState(wantedState);
|
||||
return this.backend!.getAllOutgoingRoomKeyRequestsByState(wantedState);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -270,7 +270,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
deviceId: string,
|
||||
wantedStates: number[],
|
||||
): Promise<OutgoingRoomKeyRequest[]> {
|
||||
return this.backend.getOutgoingRoomKeyRequestsByTarget(
|
||||
return this.backend!.getOutgoingRoomKeyRequestsByTarget(
|
||||
userId, deviceId, wantedStates,
|
||||
);
|
||||
}
|
||||
@ -292,7 +292,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
expectedState: number,
|
||||
updates: Partial<OutgoingRoomKeyRequest>,
|
||||
): Promise<OutgoingRoomKeyRequest | null> {
|
||||
return this.backend.updateOutgoingRoomKeyRequest(
|
||||
return this.backend!.updateOutgoingRoomKeyRequest(
|
||||
requestId, expectedState, updates,
|
||||
);
|
||||
}
|
||||
@ -310,7 +310,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
requestId: string,
|
||||
expectedState: number,
|
||||
): Promise<OutgoingRoomKeyRequest | null> {
|
||||
return this.backend.deleteOutgoingRoomKeyRequest(requestId, expectedState);
|
||||
return this.backend!.deleteOutgoingRoomKeyRequest(requestId, expectedState);
|
||||
}
|
||||
|
||||
// Olm Account
|
||||
@ -323,7 +323,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* @param {function(string)} func Called with the account pickle
|
||||
*/
|
||||
public getAccount(txn: IDBTransaction, func: (accountPickle: string | null) => void) {
|
||||
this.backend.getAccount(txn, func);
|
||||
this.backend!.getAccount(txn, func);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -334,7 +334,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* @param {string} accountPickle The new account pickle to store.
|
||||
*/
|
||||
public storeAccount(txn: IDBTransaction, accountPickle: string): void {
|
||||
this.backend.storeAccount(txn, accountPickle);
|
||||
this.backend!.storeAccount(txn, accountPickle);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -349,7 +349,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
txn: IDBTransaction,
|
||||
func: (keys: Record<string, ICrossSigningKey> | null) => void,
|
||||
): void {
|
||||
this.backend.getCrossSigningKeys(txn, func);
|
||||
this.backend!.getCrossSigningKeys(txn, func);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -362,7 +362,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
func: (key: SecretStorePrivateKeys[K] | null) => void,
|
||||
type: K,
|
||||
): void {
|
||||
this.backend.getSecretStorePrivateKey(txn, func, type);
|
||||
this.backend!.getSecretStorePrivateKey(txn, func, type);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -372,7 +372,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* @param {string} keys keys object as getCrossSigningKeys()
|
||||
*/
|
||||
public storeCrossSigningKeys(txn: IDBTransaction, keys: Record<string, ICrossSigningKey>): void {
|
||||
this.backend.storeCrossSigningKeys(txn, keys);
|
||||
this.backend!.storeCrossSigningKeys(txn, keys);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -387,7 +387,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
type: K,
|
||||
key: SecretStorePrivateKeys[K],
|
||||
): void {
|
||||
this.backend.storeSecretStorePrivateKey(txn, type, key);
|
||||
this.backend!.storeSecretStorePrivateKey(txn, type, key);
|
||||
}
|
||||
|
||||
// Olm sessions
|
||||
@ -398,7 +398,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* @param {function(int)} func Called with the count of sessions
|
||||
*/
|
||||
public countEndToEndSessions(txn: IDBTransaction, func: (count: number) => void): void {
|
||||
this.backend.countEndToEndSessions(txn, func);
|
||||
this.backend!.countEndToEndSessions(txn, func);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -417,9 +417,9 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
deviceKey: string,
|
||||
sessionId: string,
|
||||
txn: IDBTransaction,
|
||||
func: (sessions: { [ sessionId: string ]: ISessionInfo }) => void,
|
||||
func: (session: ISessionInfo | null) => void,
|
||||
): void {
|
||||
this.backend.getEndToEndSession(deviceKey, sessionId, txn, func);
|
||||
this.backend!.getEndToEndSession(deviceKey, sessionId, txn, func);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -438,7 +438,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
txn: IDBTransaction,
|
||||
func: (sessions: { [sessionId: string]: ISessionInfo }) => void,
|
||||
): void {
|
||||
this.backend.getEndToEndSessions(deviceKey, txn, func);
|
||||
this.backend!.getEndToEndSessions(deviceKey, txn, func);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -448,8 +448,8 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* an object with, deviceKey, lastReceivedMessageTs, sessionId
|
||||
* and session keys.
|
||||
*/
|
||||
public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo) => void): void {
|
||||
this.backend.getAllEndToEndSessions(txn, func);
|
||||
public getAllEndToEndSessions(txn: IDBTransaction, func: (session: ISessionInfo | null) => void): void {
|
||||
this.backend!.getAllEndToEndSessions(txn, func);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -465,19 +465,19 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
sessionInfo: ISessionInfo,
|
||||
txn: IDBTransaction,
|
||||
): void {
|
||||
this.backend.storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn);
|
||||
this.backend!.storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn);
|
||||
}
|
||||
|
||||
public storeEndToEndSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void> {
|
||||
return this.backend.storeEndToEndSessionProblem(deviceKey, type, fixed);
|
||||
return this.backend!.storeEndToEndSessionProblem(deviceKey, type, fixed);
|
||||
}
|
||||
|
||||
public getEndToEndSessionProblem(deviceKey: string, timestamp: number): Promise<IProblem | null> {
|
||||
return this.backend.getEndToEndSessionProblem(deviceKey, timestamp);
|
||||
return this.backend!.getEndToEndSessionProblem(deviceKey, timestamp);
|
||||
}
|
||||
|
||||
public filterOutNotifiedErrorDevices(devices: IOlmDevice[]): Promise<IOlmDevice[]> {
|
||||
return this.backend.filterOutNotifiedErrorDevices(devices);
|
||||
return this.backend!.filterOutNotifiedErrorDevices(devices);
|
||||
}
|
||||
|
||||
// Inbound group sessions
|
||||
@ -497,7 +497,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
txn: IDBTransaction,
|
||||
func: (groupSession: InboundGroupSessionData | null, groupSessionWithheld: IWithheld | null) => void,
|
||||
): void {
|
||||
this.backend.getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func);
|
||||
this.backend!.getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -511,7 +511,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
txn: IDBTransaction,
|
||||
func: (session: ISession | null) => void,
|
||||
): void {
|
||||
this.backend.getAllEndToEndInboundGroupSessions(txn, func);
|
||||
this.backend!.getAllEndToEndInboundGroupSessions(txn, func);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -529,7 +529,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
sessionData: InboundGroupSessionData,
|
||||
txn: IDBTransaction,
|
||||
): void {
|
||||
this.backend.addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn);
|
||||
this.backend!.addEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -547,7 +547,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
sessionData: InboundGroupSessionData,
|
||||
txn: IDBTransaction,
|
||||
): void {
|
||||
this.backend.storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn);
|
||||
this.backend!.storeEndToEndInboundGroupSession(senderCurve25519Key, sessionId, sessionData, txn);
|
||||
}
|
||||
|
||||
public storeEndToEndInboundGroupSessionWithheld(
|
||||
@ -556,7 +556,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
sessionData: IWithheld,
|
||||
txn: IDBTransaction,
|
||||
): void {
|
||||
this.backend.storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn);
|
||||
this.backend!.storeEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId, sessionData, txn);
|
||||
}
|
||||
|
||||
// End-to-end device tracking
|
||||
@ -572,7 +572,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* @param {*} txn An active transaction. See doTxn().
|
||||
*/
|
||||
public storeEndToEndDeviceData(deviceData: IDeviceData, txn: IDBTransaction): void {
|
||||
this.backend.storeEndToEndDeviceData(deviceData, txn);
|
||||
this.backend!.storeEndToEndDeviceData(deviceData, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -583,7 +583,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* device data
|
||||
*/
|
||||
public getEndToEndDeviceData(txn: IDBTransaction, func: (deviceData: IDeviceData | null) => void): void {
|
||||
this.backend.getEndToEndDeviceData(txn, func);
|
||||
this.backend!.getEndToEndDeviceData(txn, func);
|
||||
}
|
||||
|
||||
// End to End Rooms
|
||||
@ -595,7 +595,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* @param {*} txn An active transaction. See doTxn().
|
||||
*/
|
||||
public storeEndToEndRoom(roomId: string, roomInfo: IRoomEncryption, txn: IDBTransaction): void {
|
||||
this.backend.storeEndToEndRoom(roomId, roomInfo, txn);
|
||||
this.backend!.storeEndToEndRoom(roomId, roomInfo, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -604,7 +604,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* @param {function(Object)} func Function called with the end to end encrypted rooms
|
||||
*/
|
||||
public getEndToEndRooms(txn: IDBTransaction, func: (rooms: Record<string, IRoomEncryption>) => void): void {
|
||||
this.backend.getEndToEndRooms(txn, func);
|
||||
this.backend!.getEndToEndRooms(txn, func);
|
||||
}
|
||||
|
||||
// session backups
|
||||
@ -616,7 +616,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* @returns {Promise} resolves to an array of inbound group sessions
|
||||
*/
|
||||
public getSessionsNeedingBackup(limit: number): Promise<ISession[]> {
|
||||
return this.backend.getSessionsNeedingBackup(limit);
|
||||
return this.backend!.getSessionsNeedingBackup(limit);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -625,7 +625,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* @returns {Promise} resolves to the number of sessions
|
||||
*/
|
||||
public countSessionsNeedingBackup(txn?: IDBTransaction): Promise<number> {
|
||||
return this.backend.countSessionsNeedingBackup(txn);
|
||||
return this.backend!.countSessionsNeedingBackup(txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -635,7 +635,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* @returns {Promise} resolves when the sessions are unmarked
|
||||
*/
|
||||
public unmarkSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise<void> {
|
||||
return this.backend.unmarkSessionsNeedingBackup(sessions, txn);
|
||||
return this.backend!.unmarkSessionsNeedingBackup(sessions, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -645,7 +645,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* @returns {Promise} resolves when the sessions are marked
|
||||
*/
|
||||
public markSessionsNeedingBackup(sessions: ISession[], txn?: IDBTransaction): Promise<void> {
|
||||
return this.backend.markSessionsNeedingBackup(sessions, txn);
|
||||
return this.backend!.markSessionsNeedingBackup(sessions, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -661,7 +661,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
sessionId: string,
|
||||
txn?: IDBTransaction,
|
||||
): void {
|
||||
this.backend.addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn);
|
||||
this.backend!.addSharedHistoryInboundGroupSession(roomId, senderKey, sessionId, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -674,7 +674,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
roomId: string,
|
||||
txn?: IDBTransaction,
|
||||
): Promise<[senderKey: string, sessionId: string][]> {
|
||||
return this.backend.getSharedHistoryInboundGroupSessions(roomId, txn);
|
||||
return this.backend!.getSharedHistoryInboundGroupSessions(roomId, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -685,7 +685,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
parkedData: ParkedSharedHistory,
|
||||
txn?: IDBTransaction,
|
||||
): void {
|
||||
this.backend.addParkedSharedHistory(roomId, parkedData, txn);
|
||||
this.backend!.addParkedSharedHistory(roomId, parkedData, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -695,7 +695,7 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
roomId: string,
|
||||
txn?: IDBTransaction,
|
||||
): Promise<ParkedSharedHistory[]> {
|
||||
return this.backend.takeParkedSharedHistory(roomId, txn);
|
||||
return this.backend!.takeParkedSharedHistory(roomId, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -720,7 +720,12 @@ export class IndexedDBCryptoStore implements CryptoStore {
|
||||
* reject with that exception. On synchronous backends, the
|
||||
* exception will propagate to the caller of the getFoo method.
|
||||
*/
|
||||
doTxn<T>(mode: Mode, stores: Iterable<string>, func: (txn: IDBTransaction) => T, log?: PrefixedLogger): Promise<T> {
|
||||
return this.backend.doTxn(mode, stores, func, log);
|
||||
public doTxn<T>(
|
||||
mode: Mode,
|
||||
stores: Iterable<string>,
|
||||
func: (txn: IDBTransaction) => T,
|
||||
log?: PrefixedLogger,
|
||||
): Promise<T> {
|
||||
return this.backend!.doTxn<T>(mode, stores, func as (txn: unknown) => T, log);
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
public static exists(store: Storage): boolean {
|
||||
const length = store.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (store.key(i).startsWith(E2E_PREFIX)) {
|
||||
if (store.key(i)?.startsWith(E2E_PREFIX)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -85,7 +85,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
public countEndToEndSessions(txn: unknown, func: (count: number) => void): void {
|
||||
let count = 0;
|
||||
for (let i = 0; i < this.store.length; ++i) {
|
||||
if (this.store.key(i).startsWith(keyEndToEndSessions(''))) ++count;
|
||||
if (this.store.key(i)?.startsWith(keyEndToEndSessions(''))) ++count;
|
||||
}
|
||||
func(count);
|
||||
}
|
||||
@ -129,8 +129,8 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
|
||||
public getAllEndToEndSessions(txn: unknown, func: (session: ISessionInfo) => void): void {
|
||||
for (let i = 0; i < this.store.length; ++i) {
|
||||
if (this.store.key(i).startsWith(keyEndToEndSessions(''))) {
|
||||
const deviceKey = this.store.key(i).split('/')[1];
|
||||
if (this.store.key(i)?.startsWith(keyEndToEndSessions(''))) {
|
||||
const deviceKey = this.store.key(i)!.split('/')[1];
|
||||
for (const sess of Object.values(this._getEndToEndSessions(deviceKey))) {
|
||||
func(sess);
|
||||
}
|
||||
@ -220,7 +220,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
public getAllEndToEndInboundGroupSessions(txn: unknown, func: (session: ISession | null) => void): void {
|
||||
for (let i = 0; i < this.store.length; ++i) {
|
||||
const key = this.store.key(i);
|
||||
if (key.startsWith(KEY_INBOUND_SESSION_PREFIX)) {
|
||||
if (key?.startsWith(KEY_INBOUND_SESSION_PREFIX)) {
|
||||
// we can't use split, as the components we are trying to split out
|
||||
// might themselves contain '/' characters. We rely on the
|
||||
// senderKey being a (32-byte) curve25519 key, base64-encoded
|
||||
@ -229,7 +229,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
func({
|
||||
senderKey: key.slice(KEY_INBOUND_SESSION_PREFIX.length, KEY_INBOUND_SESSION_PREFIX.length + 43),
|
||||
sessionId: key.slice(KEY_INBOUND_SESSION_PREFIX.length + 44),
|
||||
sessionData: getJsonItem(this.store, key),
|
||||
sessionData: getJsonItem(this.store, key)!,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -297,9 +297,9 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
|
||||
for (let i = 0; i < this.store.length; ++i) {
|
||||
const key = this.store.key(i);
|
||||
if (key.startsWith(prefix)) {
|
||||
if (key?.startsWith(prefix)) {
|
||||
const roomId = key.slice(prefix.length);
|
||||
result[roomId] = getJsonItem(this.store, key);
|
||||
result[roomId] = getJsonItem(this.store, key)!;
|
||||
}
|
||||
}
|
||||
func(result);
|
||||
@ -320,7 +320,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
sessions.push({
|
||||
senderKey: senderKey,
|
||||
sessionId: sessionId,
|
||||
sessionData: sessionData,
|
||||
sessionData: sessionData!,
|
||||
});
|
||||
},
|
||||
);
|
||||
@ -417,10 +417,10 @@ function getJsonItem<T>(store: Storage, key: string): T | null {
|
||||
try {
|
||||
// if the key is absent, store.getItem() returns null, and
|
||||
// JSON.parse(null) === null, so this returns null.
|
||||
return JSON.parse(store.getItem(key));
|
||||
return JSON.parse(store.getItem(key)!);
|
||||
} catch (e) {
|
||||
logger.log("Error: Failed to get key %s: %s", key, e.stack || e);
|
||||
logger.log(e.stack);
|
||||
logger.log("Error: Failed to get key %s: %s", key, (<Error>e).message);
|
||||
logger.log((<Error>e).stack);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ export class MemoryCryptoStore implements CryptoStore {
|
||||
private inboundGroupSessions: { [sessionKey: string]: InboundGroupSessionData } = {};
|
||||
private inboundGroupSessionsWithheld: Record<string, IWithheld> = {};
|
||||
// Opaque device data object
|
||||
private deviceData: IDeviceData = null;
|
||||
private deviceData: IDeviceData | null = null;
|
||||
private rooms: { [roomId: string]: IRoomEncryption } = {};
|
||||
private sessionsNeedingBackup: { [sessionKey: string]: boolean } = {};
|
||||
private sharedHistoryInboundGroupSessions: { [roomId: string]: [senderKey: string, sessionId: string][] } = {};
|
||||
|
@ -55,14 +55,14 @@ export class VerificationBase<
|
||||
> extends TypedEventEmitter<Events | VerificationEvent, Arguments, VerificationEventHandlerMap> {
|
||||
private cancelled = false;
|
||||
private _done = false;
|
||||
private promise: Promise<void> = null;
|
||||
private transactionTimeoutTimer: ReturnType<typeof setTimeout> = null;
|
||||
protected expectedEvent: string;
|
||||
private resolve: () => void;
|
||||
private reject: (e: Error | MatrixEvent) => void;
|
||||
private resolveEvent: (e: MatrixEvent) => void;
|
||||
private rejectEvent: (e: Error) => void;
|
||||
private started: boolean;
|
||||
private promise: Promise<void> | null = null;
|
||||
private transactionTimeoutTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
protected expectedEvent?: string;
|
||||
private resolve?: () => void;
|
||||
private reject?: (e: Error | MatrixEvent) => void;
|
||||
private resolveEvent?: (e: MatrixEvent) => void;
|
||||
private rejectEvent?: (e: Error) => void;
|
||||
private started?: boolean;
|
||||
|
||||
/**
|
||||
* Base class for verification methods.
|
||||
@ -187,7 +187,7 @@ export class VerificationBase<
|
||||
this.expectedEvent = undefined;
|
||||
this.rejectEvent = undefined;
|
||||
this.resetTimer();
|
||||
this.resolveEvent(e);
|
||||
this.resolveEvent?.(e);
|
||||
}
|
||||
} else if (e.getType() === EventType.KeyVerificationCancel) {
|
||||
const reject = this.reject;
|
||||
@ -218,11 +218,11 @@ export class VerificationBase<
|
||||
}
|
||||
}
|
||||
|
||||
public done(): Promise<KeysDuringVerification | void> {
|
||||
public async done(): Promise<KeysDuringVerification | void> {
|
||||
this.endTimer(); // always kill the activity timer
|
||||
if (!this._done) {
|
||||
this.request.onVerifierFinished();
|
||||
this.resolve();
|
||||
this.resolve?.();
|
||||
return requestKeysDuringVerification(this.baseApis, this.userId, this.deviceId);
|
||||
}
|
||||
}
|
||||
@ -291,7 +291,7 @@ export class VerificationBase<
|
||||
this.endTimer();
|
||||
resolve(...args);
|
||||
};
|
||||
this.reject = (e: Error) => {
|
||||
this.reject = (e: Error | MatrixEvent) => {
|
||||
this._done = true;
|
||||
this.endTimer();
|
||||
reject(e);
|
||||
@ -301,12 +301,12 @@ export class VerificationBase<
|
||||
this.started = true;
|
||||
this.resetTimer(); // restart the timeout
|
||||
new Promise<void>((resolve, reject) => {
|
||||
const crossSignId = this.baseApis.crypto.deviceList.getStoredCrossSigningForUser(this.userId)?.getId();
|
||||
const crossSignId = this.baseApis.crypto!.deviceList.getStoredCrossSigningForUser(this.userId)?.getId();
|
||||
if (crossSignId === this.deviceId) {
|
||||
reject(new Error("Device ID is the same as the cross-signing ID"));
|
||||
}
|
||||
resolve();
|
||||
}).then(() => this.doVerification()).then(this.done.bind(this), this.cancel.bind(this));
|
||||
}).then(() => this.doVerification!()).then(this.done.bind(this), this.cancel.bind(this));
|
||||
}
|
||||
return this.promise;
|
||||
}
|
||||
@ -326,7 +326,7 @@ export class VerificationBase<
|
||||
verifier(keyId, device, keyInfo);
|
||||
verifiedDevices.push([deviceId, keyId, device.keys[keyId]]);
|
||||
} else {
|
||||
const crossSigningInfo = this.baseApis.crypto.deviceList.getStoredCrossSigningForUser(userId);
|
||||
const crossSigningInfo = this.baseApis.crypto!.deviceList.getStoredCrossSigningForUser(userId);
|
||||
if (crossSigningInfo && crossSigningInfo.getId() === deviceId) {
|
||||
verifier(keyId, DeviceInfo.fromStorage({
|
||||
keys: {
|
||||
@ -356,7 +356,7 @@ export class VerificationBase<
|
||||
// to upload each signature in a separate API call which is silly because the
|
||||
// API supports as many signatures as you like.
|
||||
for (const [deviceId, keyId, key] of verifiedDevices) {
|
||||
await this.baseApis.crypto.setDeviceVerification(userId, deviceId, true, null, null, { [keyId]: key });
|
||||
await this.baseApis.crypto!.setDeviceVerification(userId, deviceId, true, null, null, { [keyId]: key });
|
||||
}
|
||||
|
||||
// if one of the user's own devices is being marked as verified / unverified,
|
||||
|
@ -49,7 +49,7 @@ type EventHandlerMap = {
|
||||
* @extends {module:crypto/verification/Base}
|
||||
*/
|
||||
export class ReciprocateQRCode extends Base<QrCodeEvent, EventHandlerMap> {
|
||||
public reciprocateQREvent: IReciprocateQr;
|
||||
public reciprocateQREvent?: IReciprocateQr;
|
||||
|
||||
public static factory(
|
||||
channel: IVerificationChannel,
|
||||
@ -76,7 +76,7 @@ export class ReciprocateQRCode extends Base<QrCodeEvent, EventHandlerMap> {
|
||||
|
||||
const { qrCodeData } = this.request;
|
||||
// 1. check the secret
|
||||
if (this.startEvent.getContent()['secret'] !== qrCodeData.encodedSharedSecret) {
|
||||
if (this.startEvent.getContent()['secret'] !== qrCodeData?.encodedSharedSecret) {
|
||||
throw newKeyMismatchError();
|
||||
}
|
||||
|
||||
@ -92,21 +92,21 @@ export class ReciprocateQRCode extends Base<QrCodeEvent, EventHandlerMap> {
|
||||
// 3. determine key to sign / mark as trusted
|
||||
const keys: Record<string, string> = {};
|
||||
|
||||
switch (qrCodeData.mode) {
|
||||
switch (qrCodeData?.mode) {
|
||||
case Mode.VerifyOtherUser: {
|
||||
// add master key to keys to be signed, only if we're not doing self-verification
|
||||
const masterKey = qrCodeData.otherUserMasterKey;
|
||||
keys[`ed25519:${masterKey}`] = masterKey;
|
||||
keys[`ed25519:${masterKey}`] = masterKey!;
|
||||
break;
|
||||
}
|
||||
case Mode.VerifySelfTrusted: {
|
||||
const deviceId = this.request.targetDevice.deviceId;
|
||||
keys[`ed25519:${deviceId}`] = qrCodeData.otherDeviceKey;
|
||||
keys[`ed25519:${deviceId}`] = qrCodeData.otherDeviceKey!;
|
||||
break;
|
||||
}
|
||||
case Mode.VerifySelfUntrusted: {
|
||||
const masterKey = qrCodeData.myMasterKey;
|
||||
keys[`ed25519:${masterKey}`] = masterKey;
|
||||
keys[`ed25519:${masterKey}`] = masterKey!;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -158,41 +158,41 @@ export class QRCodeData {
|
||||
public readonly mode: Mode,
|
||||
private readonly sharedSecret: string,
|
||||
// only set when mode is MODE_VERIFY_OTHER_USER, master key of other party at time of generating QR code
|
||||
public readonly otherUserMasterKey: string | undefined,
|
||||
public readonly otherUserMasterKey: string | null,
|
||||
// only set when mode is MODE_VERIFY_SELF_TRUSTED, device key of other party at time of generating QR code
|
||||
public readonly otherDeviceKey: string | undefined,
|
||||
public readonly otherDeviceKey: string | null,
|
||||
// only set when mode is MODE_VERIFY_SELF_UNTRUSTED, own master key at time of generating QR code
|
||||
public readonly myMasterKey: string | undefined,
|
||||
public readonly myMasterKey: string | null,
|
||||
private readonly buffer: Buffer,
|
||||
) {}
|
||||
|
||||
public static async create(request: VerificationRequest, client: MatrixClient): Promise<QRCodeData> {
|
||||
const sharedSecret = QRCodeData.generateSharedSecret();
|
||||
const mode = QRCodeData.determineMode(request, client);
|
||||
let otherUserMasterKey = null;
|
||||
let otherDeviceKey = null;
|
||||
let myMasterKey = null;
|
||||
let otherUserMasterKey: string | null = null;
|
||||
let otherDeviceKey: string | null = null;
|
||||
let myMasterKey: string | null = null;
|
||||
if (mode === Mode.VerifyOtherUser) {
|
||||
const otherUserCrossSigningInfo =
|
||||
client.getStoredCrossSigningForUser(request.otherUserId);
|
||||
otherUserMasterKey = otherUserCrossSigningInfo.getId("master");
|
||||
const otherUserCrossSigningInfo = client.getStoredCrossSigningForUser(request.otherUserId);
|
||||
otherUserMasterKey = otherUserCrossSigningInfo!.getId("master");
|
||||
} else if (mode === Mode.VerifySelfTrusted) {
|
||||
otherDeviceKey = await QRCodeData.getOtherDeviceKey(request, client);
|
||||
} else if (mode === Mode.VerifySelfUntrusted) {
|
||||
const myUserId = client.getUserId();
|
||||
const myUserId = client.getUserId()!;
|
||||
const myCrossSigningInfo = client.getStoredCrossSigningForUser(myUserId);
|
||||
myMasterKey = myCrossSigningInfo.getId("master");
|
||||
myMasterKey = myCrossSigningInfo!.getId("master");
|
||||
}
|
||||
const qrData = QRCodeData.generateQrData(
|
||||
request, client, mode,
|
||||
request,
|
||||
client,
|
||||
mode,
|
||||
sharedSecret,
|
||||
otherUserMasterKey,
|
||||
otherDeviceKey,
|
||||
myMasterKey,
|
||||
otherUserMasterKey!,
|
||||
otherDeviceKey!,
|
||||
myMasterKey!,
|
||||
);
|
||||
const buffer = QRCodeData.generateBuffer(qrData);
|
||||
return new QRCodeData(mode, sharedSecret,
|
||||
otherUserMasterKey, otherDeviceKey, myMasterKey, buffer);
|
||||
return new QRCodeData(mode, sharedSecret, otherUserMasterKey, otherDeviceKey, myMasterKey, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -213,12 +213,11 @@ export class QRCodeData {
|
||||
}
|
||||
|
||||
private static async getOtherDeviceKey(request: VerificationRequest, client: MatrixClient): Promise<string> {
|
||||
const myUserId = client.getUserId();
|
||||
const myUserId = client.getUserId()!;
|
||||
const otherDevice = request.targetDevice;
|
||||
const otherDeviceId = otherDevice ? otherDevice.deviceId : null;
|
||||
const device = client.getStoredDevice(myUserId, otherDeviceId);
|
||||
const device = otherDevice.deviceId ? client.getStoredDevice(myUserId, otherDevice.deviceId) : undefined;
|
||||
if (!device) {
|
||||
throw new Error("could not find device " + otherDeviceId);
|
||||
throw new Error("could not find device " + otherDevice?.deviceId);
|
||||
}
|
||||
return device.getFingerprint();
|
||||
}
|
||||
@ -245,11 +244,11 @@ export class QRCodeData {
|
||||
client: MatrixClient,
|
||||
mode: Mode,
|
||||
encodedSharedSecret: string,
|
||||
otherUserMasterKey: string,
|
||||
otherDeviceKey: string,
|
||||
myMasterKey: string,
|
||||
otherUserMasterKey?: string,
|
||||
otherDeviceKey?: string,
|
||||
myMasterKey?: string,
|
||||
): IQrData {
|
||||
const myUserId = client.getUserId();
|
||||
const myUserId = client.getUserId()!;
|
||||
const transactionId = request.channel.transactionId;
|
||||
const qrData = {
|
||||
prefix: BINARY_PREFIX,
|
||||
@ -265,18 +264,18 @@ export class QRCodeData {
|
||||
|
||||
if (mode === Mode.VerifyOtherUser) {
|
||||
// First key is our master cross signing key
|
||||
qrData.firstKeyB64 = myCrossSigningInfo.getId("master");
|
||||
qrData.firstKeyB64 = myCrossSigningInfo!.getId("master")!;
|
||||
// Second key is the other user's master cross signing key
|
||||
qrData.secondKeyB64 = otherUserMasterKey;
|
||||
qrData.secondKeyB64 = otherUserMasterKey!;
|
||||
} else if (mode === Mode.VerifySelfTrusted) {
|
||||
// First key is our master cross signing key
|
||||
qrData.firstKeyB64 = myCrossSigningInfo.getId("master");
|
||||
qrData.secondKeyB64 = otherDeviceKey;
|
||||
qrData.firstKeyB64 = myCrossSigningInfo!.getId("master")!;
|
||||
qrData.secondKeyB64 = otherDeviceKey!;
|
||||
} else if (mode === Mode.VerifySelfUntrusted) {
|
||||
// First key is our device's key
|
||||
qrData.firstKeyB64 = client.getDeviceEd25519Key();
|
||||
qrData.firstKeyB64 = client.getDeviceEd25519Key()!;
|
||||
// Second key is what we think our master cross signing key is
|
||||
qrData.secondKeyB64 = myMasterKey;
|
||||
qrData.secondKeyB64 = myMasterKey!;
|
||||
}
|
||||
return qrData;
|
||||
}
|
||||
|
@ -96,25 +96,25 @@ export class VerificationRequest<
|
||||
private eventsByUs = new Map<string, MatrixEvent>();
|
||||
private eventsByThem = new Map<string, MatrixEvent>();
|
||||
private _observeOnly = false;
|
||||
private timeoutTimer: ReturnType<typeof setTimeout> = null;
|
||||
private timeoutTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
private _accepting = false;
|
||||
private _declining = false;
|
||||
private verifierHasFinished = false;
|
||||
private _cancelled = false;
|
||||
private _chosenMethod: VerificationMethod = null;
|
||||
private _chosenMethod: VerificationMethod | null = null;
|
||||
// we keep a copy of the QR Code data (including other user master key) around
|
||||
// for QR reciprocate verification, to protect against
|
||||
// cross-signing identity reset between the .ready and .start event
|
||||
// and signing the wrong key after .start
|
||||
private _qrCodeData: QRCodeData = null;
|
||||
private _qrCodeData: QRCodeData | null = null;
|
||||
|
||||
// The timestamp when we received the request event from the other side
|
||||
private requestReceivedAt: number = null;
|
||||
private requestReceivedAt: number | null = null;
|
||||
|
||||
private commonMethods: VerificationMethod[] = [];
|
||||
private _phase: Phase;
|
||||
public _cancellingUserId: string; // Used in tests only
|
||||
private _verifier: VerificationBase<any, any>;
|
||||
private _verifier?: VerificationBase<any, any>;
|
||||
|
||||
constructor(
|
||||
public readonly channel: C,
|
||||
@ -204,7 +204,7 @@ export class VerificationRequest<
|
||||
}
|
||||
|
||||
/** the method picked in the .start event */
|
||||
public get chosenMethod(): VerificationMethod {
|
||||
public get chosenMethod(): VerificationMethod | null {
|
||||
return this._chosenMethod;
|
||||
}
|
||||
|
||||
@ -236,7 +236,7 @@ export class VerificationRequest<
|
||||
* The key verification request event.
|
||||
* @returns {MatrixEvent} The request event, or falsey if not found.
|
||||
*/
|
||||
public get requestEvent(): MatrixEvent {
|
||||
public get requestEvent(): MatrixEvent | undefined {
|
||||
return this.getEventByEither(REQUEST_TYPE);
|
||||
}
|
||||
|
||||
@ -246,7 +246,7 @@ export class VerificationRequest<
|
||||
}
|
||||
|
||||
/** The verifier to do the actual verification, once the method has been established. Only defined when the `phase` is PHASE_STARTED. */
|
||||
public get verifier(): VerificationBase<any, any> {
|
||||
public get verifier(): VerificationBase<any, any> | undefined {
|
||||
return this._verifier;
|
||||
}
|
||||
|
||||
@ -270,7 +270,7 @@ export class VerificationRequest<
|
||||
}
|
||||
|
||||
/** Only set after a .ready if the other party can scan a QR code */
|
||||
public get qrCodeData(): QRCodeData {
|
||||
public get qrCodeData(): QRCodeData | null {
|
||||
return this._qrCodeData;
|
||||
}
|
||||
|
||||
@ -340,7 +340,7 @@ export class VerificationRequest<
|
||||
/** The id of the user that initiated the request */
|
||||
public get requestingUserId(): string {
|
||||
if (this.initiatedByMe) {
|
||||
return this.client.getUserId();
|
||||
return this.client.getUserId()!;
|
||||
} else {
|
||||
return this.otherUserId;
|
||||
}
|
||||
@ -351,7 +351,7 @@ export class VerificationRequest<
|
||||
if (this.initiatedByMe) {
|
||||
return this.otherUserId;
|
||||
} else {
|
||||
return this.client.getUserId();
|
||||
return this.client.getUserId()!;
|
||||
}
|
||||
}
|
||||
|
||||
@ -368,7 +368,7 @@ export class VerificationRequest<
|
||||
* The id of the user that cancelled the request,
|
||||
* only defined when phase is PHASE_CANCELLED
|
||||
*/
|
||||
public get cancellingUserId(): string {
|
||||
public get cancellingUserId(): string | undefined {
|
||||
const myCancel = this.eventsByUs.get(CANCEL_TYPE);
|
||||
const theirCancel = this.eventsByThem.get(CANCEL_TYPE);
|
||||
|
||||
@ -422,7 +422,7 @@ export class VerificationRequest<
|
||||
*/
|
||||
public beginKeyVerification(
|
||||
method: VerificationMethod,
|
||||
targetDevice: ITargetDevice = null,
|
||||
targetDevice: ITargetDevice | null = null,
|
||||
): VerificationBase<any, any> {
|
||||
// need to allow also when unsent in case of to_device
|
||||
if (!this.observeOnly && !this._verifier) {
|
||||
@ -443,7 +443,7 @@ export class VerificationRequest<
|
||||
this._chosenMethod = method;
|
||||
}
|
||||
}
|
||||
return this._verifier;
|
||||
return this._verifier!;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -470,7 +470,7 @@ export class VerificationRequest<
|
||||
if (this._verifier) {
|
||||
return this._verifier.cancel(errorFactory(code, reason)());
|
||||
} else {
|
||||
this._cancellingUserId = this.client.getUserId();
|
||||
this._cancellingUserId = this.client.getUserId()!;
|
||||
await this.channel.send(CANCEL_TYPE, { code, reason });
|
||||
}
|
||||
}
|
||||
@ -525,11 +525,11 @@ export class VerificationRequest<
|
||||
}
|
||||
}
|
||||
|
||||
private getEventByEither(type: string): MatrixEvent {
|
||||
private getEventByEither(type: string): MatrixEvent | undefined {
|
||||
return this.eventsByThem.get(type) || this.eventsByUs.get(type);
|
||||
}
|
||||
|
||||
private getEventBy(type: string, byThem = false): MatrixEvent {
|
||||
private getEventBy(type: string, byThem = false): MatrixEvent | undefined {
|
||||
if (byThem) {
|
||||
return this.eventsByThem.get(type);
|
||||
} else {
|
||||
@ -548,20 +548,18 @@ export class VerificationRequest<
|
||||
transitions.push({ phase: PHASE_REQUESTED, event: requestEvent });
|
||||
}
|
||||
|
||||
const readyEvent =
|
||||
requestEvent && this.getEventBy(READY_TYPE, !hasRequestByThem);
|
||||
const readyEvent = requestEvent && this.getEventBy(READY_TYPE, !hasRequestByThem);
|
||||
if (readyEvent && phase() === PHASE_REQUESTED) {
|
||||
transitions.push({ phase: PHASE_READY, event: readyEvent });
|
||||
}
|
||||
|
||||
let startEvent;
|
||||
let startEvent: MatrixEvent | undefined;
|
||||
if (readyEvent || !requestEvent) {
|
||||
const theirStartEvent = this.eventsByThem.get(START_TYPE);
|
||||
const ourStartEvent = this.eventsByUs.get(START_TYPE);
|
||||
// any party can send .start after a .ready or unsent
|
||||
if (theirStartEvent && ourStartEvent) {
|
||||
startEvent = theirStartEvent.getSender() < ourStartEvent.getSender() ?
|
||||
theirStartEvent : ourStartEvent;
|
||||
startEvent = theirStartEvent.getSender() < ourStartEvent.getSender() ? theirStartEvent : ourStartEvent;
|
||||
} else {
|
||||
startEvent = theirStartEvent ? theirStartEvent : ourStartEvent;
|
||||
}
|
||||
@ -569,7 +567,9 @@ export class VerificationRequest<
|
||||
startEvent = this.getEventBy(START_TYPE, !hasRequestByThem);
|
||||
}
|
||||
if (startEvent) {
|
||||
const fromRequestPhase = phase() === PHASE_REQUESTED && requestEvent.getSender() !== startEvent.getSender();
|
||||
const fromRequestPhase = (
|
||||
phase() === PHASE_REQUESTED && requestEvent?.getSender() !== startEvent.getSender()
|
||||
);
|
||||
const fromUnsentPhase = phase() === PHASE_UNSENT && this.channel.canCreateRequest(START_TYPE);
|
||||
if (fromRequestPhase || phase() === PHASE_READY || fromUnsentPhase) {
|
||||
transitions.push({ phase: PHASE_STARTED, event: startEvent });
|
||||
@ -651,7 +651,7 @@ export class VerificationRequest<
|
||||
if (newEvent.getType() !== START_TYPE) {
|
||||
return false;
|
||||
}
|
||||
const oldEvent = this._verifier.startEvent;
|
||||
const oldEvent = this._verifier!.startEvent;
|
||||
|
||||
let oldRaceIdentifier;
|
||||
if (this.isSelfVerification) {
|
||||
@ -890,9 +890,9 @@ export class VerificationRequest<
|
||||
|
||||
private createVerifier(
|
||||
method: VerificationMethod,
|
||||
startEvent: MatrixEvent = null,
|
||||
targetDevice: ITargetDevice = null,
|
||||
): VerificationBase<any, any> {
|
||||
startEvent: MatrixEvent | null = null,
|
||||
targetDevice: ITargetDevice | null = null,
|
||||
): VerificationBase<any, any> | undefined {
|
||||
if (!targetDevice) {
|
||||
targetDevice = this.targetDevice;
|
||||
}
|
||||
@ -941,7 +941,7 @@ export class VerificationRequest<
|
||||
}
|
||||
}
|
||||
|
||||
public getEventFromOtherParty(type: string): MatrixEvent {
|
||||
public getEventFromOtherParty(type: string): MatrixEvent | undefined {
|
||||
return this.eventsByThem.get(type);
|
||||
}
|
||||
}
|
||||
|
@ -1,52 +0,0 @@
|
||||
// can't just do InvalidStoreError extends Error
|
||||
// because of http://babeljs.io/docs/usage/caveats/#classes
|
||||
export function InvalidStoreError(reason, value) {
|
||||
const message = `Store is invalid because ${reason}, ` +
|
||||
`please stop the client, delete all data and start the client again`;
|
||||
const instance = Reflect.construct(Error, [message]);
|
||||
Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this));
|
||||
instance.reason = reason;
|
||||
instance.value = value;
|
||||
return instance;
|
||||
}
|
||||
|
||||
InvalidStoreError.TOGGLED_LAZY_LOADING = "TOGGLED_LAZY_LOADING";
|
||||
|
||||
InvalidStoreError.prototype = Object.create(Error.prototype, {
|
||||
constructor: {
|
||||
value: Error,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
Reflect.setPrototypeOf(InvalidStoreError, Error);
|
||||
|
||||
export function InvalidCryptoStoreError(reason) {
|
||||
const message = `Crypto store is invalid because ${reason}, ` +
|
||||
`please stop the client, delete all data and start the client again`;
|
||||
const instance = Reflect.construct(Error, [message]);
|
||||
Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this));
|
||||
instance.reason = reason;
|
||||
instance.name = 'InvalidCryptoStoreError';
|
||||
return instance;
|
||||
}
|
||||
|
||||
InvalidCryptoStoreError.TOO_NEW = "TOO_NEW";
|
||||
|
||||
InvalidCryptoStoreError.prototype = Object.create(Error.prototype, {
|
||||
constructor: {
|
||||
value: Error,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
},
|
||||
});
|
||||
Reflect.setPrototypeOf(InvalidCryptoStoreError, Error);
|
||||
|
||||
export class KeySignatureUploadError extends Error {
|
||||
constructor(message, value) {
|
||||
super(message);
|
||||
this.value = value;
|
||||
}
|
||||
}
|
51
src/errors.ts
Normal file
51
src/errors.ts
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export enum InvalidStoreState {
|
||||
ToggledLazyLoading,
|
||||
}
|
||||
|
||||
export class InvalidStoreError extends Error {
|
||||
public static TOGGLED_LAZY_LOADING = InvalidStoreState.ToggledLazyLoading;
|
||||
|
||||
public constructor(public readonly reason: InvalidStoreState, public readonly value: any) {
|
||||
const message = `Store is invalid because ${reason}, ` +
|
||||
`please stop the client, delete all data and start the client again`;
|
||||
super(message);
|
||||
this.name = "InvalidStoreError";
|
||||
}
|
||||
}
|
||||
|
||||
export enum InvalidCryptoStoreState {
|
||||
TooNew = "TOO_NEW",
|
||||
}
|
||||
|
||||
export class InvalidCryptoStoreError extends Error {
|
||||
public static TOO_NEW = InvalidCryptoStoreState.TooNew;
|
||||
|
||||
public constructor(public readonly reason: InvalidCryptoStoreState) {
|
||||
const message = `Crypto store is invalid because ${reason}, ` +
|
||||
`please stop the client, delete all data and start the client again`;
|
||||
super(message);
|
||||
this.name = 'InvalidCryptoStoreError';
|
||||
}
|
||||
}
|
||||
|
||||
export class KeySignatureUploadError extends Error {
|
||||
public constructor(message: string, public readonly value: any) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -36,11 +36,11 @@ export function eventMapperFor(client: MatrixClient, options: MapperOpts): Event
|
||||
|
||||
const room = client.getRoom(plainOldJsObject.room_id);
|
||||
|
||||
let event: MatrixEvent;
|
||||
let event: MatrixEvent | undefined;
|
||||
// If the event is already known to the room, let's re-use the model rather than duplicating.
|
||||
// We avoid doing this to state events as they may be forward or backwards looking which tweaks behaviour.
|
||||
if (room && plainOldJsObject.state_key === undefined) {
|
||||
event = room.findEventById(plainOldJsObject.event_id);
|
||||
event = room.findEventById(plainOldJsObject.event_id!);
|
||||
}
|
||||
|
||||
if (!event || event.status) {
|
||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { IUsageLimit } from "../@types/partials";
|
||||
import { MatrixEvent } from "../models/event";
|
||||
|
||||
interface IErrorJson extends Partial<IUsageLimit> {
|
||||
[key: string]: any; // extensible
|
||||
@ -50,7 +51,12 @@ export class MatrixError extends HTTPError {
|
||||
public readonly errcode?: string;
|
||||
public readonly data: IErrorJson;
|
||||
|
||||
constructor(errorJson: IErrorJson = {}, public readonly httpStatus?: number, public url?: string) {
|
||||
constructor(
|
||||
errorJson: IErrorJson = {},
|
||||
public readonly httpStatus?: number,
|
||||
public url?: string,
|
||||
public event?: MatrixEvent,
|
||||
) {
|
||||
let message = errorJson.error || "Unknown message";
|
||||
if (httpStatus) {
|
||||
message = `[${httpStatus}] ${message}`;
|
||||
|
@ -73,7 +73,7 @@ export class FetchHttpApi<O extends IHttpOpts> {
|
||||
public idServerRequest<T extends {}>(
|
||||
method: Method,
|
||||
path: string,
|
||||
params: Record<string, string | string[]>,
|
||||
params: Record<string, string | string[]> | undefined,
|
||||
prefix: string,
|
||||
accessToken?: string,
|
||||
): Promise<ResponseType<T, O>> {
|
||||
@ -96,7 +96,7 @@ export class FetchHttpApi<O extends IHttpOpts> {
|
||||
headers: {},
|
||||
};
|
||||
if (accessToken) {
|
||||
opts.headers.Authorization = `Bearer ${accessToken}`;
|
||||
opts.headers!.Authorization = `Bearer ${accessToken}`;
|
||||
}
|
||||
|
||||
return this.requestOtherUrl(method, fullUri, body, opts);
|
||||
@ -286,10 +286,10 @@ export class FetchHttpApi<O extends IHttpOpts> {
|
||||
credentials: "omit", // we send credentials via headers
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.name === "AbortError") {
|
||||
if ((<Error>e).name === "AbortError") {
|
||||
throw e;
|
||||
}
|
||||
throw new ConnectionError("fetch failed", e);
|
||||
throw new ConnectionError("fetch failed", <Error>e);
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
|
@ -72,11 +72,11 @@ export function anySignal(signals: AbortSignal[]): {
|
||||
* @returns {Error}
|
||||
*/
|
||||
export function parseErrorResponse(response: XMLHttpRequest | Response, body?: string): Error {
|
||||
let contentType: ParsedMediaType;
|
||||
let contentType: ParsedMediaType | null;
|
||||
try {
|
||||
contentType = getResponseContentType(response);
|
||||
} catch (e) {
|
||||
return e;
|
||||
return <Error>e;
|
||||
}
|
||||
|
||||
if (contentType?.type === "application/json" && body) {
|
||||
|
@ -59,16 +59,16 @@ log.methodFactory = function(methodName, logLevel, loggerName) {
|
||||
* Drop-in replacement for <code>console</code> using {@link https://www.npmjs.com/package/loglevel|loglevel}.
|
||||
* Can be tailored down to specific use cases if needed.
|
||||
*/
|
||||
export const logger: PrefixedLogger = log.getLogger(DEFAULT_NAMESPACE);
|
||||
export const logger = log.getLogger(DEFAULT_NAMESPACE) as PrefixedLogger;
|
||||
logger.setLevel(log.levels.DEBUG, false);
|
||||
|
||||
export interface PrefixedLogger extends Logger {
|
||||
withPrefix?: (prefix: string) => PrefixedLogger;
|
||||
prefix?: string;
|
||||
withPrefix: (prefix: string) => PrefixedLogger;
|
||||
prefix: string;
|
||||
}
|
||||
|
||||
function extendLogger(logger: PrefixedLogger) {
|
||||
logger.withPrefix = function(prefix: string): PrefixedLogger {
|
||||
function extendLogger(logger: Logger) {
|
||||
(<PrefixedLogger>logger).withPrefix = function(prefix: string): PrefixedLogger {
|
||||
const existingPrefix = this.prefix || "";
|
||||
return getPrefixedLogger(existingPrefix + prefix);
|
||||
};
|
||||
@ -77,7 +77,7 @@ function extendLogger(logger: PrefixedLogger) {
|
||||
extendLogger(logger);
|
||||
|
||||
function getPrefixedLogger(prefix: string): PrefixedLogger {
|
||||
const prefixLogger: PrefixedLogger = log.getLogger(`${DEFAULT_NAMESPACE}-${prefix}`);
|
||||
const prefixLogger = log.getLogger(`${DEFAULT_NAMESPACE}-${prefix}`) as PrefixedLogger;
|
||||
if (prefixLogger.prefix !== prefix) {
|
||||
// Only do this setup work the first time through, as loggers are saved by name.
|
||||
extendLogger(prefixLogger);
|
||||
|
@ -68,7 +68,7 @@ export function setCryptoStoreFactory(fac) {
|
||||
}
|
||||
|
||||
export interface ICryptoCallbacks {
|
||||
getCrossSigningKey?: (keyType: string, pubKey: string) => Promise<Uint8Array>;
|
||||
getCrossSigningKey?: (keyType: string, pubKey: string) => Promise<Uint8Array | null>;
|
||||
saveCrossSigningKeys?: (keys: Record<string, Uint8Array>) => void;
|
||||
shouldUpgradeDeviceVerifications?: (
|
||||
users: Record<string, any>
|
||||
|
@ -86,7 +86,7 @@ export class MSC3089TreeSpace {
|
||||
public readonly room: Room;
|
||||
|
||||
public constructor(private client: MatrixClient, public readonly roomId: string) {
|
||||
this.room = this.client.getRoom(this.roomId);
|
||||
this.room = this.client.getRoom(this.roomId)!;
|
||||
|
||||
if (!this.room) throw new Error("Unknown room");
|
||||
}
|
||||
@ -282,7 +282,7 @@ export class MSC3089TreeSpace {
|
||||
const members = this.room.currentState.getStateEvents(EventType.RoomMember);
|
||||
for (const member of members) {
|
||||
const isNotUs = member.getStateKey() !== this.client.getUserId();
|
||||
if (isNotUs && kickMemberships.includes(member.getContent().membership)) {
|
||||
if (isNotUs && kickMemberships.includes(member.getContent().membership!)) {
|
||||
const stateKey = member.getStateKey();
|
||||
if (!stateKey) {
|
||||
throw new Error("State key not found for branch");
|
||||
|
@ -51,21 +51,21 @@ export const getBeaconInfoIdentifier = (event: MatrixEvent): BeaconIdentifier =>
|
||||
// https://github.com/matrix-org/matrix-spec-proposals/pull/3672
|
||||
export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.New>, BeaconEventHandlerMap> {
|
||||
public readonly roomId: string;
|
||||
private _beaconInfo: BeaconInfoState;
|
||||
private _isLive: boolean;
|
||||
private livenessWatchTimeout: ReturnType<typeof setTimeout>;
|
||||
private _latestLocationEvent: MatrixEvent | undefined;
|
||||
private _beaconInfo?: BeaconInfoState;
|
||||
private _isLive?: boolean;
|
||||
private livenessWatchTimeout?: ReturnType<typeof setTimeout>;
|
||||
private _latestLocationEvent?: MatrixEvent;
|
||||
|
||||
constructor(
|
||||
private rootEvent: MatrixEvent,
|
||||
) {
|
||||
super();
|
||||
this.setBeaconInfo(this.rootEvent);
|
||||
this.roomId = this.rootEvent.getRoomId();
|
||||
this.roomId = this.rootEvent.getRoomId()!;
|
||||
}
|
||||
|
||||
public get isLive(): boolean {
|
||||
return this._isLive;
|
||||
return !!this._isLive;
|
||||
}
|
||||
|
||||
public get identifier(): BeaconIdentifier {
|
||||
@ -77,14 +77,14 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
|
||||
}
|
||||
|
||||
public get beaconInfoOwner(): string {
|
||||
return this.rootEvent.getStateKey();
|
||||
return this.rootEvent.getStateKey()!;
|
||||
}
|
||||
|
||||
public get beaconInfoEventType(): string {
|
||||
return this.rootEvent.getType();
|
||||
}
|
||||
|
||||
public get beaconInfo(): BeaconInfoState {
|
||||
public get beaconInfo(): BeaconInfoState | undefined {
|
||||
return this._beaconInfo;
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
|
||||
throw new Error('Invalid updating event');
|
||||
}
|
||||
// don't update beacon with an older event
|
||||
if (beaconInfoEvent.event.origin_server_ts < this.rootEvent.event.origin_server_ts) {
|
||||
if (beaconInfoEvent.getTs() < this.rootEvent.getTs()) {
|
||||
return;
|
||||
}
|
||||
this.rootEvent = beaconInfoEvent;
|
||||
@ -130,20 +130,21 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
|
||||
}
|
||||
|
||||
this.checkLiveness();
|
||||
if (!this.beaconInfo) return;
|
||||
if (this.isLive) {
|
||||
const expiryInMs = (this._beaconInfo?.timestamp + this._beaconInfo?.timeout) - Date.now();
|
||||
const expiryInMs = (this.beaconInfo.timestamp + this.beaconInfo.timeout) - Date.now();
|
||||
if (expiryInMs > 1) {
|
||||
this.livenessWatchTimeout = setTimeout(
|
||||
() => { this.monitorLiveness(); },
|
||||
expiryInMs,
|
||||
);
|
||||
}
|
||||
} else if (this._beaconInfo?.timestamp > Date.now()) {
|
||||
} else if (this.beaconInfo.timestamp > Date.now()) {
|
||||
// beacon start timestamp is in the future
|
||||
// check liveness again then
|
||||
this.livenessWatchTimeout = setTimeout(
|
||||
() => { this.monitorLiveness(); },
|
||||
this.beaconInfo?.timestamp - Date.now(),
|
||||
this.beaconInfo.timestamp - Date.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -165,22 +166,22 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
|
||||
const { timestamp } = parsed;
|
||||
return (
|
||||
// only include positions that were taken inside the beacon's live period
|
||||
isTimestampInDuration(this._beaconInfo.timestamp, this._beaconInfo.timeout, timestamp) &&
|
||||
isTimestampInDuration(this._beaconInfo!.timestamp, this._beaconInfo!.timeout, timestamp) &&
|
||||
// ignore positions older than our current latest location
|
||||
(!this.latestLocationState || timestamp > this.latestLocationState.timestamp)
|
||||
(!this.latestLocationState || timestamp > this.latestLocationState.timestamp!)
|
||||
);
|
||||
});
|
||||
const latestLocationEvent = validLocationEvents.sort(sortEventsByLatestContentTimestamp)?.[0];
|
||||
|
||||
if (latestLocationEvent) {
|
||||
this._latestLocationEvent = latestLocationEvent;
|
||||
this.emit(BeaconEvent.LocationUpdate, this.latestLocationState);
|
||||
this.emit(BeaconEvent.LocationUpdate, this.latestLocationState!);
|
||||
}
|
||||
}
|
||||
|
||||
private clearLatestLocation = () => {
|
||||
this._latestLocationEvent = undefined;
|
||||
this.emit(BeaconEvent.LocationUpdate, this.latestLocationState);
|
||||
this.emit(BeaconEvent.LocationUpdate, this.latestLocationState!);
|
||||
};
|
||||
|
||||
private setBeaconInfo(event: MatrixEvent): void {
|
||||
@ -195,9 +196,10 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
|
||||
// when Alice's system clock deviates slightly from Bob's a beacon Alice intended to be live
|
||||
// may have a start timestamp in the future from Bob's POV
|
||||
// handle this by adding 6min of leniency to the start timestamp when it is in the future
|
||||
const startTimestamp = this._beaconInfo?.timestamp > Date.now() ?
|
||||
this._beaconInfo?.timestamp - 360000 /* 6min */ :
|
||||
this._beaconInfo?.timestamp;
|
||||
if (!this.beaconInfo) return;
|
||||
const startTimestamp = this.beaconInfo.timestamp > Date.now() ?
|
||||
this.beaconInfo.timestamp - 360000 /* 6min */ :
|
||||
this.beaconInfo.timestamp;
|
||||
this._isLive = !!this._beaconInfo?.live &&
|
||||
isTimestampInDuration(startTimestamp, this._beaconInfo?.timeout, Date.now());
|
||||
|
||||
|
@ -822,7 +822,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
||||
// linkedlist to see which comes first.
|
||||
|
||||
// first work forwards from timeline1
|
||||
let tl = timeline1;
|
||||
let tl: EventTimeline | null = timeline1;
|
||||
while (tl) {
|
||||
if (tl === timeline2) {
|
||||
// timeline1 is before timeline2
|
||||
|
@ -77,7 +77,7 @@ export class EventTimeline {
|
||||
event.sender = stateContext.getSentinelMember(event.getSender());
|
||||
}
|
||||
if (!event.target?.events?.member && event.getType() === EventType.RoomMember) {
|
||||
event.target = stateContext.getSentinelMember(event.getStateKey());
|
||||
event.target = stateContext.getSentinelMember(event.getStateKey()!);
|
||||
}
|
||||
|
||||
if (event.isState()) {
|
||||
@ -97,8 +97,8 @@ export class EventTimeline {
|
||||
private baseIndex = 0;
|
||||
private startState: RoomState;
|
||||
private endState: RoomState;
|
||||
private prevTimeline?: EventTimeline;
|
||||
private nextTimeline?: EventTimeline;
|
||||
private prevTimeline: EventTimeline | null = null;
|
||||
private nextTimeline: EventTimeline | null = null;
|
||||
public paginationRequests: Record<Direction, Promise<boolean> | null> = {
|
||||
[Direction.Backward]: null,
|
||||
[Direction.Forward]: null,
|
||||
@ -131,9 +131,6 @@ export class EventTimeline {
|
||||
this.endState = new RoomState(this.roomId);
|
||||
this.endState.paginationToken = null;
|
||||
|
||||
this.prevTimeline = null;
|
||||
this.nextTimeline = null;
|
||||
|
||||
// this is used by client.js
|
||||
this.paginationRequests = { 'b': null, 'f': null };
|
||||
|
||||
@ -226,7 +223,7 @@ export class EventTimeline {
|
||||
* Get the ID of the room for this timeline
|
||||
* @return {string} room ID
|
||||
*/
|
||||
public getRoomId(): string {
|
||||
public getRoomId(): string | null {
|
||||
return this.roomId;
|
||||
}
|
||||
|
||||
@ -234,7 +231,7 @@ export class EventTimeline {
|
||||
* Get the filter for this timeline's timelineSet (if any)
|
||||
* @return {Filter} filter
|
||||
*/
|
||||
public getFilter(): Filter {
|
||||
public getFilter(): Filter | undefined {
|
||||
return this.eventTimelineSet.getFilter();
|
||||
}
|
||||
|
||||
@ -324,7 +321,7 @@ export class EventTimeline {
|
||||
* @return {?EventTimeline} previous or following timeline, if they have been
|
||||
* joined up.
|
||||
*/
|
||||
public getNeighbouringTimeline(direction: Direction): EventTimeline {
|
||||
public getNeighbouringTimeline(direction: Direction): EventTimeline | null {
|
||||
if (direction == EventTimeline.BACKWARDS) {
|
||||
return this.prevTimeline;
|
||||
} else if (direction == EventTimeline.FORWARDS) {
|
||||
@ -391,7 +388,7 @@ export class EventTimeline {
|
||||
roomState?: RoomState,
|
||||
): void {
|
||||
let toStartOfTimeline = !!toStartOfTimelineOrOpts;
|
||||
let timelineWasEmpty: boolean;
|
||||
let timelineWasEmpty: boolean | undefined;
|
||||
if (typeof (toStartOfTimelineOrOpts) === 'object') {
|
||||
({ toStartOfTimeline, roomState, timelineWasEmpty } = toStartOfTimelineOrOpts);
|
||||
} else if (toStartOfTimelineOrOpts !== undefined) {
|
||||
|
@ -34,6 +34,7 @@ import { TypedReEmitter } from '../ReEmitter';
|
||||
import { MatrixError } from "../http-api";
|
||||
import { TypedEventEmitter } from "./typed-event-emitter";
|
||||
import { EventStatus } from "./event-status";
|
||||
import { DecryptionError } from "../crypto/algorithms";
|
||||
|
||||
export { EventStatus } from "./event-status";
|
||||
|
||||
@ -685,14 +686,6 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
||||
* attempt is completed.
|
||||
*/
|
||||
public async attemptDecryption(crypto: Crypto, options: IDecryptOptions = {}): Promise<void> {
|
||||
// For backwards compatibility purposes
|
||||
// The function signature used to be attemptDecryption(crypto, isRetry)
|
||||
if (typeof options === "boolean") {
|
||||
options = {
|
||||
isRetry: options,
|
||||
};
|
||||
}
|
||||
|
||||
// start with a couple of sanity checks.
|
||||
if (!this.isEncrypted()) {
|
||||
throw new Error("Attempt to decrypt event which isn't encrypted");
|
||||
@ -822,15 +815,19 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
||||
//
|
||||
if (this.retryDecryption) {
|
||||
// decryption error, but we have a retry queued.
|
||||
logger.log(`Got error decrypting event (id=${this.getId()}: ${e.detailedString}), but retrying`, e);
|
||||
logger.log(`Got error decrypting event (id=${this.getId()}: ` +
|
||||
`${(<DecryptionError>e).detailedString}), but retrying`, e);
|
||||
continue;
|
||||
}
|
||||
|
||||
// decryption error, no retries queued. Warn about the error and
|
||||
// set it to m.bad.encrypted.
|
||||
logger.warn(`Got error decrypting event (id=${this.getId()}: ${e.detailedString})`, e);
|
||||
logger.warn(
|
||||
`Got error decrypting event (id=${this.getId()}: ${(<DecryptionError>e).detailedString})`,
|
||||
e,
|
||||
);
|
||||
|
||||
res = this.badEncryptedMessage(e.message);
|
||||
res = this.badEncryptedMessage((<DecryptionError>e).message);
|
||||
}
|
||||
|
||||
// at this point, we've either successfully decrypted the event, or have given up
|
||||
|
@ -118,31 +118,31 @@ export class RelationsContainer {
|
||||
const { event_id: relatesToEventId, rel_type: relationType } = relation;
|
||||
const eventType = event.getType();
|
||||
|
||||
let relationsForEvent = this.relations.get(relatesToEventId);
|
||||
let relationsForEvent = this.relations.get(relatesToEventId!);
|
||||
if (!relationsForEvent) {
|
||||
relationsForEvent = new Map<RelationType | string, Map<EventType | string, Relations>>();
|
||||
this.relations.set(relatesToEventId, relationsForEvent);
|
||||
this.relations.set(relatesToEventId!, relationsForEvent);
|
||||
}
|
||||
|
||||
let relationsWithRelType = relationsForEvent.get(relationType);
|
||||
let relationsWithRelType = relationsForEvent.get(relationType!);
|
||||
if (!relationsWithRelType) {
|
||||
relationsWithRelType = new Map<EventType | string, Relations>();
|
||||
relationsForEvent.set(relationType, relationsWithRelType);
|
||||
relationsForEvent.set(relationType!, relationsWithRelType);
|
||||
}
|
||||
|
||||
let relationsWithEventType = relationsWithRelType.get(eventType);
|
||||
if (!relationsWithEventType) {
|
||||
relationsWithEventType = new Relations(
|
||||
relationType,
|
||||
relationType!,
|
||||
eventType,
|
||||
this.client,
|
||||
);
|
||||
relationsWithRelType.set(eventType, relationsWithEventType);
|
||||
|
||||
const room = this.room ?? timelineSet?.room;
|
||||
const relatesToEvent = timelineSet?.findEventById(relatesToEventId)
|
||||
?? room?.findEventById(relatesToEventId)
|
||||
?? room?.getPendingEvent(relatesToEventId);
|
||||
const relatesToEvent = timelineSet?.findEventById(relatesToEventId!)
|
||||
?? room?.findEventById(relatesToEventId!)
|
||||
?? room?.getPendingEvent(relatesToEventId!);
|
||||
if (relatesToEvent) {
|
||||
relationsWithEventType.setTargetEvent(relatesToEvent);
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
||||
private annotationsByKey: Record<string, Set<MatrixEvent>> = {};
|
||||
private annotationsBySender: Record<string, Set<MatrixEvent>> = {};
|
||||
private sortedAnnotationsByKey: [string, Set<MatrixEvent>][] = [];
|
||||
private targetEvent: MatrixEvent = null;
|
||||
private targetEvent: MatrixEvent | null = null;
|
||||
private creationEmitted = false;
|
||||
private readonly client: MatrixClient;
|
||||
|
||||
@ -107,7 +107,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
||||
this.addAnnotationToAggregation(event);
|
||||
} else if (this.relationType === RelationType.Replace && this.targetEvent && !this.targetEvent.isState()) {
|
||||
const lastReplacement = await this.getLastReplacement();
|
||||
this.targetEvent.makeReplaced(lastReplacement);
|
||||
this.targetEvent.makeReplaced(lastReplacement!);
|
||||
}
|
||||
|
||||
event.on(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
|
||||
@ -148,7 +148,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
||||
this.removeAnnotationFromAggregation(event);
|
||||
} else if (this.relationType === RelationType.Replace && this.targetEvent && !this.targetEvent.isState()) {
|
||||
const lastReplacement = await this.getLastReplacement();
|
||||
this.targetEvent.makeReplaced(lastReplacement);
|
||||
this.targetEvent.makeReplaced(lastReplacement!);
|
||||
}
|
||||
|
||||
this.emit(RelationsEvent.Remove, event);
|
||||
@ -189,10 +189,8 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
||||
}
|
||||
|
||||
private addAnnotationToAggregation(event: MatrixEvent): void {
|
||||
const { key } = event.getRelation();
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
const { key } = event.getRelation() ?? {};
|
||||
if (!key) return;
|
||||
|
||||
let eventsForKey = this.annotationsByKey[key];
|
||||
if (!eventsForKey) {
|
||||
@ -218,10 +216,8 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
||||
}
|
||||
|
||||
private removeAnnotationFromAggregation(event: MatrixEvent): void {
|
||||
const { key } = event.getRelation();
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
const { key } = event.getRelation() ?? {};
|
||||
if (!key) return;
|
||||
|
||||
const eventsForKey = this.annotationsByKey[key];
|
||||
if (eventsForKey) {
|
||||
@ -265,7 +261,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
||||
this.removeAnnotationFromAggregation(redactedEvent);
|
||||
} else if (this.relationType === RelationType.Replace && this.targetEvent && !this.targetEvent.isState()) {
|
||||
const lastReplacement = await this.getLastReplacement();
|
||||
this.targetEvent.makeReplaced(lastReplacement);
|
||||
this.targetEvent.makeReplaced(lastReplacement!);
|
||||
}
|
||||
|
||||
redactedEvent.removeListener(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
|
||||
@ -283,7 +279,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
||||
* An array of [key, events] pairs sorted by descending event count.
|
||||
* The events are stored in a Set (which preserves insertion order).
|
||||
*/
|
||||
public getSortedAnnotationsByKey() {
|
||||
public getSortedAnnotationsByKey(): [string, Set<MatrixEvent>][] | null {
|
||||
if (this.relationType !== RelationType.Annotation) {
|
||||
// Other relation types are not grouped currently.
|
||||
return null;
|
||||
@ -301,7 +297,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
||||
* An object with each relation sender as a key and the matching Set of
|
||||
* events for that sender as a value.
|
||||
*/
|
||||
public getAnnotationsBySender() {
|
||||
public getAnnotationsBySender(): Record<string, Set<MatrixEvent>> | null {
|
||||
if (this.relationType !== RelationType.Annotation) {
|
||||
// Other relation types are not grouped currently.
|
||||
return null;
|
||||
@ -335,8 +331,8 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
||||
const replaceRelation = this.targetEvent.getServerAggregatedRelation<IAggregatedRelation>(RelationType.Replace);
|
||||
const minTs = replaceRelation?.origin_server_ts;
|
||||
|
||||
const lastReplacement = this.getRelations().reduce((last, event) => {
|
||||
if (event.getSender() !== this.targetEvent.getSender()) {
|
||||
const lastReplacement = this.getRelations().reduce<MatrixEvent | null>((last, event) => {
|
||||
if (event.getSender() !== this.targetEvent!.getSender()) {
|
||||
return last;
|
||||
}
|
||||
if (minTs && minTs > event.getTs()) {
|
||||
@ -348,8 +344,8 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
||||
return event;
|
||||
}, null);
|
||||
|
||||
if (lastReplacement?.shouldAttemptDecryption()) {
|
||||
await lastReplacement.attemptDecryption(this.client.crypto);
|
||||
if (lastReplacement?.shouldAttemptDecryption() && this.client.isCryptoEnabled()) {
|
||||
await lastReplacement.attemptDecryption(this.client.crypto!);
|
||||
} else if (lastReplacement?.isBeingDecrypted()) {
|
||||
await lastReplacement.getDecryptionPromise();
|
||||
}
|
||||
|
@ -1604,7 +1604,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
// find the earliest unfiltered timeline
|
||||
let timeline = unfilteredLiveTimeline;
|
||||
while (timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS)) {
|
||||
timeline = timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS);
|
||||
timeline = timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS)!;
|
||||
}
|
||||
|
||||
timelineSet.getLiveTimeline().setPaginationToken(
|
||||
|
@ -83,7 +83,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
|
||||
private reEmitter: TypedReEmitter<EmittedEvents, EventHandlerMap>;
|
||||
|
||||
private lastEvent: MatrixEvent;
|
||||
private lastEvent!: MatrixEvent;
|
||||
private replyCount = 0;
|
||||
|
||||
public readonly room: Room;
|
||||
@ -185,7 +185,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
this.lastEvent = events.find(e => (
|
||||
!e.isRedacted() &&
|
||||
e.isRelation(THREAD_RELATION_TYPE.name)
|
||||
)) ?? this.rootEvent;
|
||||
)) ?? this.rootEvent!;
|
||||
this.emit(ThreadEvent.Update, this);
|
||||
};
|
||||
|
||||
@ -267,7 +267,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
this.client.decryptEventIfNeeded(event, {});
|
||||
} else if (!toStartOfTimeline &&
|
||||
this.initialEventsFetched &&
|
||||
event.localTimestamp > this.lastReply()?.localTimestamp
|
||||
event.localTimestamp > this.lastReply()!.localTimestamp
|
||||
) {
|
||||
this.fetchEditsWhereNeeded(event);
|
||||
this.addEventToTimeline(event, false);
|
||||
@ -289,7 +289,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
}
|
||||
}
|
||||
|
||||
private getRootEventBundledRelationship(rootEvent = this.rootEvent): IThreadBundledRelationship {
|
||||
private getRootEventBundledRelationship(rootEvent = this.rootEvent): IThreadBundledRelationship | undefined {
|
||||
return rootEvent?.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
|
||||
}
|
||||
|
||||
@ -302,7 +302,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
|
||||
if (Thread.hasServerSideSupport && bundledRelationship) {
|
||||
this.replyCount = bundledRelationship.count;
|
||||
this._currentUserParticipated = bundledRelationship.current_user_participated;
|
||||
this._currentUserParticipated = !!bundledRelationship.current_user_participated;
|
||||
|
||||
const event = new MatrixEvent({
|
||||
room_id: this.rootEvent.getRoomId(),
|
||||
@ -407,7 +407,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
}
|
||||
|
||||
public async fetchEvents(opts: IRelationsRequestOpts = { limit: 20, dir: Direction.Backward }): Promise<{
|
||||
originalEvent: MatrixEvent;
|
||||
originalEvent?: MatrixEvent;
|
||||
events: MatrixEvent[];
|
||||
nextBatch?: string | null;
|
||||
prevBatch?: string;
|
||||
@ -427,7 +427,7 @@ export class Thread extends ReadReceipt<EmittedEvents, EventHandlerMap> {
|
||||
|
||||
// When there's no nextBatch returned with a `from` request we have reached
|
||||
// the end of the thread, and therefore want to return an empty one
|
||||
if (!opts.to && !nextBatch) {
|
||||
if (!opts.to && !nextBatch && originalEvent) {
|
||||
events = [...events, originalEvent];
|
||||
}
|
||||
|
||||
|
@ -184,7 +184,7 @@ export class PushProcessor {
|
||||
|
||||
private static cachedGlobToRegex: Record<string, RegExp> = {}; // $glob: RegExp
|
||||
|
||||
private matchingRuleFromKindSet(ev: MatrixEvent, kindset: PushRuleSet): IAnnotatedPushRule {
|
||||
private matchingRuleFromKindSet(ev: MatrixEvent, kindset: PushRuleSet): IAnnotatedPushRule | null {
|
||||
for (let ruleKindIndex = 0; ruleKindIndex < RULEKINDS_IN_ORDER.length; ++ruleKindIndex) {
|
||||
const kind = RULEKINDS_IN_ORDER[ruleKindIndex];
|
||||
const ruleset = kindset[kind];
|
||||
@ -338,19 +338,19 @@ export class PushProcessor {
|
||||
private eventFulfillsDisplayNameCondition(cond: IContainsDisplayNameCondition, ev: MatrixEvent): boolean {
|
||||
let content = ev.getContent();
|
||||
if (ev.isEncrypted() && ev.getClearContent()) {
|
||||
content = ev.getClearContent();
|
||||
content = ev.getClearContent()!;
|
||||
}
|
||||
if (!content || !content.body || typeof content.body != 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const room = this.client.getRoom(ev.getRoomId());
|
||||
if (!room || !room.currentState || !room.currentState.members ||
|
||||
!room.currentState.getMember(this.client.credentials.userId)) {
|
||||
const member = room?.currentState?.getMember(this.client.credentials.userId!);
|
||||
if (!member) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const displayName = room.currentState.getMember(this.client.credentials.userId).name;
|
||||
const displayName = member.name;
|
||||
|
||||
// N.B. we can't use \b as it chokes on unicode. however \W seems to be okay
|
||||
// as shorthand for [^0-9A-Za-z_].
|
||||
@ -396,7 +396,7 @@ export class PushProcessor {
|
||||
|
||||
private valueForDottedKey(key: string, ev: MatrixEvent): any {
|
||||
const parts = key.split('.');
|
||||
let val;
|
||||
let val: any;
|
||||
|
||||
// special-case the first component to deal with encrypted messages
|
||||
const firstPart = parts[0];
|
||||
@ -412,7 +412,7 @@ export class PushProcessor {
|
||||
}
|
||||
|
||||
while (parts.length > 0) {
|
||||
const thisPart = parts.shift();
|
||||
const thisPart = parts.shift()!;
|
||||
if (isNullOrUndefined(val[thisPart])) {
|
||||
return null;
|
||||
}
|
||||
@ -421,7 +421,7 @@ export class PushProcessor {
|
||||
return val;
|
||||
}
|
||||
|
||||
private matchingRuleForEventWithRulesets(ev: MatrixEvent, rulesets): IAnnotatedPushRule {
|
||||
private matchingRuleForEventWithRulesets(ev: MatrixEvent, rulesets?: IPushRules): IAnnotatedPushRule | null {
|
||||
if (!rulesets) {
|
||||
return null;
|
||||
}
|
||||
@ -432,7 +432,7 @@ export class PushProcessor {
|
||||
return this.matchingRuleFromKindSet(ev, rulesets.global);
|
||||
}
|
||||
|
||||
private pushActionsForEventAndRulesets(ev: MatrixEvent, rulesets): IActionsObject {
|
||||
private pushActionsForEventAndRulesets(ev: MatrixEvent, rulesets?: IPushRules): IActionsObject {
|
||||
const rule = this.matchingRuleForEventWithRulesets(ev, rulesets);
|
||||
if (!rule) {
|
||||
return {} as IActionsObject;
|
||||
@ -504,9 +504,9 @@ export class PushProcessor {
|
||||
* @param {string} ruleId The ID of the rule to search for
|
||||
* @return {object} The push rule, or null if no such rule was found
|
||||
*/
|
||||
public getPushRuleById(ruleId: string): IPushRule {
|
||||
public getPushRuleById(ruleId: string): IPushRule | null {
|
||||
for (const scope of ['global']) {
|
||||
if (this.client.pushRules[scope] === undefined) continue;
|
||||
if (this.client.pushRules?.[scope] === undefined) continue;
|
||||
|
||||
for (const kind of RULEKINDS_IN_ORDER) {
|
||||
if (this.client.pushRules[scope][kind] === undefined) continue;
|
||||
|
@ -36,14 +36,16 @@ let count = 0;
|
||||
// the key for our callback with the real global.setTimeout
|
||||
let realCallbackKey: NodeJS.Timeout | number;
|
||||
|
||||
// a sorted list of the callbacks to be run.
|
||||
// each is an object with keys [runAt, func, params, key].
|
||||
const callbackList: {
|
||||
type Callback = {
|
||||
runAt: number;
|
||||
func: (...params: any[]) => void;
|
||||
params: any[];
|
||||
key: number;
|
||||
}[] = [];
|
||||
};
|
||||
|
||||
// a sorted list of the callbacks to be run.
|
||||
// each is an object with keys [runAt, func, params, key].
|
||||
const callbackList: Callback[] = [];
|
||||
|
||||
// var debuglog = logger.log.bind(logger);
|
||||
const debuglog = function(...params: any[]) {};
|
||||
@ -135,19 +137,19 @@ function scheduleRealCallback(): void {
|
||||
}
|
||||
|
||||
function runCallbacks(): void {
|
||||
let cb;
|
||||
let cb: Callback;
|
||||
const timestamp = Date.now();
|
||||
debuglog("runCallbacks: now:", timestamp);
|
||||
|
||||
// get the list of things to call
|
||||
const callbacksToRun = [];
|
||||
const callbacksToRun: Callback[] = [];
|
||||
// eslint-disable-next-line
|
||||
while (true) {
|
||||
const first = callbackList[0];
|
||||
if (!first || first.runAt > timestamp) {
|
||||
break;
|
||||
}
|
||||
cb = callbackList.shift();
|
||||
cb = callbackList.shift()!;
|
||||
debuglog("runCallbacks: popping", cb.key);
|
||||
callbacksToRun.push(cb);
|
||||
}
|
||||
@ -162,8 +164,7 @@ function runCallbacks(): void {
|
||||
try {
|
||||
cb.func.apply(global, cb.params);
|
||||
} catch (e) {
|
||||
logger.error("Uncaught exception in callback function",
|
||||
e.stack || e);
|
||||
logger.error("Uncaught exception in callback function", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -203,13 +203,13 @@ export class MSC3906Rendezvous {
|
||||
true, false, true,
|
||||
);
|
||||
|
||||
const masterPublicKey = this.client.crypto.crossSigningInfo.getId('master');
|
||||
const masterPublicKey = this.client.crypto.crossSigningInfo.getId('master')!;
|
||||
|
||||
await this.send({
|
||||
type: PayloadType.Finish,
|
||||
outcome: Outcome.Verified,
|
||||
verifying_device_id: this.client.getDeviceId(),
|
||||
verifying_device_key: this.client.getDeviceEd25519Key(),
|
||||
verifying_device_key: this.client.getDeviceEd25519Key()!,
|
||||
master_key: masterPublicKey,
|
||||
});
|
||||
|
||||
|
@ -22,6 +22,7 @@ import { Room } from "./models/room";
|
||||
import { IHierarchyRoom, IHierarchyRelation } from "./@types/spaces";
|
||||
import { MatrixClient } from "./client";
|
||||
import { EventType } from "./@types/event";
|
||||
import { MatrixError } from "./http-api";
|
||||
|
||||
export class RoomHierarchy {
|
||||
// Map from room id to list of servers which are listed as a via somewhere in the loaded hierarchy
|
||||
@ -30,7 +31,7 @@ export class RoomHierarchy {
|
||||
public readonly backRefs = new Map<string, string[]>();
|
||||
// Map from room id to object
|
||||
public readonly roomMap = new Map<string, IHierarchyRoom>();
|
||||
private loadRequest: ReturnType<MatrixClient["getRoomHierarchy"]>;
|
||||
private loadRequest?: ReturnType<MatrixClient["getRoomHierarchy"]>;
|
||||
private nextBatch?: string;
|
||||
private _rooms?: IHierarchyRoom[];
|
||||
private serverSupportError?: Error;
|
||||
@ -65,7 +66,7 @@ export class RoomHierarchy {
|
||||
return !!this.loadRequest;
|
||||
}
|
||||
|
||||
public get rooms(): IHierarchyRoom[] {
|
||||
public get rooms(): IHierarchyRoom[] | undefined {
|
||||
return this._rooms;
|
||||
}
|
||||
|
||||
@ -84,15 +85,15 @@ export class RoomHierarchy {
|
||||
try {
|
||||
({ rooms, next_batch: this.nextBatch } = await this.loadRequest);
|
||||
} catch (e) {
|
||||
if (e.errcode === "M_UNRECOGNIZED") {
|
||||
this.serverSupportError = e;
|
||||
if ((<MatrixError>e).errcode === "M_UNRECOGNIZED") {
|
||||
this.serverSupportError = <MatrixError>e;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
|
||||
return [];
|
||||
} finally {
|
||||
this.loadRequest = null;
|
||||
this.loadRequest = undefined;
|
||||
}
|
||||
|
||||
if (this._rooms) {
|
||||
@ -112,14 +113,14 @@ export class RoomHierarchy {
|
||||
if (!this.backRefs.has(childRoomId)) {
|
||||
this.backRefs.set(childRoomId, []);
|
||||
}
|
||||
this.backRefs.get(childRoomId).push(room.room_id);
|
||||
this.backRefs.get(childRoomId)!.push(room.room_id);
|
||||
|
||||
// fill viaMap
|
||||
if (Array.isArray(ev.content.via)) {
|
||||
if (!this.viaMap.has(childRoomId)) {
|
||||
this.viaMap.set(childRoomId, new Set());
|
||||
}
|
||||
const vias = this.viaMap.get(childRoomId);
|
||||
const vias = this.viaMap.get(childRoomId)!;
|
||||
ev.content.via.forEach(via => vias.add(via));
|
||||
}
|
||||
});
|
||||
@ -128,11 +129,11 @@ export class RoomHierarchy {
|
||||
return rooms;
|
||||
}
|
||||
|
||||
public getRelation(parentId: string, childId: string): IHierarchyRelation {
|
||||
public getRelation(parentId: string, childId: string): IHierarchyRelation | undefined {
|
||||
return this.roomMap.get(parentId)?.children_state.find(e => e.state_key === childId);
|
||||
}
|
||||
|
||||
public isSuggested(parentId: string, childId: string): boolean {
|
||||
public isSuggested(parentId: string, childId: string): boolean | undefined {
|
||||
return this.getRelation(parentId, childId)?.content.suggested;
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
|
||||
* @see module:scheduler~retryAlgorithm
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
public static RETRY_BACKOFF_RATELIMIT(event: MatrixEvent, attempts: number, err: MatrixError): number {
|
||||
public static RETRY_BACKOFF_RATELIMIT(event: MatrixEvent | null, attempts: number, err: MatrixError): number {
|
||||
if (err.httpStatus === 400 || err.httpStatus === 403 || err.httpStatus === 401) {
|
||||
// client error; no amount of retrying with save you now.
|
||||
return -1;
|
||||
@ -114,7 +114,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
|
||||
// }, ...]
|
||||
private readonly queues: Record<string, IQueueEntry<T>[]> = {};
|
||||
private activeQueues: string[] = [];
|
||||
private procFn: ProcessFunction<T> = null;
|
||||
private procFn: ProcessFunction<T> | null = null;
|
||||
|
||||
constructor(
|
||||
public readonly retryAlgorithm = MatrixScheduler.RETRY_BACKOFF_RATELIMIT,
|
||||
@ -130,7 +130,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
|
||||
* this array <i>will</i> modify the underlying event in the queue.
|
||||
* @see MatrixScheduler.removeEventFromQueue To remove an event from the queue.
|
||||
*/
|
||||
public getQueueForEvent(event: MatrixEvent): MatrixEvent[] {
|
||||
public getQueueForEvent(event: MatrixEvent): MatrixEvent[] | null {
|
||||
const name = this.queueAlgorithm(event);
|
||||
if (!name || !this.queues[name]) {
|
||||
return null;
|
||||
@ -159,6 +159,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
|
||||
removed = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return removed;
|
||||
}
|
||||
@ -239,7 +240,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
|
||||
// This way enqueued relations/redactions to enqueued events can receive
|
||||
// the remove id of their target before being sent.
|
||||
Promise.resolve().then(() => {
|
||||
return this.procFn(obj.event);
|
||||
return this.procFn!(obj.event);
|
||||
}).then((res) => {
|
||||
// remove this from the queue
|
||||
this.removeNextEvent(queueName);
|
||||
@ -265,18 +266,18 @@ export class MatrixScheduler<T = ISendEventResponse> {
|
||||
});
|
||||
};
|
||||
|
||||
private peekNextEvent(queueName: string): IQueueEntry<T> {
|
||||
private peekNextEvent(queueName: string): IQueueEntry<T> | undefined {
|
||||
const queue = this.queues[queueName];
|
||||
if (!Array.isArray(queue)) {
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
return queue[0];
|
||||
}
|
||||
|
||||
private removeNextEvent(queueName: string): IQueueEntry<T> {
|
||||
private removeNextEvent(queueName: string): IQueueEntry<T> | undefined {
|
||||
const queue = this.queues[queueName];
|
||||
if (!Array.isArray(queue)) {
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
return queue.shift();
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ class ExtensionE2EE implements Extension {
|
||||
return ExtensionState.PreProcess;
|
||||
}
|
||||
|
||||
public onRequest(isInitial: boolean): object {
|
||||
public onRequest(isInitial: boolean): object | undefined {
|
||||
if (!isInitial) {
|
||||
return undefined;
|
||||
}
|
||||
@ -90,7 +90,7 @@ class ExtensionE2EE implements Extension {
|
||||
}
|
||||
|
||||
class ExtensionToDevice implements Extension {
|
||||
private nextBatch?: string = null;
|
||||
private nextBatch: string | null = null;
|
||||
|
||||
constructor(private readonly client: MatrixClient) {}
|
||||
|
||||
@ -114,7 +114,7 @@ class ExtensionToDevice implements Extension {
|
||||
}
|
||||
|
||||
public async onResponse(data: object): Promise<void> {
|
||||
const cancelledKeyVerificationTxns = [];
|
||||
const cancelledKeyVerificationTxns: string[] = [];
|
||||
data["events"] = data["events"] || [];
|
||||
data["events"]
|
||||
.map(this.client.getEventMapper())
|
||||
@ -125,7 +125,7 @@ class ExtensionToDevice implements Extension {
|
||||
// so we can flag the verification events as cancelled in the loop
|
||||
// below.
|
||||
if (toDeviceEvent.getType() === "m.key.verification.cancel") {
|
||||
const txnId = toDeviceEvent.getContent()['transaction_id'];
|
||||
const txnId: string | undefined = toDeviceEvent.getContent()['transaction_id'];
|
||||
if (txnId) {
|
||||
cancelledKeyVerificationTxns.push(txnId);
|
||||
}
|
||||
@ -177,7 +177,7 @@ class ExtensionAccountData implements Extension {
|
||||
return ExtensionState.PostProcess;
|
||||
}
|
||||
|
||||
public onRequest(isInitial: boolean): object {
|
||||
public onRequest(isInitial: boolean): object | undefined {
|
||||
if (!isInitial) {
|
||||
return undefined;
|
||||
}
|
||||
@ -235,9 +235,9 @@ class ExtensionAccountData implements Extension {
|
||||
* sliding sync API, see sliding-sync.ts or the class SlidingSync.
|
||||
*/
|
||||
export class SlidingSyncSdk {
|
||||
private syncState: SyncState = null;
|
||||
private syncStateData: ISyncStateData;
|
||||
private lastPos: string = null;
|
||||
private syncState: SyncState | null = null;
|
||||
private syncStateData?: ISyncStateData;
|
||||
private lastPos: string | null = null;
|
||||
private failCount = 0;
|
||||
private notifEvents: MatrixEvent[] = []; // accumulator of sync events in the current sync response
|
||||
|
||||
@ -259,7 +259,7 @@ export class SlidingSyncSdk {
|
||||
}
|
||||
|
||||
if (client.getNotifTimelineSet()) {
|
||||
client.reEmitter.reEmit(client.getNotifTimelineSet(), [
|
||||
client.reEmitter.reEmit(client.getNotifTimelineSet()!, [
|
||||
RoomEvent.Timeline,
|
||||
RoomEvent.TimelineReset,
|
||||
]);
|
||||
@ -293,7 +293,7 @@ export class SlidingSyncSdk {
|
||||
this.processRoomData(this.client, room, roomData);
|
||||
}
|
||||
|
||||
private onLifecycle(state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, err: Error | null): void {
|
||||
private onLifecycle(state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, err?: Error): void {
|
||||
if (err) {
|
||||
logger.debug("onLifecycle", state, err);
|
||||
}
|
||||
@ -306,7 +306,7 @@ export class SlidingSyncSdk {
|
||||
// Element won't stop showing the initial loading spinner unless we fire SyncState.Prepared
|
||||
if (!this.lastPos) {
|
||||
this.updateSyncState(SyncState.Prepared, {
|
||||
oldSyncToken: this.lastPos,
|
||||
oldSyncToken: undefined,
|
||||
nextSyncToken: resp.pos,
|
||||
catchingUp: false,
|
||||
fromCache: false,
|
||||
@ -315,7 +315,7 @@ export class SlidingSyncSdk {
|
||||
// Conversely, Element won't show the room list unless there is at least 1x SyncState.Syncing
|
||||
// so hence for the very first sync we will fire prepared then immediately syncing.
|
||||
this.updateSyncState(SyncState.Syncing, {
|
||||
oldSyncToken: this.lastPos,
|
||||
oldSyncToken: this.lastPos!,
|
||||
nextSyncToken: resp.pos,
|
||||
catchingUp: false,
|
||||
fromCache: false,
|
||||
@ -357,7 +357,7 @@ export class SlidingSyncSdk {
|
||||
* store.
|
||||
*/
|
||||
public async peek(_roomId: string): Promise<Room> {
|
||||
return null; // TODO
|
||||
return null!; // TODO
|
||||
}
|
||||
|
||||
/**
|
||||
@ -373,7 +373,7 @@ export class SlidingSyncSdk {
|
||||
* @see module:client~MatrixClient#event:"sync"
|
||||
* @return {?String}
|
||||
*/
|
||||
public getSyncState(): SyncState {
|
||||
public getSyncState(): SyncState | null {
|
||||
return this.syncState;
|
||||
}
|
||||
|
||||
@ -385,8 +385,8 @@ export class SlidingSyncSdk {
|
||||
* this object.
|
||||
* @return {?Object}
|
||||
*/
|
||||
public getSyncStateData(): ISyncStateData {
|
||||
return this.syncStateData;
|
||||
public getSyncStateData(): ISyncStateData | null {
|
||||
return this.syncStateData ?? null;
|
||||
}
|
||||
|
||||
private shouldAbortSync(error: MatrixError): boolean {
|
||||
@ -500,8 +500,7 @@ export class SlidingSyncSdk {
|
||||
if (roomData.initial) {
|
||||
// set the back-pagination token. Do this *before* adding any
|
||||
// events so that clients can start back-paginating.
|
||||
room.getLiveTimeline().setPaginationToken(
|
||||
roomData.prev_batch, EventTimeline.BACKWARDS);
|
||||
room.getLiveTimeline().setPaginationToken(roomData.prev_batch ?? null, EventTimeline.BACKWARDS);
|
||||
}
|
||||
|
||||
/* TODO
|
||||
@ -687,7 +686,7 @@ export class SlidingSyncSdk {
|
||||
// slightly naughty by doctoring the invite event but this means all
|
||||
// the code paths remain the same between invite/join display name stuff
|
||||
// which is a worthy trade-off for some minor pollution.
|
||||
const inviteEvent = member.events.member;
|
||||
const inviteEvent = member.events.member!;
|
||||
if (inviteEvent.getContent().membership !== "invite") {
|
||||
// between resolving and now they have since joined, so don't clobber
|
||||
return;
|
||||
@ -723,7 +722,7 @@ export class SlidingSyncSdk {
|
||||
break;
|
||||
} catch (err) {
|
||||
logger.error("Getting push rules failed", err);
|
||||
if (this.shouldAbortSync(err)) {
|
||||
if (this.shouldAbortSync(<MatrixError>err)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -787,7 +786,7 @@ export class SlidingSyncSdk {
|
||||
return a.getTs() - b.getTs();
|
||||
});
|
||||
this.notifEvents.forEach((event) => {
|
||||
this.client.getNotifTimelineSet().addLiveEvent(event);
|
||||
this.client.getNotifTimelineSet()?.addLiveEvent(event);
|
||||
});
|
||||
this.notifEvents = [];
|
||||
}
|
||||
@ -815,7 +814,7 @@ function ensureNameEvent(client: MatrixClient, roomId: string, roomData: MSC3575
|
||||
content: {
|
||||
name: roomData.name,
|
||||
},
|
||||
sender: client.getUserId(),
|
||||
sender: client.getUserId()!,
|
||||
origin_server_ts: new Date().getTime(),
|
||||
});
|
||||
return roomData;
|
||||
@ -824,7 +823,7 @@ function ensureNameEvent(client: MatrixClient, roomId: string, roomData: MSC3575
|
||||
// Helper functions which set up JS SDK structs are below and are identical to the sync v2 counterparts,
|
||||
// just outside the class.
|
||||
|
||||
function mapEvents(client: MatrixClient, roomId: string, events: object[], decrypt = true): MatrixEvent[] {
|
||||
function mapEvents(client: MatrixClient, roomId: string | undefined, events: object[], decrypt = true): MatrixEvent[] {
|
||||
const mapper = client.getEventMapper({ decrypt });
|
||||
return (events as Array<IStrippedState | IRoomEvent | IStateEvent | IMinimalEvent>).map(function(e) {
|
||||
e["room_id"] = roomId;
|
||||
|
@ -19,6 +19,7 @@ import { MatrixClient } from "./client";
|
||||
import { IRoomEvent, IStateEvent } from "./sync-accumulator";
|
||||
import { TypedEventEmitter } from "./models/typed-event-emitter";
|
||||
import { sleep, IDeferred, defer } from "./utils";
|
||||
import { HTTPError } from "./http-api";
|
||||
|
||||
// /sync requests allow you to set a timeout= but the request may continue
|
||||
// beyond that and wedge forever, so we need to track how long we are willing
|
||||
@ -153,12 +154,12 @@ export enum SlidingSyncState {
|
||||
* multiple sliding windows, and maintains the index->room_id mapping.
|
||||
*/
|
||||
class SlidingList {
|
||||
private list: MSC3575List;
|
||||
private isModified: boolean;
|
||||
private list!: MSC3575List;
|
||||
private isModified?: boolean;
|
||||
|
||||
// returned data
|
||||
public roomIndexToRoomId: Record<number, string>;
|
||||
public joinedCount: number;
|
||||
public roomIndexToRoomId: Record<number, string> = {};
|
||||
public joinedCount = 0;
|
||||
|
||||
/**
|
||||
* Construct a new sliding list.
|
||||
@ -271,7 +272,7 @@ export interface Extension {
|
||||
* @param isInitial True when this is part of the initial request (send sticky params)
|
||||
* @returns The request JSON to send.
|
||||
*/
|
||||
onRequest(isInitial: boolean): object;
|
||||
onRequest(isInitial: boolean): object | undefined;
|
||||
/**
|
||||
* A function which is called when there is response JSON under this extension.
|
||||
* @param data The response JSON under the extension name.
|
||||
@ -322,7 +323,7 @@ export enum SlidingSyncEvent {
|
||||
export type SlidingSyncEventHandlerMap = {
|
||||
[SlidingSyncEvent.RoomData]: (roomId: string, roomData: MSC3575RoomData) => void;
|
||||
[SlidingSyncEvent.Lifecycle]: (
|
||||
state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, err: Error | null,
|
||||
state: SlidingSyncState, resp: MSC3575SlidingSyncResponse | null, err?: Error,
|
||||
) => void;
|
||||
[SlidingSyncEvent.List]: (
|
||||
listIndex: number, joinedCount: number, roomIndexToRoomId: Record<number, string>,
|
||||
@ -342,7 +343,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
// flag set when resend() is called because we cannot rely on detecting AbortError in JS SDK :(
|
||||
private needsResend = false;
|
||||
// the txn_id to send with the next request.
|
||||
private txnId?: string = null;
|
||||
private txnId: string | null = null;
|
||||
// a list (in chronological order of when they were sent) of objects containing the txn ID and
|
||||
// a defer to resolve/reject depending on whether they were successfully sent or not.
|
||||
private txnIdDefers: (IDeferred<string> & { txnId: string})[] = [];
|
||||
@ -387,7 +388,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
* @param index The list index
|
||||
* @returns The list data which contains the rooms in this list
|
||||
*/
|
||||
public getListData(index: number): {joinedCount: number, roomIndexToRoomId: Record<number, string>} {
|
||||
public getListData(index: number): {joinedCount: number, roomIndexToRoomId: Record<number, string>} | null {
|
||||
if (!this.lists[index]) {
|
||||
return null;
|
||||
}
|
||||
@ -403,7 +404,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
* @param index The list index to get the list for.
|
||||
* @returns A copy of the list or undefined.
|
||||
*/
|
||||
public getList(index: number): MSC3575List {
|
||||
public getList(index: number): MSC3575List | null {
|
||||
if (!this.lists[index]) {
|
||||
return null;
|
||||
}
|
||||
@ -532,7 +533,11 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
* @param {object} resp The raw sync response JSON
|
||||
* @param {Error?} err Any error that occurred when making the request e.g. network errors.
|
||||
*/
|
||||
private invokeLifecycleListeners(state: SlidingSyncState, resp: MSC3575SlidingSyncResponse, err?: Error): void {
|
||||
private invokeLifecycleListeners(
|
||||
state: SlidingSyncState,
|
||||
resp: MSC3575SlidingSyncResponse | null,
|
||||
err?: Error,
|
||||
): void {
|
||||
this.emit(SlidingSyncEvent.Lifecycle, state, resp, err);
|
||||
}
|
||||
|
||||
@ -772,11 +777,11 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
public async start() {
|
||||
this.abortController = new AbortController();
|
||||
|
||||
let currentPos: string;
|
||||
let currentPos: string | undefined;
|
||||
while (!this.terminated) {
|
||||
this.needsResend = false;
|
||||
let doNotUpdateList = false;
|
||||
let resp: MSC3575SlidingSyncResponse;
|
||||
let resp: MSC3575SlidingSyncResponse | undefined;
|
||||
try {
|
||||
const listModifiedCount = this.listModifiedCount;
|
||||
const reqBody: MSC3575SlidingSyncRequest = {
|
||||
@ -837,13 +842,13 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
resp,
|
||||
);
|
||||
} catch (err) {
|
||||
if (err.httpStatus) {
|
||||
if ((<HTTPError>err).httpStatus) {
|
||||
this.invokeLifecycleListeners(
|
||||
SlidingSyncState.RequestFinished,
|
||||
null,
|
||||
err,
|
||||
<Error>err,
|
||||
);
|
||||
if (err.httpStatus === 400) {
|
||||
if ((<HTTPError>err).httpStatus === 400) {
|
||||
// session probably expired TODO: assign an errcode
|
||||
// so drop state and re-request
|
||||
this.resetup();
|
||||
@ -851,7 +856,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
await sleep(50); // in case the 400 was for something else; don't tightloop
|
||||
continue;
|
||||
} // else fallthrough to generic error handling
|
||||
} else if (this.needsResend || err.name === "AbortError") {
|
||||
} else if (this.needsResend || (<Error>err).name === "AbortError") {
|
||||
continue; // don't sleep as we caused this error by abort()ing the request.
|
||||
}
|
||||
logger.error(err);
|
||||
@ -865,7 +870,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
Object.keys(resp.rooms).forEach((roomId) => {
|
||||
this.invokeRoomDataListeners(
|
||||
roomId,
|
||||
resp.rooms[roomId],
|
||||
resp!.rooms[roomId],
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -150,7 +150,7 @@ export interface IStore {
|
||||
* @param {string} filterName
|
||||
* @param {string} filterId
|
||||
*/
|
||||
setFilterIdByName(filterName: string, filterId: string): void;
|
||||
setFilterIdByName(filterName: string, filterId?: string): void;
|
||||
|
||||
/**
|
||||
* Store user-scoped account data events
|
||||
|
@ -274,14 +274,14 @@ export class MemoryStore implements IStore {
|
||||
* @param {string} filterName
|
||||
* @param {string} filterId
|
||||
*/
|
||||
public setFilterIdByName(filterName: string, filterId: string) {
|
||||
public setFilterIdByName(filterName: string, filterId?: string) {
|
||||
if (!this.localStorage) {
|
||||
return;
|
||||
}
|
||||
const key = "mxjssdk_memory_filter_" + filterName;
|
||||
try {
|
||||
if (isValidFilterId(filterId)) {
|
||||
this.localStorage.setItem(key, filterId);
|
||||
this.localStorage.setItem(key, filterId!);
|
||||
} else {
|
||||
this.localStorage.removeItem(key);
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ import { IndexedToDeviceBatch, ToDeviceBatch } from "../models/ToDeviceMessage";
|
||||
*/
|
||||
export class StubStore implements IStore {
|
||||
public readonly accountData = {}; // stub
|
||||
private fromToken: string = null;
|
||||
private fromToken: string | null = null;
|
||||
|
||||
/** @return {Promise<boolean>} whether or not the database was newly created in this session. */
|
||||
public isNewlyCreated(): Promise<boolean> {
|
||||
@ -170,7 +170,7 @@ export class StubStore implements IStore {
|
||||
* @param {string} filterName
|
||||
* @param {string} filterId
|
||||
*/
|
||||
public setFilterIdByName(filterName: string, filterId: string) {}
|
||||
public setFilterIdByName(filterName: string, filterId?: string) {}
|
||||
|
||||
/**
|
||||
* Store user-scoped account data events
|
||||
@ -223,7 +223,7 @@ export class StubStore implements IStore {
|
||||
* client state to where it was at the last save, or null if there
|
||||
* is no saved sync data.
|
||||
*/
|
||||
public getSavedSync(): Promise<ISavedSync> {
|
||||
public getSavedSync(): Promise<ISavedSync | null> {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
@ -244,7 +244,7 @@ export class StubStore implements IStore {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public getOutOfBandMembers(): Promise<IStateEventWithRoomId[]> {
|
||||
public getOutOfBandMembers(): Promise<IStateEventWithRoomId[] | null> {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
|
@ -191,7 +191,7 @@ export class SyncAccumulator {
|
||||
// accumulated. We remember this so that any caller can obtain a
|
||||
// coherent /sync response and know at what point they should be
|
||||
// streaming from without losing events.
|
||||
private nextBatch: string = null;
|
||||
private nextBatch: string | null = null;
|
||||
|
||||
/**
|
||||
* @param {Object} opts
|
||||
@ -384,8 +384,8 @@ export class SyncAccumulator {
|
||||
if (data.unread_notifications) {
|
||||
currentData._unreadNotifications = data.unread_notifications;
|
||||
}
|
||||
currentData._unreadThreadNotifications = data[UNREAD_THREAD_NOTIFICATIONS.stable]
|
||||
?? data[UNREAD_THREAD_NOTIFICATIONS.unstable]
|
||||
currentData._unreadThreadNotifications = data[UNREAD_THREAD_NOTIFICATIONS.stable!]
|
||||
?? data[UNREAD_THREAD_NOTIFICATIONS.unstable!]
|
||||
?? undefined;
|
||||
|
||||
if (data.summary) {
|
||||
@ -429,7 +429,7 @@ export class SyncAccumulator {
|
||||
Object.entries(e.content[eventId]).forEach(([key, value]) => {
|
||||
if (!isSupportedReceiptType(key)) return;
|
||||
|
||||
Object.keys(value).forEach((userId) => {
|
||||
Object.keys(value!).forEach((userId) => {
|
||||
// clobber on user ID
|
||||
currentData._readReceipts[userId] = {
|
||||
data: e.content[eventId][key][userId],
|
||||
@ -477,16 +477,16 @@ export class SyncAccumulator {
|
||||
|
||||
currentData._timeline.push({
|
||||
event: transformedEvent,
|
||||
token: index === 0 ? data.timeline.prev_batch : null,
|
||||
token: index === 0 ? (data.timeline.prev_batch ?? null) : null,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// attempt to prune the timeline by jumping between events which have
|
||||
// pagination tokens.
|
||||
if (currentData._timeline.length > this.opts.maxTimelineEntries) {
|
||||
if (currentData._timeline.length > this.opts.maxTimelineEntries!) {
|
||||
const startIndex = (
|
||||
currentData._timeline.length - this.opts.maxTimelineEntries
|
||||
currentData._timeline.length - this.opts.maxTimelineEntries!
|
||||
);
|
||||
for (let i = startIndex; i < currentData._timeline.length; i++) {
|
||||
if (currentData._timeline[i].token) {
|
||||
@ -657,14 +657,14 @@ export class SyncAccumulator {
|
||||
});
|
||||
|
||||
return {
|
||||
nextBatch: this.nextBatch,
|
||||
nextBatch: this.nextBatch!,
|
||||
roomsData: data,
|
||||
accountData: accData,
|
||||
};
|
||||
}
|
||||
|
||||
public getNextBatchToken(): string {
|
||||
return this.nextBatch;
|
||||
return this.nextBatch!;
|
||||
}
|
||||
}
|
||||
|
||||
|
244
src/sync.ts
244
src/sync.ts
@ -33,7 +33,7 @@ import { Filter } from "./filter";
|
||||
import { EventTimeline } from "./models/event-timeline";
|
||||
import { PushProcessor } from "./pushprocessor";
|
||||
import { logger } from './logger';
|
||||
import { InvalidStoreError } from './errors';
|
||||
import { InvalidStoreError, InvalidStoreState } from './errors';
|
||||
import { ClientEvent, IStoredClientOpts, MatrixClient, PendingEventOrdering } from "./client";
|
||||
import {
|
||||
IEphemeral,
|
||||
@ -117,7 +117,7 @@ interface ISyncOptions {
|
||||
}
|
||||
|
||||
export interface ISyncStateData {
|
||||
error?: MatrixError;
|
||||
error?: Error;
|
||||
oldSyncToken?: string;
|
||||
nextSyncToken?: string;
|
||||
catchingUp?: boolean;
|
||||
@ -163,14 +163,14 @@ type WrappedRoom<T> = T & {
|
||||
*/
|
||||
export class SyncApi {
|
||||
private _peekRoom: Optional<Room> = null;
|
||||
private currentSyncRequest: Optional<Promise<ISyncResponse>> = null;
|
||||
private currentSyncRequest?: Promise<ISyncResponse>;
|
||||
private abortController?: AbortController;
|
||||
private syncState: Optional<SyncState> = null;
|
||||
private syncStateData: Optional<ISyncStateData> = null; // additional data (eg. error object for failed sync)
|
||||
private syncState: SyncState | null = null;
|
||||
private syncStateData?: ISyncStateData; // additional data (eg. error object for failed sync)
|
||||
private catchingUp = false;
|
||||
private running = false;
|
||||
private keepAliveTimer: Optional<ReturnType<typeof setTimeout>> = null;
|
||||
private connectionReturnedDefer: Optional<IDeferred<boolean>> = null;
|
||||
private keepAliveTimer?: ReturnType<typeof setTimeout>;
|
||||
private connectionReturnedDefer?: IDeferred<boolean>;
|
||||
private notifEvents: MatrixEvent[] = []; // accumulator of sync events in the current sync response
|
||||
private failedSyncCount = 0; // Number of consecutive failed /sync requests
|
||||
private storeIsInvalid = false; // flag set if the store needs to be cleared before we can start
|
||||
@ -189,7 +189,7 @@ export class SyncApi {
|
||||
}
|
||||
|
||||
if (client.getNotifTimelineSet()) {
|
||||
client.reEmitter.reEmit(client.getNotifTimelineSet(), [
|
||||
client.reEmitter.reEmit(client.getNotifTimelineSet()!, [
|
||||
RoomEvent.Timeline,
|
||||
RoomEvent.TimelineReset,
|
||||
]);
|
||||
@ -281,7 +281,7 @@ export class SyncApi {
|
||||
* Sync rooms the user has left.
|
||||
* @return {Promise} Resolved when they've been added to the store.
|
||||
*/
|
||||
public syncLeftRooms() {
|
||||
public async syncLeftRooms(): Promise<Room[]> {
|
||||
const client = this.client;
|
||||
|
||||
// grab a filter with limit=1 and include_leave=true
|
||||
@ -289,55 +289,62 @@ export class SyncApi {
|
||||
filter.setTimelineLimit(1);
|
||||
filter.setIncludeLeaveRooms(true);
|
||||
|
||||
const localTimeoutMs = this.opts.pollTimeout + BUFFER_PERIOD_MS;
|
||||
const localTimeoutMs = this.opts.pollTimeout! + BUFFER_PERIOD_MS;
|
||||
|
||||
const filterId = await client.getOrCreateFilter(
|
||||
getFilterName(client.credentials.userId!, "LEFT_ROOMS"), filter,
|
||||
);
|
||||
|
||||
const qps: ISyncParams = {
|
||||
timeout: 0, // don't want to block since this is a single isolated req
|
||||
filter: filterId,
|
||||
};
|
||||
|
||||
return client.getOrCreateFilter(
|
||||
getFilterName(client.credentials.userId, "LEFT_ROOMS"), filter,
|
||||
).then(function(filterId) {
|
||||
qps.filter = filterId;
|
||||
return client.http.authedRequest<ISyncResponse>(Method.Get, "/sync", qps as any, undefined, {
|
||||
localTimeoutMs,
|
||||
});
|
||||
}).then(async (data) => {
|
||||
let leaveRooms = [];
|
||||
if (data.rooms?.leave) {
|
||||
leaveRooms = this.mapSyncResponseToRoomArray(data.rooms.leave);
|
||||
}
|
||||
return Promise.all(leaveRooms.map(async (leaveObj) => {
|
||||
const room = leaveObj.room;
|
||||
if (!leaveObj.isBrandNewRoom) {
|
||||
// the intention behind syncLeftRooms is to add in rooms which were
|
||||
// *omitted* from the initial /sync. Rooms the user were joined to
|
||||
// but then left whilst the app is running will appear in this list
|
||||
// and we do not want to bother with them since they will have the
|
||||
// current state already (and may get dupe messages if we add
|
||||
// yet more timeline events!), so skip them.
|
||||
// NB: When we persist rooms to localStorage this will be more
|
||||
// complicated...
|
||||
return;
|
||||
}
|
||||
leaveObj.timeline = leaveObj.timeline || {};
|
||||
const events = this.mapSyncEventsFormat(leaveObj.timeline, room);
|
||||
|
||||
const stateEvents = this.mapSyncEventsFormat(leaveObj.state, room);
|
||||
|
||||
// set the back-pagination token. Do this *before* adding any
|
||||
// events so that clients can start back-paginating.
|
||||
room.getLiveTimeline().setPaginationToken(leaveObj.timeline.prev_batch, EventTimeline.BACKWARDS);
|
||||
|
||||
await this.processRoomEvents(room, stateEvents, events);
|
||||
|
||||
room.recalculate();
|
||||
client.store.storeRoom(room);
|
||||
client.emit(ClientEvent.Room, room);
|
||||
|
||||
this.processEventsForNotifs(room, events);
|
||||
return room;
|
||||
}));
|
||||
const data = await client.http.authedRequest<ISyncResponse>(Method.Get, "/sync", qps as any, undefined, {
|
||||
localTimeoutMs,
|
||||
});
|
||||
|
||||
let leaveRooms: WrappedRoom<ILeftRoom>[] = [];
|
||||
if (data.rooms?.leave) {
|
||||
leaveRooms = this.mapSyncResponseToRoomArray(data.rooms.leave);
|
||||
}
|
||||
|
||||
const rooms = await Promise.all(leaveRooms.map(async (leaveObj) => {
|
||||
const room = leaveObj.room;
|
||||
if (!leaveObj.isBrandNewRoom) {
|
||||
// the intention behind syncLeftRooms is to add in rooms which were
|
||||
// *omitted* from the initial /sync. Rooms the user were joined to
|
||||
// but then left whilst the app is running will appear in this list
|
||||
// and we do not want to bother with them since they will have the
|
||||
// current state already (and may get dupe messages if we add
|
||||
// yet more timeline events!), so skip them.
|
||||
// NB: When we persist rooms to localStorage this will be more
|
||||
// complicated...
|
||||
return;
|
||||
}
|
||||
leaveObj.timeline = leaveObj.timeline || {
|
||||
prev_batch: null,
|
||||
events: [],
|
||||
};
|
||||
const events = this.mapSyncEventsFormat(leaveObj.timeline, room);
|
||||
|
||||
const stateEvents = this.mapSyncEventsFormat(leaveObj.state, room);
|
||||
|
||||
// set the back-pagination token. Do this *before* adding any
|
||||
// events so that clients can start back-paginating.
|
||||
room.getLiveTimeline().setPaginationToken(leaveObj.timeline.prev_batch, EventTimeline.BACKWARDS);
|
||||
|
||||
await this.processRoomEvents(room, stateEvents, events);
|
||||
|
||||
room.recalculate();
|
||||
client.store.storeRoom(room);
|
||||
client.emit(ClientEvent.Room, room);
|
||||
|
||||
this.processEventsForNotifs(room, events);
|
||||
return room;
|
||||
}));
|
||||
|
||||
return rooms.filter(Boolean) as Room[];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -348,7 +355,7 @@ export class SyncApi {
|
||||
* store.
|
||||
*/
|
||||
public peek(roomId: string): Promise<Room> {
|
||||
if (this._peekRoom && this._peekRoom.roomId === roomId) {
|
||||
if (this._peekRoom?.roomId === roomId) {
|
||||
return Promise.resolve(this._peekRoom);
|
||||
}
|
||||
|
||||
@ -388,28 +395,28 @@ export class SyncApi {
|
||||
// fire off pagination requests in response to the Room.timeline
|
||||
// events.
|
||||
if (response.messages.start) {
|
||||
this._peekRoom.oldState.paginationToken = response.messages.start;
|
||||
this._peekRoom!.oldState.paginationToken = response.messages.start;
|
||||
}
|
||||
|
||||
// set the state of the room to as it was after the timeline executes
|
||||
this._peekRoom.oldState.setStateEvents(oldStateEvents);
|
||||
this._peekRoom.currentState.setStateEvents(stateEvents);
|
||||
this._peekRoom!.oldState.setStateEvents(oldStateEvents);
|
||||
this._peekRoom!.currentState.setStateEvents(stateEvents);
|
||||
|
||||
this.resolveInvites(this._peekRoom);
|
||||
this._peekRoom.recalculate();
|
||||
this.resolveInvites(this._peekRoom!);
|
||||
this._peekRoom!.recalculate();
|
||||
|
||||
// roll backwards to diverge old state. addEventsToTimeline
|
||||
// will overwrite the pagination token, so make sure it overwrites
|
||||
// it with the right thing.
|
||||
this._peekRoom.addEventsToTimeline(messages.reverse(), true,
|
||||
this._peekRoom.getLiveTimeline(),
|
||||
this._peekRoom!.addEventsToTimeline(messages.reverse(), true,
|
||||
this._peekRoom!.getLiveTimeline(),
|
||||
response.messages.start);
|
||||
|
||||
client.store.storeRoom(this._peekRoom);
|
||||
client.emit(ClientEvent.Room, this._peekRoom);
|
||||
client.store.storeRoom(this._peekRoom!);
|
||||
client.emit(ClientEvent.Room, this._peekRoom!);
|
||||
|
||||
this.peekPoll(this._peekRoom);
|
||||
return this._peekRoom;
|
||||
this.peekPoll(this._peekRoom!);
|
||||
return this._peekRoom!;
|
||||
});
|
||||
}
|
||||
|
||||
@ -437,7 +444,10 @@ export class SyncApi {
|
||||
room_id: peekRoom.roomId,
|
||||
timeout: String(30 * 1000),
|
||||
from: token,
|
||||
}, undefined, { localTimeoutMs: 50 * 1000 }).then((res) => {
|
||||
}, undefined, {
|
||||
localTimeoutMs: 50 * 1000,
|
||||
abortSignal: this.abortController?.signal,
|
||||
}).then((res) => {
|
||||
if (this._peekRoom !== peekRoom) {
|
||||
debuglog("Stopped peeking in room %s", peekRoom.roomId);
|
||||
return;
|
||||
@ -487,7 +497,7 @@ export class SyncApi {
|
||||
* @see module:client~MatrixClient#event:"sync"
|
||||
* @return {?String}
|
||||
*/
|
||||
public getSyncState(): SyncState {
|
||||
public getSyncState(): SyncState | null {
|
||||
return this.syncState;
|
||||
}
|
||||
|
||||
@ -499,18 +509,18 @@ export class SyncApi {
|
||||
* this object.
|
||||
* @return {?Object}
|
||||
*/
|
||||
public getSyncStateData(): ISyncStateData {
|
||||
return this.syncStateData;
|
||||
public getSyncStateData(): ISyncStateData | null {
|
||||
return this.syncStateData ?? null;
|
||||
}
|
||||
|
||||
public async recoverFromSyncStartupError(savedSyncPromise: Promise<void>, err: MatrixError): Promise<void> {
|
||||
public async recoverFromSyncStartupError(savedSyncPromise: Promise<void> | undefined, error: Error): Promise<void> {
|
||||
// Wait for the saved sync to complete - we send the pushrules and filter requests
|
||||
// before the saved sync has finished so they can run in parallel, but only process
|
||||
// the results after the saved sync is done. Equivalently, we wait for it to finish
|
||||
// before reporting failures from these functions.
|
||||
await savedSyncPromise;
|
||||
const keepaliveProm = this.startKeepAlives();
|
||||
this.updateSyncState(SyncState.Error, { error: err });
|
||||
this.updateSyncState(SyncState.Error, { error });
|
||||
await keepaliveProm;
|
||||
}
|
||||
|
||||
@ -553,11 +563,11 @@ export class SyncApi {
|
||||
this.client.pushRules = result;
|
||||
} catch (err) {
|
||||
logger.error("Getting push rules failed", err);
|
||||
if (this.shouldAbortSync(err)) return;
|
||||
if (this.shouldAbortSync(<MatrixError>err)) return;
|
||||
// wait for saved sync to complete before doing anything else,
|
||||
// otherwise the sync state will end up being incorrect
|
||||
debuglog("Waiting for saved sync before retrying push rules...");
|
||||
await this.recoverFromSyncStartupError(this.savedSyncPromise, err);
|
||||
await this.recoverFromSyncStartupError(this.savedSyncPromise, <Error>err);
|
||||
return this.getPushRules(); // try again
|
||||
}
|
||||
};
|
||||
@ -595,8 +605,7 @@ export class SyncApi {
|
||||
const shouldClear = await this.wasLazyLoadingToggled(this.opts.lazyLoadMembers);
|
||||
if (shouldClear) {
|
||||
this.storeIsInvalid = true;
|
||||
const reason = InvalidStoreError.TOGGLED_LAZY_LOADING;
|
||||
const error = new InvalidStoreError(reason, !!this.opts.lazyLoadMembers);
|
||||
const error = new InvalidStoreError(InvalidStoreState.ToggledLazyLoading, !!this.opts.lazyLoadMembers);
|
||||
this.updateSyncState(SyncState.Error, { error });
|
||||
// bail out of the sync loop now: the app needs to respond to this error.
|
||||
// we leave the state as 'ERROR' which isn't great since this normally means
|
||||
@ -632,20 +641,20 @@ export class SyncApi {
|
||||
|
||||
let filterId: string;
|
||||
try {
|
||||
filterId = await this.client.getOrCreateFilter(getFilterName(this.client.credentials.userId), filter);
|
||||
filterId = await this.client.getOrCreateFilter(getFilterName(this.client.credentials.userId!), filter);
|
||||
} catch (err) {
|
||||
logger.error("Getting filter failed", err);
|
||||
if (this.shouldAbortSync(err)) return {};
|
||||
if (this.shouldAbortSync(<MatrixError>err)) return {};
|
||||
// wait for saved sync to complete before doing anything else,
|
||||
// otherwise the sync state will end up being incorrect
|
||||
debuglog("Waiting for saved sync before retrying filter...");
|
||||
await this.recoverFromSyncStartupError(this.savedSyncPromise, err);
|
||||
await this.recoverFromSyncStartupError(this.savedSyncPromise, <Error>err);
|
||||
return this.getFilter(); // try again
|
||||
}
|
||||
return { filter, filterId };
|
||||
};
|
||||
|
||||
private savedSyncPromise: Promise<void>;
|
||||
private savedSyncPromise?: Promise<void>;
|
||||
|
||||
/**
|
||||
* Main entry point
|
||||
@ -701,7 +710,7 @@ export class SyncApi {
|
||||
// /notifications API somehow.
|
||||
this.client.resetNotifTimelineSet();
|
||||
|
||||
if (this.currentSyncRequest === null) {
|
||||
if (!this.currentSyncRequest) {
|
||||
let firstSyncFilter = filterId;
|
||||
const savedSyncToken = await savedSyncTokenPromise;
|
||||
|
||||
@ -711,7 +720,7 @@ export class SyncApi {
|
||||
debuglog("Sending initial sync request...");
|
||||
const initialFilter = this.buildDefaultFilter();
|
||||
initialFilter.setDefinition(filter.getDefinition());
|
||||
initialFilter.setTimelineLimit(this.opts.initialSyncLimit);
|
||||
initialFilter.setTimelineLimit(this.opts.initialSyncLimit!);
|
||||
// Use an inline filter, no point uploading it for a single usage
|
||||
firstSyncFilter = JSON.stringify(initialFilter.getDefinition());
|
||||
}
|
||||
@ -742,7 +751,7 @@ export class SyncApi {
|
||||
this.abortController?.abort();
|
||||
if (this.keepAliveTimer) {
|
||||
clearTimeout(this.keepAliveTimer);
|
||||
this.keepAliveTimer = null;
|
||||
this.keepAliveTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@ -813,16 +822,16 @@ export class SyncApi {
|
||||
let data: ISyncResponse;
|
||||
try {
|
||||
//debuglog('Starting sync since=' + syncToken);
|
||||
if (this.currentSyncRequest === null) {
|
||||
if (!this.currentSyncRequest) {
|
||||
this.currentSyncRequest = this.doSyncRequest(syncOptions, syncToken);
|
||||
}
|
||||
data = await this.currentSyncRequest;
|
||||
} catch (e) {
|
||||
const abort = await this.onSyncError(e);
|
||||
const abort = await this.onSyncError(<MatrixError>e);
|
||||
if (abort) return;
|
||||
continue;
|
||||
} finally {
|
||||
this.currentSyncRequest = null;
|
||||
this.currentSyncRequest = undefined;
|
||||
}
|
||||
|
||||
//debuglog('Completed sync, next_batch=' + data.next_batch);
|
||||
@ -838,7 +847,7 @@ export class SyncApi {
|
||||
await this.client.store.setSyncData(data);
|
||||
|
||||
const syncEventData = {
|
||||
oldSyncToken: syncToken,
|
||||
oldSyncToken: syncToken ?? undefined,
|
||||
nextSyncToken: data.next_batch,
|
||||
catchingUp: this.catchingUp,
|
||||
};
|
||||
@ -857,7 +866,7 @@ export class SyncApi {
|
||||
logger.error("Caught /sync error", e);
|
||||
|
||||
// Emit the exception for client handling
|
||||
this.client.emit(ClientEvent.SyncUnexpectedError, e);
|
||||
this.client.emit(ClientEvent.SyncUnexpectedError, <Error>e);
|
||||
}
|
||||
|
||||
// update this as it may have changed
|
||||
@ -897,13 +906,13 @@ export class SyncApi {
|
||||
debuglog("Sync no longer running: exiting.");
|
||||
if (this.connectionReturnedDefer) {
|
||||
this.connectionReturnedDefer.reject();
|
||||
this.connectionReturnedDefer = null;
|
||||
this.connectionReturnedDefer = undefined;
|
||||
}
|
||||
this.updateSyncState(SyncState.Stopped);
|
||||
}
|
||||
}
|
||||
|
||||
private doSyncRequest(syncOptions: ISyncOptions, syncToken: string): Promise<ISyncResponse> {
|
||||
private doSyncRequest(syncOptions: ISyncOptions, syncToken: string | null): Promise<ISyncResponse> {
|
||||
const qps = this.getSyncParams(syncOptions, syncToken);
|
||||
return this.client.http.authedRequest<ISyncResponse>(Method.Get, "/sync", qps as any, undefined, {
|
||||
localTimeoutMs: qps.timeout + BUFFER_PERIOD_MS,
|
||||
@ -911,8 +920,8 @@ export class SyncApi {
|
||||
});
|
||||
}
|
||||
|
||||
private getSyncParams(syncOptions: ISyncOptions, syncToken: string): ISyncParams {
|
||||
let pollTimeout = this.opts.pollTimeout;
|
||||
private getSyncParams(syncOptions: ISyncOptions, syncToken: string | null): ISyncParams {
|
||||
let timeout = this.opts.pollTimeout!;
|
||||
|
||||
if (this.getSyncState() !== SyncState.Syncing || this.catchingUp) {
|
||||
// unless we are happily syncing already, we want the server to return
|
||||
@ -927,7 +936,7 @@ export class SyncApi {
|
||||
// for us. We do that by calling it with a zero timeout until it
|
||||
// doesn't give us any more to_device messages.
|
||||
this.catchingUp = true;
|
||||
pollTimeout = 0;
|
||||
timeout = 0;
|
||||
}
|
||||
|
||||
let filter = syncOptions.filter;
|
||||
@ -935,10 +944,7 @@ export class SyncApi {
|
||||
filter = this.getGuestFilter();
|
||||
}
|
||||
|
||||
const qps: ISyncParams = {
|
||||
filter,
|
||||
timeout: pollTimeout,
|
||||
};
|
||||
const qps: ISyncParams = { filter, timeout };
|
||||
|
||||
if (this.opts.disablePresence) {
|
||||
qps.set_presence = SetPresence.Offline;
|
||||
@ -953,7 +959,7 @@ export class SyncApi {
|
||||
qps._cacheBuster = Date.now();
|
||||
}
|
||||
|
||||
if ([SyncState.Reconnecting, SyncState.Error].includes(this.getSyncState())) {
|
||||
if ([SyncState.Reconnecting, SyncState.Error].includes(this.getSyncState()!)) {
|
||||
// we think the connection is dead. If it comes back up, we won't know
|
||||
// about it till /sync returns. If the timeout= is high, this could
|
||||
// be a long time. Set it to 0 when doing retries so we don't have to wait
|
||||
@ -969,7 +975,7 @@ export class SyncApi {
|
||||
debuglog("Sync no longer running: exiting");
|
||||
if (this.connectionReturnedDefer) {
|
||||
this.connectionReturnedDefer.reject();
|
||||
this.connectionReturnedDefer = null;
|
||||
this.connectionReturnedDefer = undefined;
|
||||
}
|
||||
this.updateSyncState(SyncState.Stopped);
|
||||
return true; // abort
|
||||
@ -994,7 +1000,7 @@ export class SyncApi {
|
||||
// if they wish.
|
||||
const keepAlivePromise = this.startKeepAlives();
|
||||
|
||||
this.currentSyncRequest = null;
|
||||
this.currentSyncRequest = undefined;
|
||||
// Transition from RECONNECTING to ERROR after a given number of failed syncs
|
||||
this.updateSyncState(
|
||||
this.failedSyncCount >= FAILED_SYNC_ERROR_THRESHOLD ?
|
||||
@ -1073,7 +1079,7 @@ export class SyncApi {
|
||||
|
||||
// handle presence events (User objects)
|
||||
if (Array.isArray(data.presence?.events)) {
|
||||
data.presence.events.map(client.getEventMapper()).forEach(
|
||||
data.presence!.events.map(client.getEventMapper()).forEach(
|
||||
function(presenceEvent) {
|
||||
let user = client.store.getUser(presenceEvent.getSender());
|
||||
if (user) {
|
||||
@ -1113,9 +1119,9 @@ export class SyncApi {
|
||||
}
|
||||
|
||||
// handle to-device events
|
||||
if (Array.isArray(data.to_device?.events) && data.to_device.events.length > 0) {
|
||||
const cancelledKeyVerificationTxns = [];
|
||||
data.to_device.events
|
||||
if (Array.isArray(data.to_device?.events) && data.to_device!.events.length > 0) {
|
||||
const cancelledKeyVerificationTxns: string[] = [];
|
||||
data.to_device!.events
|
||||
.filter((eventJSON) => {
|
||||
if (
|
||||
eventJSON.type === EventType.RoomMessageEncrypted &&
|
||||
@ -1137,7 +1143,7 @@ export class SyncApi {
|
||||
// so we can flag the verification events as cancelled in the loop
|
||||
// below.
|
||||
if (toDeviceEvent.getType() === "m.key.verification.cancel") {
|
||||
const txnId = toDeviceEvent.getContent()['transaction_id'];
|
||||
const txnId: string = toDeviceEvent.getContent()['transaction_id'];
|
||||
if (txnId) {
|
||||
cancelledKeyVerificationTxns.push(txnId);
|
||||
}
|
||||
@ -1206,13 +1212,13 @@ export class SyncApi {
|
||||
|
||||
await this.processRoomEvents(room, stateEvents);
|
||||
|
||||
const inviter = room.currentState.getStateEvents(EventType.RoomMember, client.getUserId())?.getSender();
|
||||
const inviter = room.currentState.getStateEvents(EventType.RoomMember, client.getUserId()!)?.getSender();
|
||||
|
||||
if (client.isCryptoEnabled()) {
|
||||
const parkedHistory = await client.crypto.cryptoStore.takeParkedSharedHistory(room.roomId);
|
||||
const parkedHistory = await client.crypto!.cryptoStore.takeParkedSharedHistory(room.roomId);
|
||||
for (const parked of parkedHistory) {
|
||||
if (parked.senderId === inviter) {
|
||||
await client.crypto.olmDevice.addInboundGroupSession(
|
||||
await client.crypto!.olmDevice.addInboundGroupSession(
|
||||
room.roomId,
|
||||
parked.senderKey,
|
||||
parked.forwardingCurve25519KeyChain,
|
||||
@ -1256,7 +1262,7 @@ export class SyncApi {
|
||||
if (joinObj.unread_notifications) {
|
||||
room.setUnreadNotificationCount(
|
||||
NotificationCountType.Total,
|
||||
joinObj.unread_notifications.notification_count,
|
||||
joinObj.unread_notifications.notification_count ?? 0,
|
||||
);
|
||||
|
||||
// We track unread notifications ourselves in encrypted rooms, so don't
|
||||
@ -1266,13 +1272,13 @@ export class SyncApi {
|
||||
if (!encrypted || room.getUnreadNotificationCount(NotificationCountType.Highlight) <= 0) {
|
||||
room.setUnreadNotificationCount(
|
||||
NotificationCountType.Highlight,
|
||||
joinObj.unread_notifications.highlight_count,
|
||||
joinObj.unread_notifications.highlight_count ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const unreadThreadNotifications = joinObj[UNREAD_THREAD_NOTIFICATIONS.name]
|
||||
?? joinObj[UNREAD_THREAD_NOTIFICATIONS.altName];
|
||||
?? joinObj[UNREAD_THREAD_NOTIFICATIONS.altName!];
|
||||
if (unreadThreadNotifications) {
|
||||
// Only partially reset unread notification
|
||||
// We want to keep the client-generated count. Particularly important
|
||||
@ -1283,7 +1289,7 @@ export class SyncApi {
|
||||
room.setThreadUnreadNotificationCount(
|
||||
threadId,
|
||||
NotificationCountType.Total,
|
||||
unreadNotification.notification_count,
|
||||
unreadNotification.notification_count ?? 0,
|
||||
);
|
||||
|
||||
const hasNoNotifications =
|
||||
@ -1292,7 +1298,7 @@ export class SyncApi {
|
||||
room.setThreadUnreadNotificationCount(
|
||||
threadId,
|
||||
NotificationCountType.Highlight,
|
||||
unreadNotification.highlight_count,
|
||||
unreadNotification.highlight_count ?? 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1349,8 +1355,8 @@ export class SyncApi {
|
||||
if (limited) {
|
||||
room.resetLiveTimeline(
|
||||
joinObj.timeline.prev_batch,
|
||||
this.opts.canResetEntireTimeline(room.roomId) ?
|
||||
null : syncEventData.oldSyncToken,
|
||||
this.opts.canResetEntireTimeline!(room.roomId) ?
|
||||
null : (syncEventData.oldSyncToken ?? null),
|
||||
);
|
||||
|
||||
// We have to assume any gap in any timeline is
|
||||
@ -1448,7 +1454,7 @@ export class SyncApi {
|
||||
return a.getTs() - b.getTs();
|
||||
});
|
||||
this.notifEvents.forEach(function(event) {
|
||||
client.getNotifTimelineSet().addLiveEvent(event);
|
||||
client.getNotifTimelineSet()?.addLiveEvent(event);
|
||||
});
|
||||
}
|
||||
|
||||
@ -1523,7 +1529,7 @@ export class SyncApi {
|
||||
clearTimeout(this.keepAliveTimer);
|
||||
if (this.connectionReturnedDefer) {
|
||||
this.connectionReturnedDefer.resolve(connDidFail);
|
||||
this.connectionReturnedDefer = null;
|
||||
this.connectionReturnedDefer = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1639,7 +1645,7 @@ export class SyncApi {
|
||||
// the code paths remain the same between invite/join display name stuff
|
||||
// which is a worthy trade-off for some minor pollution.
|
||||
const inviteEvent = member.events.member;
|
||||
if (inviteEvent.getContent().membership !== "invite") {
|
||||
if (inviteEvent?.getContent().membership !== "invite") {
|
||||
// between resolving and now they have since joined, so don't clobber
|
||||
return;
|
||||
}
|
||||
@ -1802,7 +1808,7 @@ function createNewUser(client: MatrixClient, userId: string): User {
|
||||
export function _createAndReEmitRoom(client: MatrixClient, roomId: string, opts: Partial<IStoredClientOpts>): Room {
|
||||
const { timelineSupport } = client;
|
||||
|
||||
const room = new Room(roomId, client, client.getUserId(), {
|
||||
const room = new Room(roomId, client, client.getUserId()!, {
|
||||
lazyLoadMembers: opts.lazyLoadMembers,
|
||||
pendingEventOrdering: opts.pendingEventOrdering,
|
||||
timelineSupport,
|
||||
@ -1832,7 +1838,7 @@ export function _createAndReEmitRoom(client: MatrixClient, roomId: string, opts:
|
||||
// We need to add a listener for RoomState.members in order to hook them
|
||||
// correctly.
|
||||
room.on(RoomStateEvent.NewMember, (event, state, member) => {
|
||||
member.user = client.getUser(member.userId);
|
||||
member.user = client.getUser(member.userId) ?? undefined;
|
||||
client.reEmitter.reEmit(member, [
|
||||
RoomMemberEvent.Name,
|
||||
RoomMemberEvent.Typing,
|
||||
|
@ -16,6 +16,8 @@ limitations under the License.
|
||||
|
||||
/** @module timeline-window */
|
||||
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
|
||||
import { Direction, EventTimeline } from './models/event-timeline';
|
||||
import { logger } from './logger';
|
||||
import { MatrixClient } from "./client";
|
||||
@ -49,8 +51,8 @@ export class TimelineWindow {
|
||||
// 'end' of the window.
|
||||
//
|
||||
// start.index is inclusive; end.index is exclusive.
|
||||
private start?: TimelineIndex = null;
|
||||
private end?: TimelineIndex = null;
|
||||
private start?: TimelineIndex;
|
||||
private end?: TimelineIndex;
|
||||
private eventCount = 0;
|
||||
|
||||
/**
|
||||
@ -102,7 +104,11 @@ export class TimelineWindow {
|
||||
public load(initialEventId?: string, initialWindowSize = 20): Promise<void> {
|
||||
// given an EventTimeline, find the event we were looking for, and initialise our
|
||||
// fields so that the event in question is in the middle of the window.
|
||||
const initFields = (timeline: EventTimeline) => {
|
||||
const initFields = (timeline: Optional<EventTimeline>) => {
|
||||
if (!timeline) {
|
||||
throw new Error("No timeline given to initFields");
|
||||
}
|
||||
|
||||
let eventIndex: number;
|
||||
|
||||
const events = timeline.getEvents();
|
||||
@ -153,11 +159,11 @@ export class TimelineWindow {
|
||||
* @return {TimelineIndex} The requested timeline index if one exists, null
|
||||
* otherwise.
|
||||
*/
|
||||
public getTimelineIndex(direction: Direction): TimelineIndex {
|
||||
public getTimelineIndex(direction: Direction): TimelineIndex | null {
|
||||
if (direction == EventTimeline.BACKWARDS) {
|
||||
return this.start;
|
||||
return this.start ?? null;
|
||||
} else if (direction == EventTimeline.FORWARDS) {
|
||||
return this.end;
|
||||
return this.end ?? null;
|
||||
} else {
|
||||
throw new Error("Invalid direction '" + direction + "'");
|
||||
}
|
||||
@ -299,7 +305,7 @@ export class TimelineWindow {
|
||||
backwards: direction == EventTimeline.BACKWARDS,
|
||||
limit: size,
|
||||
}).finally(function() {
|
||||
tl.pendingPaginate = null;
|
||||
tl.pendingPaginate = undefined;
|
||||
}).then((r) => {
|
||||
debuglog("TimelineWindow: request completed with result " + r);
|
||||
if (!r) {
|
||||
@ -334,11 +340,17 @@ export class TimelineWindow {
|
||||
*/
|
||||
public unpaginate(delta: number, startOfTimeline: boolean): void {
|
||||
const tl = startOfTimeline ? this.start : this.end;
|
||||
if (!tl) {
|
||||
throw new Error(
|
||||
`Attempting to unpaginate startOfTimeline=${startOfTimeline} but don't have this direction`,
|
||||
);
|
||||
}
|
||||
|
||||
// sanity-check the delta
|
||||
if (delta > this.eventCount || delta < 0) {
|
||||
throw new Error("Attemting to unpaginate " + delta + " events, but " +
|
||||
"only have " + this.eventCount + " in the timeline");
|
||||
throw new Error(
|
||||
`Attemting to unpaginate ${delta} events, but only have ${this.eventCount} in the timeline`,
|
||||
);
|
||||
}
|
||||
|
||||
while (delta > 0) {
|
||||
@ -368,7 +380,7 @@ export class TimelineWindow {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result = [];
|
||||
const result: MatrixEvent[] = [];
|
||||
|
||||
// iterate through each timeline between this.start and this.end
|
||||
// (inclusive).
|
||||
@ -390,7 +402,7 @@ export class TimelineWindow {
|
||||
if (timeline === this.start.timeline) {
|
||||
startIndex = this.start.index + timeline.getBaseIndex();
|
||||
}
|
||||
if (timeline === this.end.timeline) {
|
||||
if (timeline === this.end?.timeline) {
|
||||
endIndex = this.end.index + timeline.getBaseIndex();
|
||||
}
|
||||
|
||||
@ -399,10 +411,10 @@ export class TimelineWindow {
|
||||
}
|
||||
|
||||
// if we're not done, iterate to the next timeline.
|
||||
if (timeline === this.end.timeline) {
|
||||
if (timeline === this.end?.timeline) {
|
||||
break;
|
||||
} else {
|
||||
timeline = timeline.getNeighbouringTimeline(EventTimeline.FORWARDS);
|
||||
timeline = timeline.getNeighbouringTimeline(EventTimeline.FORWARDS)!;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ export function internaliseString(str: string): string {
|
||||
}
|
||||
|
||||
// Return any cached string reference
|
||||
return interns.get(str);
|
||||
return interns.get(str)!;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -412,7 +412,7 @@ export function defer<T = void>(): IDeferred<T> {
|
||||
|
||||
export async function promiseMapSeries<T>(
|
||||
promises: Array<T | Promise<T>>,
|
||||
fn: (t: T) => Promise<unknown> | void, // if async/promise we don't care about the type as we only await resolution
|
||||
fn: (t: T) => Promise<unknown> | undefined, // if async we don't care about the type as we only await resolution
|
||||
): Promise<void> {
|
||||
for (const o of promises) {
|
||||
await fn(await o);
|
||||
|
@ -47,6 +47,7 @@ import { CallFeed } from './callFeed';
|
||||
import { MatrixClient } from "../client";
|
||||
import { ISendEventResponse } from "../@types/requests";
|
||||
import { EventEmitterEvents, TypedEventEmitter } from "../models/typed-event-emitter";
|
||||
import { MatrixError } from "../http-api";
|
||||
|
||||
// events: hangup, error(err), replaced(call), state(state, oldState)
|
||||
|
||||
@ -68,7 +69,7 @@ import { EventEmitterEvents, TypedEventEmitter } from "../models/typed-event-emi
|
||||
|
||||
interface CallOpts {
|
||||
roomId?: string;
|
||||
client?: any; // Fix when client is TSified
|
||||
client: MatrixClient;
|
||||
forceTURN?: boolean;
|
||||
turnServers?: Array<TurnServer>;
|
||||
}
|
||||
@ -227,7 +228,7 @@ const FALLBACK_ICE_SERVER = 'stun:turn.matrix.org';
|
||||
const CALL_TIMEOUT_MS = 60000;
|
||||
|
||||
export class CallError extends Error {
|
||||
code: string;
|
||||
public readonly code: string;
|
||||
|
||||
constructor(code: CallErrorCode, msg: string, err: Error) {
|
||||
// Still don't think there's any way to have proper nested errors
|
||||
@ -271,33 +272,33 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
public roomId: string;
|
||||
public callId: string;
|
||||
public state = CallState.Fledgling;
|
||||
public hangupParty: CallParty;
|
||||
public hangupReason: string;
|
||||
public direction: CallDirection;
|
||||
public hangupParty?: CallParty;
|
||||
public hangupReason?: string;
|
||||
public direction?: CallDirection;
|
||||
public ourPartyId: string;
|
||||
|
||||
private client: MatrixClient;
|
||||
private forceTURN: boolean;
|
||||
private turnServers: Array<TurnServer>;
|
||||
private readonly client: MatrixClient;
|
||||
private readonly forceTURN: boolean;
|
||||
private readonly turnServers: Array<TurnServer>;
|
||||
// A queue for candidates waiting to go out.
|
||||
// We try to amalgamate candidates into a single candidate message where
|
||||
// possible
|
||||
private candidateSendQueue: Array<RTCIceCandidate> = [];
|
||||
private candidateSendTries = 0;
|
||||
private sentEndOfCandidates = false;
|
||||
private peerConn: RTCPeerConnection;
|
||||
private peerConn?: RTCPeerConnection;
|
||||
private feeds: Array<CallFeed> = [];
|
||||
private usermediaSenders: Array<RTCRtpSender> = [];
|
||||
private screensharingSenders: Array<RTCRtpSender> = [];
|
||||
private inviteOrAnswerSent = false;
|
||||
private waitForLocalAVStream: boolean;
|
||||
private successor: MatrixCall;
|
||||
private opponentMember: RoomMember;
|
||||
private opponentVersion: number | string;
|
||||
private successor?: MatrixCall;
|
||||
private opponentMember?: RoomMember;
|
||||
private opponentVersion?: number | string;
|
||||
// The party ID of the other side: undefined if we haven't chosen a partner
|
||||
// yet, null if we have but they didn't send a party ID.
|
||||
private opponentPartyId: string;
|
||||
private opponentCaps: CallCapabilities;
|
||||
private opponentPartyId?: string | null;
|
||||
private opponentCaps?: CallCapabilities;
|
||||
private inviteTimeout?: ReturnType<typeof setTimeout>;
|
||||
|
||||
// The logic of when & if a call is on hold is nontrivial and explained in is*OnHold
|
||||
@ -307,7 +308,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
// the stats for the call at the point it ended. We can't get these after we
|
||||
// tear the call down, so we just grab a snapshot before we stop the call.
|
||||
// The typescript definitions have this type as 'any' :(
|
||||
private callStatsAtEnd: any[];
|
||||
private callStatsAtEnd?: any[];
|
||||
|
||||
// Perfect negotiation state: https://www.w3.org/TR/webrtc/#perfect-negotiation-example
|
||||
private makingOffer = false;
|
||||
@ -318,9 +319,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
// the call) we buffer them up here so we can then add the ones from the party we pick
|
||||
private remoteCandidateBuffer = new Map<string, RTCIceCandidate[]>();
|
||||
|
||||
private remoteAssertedIdentity: AssertedIdentity;
|
||||
|
||||
private remoteSDPStreamMetadata: SDPStreamMetadata;
|
||||
private remoteAssertedIdentity?: AssertedIdentity;
|
||||
private remoteSDPStreamMetadata?: SDPStreamMetadata;
|
||||
|
||||
private callLengthInterval?: ReturnType<typeof setInterval>;
|
||||
private callLength = 0;
|
||||
@ -366,13 +366,13 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
* @param options An object providing configuration options for the data channel.
|
||||
*/
|
||||
public createDataChannel(label: string, options: RTCDataChannelInit) {
|
||||
const dataChannel = this.peerConn.createDataChannel(label, options);
|
||||
const dataChannel = this.peerConn!.createDataChannel(label, options);
|
||||
this.emit(CallEvent.DataChannel, dataChannel);
|
||||
logger.debug("created data channel");
|
||||
return dataChannel;
|
||||
}
|
||||
|
||||
public getOpponentMember(): RoomMember {
|
||||
public getOpponentMember(): RoomMember | undefined {
|
||||
return this.opponentMember;
|
||||
}
|
||||
|
||||
@ -384,7 +384,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
return Boolean(this.opponentCaps && this.opponentCaps["m.call.dtmf"]);
|
||||
}
|
||||
|
||||
public getRemoteAssertedIdentity(): AssertedIdentity {
|
||||
public getRemoteAssertedIdentity(): AssertedIdentity | undefined {
|
||||
return this.remoteAssertedIdentity;
|
||||
}
|
||||
|
||||
@ -395,64 +395,58 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
|
||||
public get hasLocalUserMediaVideoTrack(): boolean {
|
||||
return this.localUsermediaStream?.getVideoTracks().length > 0;
|
||||
return !!this.localUsermediaStream?.getVideoTracks().length;
|
||||
}
|
||||
|
||||
public get hasRemoteUserMediaVideoTrack(): boolean {
|
||||
return this.getRemoteFeeds().some((feed) => {
|
||||
return (
|
||||
feed.purpose === SDPStreamMetadataPurpose.Usermedia &&
|
||||
feed.stream.getVideoTracks().length > 0
|
||||
);
|
||||
return feed.purpose === SDPStreamMetadataPurpose.Usermedia && feed.stream?.getVideoTracks().length;
|
||||
});
|
||||
}
|
||||
|
||||
public get hasLocalUserMediaAudioTrack(): boolean {
|
||||
return this.localUsermediaStream?.getAudioTracks().length > 0;
|
||||
return !!this.localUsermediaStream?.getAudioTracks().length;
|
||||
}
|
||||
|
||||
public get hasRemoteUserMediaAudioTrack(): boolean {
|
||||
return this.getRemoteFeeds().some((feed) => {
|
||||
return (
|
||||
feed.purpose === SDPStreamMetadataPurpose.Usermedia &&
|
||||
feed.stream.getAudioTracks().length > 0
|
||||
);
|
||||
return feed.purpose === SDPStreamMetadataPurpose.Usermedia && !!feed.stream?.getAudioTracks().length;
|
||||
});
|
||||
}
|
||||
|
||||
public get localUsermediaFeed(): CallFeed {
|
||||
public get localUsermediaFeed(): CallFeed | undefined {
|
||||
return this.getLocalFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Usermedia);
|
||||
}
|
||||
|
||||
public get localScreensharingFeed(): CallFeed {
|
||||
public get localScreensharingFeed(): CallFeed | undefined {
|
||||
return this.getLocalFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare);
|
||||
}
|
||||
|
||||
public get localUsermediaStream(): MediaStream {
|
||||
public get localUsermediaStream(): MediaStream | undefined {
|
||||
return this.localUsermediaFeed?.stream;
|
||||
}
|
||||
|
||||
public get localScreensharingStream(): MediaStream {
|
||||
public get localScreensharingStream(): MediaStream | undefined {
|
||||
return this.localScreensharingFeed?.stream;
|
||||
}
|
||||
|
||||
public get remoteUsermediaFeed(): CallFeed {
|
||||
public get remoteUsermediaFeed(): CallFeed | undefined {
|
||||
return this.getRemoteFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Usermedia);
|
||||
}
|
||||
|
||||
public get remoteScreensharingFeed(): CallFeed {
|
||||
public get remoteScreensharingFeed(): CallFeed | undefined {
|
||||
return this.getRemoteFeeds().find((feed) => feed.purpose === SDPStreamMetadataPurpose.Screenshare);
|
||||
}
|
||||
|
||||
public get remoteUsermediaStream(): MediaStream {
|
||||
public get remoteUsermediaStream(): MediaStream | undefined {
|
||||
return this.remoteUsermediaFeed?.stream;
|
||||
}
|
||||
|
||||
public get remoteScreensharingStream(): MediaStream {
|
||||
public get remoteScreensharingStream(): MediaStream | undefined {
|
||||
return this.remoteScreensharingFeed?.stream;
|
||||
}
|
||||
|
||||
private getFeedByStreamId(streamId: string): CallFeed {
|
||||
private getFeedByStreamId(streamId: string): CallFeed | undefined {
|
||||
return this.getFeeds().find((feed) => feed.stream.id === streamId);
|
||||
}
|
||||
|
||||
@ -513,10 +507,10 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
return;
|
||||
}
|
||||
|
||||
const userId = this.getOpponentMember().userId;
|
||||
const purpose = this.remoteSDPStreamMetadata[stream.id].purpose;
|
||||
const audioMuted = this.remoteSDPStreamMetadata[stream.id].audio_muted;
|
||||
const videoMuted = this.remoteSDPStreamMetadata[stream.id].video_muted;
|
||||
const userId = this.getOpponentMember()!.userId;
|
||||
const purpose = this.remoteSDPStreamMetadata![stream.id].purpose;
|
||||
const audioMuted = this.remoteSDPStreamMetadata![stream.id].audio_muted;
|
||||
const videoMuted = this.remoteSDPStreamMetadata![stream.id].video_muted;
|
||||
|
||||
if (!purpose) {
|
||||
logger.warn(`Ignoring stream with id ${stream.id} because we didn't get any metadata about it`);
|
||||
@ -546,7 +540,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
* This method is used ONLY if the other client doesn't support sending SDPStreamMetadata
|
||||
*/
|
||||
private pushRemoteFeedWithoutMetadata(stream: MediaStream): void {
|
||||
const userId = this.getOpponentMember().userId;
|
||||
const userId = this.getOpponentMember()!.userId;
|
||||
// We can guess the purpose here since the other client can only send one stream
|
||||
const purpose = SDPStreamMetadataPurpose.Usermedia;
|
||||
const oldRemoteStream = this.feeds.find((feed) => !feed.isLocal())?.stream;
|
||||
@ -580,7 +574,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
|
||||
private pushNewLocalFeed(stream: MediaStream, purpose: SDPStreamMetadataPurpose, addToPeerConnection = true): void {
|
||||
const userId = this.client.getUserId();
|
||||
const userId = this.client.getUserId()!;
|
||||
|
||||
// Tracks don't always start off enabled, eg. chrome will give a disabled
|
||||
// audio track if you ask for user media audio and already had one that
|
||||
@ -631,7 +625,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
`enabled=${track.enabled}` +
|
||||
`) to peer connection`,
|
||||
);
|
||||
senderArray.push(this.peerConn.addTrack(track, callFeed.stream));
|
||||
senderArray.push(this.peerConn!.addTrack(track, callFeed.stream));
|
||||
}
|
||||
}
|
||||
|
||||
@ -656,7 +650,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
: this.screensharingSenders;
|
||||
|
||||
for (const sender of senderArray) {
|
||||
this.peerConn.removeTrack(sender);
|
||||
this.peerConn?.removeTrack(sender);
|
||||
}
|
||||
// Empty the array
|
||||
senderArray.splice(0, senderArray.length);
|
||||
@ -687,7 +681,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
|
||||
// The typescript definitions have this type as 'any' :(
|
||||
public async getCurrentCallStats(): Promise<any[]> {
|
||||
public async getCurrentCallStats(): Promise<any[] | undefined> {
|
||||
if (this.callHasEnded()) {
|
||||
return this.callStatsAtEnd;
|
||||
}
|
||||
@ -695,7 +689,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
return this.collectCallStats();
|
||||
}
|
||||
|
||||
private async collectCallStats(): Promise<any[]> {
|
||||
private async collectCallStats(): Promise<any[] | undefined> {
|
||||
// This happens when the call fails before it starts.
|
||||
// For example when we fail to get capture sources
|
||||
if (!this.peerConn) return;
|
||||
@ -765,8 +759,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.hangupParty = CallParty.Remote; // effectively
|
||||
this.setState(CallState.Ended);
|
||||
this.stopAllMedia();
|
||||
if (this.peerConn.signalingState != 'closed') {
|
||||
this.peerConn.close();
|
||||
if (this.peerConn?.signalingState != 'closed') {
|
||||
this.peerConn?.close();
|
||||
}
|
||||
this.emit(CallEvent.Hangup);
|
||||
}
|
||||
@ -786,7 +780,9 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
|
||||
private shouldAnswerWithMediaType(
|
||||
wantedValue: boolean | undefined, valueOfTheOtherSide: boolean | undefined, type: "audio" | "video",
|
||||
wantedValue: boolean | undefined,
|
||||
valueOfTheOtherSide: boolean | undefined,
|
||||
type: "audio" | "video",
|
||||
): boolean {
|
||||
if (wantedValue && !valueOfTheOtherSide) {
|
||||
// TODO: Figure out how to do this
|
||||
@ -832,7 +828,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
const usermediaFeed = new CallFeed({
|
||||
client: this.client,
|
||||
roomId: this.roomId,
|
||||
userId: this.client.getUserId(),
|
||||
userId: this.client.getUserId()!,
|
||||
stream,
|
||||
purpose: SDPStreamMetadataPurpose.Usermedia,
|
||||
audioMuted: false,
|
||||
@ -854,7 +850,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.waitForLocalAVStream = false;
|
||||
await this.answer(answerWithAudio, false);
|
||||
} else {
|
||||
this.getUserMediaFailed(e);
|
||||
this.getUserMediaFailed(<Error>e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -953,7 +949,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
} catch (error) {
|
||||
logger.error("Failed to upgrade the call", error);
|
||||
this.emit(CallEvent.Error,
|
||||
new CallError(CallErrorCode.NoUserMedia, "Failed to get camera access: ", error),
|
||||
new CallError(CallErrorCode.NoUserMedia, "Failed to get camera access: ", <Error>error),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1008,10 +1004,10 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
} else {
|
||||
for (const sender of this.screensharingSenders) {
|
||||
this.peerConn.removeTrack(sender);
|
||||
this.peerConn?.removeTrack(sender);
|
||||
}
|
||||
this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream);
|
||||
this.deleteFeedByStream(this.localScreensharingStream);
|
||||
this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream!);
|
||||
this.deleteFeedByStream(this.localScreensharingStream!);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1032,13 +1028,9 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
const stream = await this.client.getMediaHandler().getScreensharingStream(desktopCapturerSourceId);
|
||||
if (!stream) return false;
|
||||
|
||||
const track = stream.getTracks().find((track) => {
|
||||
return track.kind === "video";
|
||||
});
|
||||
const sender = this.usermediaSenders.find((sender) => {
|
||||
return sender.track?.kind === "video";
|
||||
});
|
||||
sender.replaceTrack(track);
|
||||
const track = stream.getTracks().find(track => track.kind === "video");
|
||||
const sender = this.usermediaSenders.find(sender => sender.track?.kind === "video");
|
||||
sender?.replaceTrack(track ?? null);
|
||||
|
||||
this.pushNewLocalFeed(stream, SDPStreamMetadataPurpose.Screenshare, false);
|
||||
|
||||
@ -1048,16 +1040,12 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const track = this.localUsermediaStream.getTracks().find((track) => {
|
||||
return track.kind === "video";
|
||||
});
|
||||
const sender = this.usermediaSenders.find((sender) => {
|
||||
return sender.track?.kind === "video";
|
||||
});
|
||||
sender.replaceTrack(track);
|
||||
const track = this.localUsermediaStream?.getTracks().find((track) => track.kind === "video");
|
||||
const sender = this.usermediaSenders.find((sender) => sender.track?.kind === "video");
|
||||
sender?.replaceTrack(track ?? null);
|
||||
|
||||
this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream);
|
||||
this.deleteFeedByStream(this.localScreensharingStream);
|
||||
this.client.getMediaHandler().stopScreensharingStream(this.localScreensharingStream!);
|
||||
this.deleteFeedByStream(this.localScreensharingStream!);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -1070,22 +1058,22 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
public async updateLocalUsermediaStream(
|
||||
stream: MediaStream, forceAudio = false, forceVideo = false,
|
||||
): Promise<void> {
|
||||
const callFeed = this.localUsermediaFeed;
|
||||
const callFeed = this.localUsermediaFeed!;
|
||||
const audioEnabled = forceAudio || (!callFeed.isAudioMuted() && !this.remoteOnHold);
|
||||
const videoEnabled = forceVideo || (!callFeed.isVideoMuted() && !this.remoteOnHold);
|
||||
setTracksEnabled(stream.getAudioTracks(), audioEnabled);
|
||||
setTracksEnabled(stream.getVideoTracks(), videoEnabled);
|
||||
|
||||
// We want to keep the same stream id, so we replace the tracks rather than the whole stream
|
||||
for (const track of this.localUsermediaStream.getTracks()) {
|
||||
this.localUsermediaStream.removeTrack(track);
|
||||
for (const track of this.localUsermediaStream!.getTracks()) {
|
||||
this.localUsermediaStream!.removeTrack(track);
|
||||
track.stop();
|
||||
}
|
||||
for (const track of stream.getTracks()) {
|
||||
this.localUsermediaStream.addTrack(track);
|
||||
this.localUsermediaStream!.addTrack(track);
|
||||
}
|
||||
|
||||
const newSenders = [];
|
||||
const newSenders: RTCRtpSender[] = [];
|
||||
|
||||
for (const track of stream.getTracks()) {
|
||||
const oldSender = this.usermediaSenders.find((sender) => sender.track?.kind === track.kind);
|
||||
@ -1111,7 +1099,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
`streamPurpose="${callFeed.purpose}"` +
|
||||
`) to peer connection`,
|
||||
);
|
||||
newSender = this.peerConn.addTrack(track, this.localUsermediaStream);
|
||||
newSender = this.peerConn!.addTrack(track, this.localUsermediaStream!);
|
||||
}
|
||||
|
||||
newSenders.push(newSender);
|
||||
@ -1149,7 +1137,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
* (including if the call is not set up yet).
|
||||
*/
|
||||
public isLocalVideoMuted(): boolean {
|
||||
return this.localUsermediaFeed?.isVideoMuted();
|
||||
return this.localUsermediaFeed?.isVideoMuted() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1181,7 +1169,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
* is not set up yet).
|
||||
*/
|
||||
public isMicrophoneMuted(): boolean {
|
||||
return this.localUsermediaFeed?.isAudioMuted();
|
||||
return this.localUsermediaFeed?.isAudioMuted() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1196,7 +1184,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
if (this.isRemoteOnHold() === onHold) return;
|
||||
this.remoteOnHold = onHold;
|
||||
|
||||
for (const transceiver of this.peerConn.getTransceivers()) {
|
||||
for (const transceiver of this.peerConn!.getTransceivers()) {
|
||||
// We don't send hold music or anything so we're not actually
|
||||
// sending anything, but sendrecv is fairly standard for hold and
|
||||
// it makes it a lot easier to figure out who's put who on hold.
|
||||
@ -1219,8 +1207,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
|
||||
// We consider a call to be on hold only if *all* the tracks are on hold
|
||||
// (is this the right thing to do?)
|
||||
for (const transceiver of this.peerConn.getTransceivers()) {
|
||||
const trackOnHold = ['inactive', 'recvonly'].includes(transceiver.currentDirection);
|
||||
for (const transceiver of this.peerConn!.getTransceivers()) {
|
||||
const trackOnHold = ['inactive', 'recvonly'].includes(transceiver.currentDirection!);
|
||||
|
||||
if (!trackOnHold) callOnHold = false;
|
||||
}
|
||||
@ -1233,8 +1221,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
* @param digit The digit (nb. string - '#' and '*' are dtmf too)
|
||||
*/
|
||||
public sendDtmfDigit(digit: string): void {
|
||||
for (const sender of this.peerConn.getSenders()) {
|
||||
if (sender.track.kind === 'audio' && sender.dtmf) {
|
||||
for (const sender of this.peerConn!.getSenders()) {
|
||||
if (sender.track?.kind === 'audio' && sender.dtmf) {
|
||||
sender.dtmf.insertDTMF(digit);
|
||||
return;
|
||||
}
|
||||
@ -1251,8 +1239,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
const micShouldBeMuted = this.isMicrophoneMuted() || this.remoteOnHold;
|
||||
const vidShouldBeMuted = this.isLocalVideoMuted() || this.remoteOnHold;
|
||||
|
||||
setTracksEnabled(this.localUsermediaStream.getAudioTracks(), !micShouldBeMuted);
|
||||
setTracksEnabled(this.localUsermediaStream.getVideoTracks(), !vidShouldBeMuted);
|
||||
setTracksEnabled(this.localUsermediaStream!.getAudioTracks(), !micShouldBeMuted);
|
||||
setTracksEnabled(this.localUsermediaStream!.getVideoTracks(), !vidShouldBeMuted);
|
||||
}
|
||||
|
||||
private gotCallFeedsForInvite(callFeeds: CallFeed[]): void {
|
||||
@ -1278,10 +1266,10 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
private async sendAnswer(): Promise<void> {
|
||||
const answerContent = {
|
||||
answer: {
|
||||
sdp: this.peerConn.localDescription.sdp,
|
||||
sdp: this.peerConn!.localDescription!.sdp,
|
||||
// type is now deprecated as of Matrix VoIP v1, but
|
||||
// required to still be sent for backwards compat
|
||||
type: this.peerConn.localDescription.type,
|
||||
type: this.peerConn!.localDescription!.type,
|
||||
},
|
||||
[SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(),
|
||||
} as MCallAnswer;
|
||||
@ -1305,15 +1293,15 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
} catch (error) {
|
||||
// We've failed to answer: back to the ringing state
|
||||
this.setState(CallState.Ringing);
|
||||
this.client.cancelPendingEvent(error.event);
|
||||
if (error instanceof MatrixError && error.event) this.client.cancelPendingEvent(error.event);
|
||||
|
||||
let code = CallErrorCode.SendAnswer;
|
||||
let message = "Failed to send answer";
|
||||
if (error.name == 'UnknownDeviceError') {
|
||||
if ((<Error>error).name == 'UnknownDeviceError') {
|
||||
code = CallErrorCode.UnknownDevices;
|
||||
message = "Unknown devices present in the room";
|
||||
}
|
||||
this.emit(CallEvent.Error, new CallError(code, message, error));
|
||||
this.emit(CallEvent.Error, new CallError(code, message, <Error>error));
|
||||
throw error;
|
||||
}
|
||||
|
||||
@ -1333,10 +1321,10 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
|
||||
this.setState(CallState.CreateAnswer);
|
||||
|
||||
let myAnswer;
|
||||
let myAnswer: RTCSessionDescriptionInit;
|
||||
try {
|
||||
this.getRidOfRTXCodecs();
|
||||
myAnswer = await this.peerConn.createAnswer();
|
||||
myAnswer = await this.peerConn!.createAnswer();
|
||||
} catch (err) {
|
||||
logger.debug("Failed to create answer: ", err);
|
||||
this.terminate(CallParty.Local, CallErrorCode.CreateAnswer, true);
|
||||
@ -1344,7 +1332,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
|
||||
try {
|
||||
await this.peerConn.setLocalDescription(myAnswer);
|
||||
await this.peerConn!.setLocalDescription(myAnswer);
|
||||
this.setState(CallState.Connecting);
|
||||
|
||||
// Allow a short time for initial candidates to be gathered
|
||||
@ -1364,7 +1352,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
* Internal
|
||||
* @param {Object} event
|
||||
*/
|
||||
private gotLocalIceCandidate = (event: RTCPeerConnectionIceEvent): Promise<void> => {
|
||||
private gotLocalIceCandidate = (event: RTCPeerConnectionIceEvent): void => {
|
||||
if (event.candidate) {
|
||||
logger.debug(
|
||||
"Call " + this.callId + " got local ICE " + event.candidate.sdpMid + " candidate: " +
|
||||
@ -1384,8 +1372,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
};
|
||||
|
||||
private onIceGatheringStateChange = (event: Event): void => {
|
||||
logger.debug("ice gathering state changed to " + this.peerConn.iceGatheringState);
|
||||
if (this.peerConn.iceGatheringState === 'complete' && !this.sentEndOfCandidates) {
|
||||
logger.debug("ice gathering state changed to " + this.peerConn?.iceGatheringState);
|
||||
if (this.peerConn?.iceGatheringState === 'complete' && !this.sentEndOfCandidates) {
|
||||
// If we didn't get an empty-string candidate to signal the end of candidates,
|
||||
// create one ourselves now gathering has finished.
|
||||
// We cast because the interface lists all the properties as required but we
|
||||
@ -1471,7 +1459,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
|
||||
try {
|
||||
await this.peerConn.setRemoteDescription(content.answer);
|
||||
await this.peerConn!.setRemoteDescription(content.answer);
|
||||
} catch (e) {
|
||||
logger.debug("Failed to set remote description", e);
|
||||
this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false);
|
||||
@ -1530,7 +1518,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Perfect_negotiation
|
||||
const offerCollision = (
|
||||
(description.type === 'offer') &&
|
||||
(this.makingOffer || this.peerConn.signalingState !== 'stable')
|
||||
(this.makingOffer || this.peerConn!.signalingState !== 'stable')
|
||||
);
|
||||
|
||||
this.ignoreOffer = !polite && offerCollision;
|
||||
@ -1549,15 +1537,15 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
|
||||
try {
|
||||
await this.peerConn.setRemoteDescription(description);
|
||||
await this.peerConn!.setRemoteDescription(description);
|
||||
|
||||
if (description.type === 'offer') {
|
||||
this.getRidOfRTXCodecs();
|
||||
const localDescription = await this.peerConn.createAnswer();
|
||||
await this.peerConn.setLocalDescription(localDescription);
|
||||
const localDescription = await this.peerConn!.createAnswer();
|
||||
await this.peerConn!.setLocalDescription(localDescription);
|
||||
|
||||
this.sendVoipEvent(EventType.CallNegotiate, {
|
||||
description: this.peerConn.localDescription,
|
||||
description: this.peerConn!.localDescription,
|
||||
[SDPStreamMetadataKey]: this.getLocalSDPStreamMetadata(),
|
||||
});
|
||||
}
|
||||
@ -1577,10 +1565,10 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.remoteSDPStreamMetadata = utils.recursivelyAssign(this.remoteSDPStreamMetadata || {}, metadata, true);
|
||||
for (const feed of this.getRemoteFeeds()) {
|
||||
const streamId = feed.stream.id;
|
||||
const metadata = this.remoteSDPStreamMetadata[streamId];
|
||||
const metadata = this.remoteSDPStreamMetadata![streamId];
|
||||
|
||||
feed.setAudioVideoMuted(metadata?.audio_muted, metadata?.video_muted);
|
||||
feed.purpose = this.remoteSDPStreamMetadata[streamId]?.purpose;
|
||||
feed.purpose = this.remoteSDPStreamMetadata![streamId]?.purpose;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1618,14 +1606,14 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
|
||||
try {
|
||||
await this.peerConn.setLocalDescription(description);
|
||||
await this.peerConn!.setLocalDescription(description);
|
||||
} catch (err) {
|
||||
logger.debug("Error setting local description!", err);
|
||||
this.terminate(CallParty.Local, CallErrorCode.SetLocalDescription, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.peerConn.iceGatheringState === 'gathering') {
|
||||
if (this.peerConn!.iceGatheringState === 'gathering') {
|
||||
// Allow a short time for initial candidates to be gathered
|
||||
await new Promise(resolve => {
|
||||
setTimeout(resolve, 200);
|
||||
@ -1642,9 +1630,9 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
|
||||
// clunky because TypeScript can't follow the types through if we use an expression as the key
|
||||
if (this.state === CallState.CreateOffer) {
|
||||
content.offer = this.peerConn.localDescription;
|
||||
content.offer = this.peerConn!.localDescription!;
|
||||
} else {
|
||||
content.description = this.peerConn.localDescription;
|
||||
content.description = this.peerConn!.localDescription!;
|
||||
}
|
||||
|
||||
content.capabilities = {
|
||||
@ -1663,7 +1651,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
await this.sendVoipEvent(eventType, content);
|
||||
} catch (error) {
|
||||
logger.error("Failed to send invite", error);
|
||||
if (error.event) this.client.cancelPendingEvent(error.event);
|
||||
if (error instanceof MatrixError && error.event) this.client.cancelPendingEvent(error.event);
|
||||
|
||||
let code = CallErrorCode.SignallingFailed;
|
||||
let message = "Signalling failed";
|
||||
@ -1671,12 +1659,12 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
code = CallErrorCode.SendInvite;
|
||||
message = "Failed to send invite";
|
||||
}
|
||||
if (error.name == 'UnknownDeviceError') {
|
||||
if ((<Error>error).name == 'UnknownDeviceError') {
|
||||
code = CallErrorCode.UnknownDevices;
|
||||
message = "Unknown devices present in the room";
|
||||
}
|
||||
|
||||
this.emit(CallEvent.Error, new CallError(code, message, error));
|
||||
this.emit(CallEvent.Error, new CallError(code, message, <Error>error));
|
||||
this.terminate(CallParty.Local, code, false);
|
||||
|
||||
// no need to carry on & send the candidate queue, but we also
|
||||
@ -1734,11 +1722,11 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
return; // because ICE can still complete as we're ending the call
|
||||
}
|
||||
logger.debug(
|
||||
"Call ID " + this.callId + ": ICE connection state changed to: " + this.peerConn.iceConnectionState,
|
||||
"Call ID " + this.callId + ": ICE connection state changed to: " + this.peerConn?.iceConnectionState,
|
||||
);
|
||||
// ideally we'd consider the call to be connected when we get media but
|
||||
// chrome doesn't implement any of the 'onstarted' events yet
|
||||
if (this.peerConn.iceConnectionState == 'connected') {
|
||||
if (this.peerConn?.iceConnectionState == 'connected') {
|
||||
this.setState(CallState.Connected);
|
||||
|
||||
if (!this.callLengthInterval) {
|
||||
@ -1747,16 +1735,13 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.emit(CallEvent.LengthChanged, this.callLength);
|
||||
}, 1000);
|
||||
}
|
||||
} else if (this.peerConn.iceConnectionState == 'failed') {
|
||||
} else if (this.peerConn?.iceConnectionState == 'failed') {
|
||||
this.hangup(CallErrorCode.IceFailed, false);
|
||||
}
|
||||
};
|
||||
|
||||
private onSignallingStateChanged = (): void => {
|
||||
logger.debug(
|
||||
"call " + this.callId + ": Signalling state changed to: " +
|
||||
this.peerConn.signalingState,
|
||||
);
|
||||
logger.debug(`call ${this.callId}: Signalling state changed to: ${this.peerConn?.signalingState}`);
|
||||
};
|
||||
|
||||
private onTrack = (ev: RTCTrackEvent): void => {
|
||||
@ -1796,8 +1781,8 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
// RTCRtpReceiver.getCapabilities and RTCRtpSender.getCapabilities don't seem to be supported on FF
|
||||
if (!RTCRtpReceiver.getCapabilities || !RTCRtpSender.getCapabilities) return;
|
||||
|
||||
const recvCodecs = RTCRtpReceiver.getCapabilities("video").codecs;
|
||||
const sendCodecs = RTCRtpSender.getCapabilities("video").codecs;
|
||||
const recvCodecs = RTCRtpReceiver.getCapabilities("video")!.codecs;
|
||||
const sendCodecs = RTCRtpSender.getCapabilities("video")!.codecs;
|
||||
const codecs = [...sendCodecs, ...recvCodecs];
|
||||
|
||||
for (const codec of codecs) {
|
||||
@ -1807,7 +1792,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
}
|
||||
|
||||
for (const trans of this.peerConn.getTransceivers()) {
|
||||
for (const trans of this.peerConn!.getTransceivers()) {
|
||||
if (
|
||||
this.screensharingSenders.includes(trans.sender) &&
|
||||
(
|
||||
@ -1831,10 +1816,10 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.makingOffer = true;
|
||||
try {
|
||||
this.getRidOfRTXCodecs();
|
||||
const myOffer = await this.peerConn.createOffer();
|
||||
const myOffer = await this.peerConn!.createOffer();
|
||||
await this.gotLocalOffer(myOffer);
|
||||
} catch (e) {
|
||||
this.getLocalOfferFailed(e);
|
||||
this.getLocalOfferFailed(<Error>e);
|
||||
return;
|
||||
} finally {
|
||||
this.makingOffer = false;
|
||||
@ -1961,9 +1946,11 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
* Transfers this call to the target call, effectively 'joining' the
|
||||
* two calls (so the remote parties on each call are connected together).
|
||||
*/
|
||||
public async transferToCall(transferTargetCall?: MatrixCall): Promise<void> {
|
||||
const targetProfileInfo = await this.client.getProfileInfo(transferTargetCall.getOpponentMember().userId);
|
||||
const transfereeProfileInfo = await this.client.getProfileInfo(this.getOpponentMember().userId);
|
||||
public async transferToCall(transferTargetCall: MatrixCall): Promise<void> {
|
||||
const targetUserId = transferTargetCall.getOpponentMember()?.userId;
|
||||
const targetProfileInfo = targetUserId ? await this.client.getProfileInfo(targetUserId) : undefined;
|
||||
const opponentUserId = this.getOpponentMember()?.userId;
|
||||
const transfereeProfileInfo = opponentUserId ? await this.client.getProfileInfo(opponentUserId) : undefined;
|
||||
|
||||
const newCallId = genCallID();
|
||||
|
||||
@ -1972,9 +1959,9 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
// ID of the new call (but we can use the same function to generate it)
|
||||
replacement_id: genCallID(),
|
||||
target_user: {
|
||||
id: this.getOpponentMember().userId,
|
||||
display_name: transfereeProfileInfo.displayname,
|
||||
avatar_url: transfereeProfileInfo.avatar_url,
|
||||
id: opponentUserId,
|
||||
display_name: transfereeProfileInfo?.displayname,
|
||||
avatar_url: transfereeProfileInfo?.avatar_url,
|
||||
},
|
||||
await_call: newCallId,
|
||||
} as MCallReplacesEvent;
|
||||
@ -1984,9 +1971,9 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
const bodyToTransferee = {
|
||||
replacement_id: genCallID(),
|
||||
target_user: {
|
||||
id: transferTargetCall.getOpponentMember().userId,
|
||||
display_name: targetProfileInfo.displayname,
|
||||
avatar_url: targetProfileInfo.avatar_url,
|
||||
id: targetUserId,
|
||||
display_name: targetProfileInfo?.displayname,
|
||||
avatar_url: targetProfileInfo?.avatar_url,
|
||||
},
|
||||
create_call: newCallId,
|
||||
} as MCallReplacesEvent;
|
||||
@ -2072,7 +2059,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
} catch (error) {
|
||||
// don't retry this event: we'll send another one later as we might
|
||||
// have more candidates by then.
|
||||
if (error.event) this.client.cancelPendingEvent(error.event);
|
||||
if (error instanceof MatrixError && error.event) this.client.cancelPendingEvent(error.event);
|
||||
|
||||
// put all the candidates we failed to send back in the queue
|
||||
this.candidateSendQueue.push(...candidates);
|
||||
@ -2086,7 +2073,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
const code = CallErrorCode.SignallingFailed;
|
||||
const message = "Signalling failed";
|
||||
|
||||
this.emit(CallEvent.Error, new CallError(code, message, error));
|
||||
this.emit(CallEvent.Error, new CallError(code, message, <Error>error));
|
||||
this.hangup(code, false);
|
||||
|
||||
return;
|
||||
@ -2123,7 +2110,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
const callFeed = new CallFeed({
|
||||
client: this.client,
|
||||
roomId: this.roomId,
|
||||
userId: this.client.getUserId(),
|
||||
userId: this.client.getUserId()!,
|
||||
stream,
|
||||
purpose: SDPStreamMetadataPurpose.Usermedia,
|
||||
audioMuted: false,
|
||||
@ -2131,7 +2118,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
});
|
||||
await this.placeCallWithCallFeeds([callFeed]);
|
||||
} catch (e) {
|
||||
this.getUserMediaFailed(e);
|
||||
this.getUserMediaFailed(<Error>e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -2214,12 +2201,12 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
}
|
||||
|
||||
private async addBufferedIceCandidates(): Promise<void> {
|
||||
const bufferedCandidates = this.remoteCandidateBuffer.get(this.opponentPartyId);
|
||||
const bufferedCandidates = this.remoteCandidateBuffer.get(this.opponentPartyId!);
|
||||
if (bufferedCandidates) {
|
||||
logger.info(`Adding ${bufferedCandidates.length} buffered candidates for opponent ${this.opponentPartyId}`);
|
||||
await this.addIceCandidates(bufferedCandidates);
|
||||
}
|
||||
this.remoteCandidateBuffer = null;
|
||||
this.remoteCandidateBuffer.clear();
|
||||
}
|
||||
|
||||
private async addIceCandidates(candidates: RTCIceCandidate[]): Promise<void> {
|
||||
@ -2235,7 +2222,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
"Call " + this.callId + " got remote ICE " + candidate.sdpMid + " candidate: " + candidate.candidate,
|
||||
);
|
||||
try {
|
||||
await this.peerConn.addIceCandidate(candidate);
|
||||
await this.peerConn!.addIceCandidate(candidate);
|
||||
} catch (err) {
|
||||
if (!this.ignoreOffer) {
|
||||
logger.info("Failed to add remote ICE candidate", err);
|
||||
@ -2299,7 +2286,11 @@ export function supportsMatrixCall(): boolean {
|
||||
* since it's only possible to set this option on outbound calls.
|
||||
* @return {MatrixCall} the call or null if the browser doesn't support calling.
|
||||
*/
|
||||
export function createNewMatrixCall(client: any, roomId: string, options?: CallOpts): MatrixCall | null {
|
||||
export function createNewMatrixCall(
|
||||
client: MatrixClient,
|
||||
roomId: string,
|
||||
options?: Pick<CallOpts, "forceTURN">,
|
||||
): MatrixCall | null {
|
||||
if (!supportsMatrixCall()) return null;
|
||||
|
||||
const optionsForceTURN = options ? options.forceTURN : false;
|
||||
|
@ -163,7 +163,7 @@ export class CallEventHandler {
|
||||
this.client,
|
||||
event.getRoomId(),
|
||||
{ forceTURN: this.client.forceTURN },
|
||||
);
|
||||
) ?? undefined;
|
||||
if (!call) {
|
||||
logger.log(
|
||||
"Incoming call ID " + content.call_id + " but this client " +
|
||||
@ -181,13 +181,13 @@ export class CallEventHandler {
|
||||
|
||||
// if we stashed candidate events for that call ID, play them back now
|
||||
if (this.candidateEventsByCall.get(call.callId)) {
|
||||
for (const ev of this.candidateEventsByCall.get(call.callId)) {
|
||||
for (const ev of this.candidateEventsByCall.get(call.callId)!) {
|
||||
call.onRemoteIceCandidatesReceived(ev);
|
||||
}
|
||||
}
|
||||
|
||||
// Were we trying to call that user (room)?
|
||||
let existingCall: MatrixCall;
|
||||
let existingCall: MatrixCall | undefined;
|
||||
for (const thisCall of this.calls.values()) {
|
||||
const isCalling = [CallState.WaitLocalMedia, CallState.CreateOffer, CallState.InviteSent].includes(
|
||||
thisCall.state,
|
||||
@ -238,7 +238,7 @@ export class CallEventHandler {
|
||||
if (!this.candidateEventsByCall.has(content.call_id)) {
|
||||
this.candidateEventsByCall.set(content.call_id, []);
|
||||
}
|
||||
this.candidateEventsByCall.get(content.call_id).push(event);
|
||||
this.candidateEventsByCall.get(content.call_id)!.push(event);
|
||||
} else {
|
||||
call.onRemoteIceCandidatesReceived(event);
|
||||
}
|
||||
@ -250,7 +250,7 @@ export class CallEventHandler {
|
||||
// if not live, store the fact that the call has ended because
|
||||
// we're probably getting events backwards so
|
||||
// the hangup will come before the invite
|
||||
call = createNewMatrixCall(this.client, event.getRoomId());
|
||||
call = createNewMatrixCall(this.client, event.getRoomId()) ?? undefined;
|
||||
if (call) {
|
||||
call.callId = content.call_id;
|
||||
call.initWithHangup(event);
|
||||
|
@ -64,12 +64,12 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
||||
private audioMuted: boolean;
|
||||
private videoMuted: boolean;
|
||||
private measuringVolumeActivity = false;
|
||||
private audioContext: AudioContext;
|
||||
private analyser: AnalyserNode;
|
||||
private frequencyBinCount: Float32Array;
|
||||
private audioContext?: AudioContext;
|
||||
private analyser?: AnalyserNode;
|
||||
private frequencyBinCount?: Float32Array;
|
||||
private speakingThreshold = SPEAKING_THRESHOLD;
|
||||
private speaking = false;
|
||||
private volumeLooperTimeout: ReturnType<typeof setTimeout>;
|
||||
private volumeLooperTimeout?: ReturnType<typeof setTimeout>;
|
||||
|
||||
constructor(opts: ICallFeedOpts) {
|
||||
super();
|
||||
@ -83,6 +83,7 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
||||
this.speakingVolumeSamples = new Array(SPEAKING_SAMPLE_COUNT).fill(-Infinity);
|
||||
|
||||
this.updateStream(null, opts.stream);
|
||||
this.stream = opts.stream; // updateStream does this, but this makes TS happier
|
||||
|
||||
if (this.hasAudioTrack) {
|
||||
this.initVolumeMeasuring();
|
||||
@ -93,22 +94,21 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
||||
return this.stream.getAudioTracks().length > 0;
|
||||
}
|
||||
|
||||
private updateStream(oldStream: MediaStream, newStream: MediaStream): void {
|
||||
private updateStream(oldStream: MediaStream | null, newStream: MediaStream): void {
|
||||
if (newStream === oldStream) return;
|
||||
|
||||
if (oldStream) {
|
||||
oldStream.removeEventListener("addtrack", this.onAddTrack);
|
||||
this.measureVolumeActivity(false);
|
||||
}
|
||||
if (newStream) {
|
||||
this.stream = newStream;
|
||||
newStream.addEventListener("addtrack", this.onAddTrack);
|
||||
|
||||
if (this.hasAudioTrack) {
|
||||
this.initVolumeMeasuring();
|
||||
} else {
|
||||
this.measureVolumeActivity(false);
|
||||
}
|
||||
this.stream = newStream;
|
||||
newStream.addEventListener("addtrack", this.onAddTrack);
|
||||
|
||||
if (this.hasAudioTrack) {
|
||||
this.initVolumeMeasuring();
|
||||
} else {
|
||||
this.measureVolumeActivity(false);
|
||||
}
|
||||
|
||||
this.emit(CallFeedEvent.NewStream, this.stream);
|
||||
@ -138,9 +138,9 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
||||
* Returns callRoom member
|
||||
* @returns member of the callRoom
|
||||
*/
|
||||
public getMember(): RoomMember {
|
||||
public getMember(): RoomMember | null {
|
||||
const callRoom = this.client.getRoom(this.roomId);
|
||||
return callRoom.getMember(this.userId);
|
||||
return callRoom?.getMember(this.userId) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -177,9 +177,10 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
||||
/**
|
||||
* Set one or both of feed's internal audio and video video mute state
|
||||
* Either value may be null to leave it as-is
|
||||
* @param muted is the feed's video muted?
|
||||
* @param audioMuted is the feed's audio muted?
|
||||
* @param videoMuted is the feed's video muted?
|
||||
*/
|
||||
public setAudioVideoMuted(audioMuted: boolean, videoMuted: boolean): void {
|
||||
public setAudioVideoMuted(audioMuted: boolean | null, videoMuted: boolean | null): void {
|
||||
if (audioMuted !== null) {
|
||||
if (this.audioMuted !== audioMuted) {
|
||||
this.speakingVolumeSamples.fill(-Infinity);
|
||||
@ -216,12 +217,12 @@ export class CallFeed extends TypedEventEmitter<CallFeedEvent, EventHandlerMap>
|
||||
|
||||
if (!this.measuringVolumeActivity) return;
|
||||
|
||||
this.analyser.getFloatFrequencyData(this.frequencyBinCount);
|
||||
this.analyser.getFloatFrequencyData(this.frequencyBinCount!);
|
||||
|
||||
let maxVolume = -Infinity;
|
||||
for (let i = 0; i < this.frequencyBinCount.length; i++) {
|
||||
if (this.frequencyBinCount[i] > maxVolume) {
|
||||
maxVolume = this.frequencyBinCount[i];
|
||||
for (let i = 0; i < this.frequencyBinCount!.length; i++) {
|
||||
if (this.frequencyBinCount![i] > maxVolume) {
|
||||
maxVolume = this.frequencyBinCount![i];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,8 +22,8 @@ import { MatrixClient } from "../client";
|
||||
import { CallState } from "./call";
|
||||
|
||||
export class MediaHandler {
|
||||
private audioInput: string;
|
||||
private videoInput: string;
|
||||
private audioInput?: string;
|
||||
private videoInput?: string;
|
||||
private localUserMediaStream?: MediaStream;
|
||||
public userMediaStreams: MediaStream[] = [];
|
||||
public screensharingStreams: MediaStream[] = [];
|
||||
@ -75,7 +75,7 @@ export class MediaHandler {
|
||||
for (const call of this.client.callEventHandler.calls.values()) {
|
||||
if (call.state === CallState.Ended || !callMediaStreamParams.has(call.callId)) continue;
|
||||
|
||||
const { audio, video } = callMediaStreamParams.get(call.callId);
|
||||
const { audio, video } = callMediaStreamParams.get(call.callId)!;
|
||||
|
||||
// This stream won't be reusable as we will replace the tracks of the old stream
|
||||
const stream = await this.getUserMediaStream(audio, video, false);
|
||||
@ -121,9 +121,9 @@ export class MediaHandler {
|
||||
const settings = track.getSettings();
|
||||
|
||||
if (track.kind === "audio") {
|
||||
this.audioInput = settings.deviceId;
|
||||
this.audioInput = settings.deviceId!;
|
||||
} else if (track.kind === "video") {
|
||||
this.videoInput = settings.deviceId;
|
||||
this.videoInput = settings.deviceId!;
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,7 +179,10 @@ export class MediaHandler {
|
||||
* @param reusable is allowed to be reused by the MediaHandler
|
||||
* @returns {MediaStream} based on passed parameters
|
||||
*/
|
||||
public async getScreensharingStream(desktopCapturerSourceId: string, reusable = true): Promise<MediaStream | null> {
|
||||
public async getScreensharingStream(
|
||||
desktopCapturerSourceId?: string,
|
||||
reusable = true,
|
||||
): Promise<MediaStream | null> {
|
||||
let stream: MediaStream;
|
||||
|
||||
if (this.screensharingStreams.length === 0) {
|
||||
|
Reference in New Issue
Block a user