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

Enable noImplicitAny (#2895)

* Stash noImplicitAny work

* Enable noImplicitAny

* Update olm

* Fun

* Fix msgid stuff

* Fix tests

* Attempt to fix Browserify
This commit is contained in:
Michael Telatynski
2022-12-06 18:21:44 +00:00
committed by GitHub
parent 6f81371e61
commit 8d018f9c2d
83 changed files with 1615 additions and 1428 deletions

View File

@ -64,7 +64,7 @@ jobs:
- name: Generate Docs
run: "yarn run gendoc"
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
@ -72,38 +72,3 @@ jobs:
path: _docs
# We'll only use this in a workflow_run, then we're done with it
retention-days: 1
tsc-strict:
name: Typescript Strict Error Checker
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
pull-requests: read
checks: write
steps:
- uses: actions/checkout@v3
- name: Get diff lines
id: diff
uses: Equip-Collaboration/diff-line-numbers@v1.0.0
with:
include: '["\\.tsx?$"]'
- name: Detecting files changed
id: files
uses: futuratrepadeira/changed-files@v4.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
pattern: '^.*\.tsx?$'
- uses: t3chguy/typescript-check-action@main
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
use-check: false
check-fail-mode: added
output-behaviour: annotate
ts-extra-args: '--noImplicitAny'
files-changed: ${{ steps.files.outputs.files_updated }}
files-added: ${{ steps.files.outputs.files_created }}
files-deleted: ${{ steps.files.outputs.files_deleted }}
line-numbers: ${{ steps.diff.outputs.lineNumbers }}

View File

@ -14,7 +14,7 @@
"build:dev": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:types",
"build:types": "tsc -p tsconfig-build.json --emitDeclarationOnly",
"build:compile": "babel -d lib --verbose --extensions \".ts,.js\" src",
"build:compile-browser": "mkdirp dist && browserify -d src/browser-index.js -p [ tsify -p ./tsconfig-build.json ] -t [ babelify --sourceMaps=inline --presets [ @babel/preset-env @babel/preset-typescript ] ] | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js",
"build:compile-browser": "mkdirp dist && browserify -d src/browser-index.ts -p [ tsify -p ./tsconfig-build.json ] -t [ babelify --sourceMaps=inline --presets [ @babel/preset-env @babel/preset-typescript ] ] | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js",
"build:minify-browser": "terser dist/browser-matrix.js --compress --mangle --source-map --output dist/browser-matrix.min.js",
"gendoc": "typedoc",
"lint": "yarn lint:types && yarn lint:js",
@ -33,9 +33,9 @@
"matrix-org"
],
"main": "./src/index.ts",
"browser": "./lib/browser-index.js",
"browser": "./lib/browser-index.ts",
"matrix_src_main": "./src/index.ts",
"matrix_src_browser": "./src/browser-index.js",
"matrix_src_browser": "./src/browser-index.ts",
"matrix_lib_main": "./lib/index.js",
"matrix_lib_typings": "./lib/index.d.ts",
"author": "matrix.org",
@ -80,7 +80,7 @@
"@babel/preset-typescript": "^7.12.7",
"@babel/register": "^7.12.10",
"@casualbot/jest-sonar-reporter": "^2.2.5",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.13.tgz",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz",
"@types/bs58": "^4.0.1",
"@types/content-type": "^1.1.5",
"@types/domexception": "^4.0.0",

View File

@ -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! as Record<string, any>)[userId]).toEqual([]);
});
return response;
});

View File

@ -15,19 +15,7 @@ limitations under the License.
*/
import "../../dist/browser-matrix"; // uses browser-matrix instead of the src
import type { MatrixClient, ClientEvent } from "../../src";
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {
interface Global {
matrixcs: {
MatrixClient: typeof MatrixClient;
ClientEvent: typeof ClientEvent;
};
}
}
}
import type { default as BrowserMatrix } from "../../src/browser-index";
// stub for browser-matrix browserify tests
// @ts-ignore
@ -43,4 +31,4 @@ afterAll(() => {
global.matrixcs = {
...global.matrixcs,
timeoutSignal: () => new AbortController().signal,
};
} as typeof BrowserMatrix;

View File

@ -16,7 +16,7 @@ limitations under the License.
import HttpBackend from "matrix-mock-request";
import "./setupTests";// uses browser-matrix instead of the src
import "./setupTests"; // uses browser-matrix instead of the src
import type { MatrixClient } from "../../src";
const USER_ID = "@user:test.server";
@ -65,15 +65,16 @@ describe("Browserify Test", function() {
const syncData = {
next_batch: "batch1",
rooms: {
join: {},
},
};
syncData.rooms.join[ROOM_ID] = {
timeline: {
events: [
event,
],
limited: false,
join: {
[ROOM_ID]: {
timeline: {
events: [
event,
],
limited: false,
},
},
},
},
};

View File

@ -30,7 +30,7 @@ const ROOM_ID = "!room:id";
*
* @return {object} sync response
*/
function getSyncResponse(roomMembers) {
function getSyncResponse(roomMembers: string[]) {
const stateEvents = [
testUtils.mkEvent({
type: 'm.room.encryption',
@ -43,12 +43,10 @@ function getSyncResponse(roomMembers) {
Array.prototype.push.apply(
stateEvents,
roomMembers.map(
(m) => testUtils.mkMembership({
mship: 'join',
sender: m,
}),
),
roomMembers.map((m) => testUtils.mkMembership({
mship: 'join',
sender: m,
})),
);
const syncResponse = {
@ -73,8 +71,8 @@ describe("DeviceList management:", function() {
return;
}
let sessionStoreBackend;
let aliceTestClient;
let aliceTestClient: TestClient;
let sessionStoreBackend: Storage;
async function createTestClient() {
const testClient = new TestClient(
@ -97,7 +95,10 @@ describe("DeviceList management:", function() {
});
it("Alice shouldn't do a second /query for non-e2e-capable devices", function() {
aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} } });
aliceTestClient.expectKeyQuery({
device_keys: { '@alice:localhost': {} },
failures: {},
});
return aliceTestClient.start().then(function() {
const syncResponse = getSyncResponse(['@bob:xyz']);
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
@ -138,7 +139,10 @@ describe("DeviceList management:", function() {
it.skip("We should not get confused by out-of-order device query responses", () => {
// https://github.com/vector-im/element-web/issues/3126
aliceTestClient.expectKeyQuery({ device_keys: { '@alice:localhost': {} } });
aliceTestClient.expectKeyQuery({
device_keys: { '@alice:localhost': {} },
failures: {},
});
return aliceTestClient.start().then(() => {
aliceTestClient.httpBackend.when('GET', '/sync').respond(
200, getSyncResponse(['@bob:xyz', '@chris:abc']));
@ -164,11 +168,12 @@ describe("DeviceList management:", function() {
aliceTestClient.httpBackend.flush('/keys/query', 1).then(
() => aliceTestClient.httpBackend.flush('/send/', 1),
),
aliceTestClient.client.crypto.deviceList.saveIfDirty(),
aliceTestClient.client.crypto!.deviceList.saveIfDirty(),
]);
}).then(() => {
aliceTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
expect(data.syncToken).toEqual(1);
// @ts-ignore accessing a protected field
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
expect(data!.syncToken).toEqual(1);
});
// invalidate bob's and chris's device lists in separate syncs
@ -201,15 +206,16 @@ describe("DeviceList management:", function() {
return aliceTestClient.httpBackend.flush('/keys/query', 1);
}).then((flushed) => {
expect(flushed).toEqual(0);
return aliceTestClient.client.crypto.deviceList.saveIfDirty();
return aliceTestClient.client.crypto!.deviceList.saveIfDirty();
}).then(() => {
aliceTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
// @ts-ignore accessing a protected field
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
const bobStat = data!.trackingStatus['@bob:xyz'];
if (bobStat != 1 && bobStat != 2) {
throw new Error('Unexpected status for bob: wanted 1 or 2, got ' +
bobStat);
}
const chrisStat = data.trackingStatus['@chris:abc'];
const chrisStat = data!.trackingStatus['@chris:abc'];
if (chrisStat != 1 && chrisStat != 2) {
throw new Error(
'Unexpected status for chris: wanted 1 or 2, got ' + chrisStat,
@ -234,12 +240,13 @@ describe("DeviceList management:", function() {
// wait for the client to stop processing the response
return aliceTestClient.client.downloadKeys(['@bob:xyz']);
}).then(() => {
return aliceTestClient.client.crypto.deviceList.saveIfDirty();
return aliceTestClient.client.crypto!.deviceList.saveIfDirty();
}).then(() => {
aliceTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
// @ts-ignore accessing a protected field
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
const bobStat = data!.trackingStatus['@bob:xyz'];
expect(bobStat).toEqual(3);
const chrisStat = data.trackingStatus['@chris:abc'];
const chrisStat = data!.trackingStatus['@chris:abc'];
if (chrisStat != 1 && chrisStat != 2) {
throw new Error(
'Unexpected status for chris: wanted 1 or 2, got ' + bobStat,
@ -255,15 +262,16 @@ describe("DeviceList management:", function() {
// wait for the client to stop processing the response
return aliceTestClient.client.downloadKeys(['@chris:abc']);
}).then(() => {
return aliceTestClient.client.crypto.deviceList.saveIfDirty();
return aliceTestClient.client.crypto!.deviceList.saveIfDirty();
}).then(() => {
aliceTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
const chrisStat = data.trackingStatus['@bob:xyz'];
// @ts-ignore accessing a protected field
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
const bobStat = data!.trackingStatus['@bob:xyz'];
const chrisStat = data!.trackingStatus['@bob:xyz'];
expect(bobStat).toEqual(3);
expect(chrisStat).toEqual(3);
expect(data.syncToken).toEqual(3);
expect(data!.syncToken).toEqual(3);
});
});
});
@ -285,10 +293,11 @@ describe("DeviceList management:", function() {
},
);
await aliceTestClient.httpBackend.flush('/keys/query', 1);
await aliceTestClient.client.crypto.deviceList.saveIfDirty();
await aliceTestClient.client.crypto!.deviceList.saveIfDirty();
aliceTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
// @ts-ignore accessing a protected field
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
const bobStat = data!.trackingStatus['@bob:xyz'];
// Alice should be tracking bob's device list
expect(bobStat).toBeGreaterThan(
@ -322,10 +331,11 @@ describe("DeviceList management:", function() {
);
await aliceTestClient.flushSync();
await aliceTestClient.client.crypto.deviceList.saveIfDirty();
await aliceTestClient.client.crypto!.deviceList.saveIfDirty();
aliceTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
// @ts-ignore accessing a protected field
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
const bobStat = data!.trackingStatus['@bob:xyz'];
// Alice should have marked bob's device list as untracked
expect(bobStat).toEqual(
@ -359,15 +369,14 @@ describe("DeviceList management:", function() {
);
await aliceTestClient.flushSync();
await aliceTestClient.client.crypto.deviceList.saveIfDirty();
await aliceTestClient.client.crypto!.deviceList.saveIfDirty();
aliceTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data.trackingStatus['@bob:xyz'];
// @ts-ignore accessing a protected field
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
const bobStat = data!.trackingStatus['@bob:xyz'];
// Alice should have marked bob's device list as untracked
expect(bobStat).toEqual(
0,
);
expect(bobStat).toEqual(0);
});
});
@ -388,9 +397,7 @@ describe("DeviceList management:", function() {
const bobStat = data!.trackingStatus['@bob:xyz'];
// Alice should have marked bob's device list as untracked
expect(bobStat).toEqual(
0,
);
expect(bobStat).toEqual(0);
});
} finally {
anotherTestClient.stop();

View File

@ -28,12 +28,14 @@ limitations under the License.
// load olm before the sdk if possible
import '../olm-loader';
import type { Session } from "@matrix-org/olm";
import { logger } from '../../src/logger';
import * as testUtils from "../test-utils/test-utils";
import { TestClient } from "../TestClient";
import { CRYPTO_ENABLED, IUploadKeysRequest } from "../../src/client";
import { CRYPTO_ENABLED, IClaimKeysRequest, IQueryKeysRequest, IUploadKeysRequest } from "../../src/client";
import { ClientEvent, IContent, ISendEventResponse, MatrixClient, MatrixEvent } from "../../src/matrix";
import { DeviceInfo } from '../../src/crypto/deviceinfo';
import { IDeviceKeys, IOneTimeKey } from "../../src/crypto/dehydration";
let aliTestClient: TestClient;
const roomId = "!room:localhost";
@ -47,11 +49,7 @@ const bobAccessToken = "fewgfkuesa";
let aliMessages: IContent[];
let bobMessages: IContent[];
// IMessage isn't exported by src/crypto/algorithms/olm.ts
interface OlmPayload {
type: number;
body: string;
}
type OlmPayload = ReturnType<Session["encrypt"]>;
async function bobUploadsDeviceKeys(): Promise<void> {
bobTestClient.expectDeviceKeyUpload();
@ -71,12 +69,12 @@ function expectQueryKeys(querier: TestClient, uploader: TestClient): Promise<num
// can't query keys before bob has uploaded them
expect(uploader.deviceKeys).toBeTruthy();
const uploaderKeys = {};
uploaderKeys[uploader.deviceId!] = uploader.deviceKeys;
const uploaderKeys: Record<string, IDeviceKeys> = {};
uploaderKeys[uploader.deviceId!] = uploader.deviceKeys!;
querier.httpBackend.when("POST", "/keys/query")
.respond(200, function(_path, content: IUploadKeysRequest) {
.respond(200, function(_path, content: IQueryKeysRequest) {
expect(content.device_keys![uploader.userId!]).toEqual([]);
const result = {};
const result: Record<string, Record<string, IDeviceKeys>> = {};
result[uploader.userId!] = uploaderKeys;
return { device_keys: result };
});
@ -94,7 +92,7 @@ async function expectAliClaimKeys(): Promise<void> {
const keys = await bobTestClient.awaitOneTimeKeyUpload();
aliTestClient.httpBackend.when(
"POST", "/keys/claim",
).respond(200, function(_path, content: IUploadKeysRequest) {
).respond(200, function(_path, content: IClaimKeysRequest) {
const claimType = content.one_time_keys![bobUserId][bobDeviceId];
expect(claimType).toEqual("signed_curve25519");
let keyId = '';
@ -105,7 +103,7 @@ async function expectAliClaimKeys(): Promise<void> {
}
}
}
const result = {};
const result: Record<string, Record<string, Record<string, IOneTimeKey>>> = {};
result[bobUserId] = {};
result[bobUserId][bobDeviceId] = {};
result[bobUserId][bobDeviceId][keyId] = keys[keyId];
@ -276,22 +274,21 @@ async function recvMessage(
next_batch: "x",
rooms: {
join: {
[roomId]: {
timeline: {
events: [
testUtils.mkEvent({
type: "m.room.encrypted",
room: roomId,
content: message,
sender: sender,
}),
],
},
},
},
},
};
syncData.rooms.join[roomId] = {
timeline: {
events: [
testUtils.mkEvent({
type: "m.room.encrypted",
room: roomId,
content: message,
sender: sender,
}),
],
},
};
httpBackend.when("GET", "/sync").respond(200, syncData);
const eventPromise = new Promise<MatrixEvent>((resolve) => {
@ -335,24 +332,25 @@ function firstSync(testClient: TestClient): Promise<void> {
const syncData = {
next_batch: "x",
rooms: {
join: { },
},
};
syncData.rooms.join[roomId] = {
state: {
events: [
testUtils.mkMembership({
mship: "join",
user: aliUserId,
}),
testUtils.mkMembership({
mship: "join",
user: bobUserId,
}),
],
},
timeline: {
events: [],
join: {
[roomId]: {
state: {
events: [
testUtils.mkMembership({
mship: "join",
user: aliUserId,
}),
testUtils.mkMembership({
mship: "join",
user: bobUserId,
}),
],
},
timeline: {
events: [],
},
},
},
},
};
@ -424,7 +422,7 @@ describe("MatrixClient crypto", () => {
},
};
const bobKeys = {};
const bobKeys: Record<string, typeof bobDeviceKeys> = {};
bobKeys[bobDeviceId] = bobDeviceKeys;
aliTestClient.httpBackend.when(
"POST", "/keys/query",
@ -460,7 +458,7 @@ describe("MatrixClient crypto", () => {
},
};
const bobKeys = {};
const bobKeys: Record<string, typeof bobDeviceKeys> = {};
bobKeys[bobDeviceId] = bobDeviceKeys;
aliTestClient.httpBackend.when(
"POST", "/keys/query",
@ -515,22 +513,21 @@ describe("MatrixClient crypto", () => {
next_batch: "x",
rooms: {
join: {
[roomId]: {
timeline: {
events: [
testUtils.mkEvent({
type: "m.room.encrypted",
room: roomId,
content: message,
sender: "@bogus:sender",
}),
],
},
},
},
},
};
syncData.rooms.join[roomId] = {
timeline: {
events: [
testUtils.mkEvent({
type: "m.room.encrypted",
room: roomId,
content: message,
sender: "@bogus:sender",
}),
],
},
};
bobTestClient.httpBackend.when("GET", "/sync").respond(200, syncData);
const eventPromise = new Promise<MatrixEvent>((resolve) => {
@ -607,20 +604,21 @@ describe("MatrixClient crypto", () => {
const syncData = {
next_batch: '2',
rooms: {
join: {},
},
};
syncData.rooms.join[roomId] = {
state: {
events: [
testUtils.mkEvent({
type: 'm.room.encryption',
skey: '',
content: {
algorithm: 'm.olm.v1.curve25519-aes-sha2',
join: {
[roomId]: {
state: {
events: [
testUtils.mkEvent({
type: 'm.room.encryption',
skey: '',
content: {
algorithm: 'm.olm.v1.curve25519-aes-sha2',
},
}),
],
},
}),
],
},
},
},
};

View File

@ -536,20 +536,20 @@ describe("MatrixClient event timelines", function() {
};
});
let tl0;
let tl3;
let tl0: EventTimeline;
let tl3: EventTimeline;
return Promise.all([
client.getEventTimeline(timelineSet, EVENTS[0].event_id!,
).then(function(tl) {
expect(tl!.getEvents().length).toEqual(1);
tl0 = tl;
tl0 = tl!;
return client.getEventTimeline(timelineSet, EVENTS[2].event_id!);
}).then(function(tl) {
expect(tl!.getEvents().length).toEqual(1);
return client.getEventTimeline(timelineSet, EVENTS[3].event_id!);
}).then(function(tl) {
expect(tl!.getEvents().length).toEqual(1);
tl3 = tl;
tl3 = tl!;
return client.getEventTimeline(timelineSet, EVENTS[1].event_id!);
}).then(function(tl) {
// we expect it to get merged in with event 2
@ -953,11 +953,11 @@ describe("MatrixClient event timelines", function() {
};
});
let tl;
let tl: EventTimeline;
return Promise.all([
client.getEventTimeline(timelineSet, EVENTS[0].event_id!,
).then(function(tl0) {
tl = tl0;
tl = tl0!;
return client.paginateEventTimeline(tl, { backwards: true });
}).then(function(success) {
expect(success).toBeTruthy();
@ -1043,11 +1043,11 @@ describe("MatrixClient event timelines", function() {
};
});
let tl;
let tl: EventTimeline;
return Promise.all([
client.getEventTimeline(timelineSet, EVENTS[0].event_id!,
).then(function(tl0) {
tl = tl0;
tl = tl0!;
return client.paginateEventTimeline(
tl, { backwards: false, limit: 20 });
}).then(function(success) {
@ -1569,16 +1569,17 @@ describe("MatrixClient event timelines", function() {
const syncData = {
next_batch: "batch1",
rooms: {
join: {},
},
};
syncData.rooms.join[roomId] = {
timeline: {
events: [
event,
redaction,
],
limited: false,
join: {
[roomId]: {
timeline: {
events: [
event,
redaction,
],
limited: false,
},
},
},
},
};
httpBackend.when("GET", "/sync").respond(200, syncData);
@ -1595,18 +1596,19 @@ describe("MatrixClient event timelines", function() {
const sync2 = {
next_batch: "batch2",
rooms: {
join: {},
},
};
sync2.rooms.join[roomId] = {
timeline: {
events: [
utils.mkMessage({
user: otherUserId, msg: "world",
}),
],
limited: true,
prev_batch: "newerTok",
join: {
[roomId]: {
timeline: {
events: [
utils.mkMessage({
user: otherUserId, msg: "world",
}),
],
limited: true,
prev_batch: "newerTok",
},
},
},
},
};
httpBackend.when("GET", "/sync").respond(200, sync2);

View File

@ -618,13 +618,13 @@ describe("MatrixClient", function() {
});
describe("partitionThreadedEvents", function() {
let room;
let room: Room;
beforeEach(() => {
room = new Room("!STrMRsukXHtqQdSeHa:matrix.org", client!, userId);
});
it("returns empty arrays when given an empty arrays", function() {
const events = [];
const events: MatrixEvent[] = [];
const [timeline, threaded] = room.partitionThreadedEvents(events);
expect(timeline).toEqual([]);
expect(threaded).toEqual([]);
@ -1645,7 +1645,7 @@ const buildEventCreate = () => new MatrixEvent({
"user_id": "@andybalaam-test1:matrix.org",
});
function assertObjectContains(obj: object, expected: any): void {
function assertObjectContains(obj: Record<string, any>, expected: any): void {
for (const k in expected) {
if (expected.hasOwnProperty(k)) {
expect(obj[k]).toEqual(expected[k]);

View File

@ -1,7 +1,7 @@
import HttpBackend from "matrix-mock-request";
import * as utils from "../test-utils/test-utils";
import { MatrixClient } from "../../src/matrix";
import { ClientEvent, MatrixClient } from "../../src/matrix";
import { MatrixScheduler } from "../../src/scheduler";
import { MemoryStore } from "../../src/store/memory";
import { MatrixError } from "../../src/http-api";
@ -65,7 +65,7 @@ describe("MatrixClient opts", function() {
});
describe("without opts.store", function() {
let client;
let client: MatrixClient;
beforeEach(function() {
client = new MatrixClient({
fetchFn: httpBackend.fetchFn as typeof global.fetch,
@ -98,7 +98,7 @@ describe("MatrixClient opts", function() {
"m.room.message", "m.room.name", "m.room.member", "m.room.member",
"m.room.create",
];
client.on("event", function(event) {
client.on(ClientEvent.Event, function(event) {
expect(expectedEventTypes.indexOf(event.getType())).not.toEqual(
-1,
);
@ -125,7 +125,7 @@ describe("MatrixClient opts", function() {
});
describe("without opts.scheduler", function() {
let client;
let client: MatrixClient;
beforeEach(function() {
client = new MatrixClient({
fetchFn: httpBackend.fetchFn as typeof global.fetch,

View File

@ -18,7 +18,16 @@ import HttpBackend from "matrix-mock-request";
import * as utils from "../test-utils/test-utils";
import { EventStatus } from "../../src/models/event";
import { MatrixError, ClientEvent, IEvent, MatrixClient, RoomEvent } from "../../src";
import {
MatrixError,
ClientEvent,
IEvent,
MatrixClient,
RoomEvent,
ISyncResponse,
IMinimalEvent,
IRoomEvent, Room,
} from "../../src";
import { TestClient } from "../TestClient";
describe("MatrixClient room timelines", function() {
@ -39,7 +48,7 @@ describe("MatrixClient room timelines", function() {
name: "Old room name",
},
});
let NEXT_SYNC_DATA;
let NEXT_SYNC_DATA: Partial<ISyncResponse>;
const SYNC_DATA = {
next_batch: "s_5_3",
rooms: {
@ -88,7 +97,7 @@ describe("MatrixClient room timelines", function() {
},
},
leave: {},
},
} as unknown as ISyncResponse["rooms"],
};
events.forEach(function(e) {
if (e.room_id !== roomId) {
@ -96,11 +105,11 @@ describe("MatrixClient room timelines", function() {
}
if (e.state_key) {
// push the current
NEXT_SYNC_DATA.rooms.join[roomId].timeline.events.push(e);
NEXT_SYNC_DATA.rooms!.join[roomId].timeline.events.push(e as unknown as IRoomEvent);
} else if (["m.typing", "m.receipt"].indexOf(e.type!) !== -1) {
NEXT_SYNC_DATA.rooms.join[roomId].ephemeral.events.push(e);
NEXT_SYNC_DATA.rooms!.join[roomId].ephemeral.events.push(e as unknown as IMinimalEvent);
} else {
NEXT_SYNC_DATA.rooms.join[roomId].timeline.events.push(e);
NEXT_SYNC_DATA.rooms!.join[roomId].timeline.events.push(e as unknown as IRoomEvent);
}
});
}
@ -237,7 +246,7 @@ describe("MatrixClient room timelines", function() {
});
describe("paginated events", function() {
let sbEvents;
let sbEvents: Partial<IEvent>[];
const sbEndTok = "pagin_end";
beforeEach(function() {
@ -559,7 +568,7 @@ describe("MatrixClient room timelines", function() {
utils.mkMessage({ user: userId, room: roomId }),
];
setNextSyncData(eventData);
NEXT_SYNC_DATA.rooms.join[roomId].timeline.limited = true;
NEXT_SYNC_DATA.rooms!.join[roomId].timeline.limited = true;
return Promise.all([
httpBackend!.flush("/versions", 1),
@ -593,7 +602,7 @@ describe("MatrixClient room timelines", function() {
utils.mkMessage({ user: userId, room: roomId }),
];
setNextSyncData(eventData);
NEXT_SYNC_DATA.rooms.join[roomId].timeline.limited = true;
NEXT_SYNC_DATA.rooms!.join[roomId].timeline.limited = true;
return Promise.all([
httpBackend!.flush("/sync", 1),
@ -638,7 +647,7 @@ describe("MatrixClient room timelines", function() {
end: "end_token",
};
let room;
let room: Room;
beforeEach(async () => {
setNextSyncData(initialSyncEventData);

View File

@ -33,7 +33,7 @@ import {
IJoinedRoom,
IStateEvent,
IMinimalEvent,
NotificationCountType,
NotificationCountType, IEphemeral,
} from "../../src";
import { UNREAD_THREAD_NOTIFICATIONS } from '../../src/@types/sync';
import * as utils from "../test-utils/test-utils";
@ -524,105 +524,101 @@ describe("MatrixClient syncing", () => {
const syncData = {
rooms: {
join: {
[roomOne]: {
timeline: {
events: [
utils.mkMessage({
room: roomOne, user: otherUserId, msg: "hello",
}),
],
},
state: {
events: [
utils.mkEvent({
type: "m.room.name", room: roomOne, user: otherUserId,
content: {
name: "Old room name",
},
}),
utils.mkMembership({
room: roomOne, mship: "join", user: otherUserId,
}),
utils.mkMembership({
room: roomOne, mship: "join", user: selfUserId,
}),
utils.mkEvent({
type: "m.room.create", room: roomOne, user: selfUserId,
content: {
creator: selfUserId,
},
}),
],
},
},
[roomTwo]: {
timeline: {
events: [
utils.mkMessage({
room: roomTwo, user: otherUserId, msg: "hiii",
}),
],
},
state: {
events: [
utils.mkMembership({
room: roomTwo, mship: "join", user: otherUserId,
name: otherDisplayName,
}),
utils.mkMembership({
room: roomTwo, mship: "join", user: selfUserId,
}),
utils.mkEvent({
type: "m.room.create", room: roomTwo, user: selfUserId,
content: {
creator: selfUserId,
},
}),
],
},
},
},
},
};
syncData.rooms.join[roomOne] = {
timeline: {
events: [
utils.mkMessage({
room: roomOne, user: otherUserId, msg: "hello",
}),
],
},
state: {
events: [
utils.mkEvent({
type: "m.room.name", room: roomOne, user: otherUserId,
content: {
name: "Old room name",
},
}),
utils.mkMembership({
room: roomOne, mship: "join", user: otherUserId,
}),
utils.mkMembership({
room: roomOne, mship: "join", user: selfUserId,
}),
utils.mkEvent({
type: "m.room.create", room: roomOne, user: selfUserId,
content: {
creator: selfUserId,
},
}),
],
},
};
syncData.rooms.join[roomTwo] = {
timeline: {
events: [
utils.mkMessage({
room: roomTwo, user: otherUserId, msg: "hiii",
}),
],
},
state: {
events: [
utils.mkMembership({
room: roomTwo, mship: "join", user: otherUserId,
name: otherDisplayName,
}),
utils.mkMembership({
room: roomTwo, mship: "join", user: selfUserId,
}),
utils.mkEvent({
type: "m.room.create", room: roomTwo, user: selfUserId,
content: {
creator: selfUserId,
},
}),
],
},
};
const nextSyncData = {
rooms: {
join: {
[roomOne]: {
state: {
events: [
utils.mkEvent({
type: "m.room.name", room: roomOne, user: selfUserId,
content: { name: "A new room name" },
}),
],
},
},
[roomTwo]: {
timeline: {
events: [
utils.mkMessage({
room: roomTwo, user: otherUserId, msg: msgText,
}),
],
},
ephemeral: {
events: [
utils.mkEvent({
type: "m.typing", room: roomTwo,
content: { user_ids: [otherUserId] },
}),
],
},
},
},
},
};
nextSyncData.rooms.join[roomOne] = {
state: {
events: [
utils.mkEvent({
type: "m.room.name", room: roomOne, user: selfUserId,
content: { name: "A new room name" },
}),
],
},
};
nextSyncData.rooms.join[roomTwo] = {
timeline: {
events: [
utils.mkMessage({
room: roomTwo, user: otherUserId, msg: msgText,
}),
],
},
ephemeral: {
events: [
utils.mkEvent({
type: "m.typing", room: roomTwo,
content: { user_ids: [otherUserId] },
}),
],
},
};
it("should continually recalculate the right room name.", () => {
httpBackend!.when("GET", "/sync").respond(200, syncData);
httpBackend!.when("GET", "/sync").respond(200, nextSyncData);
@ -635,9 +631,7 @@ describe("MatrixClient syncing", () => {
]).then(() => {
const room = client!.getRoom(roomOne)!;
// should have clobbered the name to the one from /events
expect(room.name).toEqual(
nextSyncData.rooms.join[roomOne].state.events[0].content.name,
);
expect(room.name).toEqual(nextSyncData.rooms.join[roomOne].state.events[0].content?.name);
});
});
@ -742,46 +736,48 @@ describe("MatrixClient syncing", () => {
const normalFirstSync = {
next_batch: "batch_token",
rooms: {
join: {},
},
};
normalFirstSync.rooms.join[roomOne] = {
timeline: {
events: [normalMessageEvent],
prev_batch: "pagTok",
},
state: {
events: [roomCreateEvent],
join: {
[roomOne]: {
timeline: {
events: [normalMessageEvent],
prev_batch: "pagTok",
},
state: {
events: [roomCreateEvent],
},
},
},
},
};
const nextSyncData = {
next_batch: "batch_token",
rooms: {
join: {},
},
};
nextSyncData.rooms.join[roomOne] = {
timeline: {
events: [
// In subsequent syncs, a marker event in timeline
// range should normally trigger
// `timelineNeedsRefresh=true` but this marker isn't
// being sent by the room creator so it has no
// special meaning in existing room versions.
utils.mkEvent({
type: UNSTABLE_MSC2716_MARKER.name,
room: roomOne,
// The important part we're testing is here!
// `userC` is not the room creator.
user: userC,
skey: "",
content: {
"m.insertion_id": "$abc",
join: {
[roomOne]: {
timeline: {
events: [
// In subsequent syncs, a marker event in timeline
// range should normally trigger
// `timelineNeedsRefresh=true` but this marker isn't
// being sent by the room creator so it has no
// special meaning in existing room versions.
utils.mkEvent({
type: UNSTABLE_MSC2716_MARKER.name,
room: roomOne,
// The important part we're testing is here!
// `userC` is not the room creator.
user: userC,
skey: "",
content: {
"m.insertion_id": "$abc",
},
}),
],
prev_batch: "pagTok",
},
}),
],
prev_batch: "pagTok",
},
},
},
};
@ -831,16 +827,17 @@ describe("MatrixClient syncing", () => {
const normalFirstSync = {
next_batch: "batch_token",
rooms: {
join: {},
},
};
normalFirstSync.rooms.join[roomOne] = {
timeline: {
events: [normalMessageEvent],
prev_batch: "pagTok",
},
state: {
events: [roomCreateEvent],
join: {
[roomOne]: {
timeline: {
events: [normalMessageEvent],
prev_batch: "pagTok",
},
state: {
events: [roomCreateEvent],
},
},
},
},
};
@ -849,16 +846,17 @@ describe("MatrixClient syncing", () => {
const syncData = {
next_batch: "batch_token",
rooms: {
join: {},
},
};
syncData.rooms.join[roomOne] = {
timeline: {
events: [normalMessageEvent],
prev_batch: "pagTok",
},
state: {
events: [roomCreateEvent],
join: {
[roomOne]: {
timeline: {
events: [normalMessageEvent],
prev_batch: "pagTok",
},
state: {
events: [roomCreateEvent],
},
},
},
},
};
@ -879,16 +877,17 @@ describe("MatrixClient syncing", () => {
const syncData = {
next_batch: "batch_token",
rooms: {
join: {},
},
};
syncData.rooms.join[roomOne] = {
timeline: {
events: [markerEventFromRoomCreator],
prev_batch: "pagTok",
},
state: {
events: [roomCreateEvent],
join: {
[roomOne]: {
timeline: {
events: [markerEventFromRoomCreator],
prev_batch: "pagTok",
},
state: {
events: [roomCreateEvent],
},
},
},
},
};
@ -909,19 +908,20 @@ describe("MatrixClient syncing", () => {
const syncData = {
next_batch: "batch_token",
rooms: {
join: {},
},
};
syncData.rooms.join[roomOne] = {
timeline: {
events: [normalMessageEvent],
prev_batch: "pagTok",
},
state: {
events: [
roomCreateEvent,
markerEventFromRoomCreator,
],
join: {
[roomOne]: {
timeline: {
events: [normalMessageEvent],
prev_batch: "pagTok",
},
state: {
events: [
roomCreateEvent,
markerEventFromRoomCreator,
],
},
},
},
},
};
@ -942,17 +942,18 @@ describe("MatrixClient syncing", () => {
const nextSyncData = {
next_batch: "batch_token",
rooms: {
join: {},
},
};
nextSyncData.rooms.join[roomOne] = {
timeline: {
events: [
// In subsequent syncs, a marker event in timeline
// range should trigger `timelineNeedsRefresh=true`
markerEventFromRoomCreator,
],
prev_batch: "pagTok",
join: {
[roomOne]: {
timeline: {
events: [
// In subsequent syncs, a marker event in timeline
// range should trigger `timelineNeedsRefresh=true`
markerEventFromRoomCreator,
],
prev_batch: "pagTok",
},
},
},
},
};
@ -993,24 +994,25 @@ describe("MatrixClient syncing", () => {
const nextSyncData = {
next_batch: "batch_token",
rooms: {
join: {},
},
};
nextSyncData.rooms.join[roomOne] = {
timeline: {
events: [
utils.mkMessage({
room: roomOne, user: otherUserId, msg: "hello again",
}),
],
prev_batch: "pagTok",
},
state: {
events: [
// In subsequent syncs, a marker event in state
// should trigger `timelineNeedsRefresh=true`
markerEventFromRoomCreator,
],
join: {
[roomOne]: {
timeline: {
events: [
utils.mkMessage({
room: roomOne, user: otherUserId, msg: "hello again",
}),
],
prev_batch: "pagTok",
},
state: {
events: [
// In subsequent syncs, a marker event in state
// should trigger `timelineNeedsRefresh=true`
markerEventFromRoomCreator,
],
},
},
},
},
};
@ -1095,19 +1097,20 @@ describe("MatrixClient syncing", () => {
const limitedSyncData = {
next_batch: "batch_token",
rooms: {
join: {},
},
};
limitedSyncData.rooms.join[roomOne] = {
timeline: {
events: [
utils.mkMessage({
room: roomOne, user: otherUserId, msg: "world",
}),
],
// The important part, make the sync `limited`
limited: true,
prev_batch: "newerTok",
join: {
[roomOne]: {
timeline: {
events: [
utils.mkMessage({
room: roomOne, user: otherUserId, msg: "world",
}),
],
// The important part, make the sync `limited`
limited: true,
prev_batch: "newerTok",
},
},
},
},
};
httpBackend!.when("GET", "/sync").respond(200, limitedSyncData);
@ -1167,7 +1170,7 @@ describe("MatrixClient syncing", () => {
const eventsInRoom = syncData.rooms.join[roomOne].timeline.events;
const contextUrl = `/rooms/${encodeURIComponent(roomOne)}/context/` +
`${encodeURIComponent(eventsInRoom[0].event_id)}`;
`${encodeURIComponent(eventsInRoom[0].event_id!)}`;
httpBackend!.when("GET", contextUrl)
.respond(200, () => {
return {
@ -1202,17 +1205,18 @@ describe("MatrixClient syncing", () => {
const syncData = {
next_batch: "batch_token",
rooms: {
join: {},
},
};
syncData.rooms.join[roomOne] = {
timeline: {
events: [
utils.mkMessage({
room: roomOne, user: otherUserId, msg: "hello",
}),
],
prev_batch: "pagTok",
join: {
[roomOne]: {
timeline: {
events: [
utils.mkMessage({
room: roomOne, user: otherUserId, msg: "hello",
}),
],
prev_batch: "pagTok",
},
},
},
},
};
@ -1229,17 +1233,18 @@ describe("MatrixClient syncing", () => {
const syncData = {
next_batch: "batch_token",
rooms: {
join: {},
},
};
syncData.rooms.join[roomTwo] = {
timeline: {
events: [
utils.mkMessage({
room: roomTwo, user: otherUserId, msg: "roomtwo",
}),
],
prev_batch: "roomtwotok",
join: {
[roomTwo]: {
timeline: {
events: [
utils.mkMessage({
room: roomTwo, user: otherUserId, msg: "roomtwo",
}),
],
prev_batch: "roomtwotok",
},
},
},
},
};
@ -1261,18 +1266,19 @@ describe("MatrixClient syncing", () => {
const syncData = {
next_batch: "batch_token",
rooms: {
join: {},
},
};
syncData.rooms.join[roomOne] = {
timeline: {
events: [
utils.mkMessage({
room: roomOne, user: otherUserId, msg: "world",
}),
],
limited: true,
prev_batch: "newerTok",
join: {
[roomOne]: {
timeline: {
events: [
utils.mkMessage({
room: roomOne, user: otherUserId, msg: "world",
}),
],
limited: true,
prev_batch: "newerTok",
},
},
},
},
};
httpBackend!.when("GET", "/sync").respond(200, syncData);
@ -1304,44 +1310,46 @@ describe("MatrixClient syncing", () => {
const syncData = {
rooms: {
join: {
[roomOne]: {
ephemeral: {
events: [],
} as IEphemeral,
timeline: {
events: [
utils.mkMessage({
room: roomOne, user: otherUserId, msg: "hello",
}),
utils.mkMessage({
room: roomOne, user: otherUserId, msg: "world",
}),
],
},
state: {
events: [
utils.mkEvent({
type: "m.room.name", room: roomOne, user: otherUserId,
content: {
name: "Old room name",
},
}),
utils.mkMembership({
room: roomOne, mship: "join", user: otherUserId,
}),
utils.mkMembership({
room: roomOne, mship: "join", user: selfUserId,
}),
utils.mkEvent({
type: "m.room.create", room: roomOne, user: selfUserId,
content: {
creator: selfUserId,
},
}),
],
} as Partial<IJoinedRoom>,
},
},
},
};
syncData.rooms.join[roomOne] = {
timeline: {
events: [
utils.mkMessage({
room: roomOne, user: otherUserId, msg: "hello",
}),
utils.mkMessage({
room: roomOne, user: otherUserId, msg: "world",
}),
],
},
state: {
events: [
utils.mkEvent({
type: "m.room.name", room: roomOne, user: otherUserId,
content: {
name: "Old room name",
},
}),
utils.mkMembership({
room: roomOne, mship: "join", user: otherUserId,
}),
utils.mkMembership({
room: roomOne, mship: "join", user: selfUserId,
}),
utils.mkEvent({
type: "m.room.create", room: roomOne, user: selfUserId,
content: {
creator: selfUserId,
},
}),
],
},
};
beforeEach(() => {
syncData.rooms.join[roomOne].ephemeral = {
@ -1351,16 +1359,15 @@ describe("MatrixClient syncing", () => {
it("should sync receipts from /sync.", () => {
const ackEvent = syncData.rooms.join[roomOne].timeline.events[0];
const receipt = {};
receipt[ackEvent.event_id] = {
const receipt: Record<string, any> = {};
receipt[ackEvent.event_id!] = {
"m.read": {},
};
receipt[ackEvent.event_id]["m.read"][userC] = {
receipt[ackEvent.event_id!]["m.read"][userC] = {
ts: 176592842636,
};
syncData.rooms.join[roomOne].ephemeral.events = [{
content: receipt,
room_id: roomOne,
type: "m.receipt",
}];
httpBackend!.when("GET", "/sync").respond(200, syncData);
@ -1425,7 +1432,7 @@ describe("MatrixClient syncing", () => {
},
},
},
};
} as unknown as ISyncResponse;
it("should sync unread notifications.", () => {
syncData.rooms.join[roomOne][UNREAD_THREAD_NOTIFICATIONS.name] = {
[THREAD_ID]: {
@ -1509,18 +1516,18 @@ describe("MatrixClient syncing", () => {
const syncData = {
next_batch: "batch_token",
rooms: {
leave: {},
},
};
syncData.rooms.leave[roomTwo] = {
timeline: {
events: [
utils.mkMessage({
room: roomTwo, user: otherUserId, msg: "hello",
}),
],
prev_batch: "pagTok",
leave: {
[roomTwo]: {
timeline: {
events: [
utils.mkMessage({
room: roomTwo, user: otherUserId, msg: "hello",
}),
],
prev_batch: "pagTok",
},
},
},
},
};

View File

@ -126,12 +126,13 @@ describe("megolm key backups", function() {
const syncResponse = {
next_batch: 1,
rooms: {
join: {},
},
};
syncResponse.rooms.join[ROOM_ID] = {
timeline: {
events: [ENCRYPTED_EVENT],
join: {
[ROOM_ID]: {
timeline: {
events: [ENCRYPTED_EVENT],
},
},
},
},
};

View File

@ -119,13 +119,13 @@ describe("SlidingSyncSdk", () => {
};
// find an extension on a SlidingSyncSdk instance
const findExtension = (name: string): Extension => {
const findExtension = (name: string): Extension<any, any> => {
expect(mockSlidingSync!.registerExtension).toHaveBeenCalled();
const mockFn = mockSlidingSync!.registerExtension as jest.Mock;
// find the extension
for (let i = 0; i < mockFn.mock.calls.length; i++) {
const calledExtension = mockFn.mock.calls[i][0] as Extension;
if (calledExtension && calledExtension.name() === name) {
const calledExtension = mockFn.mock.calls[i][0] as Extension<any, any>;
if (calledExtension?.name() === name) {
return calledExtension;
}
}
@ -581,7 +581,7 @@ describe("SlidingSyncSdk", () => {
});
describe("ExtensionE2EE", () => {
let ext: Extension;
let ext: Extension<any, any>;
beforeAll(async () => {
await setupClient({
@ -647,7 +647,7 @@ describe("SlidingSyncSdk", () => {
});
describe("ExtensionAccountData", () => {
let ext: Extension;
let ext: Extension<any, any>;
beforeAll(async () => {
await setupClient();
@ -773,7 +773,7 @@ describe("SlidingSyncSdk", () => {
});
describe("ExtensionToDevice", () => {
let ext: Extension;
let ext: Extension<any, any>;
beforeAll(async () => {
await setupClient();
@ -871,7 +871,7 @@ describe("SlidingSyncSdk", () => {
});
describe("ExtensionTyping", () => {
let ext: Extension;
let ext: Extension<any, any>;
beforeAll(async () => {
await setupClient();
@ -970,7 +970,7 @@ describe("SlidingSyncSdk", () => {
});
describe("ExtensionReceipts", () => {
let ext: Extension;
let ext: Extension<any, any>;
const generateReceiptResponse = (
userId: string, roomId: string, eventId: string, recType: string, ts: number,

View File

@ -18,7 +18,15 @@ limitations under the License.
import EventEmitter from "events";
import MockHttpBackend from "matrix-mock-request";
import { SlidingSync, SlidingSyncState, ExtensionState, SlidingSyncEvent } from "../../src/sliding-sync";
import {
SlidingSync,
SlidingSyncState,
ExtensionState,
SlidingSyncEvent,
Extension,
SlidingSyncEventHandlerMap,
MSC3575RoomData,
} from "../../src/sliding-sync";
import { TestClient } from "../TestClient";
import { logger } from "../../src/logger";
import { MatrixClient } from "../../src";
@ -94,7 +102,7 @@ describe("SlidingSync", () => {
is_dm: true,
},
};
const ext = {
const ext: Extension<any, any> = {
name: () => "custom_extension",
onRequest: (initial) => { return { initial: initial }; },
onResponse: (res) => { return {}; },
@ -107,7 +115,7 @@ describe("SlidingSync", () => {
slidingSync.start();
// expect everything to be sent
let txnId;
let txnId: string | undefined;
httpBackend!.when("POST", syncUrl).check(function(req) {
const body = req.data;
logger.debug("got ", body);
@ -390,8 +398,8 @@ describe("SlidingSync", () => {
}],
rooms: rooms,
});
const listenerData = {};
const dataListener = (roomId, roomData) => {
const listenerData: Record<string, MSC3575RoomData> = {};
const dataListener: SlidingSyncEventHandlerMap[SlidingSyncEvent.RoomData] = (roomId, roomData) => {
expect(listenerData[roomId]).toBeFalsy();
listenerData[roomId] = roomData;
};
@ -912,7 +920,7 @@ describe("SlidingSync", () => {
slidingSync = new SlidingSync(proxyBaseUrl, [], roomSubInfo, client!, 1);
// modification before SlidingSync.start()
const subscribePromise = slidingSync.modifyRoomSubscriptions(new Set([roomId]));
let txnId;
let txnId: string | undefined;
httpBackend!.when("POST", syncUrl).check(function(req) {
const body = req.data;
logger.debug("got ", body);
@ -944,7 +952,7 @@ describe("SlidingSync", () => {
ranges: [[0, 20]],
};
const promise = slidingSync.setList(0, newList);
let txnId;
let txnId: string | undefined;
httpBackend!.when("POST", syncUrl).check(function(req) {
const body = req.data;
logger.debug("got ", body);
@ -966,7 +974,7 @@ describe("SlidingSync", () => {
});
it("should resolve setListRanges during a connection", async () => {
const promise = slidingSync.setListRanges(0, [[20, 40]]);
let txnId;
let txnId: string | undefined;
httpBackend!.when("POST", syncUrl).check(function(req) {
const body = req.data;
logger.debug("got ", body);
@ -992,7 +1000,7 @@ describe("SlidingSync", () => {
const promise = slidingSync.modifyRoomSubscriptionInfo({
timeline_limit: 99,
});
let txnId;
let txnId: string | undefined;
httpBackend!.when("POST", syncUrl).check(function(req) {
const body = req.data;
logger.debug("got ", body);
@ -1016,7 +1024,7 @@ describe("SlidingSync", () => {
it("should reject earlier pending promises if a later transaction is acknowledged", async () => {
// i.e if we have [A,B,C] and see txn_id=C then A,B should be rejected.
const gotTxnIds: any[] = [];
const pushTxn = function(req) {
const pushTxn = function(req: MockHttpBackend["requests"][0]) {
gotTxnIds.push(req.data.txn_id);
};
const failPromise = slidingSync.setListRanges(0, [[20, 40]]);
@ -1032,7 +1040,7 @@ describe("SlidingSync", () => {
expect(failPromise2).rejects.toEqual(gotTxnIds[1]);
const okPromise = slidingSync.setListRanges(0, [[0, 20]]);
let txnId;
let txnId: string | undefined;
httpBackend!.when("POST", syncUrl).check((req) => {
txnId = req.data.txn_id;
}).respond(200, () => {
@ -1050,7 +1058,7 @@ describe("SlidingSync", () => {
it("should not reject later pending promises if an earlier transaction is acknowledged", async () => {
// i.e if we have [A,B,C] and see txn_id=B then C should not be rejected but A should.
const gotTxnIds: any[] = [];
const pushTxn = function(req) {
const pushTxn = function(req: MockHttpBackend["requests"][0]) {
gotTxnIds.push(req.data?.txn_id);
};
const A = slidingSync.setListRanges(0, [[20, 40]]);
@ -1087,7 +1095,7 @@ describe("SlidingSync", () => {
promise.finally(() => {
pending = false;
});
let txnId;
let txnId: string | undefined;
httpBackend!.when("POST", syncUrl).check(function(req) {
const body = req.data;
logger.debug("got ", body);
@ -1275,21 +1283,21 @@ describe("SlidingSync", () => {
// Pre-extensions get called BEFORE processing the sync response
const preExtName = "foobar";
let onPreExtensionRequest;
let onPreExtensionResponse;
let onPreExtensionRequest: Extension<any, any>["onRequest"];
let onPreExtensionResponse: Extension<any, any>["onResponse"];
// Post-extensions get called AFTER processing the sync response
const postExtName = "foobar2";
let onPostExtensionRequest;
let onPostExtensionResponse;
let onPostExtensionRequest: Extension<any, any>["onRequest"];
let onPostExtensionResponse: Extension<any, any>["onResponse"];
const extPre = {
const extPre: Extension<any, any> = {
name: () => preExtName,
onRequest: (initial) => { return onPreExtensionRequest(initial); },
onResponse: (res) => { return onPreExtensionResponse(res); },
when: () => ExtensionState.PreProcess,
};
const extPost = {
const extPost: Extension<any, any> = {
name: () => postExtName,
onRequest: (initial) => { return onPostExtensionRequest(initial); },
onResponse: (res) => { return onPostExtensionResponse(res); },
@ -1421,7 +1429,7 @@ describe("SlidingSync", () => {
});
function timeout(delayMs: number, reason: string): { promise: Promise<never>, cancel: () => void } {
let timeoutId;
let timeoutId: NodeJS.Timeout;
return {
promise: new Promise((resolve, reject) => {
timeoutId = setTimeout(() => {
@ -1454,7 +1462,7 @@ function listenUntil<T>(
const trace = new Error().stack?.split(`\n`)[2];
const t = timeout(timeoutMs, "timed out waiting for event " + eventName + " " + trace);
return Promise.race([new Promise<T>((resolve, reject) => {
const wrapper = (...args) => {
const wrapper = (...args: any[]) => {
try {
const data = callback(...args);
if (data) {

View File

@ -332,7 +332,7 @@ export function mkReplyMessage(
*
* @constructor
*/
export class MockStorageApi {
export class MockStorageApi implements Storage {
private data: Record<string, any> = {};
public get length() {
@ -354,6 +354,10 @@ export class MockStorageApi {
public removeItem(k: string): void {
delete this.data[k];
}
public clear(): void {
this.data = {};
}
}
/**

View File

@ -3,7 +3,7 @@ import '../olm-loader';
import { EventEmitter } from "events";
import type { PkDecryption, PkSigning } from "@matrix-org/olm";
import { MatrixClient } from "../../src/client";
import { IClaimOTKsResult, MatrixClient } from "../../src/client";
import { Crypto } from "../../src/crypto";
import { MemoryCryptoStore } from "../../src/crypto/store/memory-crypto-store";
import { MockStorageApi } from "../MockStorageApi";
@ -23,16 +23,16 @@ import { IRoomEncryption, RoomList } from "../../src/crypto/RoomList";
const Olm = global.Olm;
function awaitEvent(emitter, event) {
return new Promise((resolve, reject) => {
function awaitEvent(emitter: EventEmitter, event: string): Promise<void> {
return new Promise((resolve) => {
emitter.once(event, (result) => {
resolve(result);
});
});
}
async function keyshareEventForEvent(client, event, index): Promise<MatrixEvent> {
const roomId = event.getRoomId();
async function keyshareEventForEvent(client: MatrixClient, event: MatrixEvent, index?: number): Promise<MatrixEvent> {
const roomId = event.getRoomId()!;
const eventContent = event.getWireContent();
const key = await client.crypto!.olmDevice.getInboundGroupSessionKey(
roomId,
@ -42,16 +42,16 @@ async function keyshareEventForEvent(client, event, index): Promise<MatrixEvent>
);
const ksEvent = new MatrixEvent({
type: "m.forwarded_room_key",
sender: client.getUserId(),
sender: client.getUserId()!,
content: {
"algorithm": olmlib.MEGOLM_ALGORITHM,
"room_id": roomId,
"sender_key": eventContent.sender_key,
"sender_claimed_ed25519_key": key.sender_claimed_ed25519_key,
"sender_claimed_ed25519_key": key?.sender_claimed_ed25519_key,
"session_id": eventContent.session_id,
"session_key": key.key,
"chain_index": key.chain_index,
"forwarding_curve25519_key_chain": key.forwarding_curve_key_chain,
"session_key": key?.key,
"chain_index": key?.chain_index,
"forwarding_curve25519_key_chain": key?.forwarding_curve25519_key_chain,
"org.matrix.msc3061.shared_history": true,
},
});
@ -172,7 +172,8 @@ describe("Crypto", function() {
});
describe('Session management', function() {
const otkResponse = {
const otkResponse: IClaimOTKsResult = {
failures: {},
one_time_keys: {
'@alice:home.server': {
aliceDevice: {
@ -188,11 +189,12 @@ describe("Crypto", function() {
},
},
};
let crypto;
let mockBaseApis;
let mockRoomList;
let fakeEmitter;
let crypto: Crypto;
let mockBaseApis: MatrixClient;
let mockRoomList: RoomList;
let fakeEmitter: EventEmitter;
beforeEach(async function() {
const mockStorage = new MockStorageApi() as unknown as Storage;
@ -219,8 +221,8 @@ describe("Crypto", function() {
sendToDevice: jest.fn(),
getKeyBackupVersion: jest.fn(),
isGuest: jest.fn(),
};
mockRoomList = {};
} as unknown as MatrixClient;
mockRoomList = {} as unknown as RoomList;
fakeEmitter = new EventEmitter();
@ -233,7 +235,7 @@ describe("Crypto", function() {
mockRoomList,
[],
);
crypto.registerEventHandlers(fakeEmitter);
crypto.registerEventHandlers(fakeEmitter as any);
await crypto.init();
});
@ -245,7 +247,7 @@ describe("Crypto", function() {
const prom = new Promise<void>((resolve) => {
mockBaseApis.claimOneTimeKeys = function() {
resolve();
return otkResponse;
return Promise.resolve(otkResponse);
};
});
@ -989,7 +991,7 @@ describe("Crypto", function() {
ensureOlmSessionsForDevices.mockResolvedValue({});
encryptMessageForDevice = jest.spyOn(olmlib, "encryptMessageForDevice");
encryptMessageForDevice.mockImplementation(async (...[result,,,,,, payload]) => {
result.plaintext = JSON.stringify(payload);
result.plaintext = { type: 0, body: JSON.stringify(payload) };
});
client = new TestClient("@alice:example.org", "aliceweb");
@ -998,7 +1000,7 @@ describe("Crypto", function() {
encryptedPayload = {
algorithm: "m.olm.v1.curve25519-aes-sha2",
sender_key: client.client.crypto!.olmDevice.deviceCurve25519Key,
ciphertext: { plaintext: JSON.stringify(payload) },
ciphertext: { plaintext: { type: 0, body: JSON.stringify(payload) } },
};
});
@ -1046,7 +1048,7 @@ describe("Crypto", function() {
encryptMessageForDevice.mockImplementation(async (...[result,,,, userId, device, payload]) => {
// Refuse to encrypt to Carol's desktop device
if (userId === "@carol:example.org" && device.deviceId === "caroldesktop") return;
result.plaintext = JSON.stringify(payload);
result.plaintext = { type: 0, body: JSON.stringify(payload) };
});
client.httpBackend

View File

@ -232,7 +232,7 @@ describe.each([
return store;
}],
])("CrossSigning > createCryptoStoreCacheCallbacks [%s]", function(name, dbFactory) {
let store;
let store: IndexedDBCryptoStore;
beforeAll(() => {
store = dbFactory();

View File

@ -22,6 +22,7 @@ import { MemoryCryptoStore } from "../../../src/crypto/store/memory-crypto-store
import { DeviceList } from "../../../src/crypto/DeviceList";
import { IDownloadKeyResult, MatrixClient } from "../../../src";
import { OlmDevice } from "../../../src/crypto/OlmDevice";
import { CryptoStore } from "../../../src/crypto/store/base";
const signedDeviceList: IDownloadKeyResult = {
"failures": {},
@ -88,8 +89,8 @@ const signedDeviceList2: IDownloadKeyResult = {
};
describe('DeviceList', function() {
let downloadSpy;
let cryptoStore;
let downloadSpy: jest.Mock;
let cryptoStore: CryptoStore;
let deviceLists: DeviceList[] = [];
beforeEach(function() {
@ -112,7 +113,7 @@ describe('DeviceList', function() {
deviceId: 'HGKAWHRVJQ',
} as unknown as MatrixClient;
const mockOlm = {
verifySignature: function(key, message, signature) {},
verifySignature: function(key: string, message: string, signature: string) {},
} as unknown as OlmDevice;
const dl = new DeviceList(baseApis, cryptoStore, mockOlm, keyDownloadChunkSize);
deviceLists.push(dl);

View File

@ -17,6 +17,7 @@ limitations under the License.
import { mocked, MockedObject } from 'jest-mock';
import '../../../olm-loader';
import type { OutboundGroupSession } from "@matrix-org/olm";
import * as algorithms from "../../../../src/crypto/algorithms";
import { MemoryCryptoStore } from "../../../../src/crypto/store/memory-crypto-store";
import * as testUtils from "../../../test-utils/test-utils";
@ -31,6 +32,7 @@ import { TypedEventEmitter } from '../../../../src/models/typed-event-emitter';
import { ClientEvent, MatrixClient, RoomMember } from '../../../../src';
import { DeviceInfo, IDevice } from '../../../../src/crypto/deviceinfo';
import { DeviceTrustLevel } from '../../../../src/crypto/CrossSigning';
import { MegolmEncryption as MegolmEncryptionClass } from "../../../../src/crypto/algorithms/megolm";
const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get('m.megolm.v1.aes-sha2')!;
const MegolmEncryption = algorithms.ENCRYPTION_CLASSES.get('m.megolm.v1.aes-sha2')!;
@ -87,7 +89,7 @@ describe("MegolmDecryption", function() {
});
describe('receives some keys:', function() {
let groupSession;
let groupSession: OutboundGroupSession;
beforeEach(async function() {
groupSession = new global.Olm.OutboundGroupSession();
groupSession.create();
@ -298,10 +300,10 @@ describe("MegolmDecryption", function() {
describe("session reuse and key reshares", () => {
const rotationPeriodMs = 999 * 24 * 60 * 60 * 1000; // 999 days, so we don't have to deal with it
let megolmEncryption;
let aliceDeviceInfo;
let mockRoom;
let olmDevice;
let megolmEncryption: MegolmEncryptionClass;
let aliceDeviceInfo: DeviceInfo;
let mockRoom: Room;
let olmDevice: OlmDevice;
beforeEach(async () => {
// @ts-ignore assigning to readonly prop
@ -342,7 +344,7 @@ describe("MegolmDecryption", function() {
'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE',
),
getFingerprint: jest.fn().mockReturnValue(''),
};
} as unknown as DeviceInfo;
mockCrypto.downloadKeys.mockReturnValue(Promise.resolve({
'@alice:home.server': {
@ -365,7 +367,7 @@ describe("MegolmDecryption", function() {
algorithm: 'm.megolm.v1.aes-sha2',
rotation_period_ms: rotationPeriodMs,
},
});
}) as MegolmEncryptionClass;
// Splice the real method onto the mock object as megolm uses this method
// on the crypto class in order to encrypt / start sessions
@ -381,7 +383,7 @@ describe("MegolmDecryption", function() {
[{ userId: "@alice:home.server" }],
),
getBlacklistUnverifiedDevices: jest.fn().mockReturnValue(false),
};
} as unknown as Room;
});
it("should use larger otkTimeout when preparing to encrypt room", async () => {
@ -397,11 +399,14 @@ describe("MegolmDecryption", function() {
});
it("should generate a new session if this one needs rotation", async () => {
// @ts-ignore - private method access
const session = await megolmEncryption.prepareNewSession(false);
session.creationTime -= rotationPeriodMs + 10000; // a smidge over the rotation time
// Inject expired session which needs rotation
// @ts-ignore - private field access
megolmEncryption.setupPromise = Promise.resolve(session);
// @ts-ignore - private method access
const prepareNewSessionSpy = jest.spyOn(megolmEncryption, "prepareNewSession");
await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
body: "Some text",
@ -446,8 +451,8 @@ describe("MegolmDecryption", function() {
});
mockBaseApis.sendToDevice.mockClear();
await megolmEncryption.reshareKeyWithDevice(
olmDevice.deviceCurve25519Key,
await megolmEncryption.reshareKeyWithDevice!(
olmDevice.deviceCurve25519Key!,
ct1.session_id,
'@alice:home.server',
aliceDeviceInfo,
@ -466,8 +471,8 @@ describe("MegolmDecryption", function() {
);
mockBaseApis.queueToDevice.mockClear();
await megolmEncryption.reshareKeyWithDevice(
olmDevice.deviceCurve25519Key,
await megolmEncryption.reshareKeyWithDevice!(
olmDevice.deviceCurve25519Key!,
ct1.session_id,
'@alice:home.server',
aliceDeviceInfo,

View File

@ -31,17 +31,21 @@ function makeOlmDevice() {
return olmDevice;
}
async function setupSession(initiator, opponent) {
async function setupSession(initiator: OlmDevice, opponent: OlmDevice) {
await opponent.generateOneTimeKeys(1);
const keys = await opponent.getOneTimeKeys();
const firstKey = Object.values(keys['curve25519'])[0];
const sid = await initiator.createOutboundSession(
opponent.deviceCurve25519Key, firstKey,
);
const sid = await initiator.createOutboundSession(opponent.deviceCurve25519Key!, firstKey);
return sid;
}
function alwaysSucceed<T>(promise: Promise<T>): Promise<T | void> {
// swallow any exception thrown by a promise, so that
// Promise.all doesn't abort
return promise.catch(() => {});
}
describe("OlmDevice", function() {
if (!global.Olm) {
logger.warn('Not running megolm unit tests: libolm not present');
@ -159,11 +163,6 @@ describe("OlmDevice", function() {
}, "ABCDEFG"),
],
};
function alwaysSucceed(promise) {
// swallow any exception thrown by a promise, so that
// Promise.all doesn't abort
return promise.catch(() => {});
}
// start two tasks that try to ensure that there's an olm session
const promises = Promise.all([
@ -235,12 +234,6 @@ describe("OlmDevice", function() {
],
};
function alwaysSucceed(promise) {
// swallow any exception thrown by a promise, so that
// Promise.all doesn't abort
return promise.catch(() => {});
}
const task1 = alwaysSucceed(olmlib.ensureOlmSessionsForDevices(
aliceOlmDevice, baseApis, devicesByUserAB,
));

View File

@ -15,8 +15,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MockedObject } from "jest-mock";
import '../../olm-loader';
import { logger } from "../../../src/logger";
import * as olmlib from "../../../src/crypto/olmlib";
@ -30,7 +28,10 @@ import { Crypto } from "../../../src/crypto";
import { resetCrossSigningKeys } from "./crypto-utils";
import { BackupManager } from "../../../src/crypto/backup";
import { StubStore } from "../../../src/store/stub";
import { MatrixScheduler } from '../../../src';
import { IndexedDBCryptoStore, MatrixScheduler } from '../../../src';
import { CryptoStore } from "../../../src/crypto/store/base";
import { MegolmDecryption as MegolmDecryptionClass } from "../../../src/crypto/algorithms/megolm";
import { IKeyBackupInfo } from "../../../src/crypto/keybackup";
const Olm = global.Olm;
@ -102,29 +103,36 @@ const CURVE25519_BACKUP_INFO = {
},
};
const AES256_BACKUP_INFO = {
const AES256_BACKUP_INFO: IKeyBackupInfo = {
algorithm: "org.matrix.msc3270.v1.aes-hmac-sha2",
version: '1',
auth_data: {
// FIXME: add iv and mac
},
auth_data: {} as IKeyBackupInfo["auth_data"],
};
const keys = {};
const keys: Record<string, Uint8Array> = {};
function getCrossSigningKey(type) {
return keys[type];
function getCrossSigningKey(type: string) {
return Promise.resolve(keys[type]);
}
function saveCrossSigningKeys(k) {
function saveCrossSigningKeys(k: Record<string, Uint8Array>) {
Object.assign(keys, k);
}
function makeTestClient(cryptoStore) {
const scheduler = [
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
function makeTestScheduler(): MatrixScheduler {
return ([
"getQueueForEvent",
"queueEvent",
"removeEventFromQueue",
"setProcessFunction",
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {}) as MockedObject<MatrixScheduler>;
] as const).reduce((r, k) => {
r[k] = jest.fn();
return r;
}, {} as MatrixScheduler);
}
function makeTestClient(cryptoStore: CryptoStore) {
const scheduler = makeTestScheduler();
const store = new StubStore();
return new MatrixClient({
@ -151,36 +159,33 @@ describe("MegolmBackup", function() {
return Olm.init();
});
let olmDevice;
let mockOlmLib;
let mockCrypto;
let cryptoStore;
let megolmDecryption;
let olmDevice: OlmDevice;
let mockOlmLib: typeof olmlib;
let mockCrypto: Crypto;
let cryptoStore: CryptoStore;
let megolmDecryption: MegolmDecryptionClass;
beforeEach(async function() {
mockCrypto = testUtils.mock(Crypto, 'Crypto');
// @ts-ignore making mock
mockCrypto.backupManager = testUtils.mock(BackupManager, "BackupManager");
mockCrypto.backupKey = new Olm.PkEncryption();
mockCrypto.backupKey.set_recipient_key(
"hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
);
mockCrypto.backupInfo = CURVE25519_BACKUP_INFO;
mockCrypto.backupManager.backupInfo = CURVE25519_BACKUP_INFO;
cryptoStore = new MemoryCryptoStore();
olmDevice = new OlmDevice(cryptoStore);
// we stub out the olm encryption bits
mockOlmLib = {};
mockOlmLib = {} as unknown as typeof olmlib;
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
mockOlmLib.encryptMessageForDevice =
jest.fn().mockResolvedValue(undefined);
});
describe("backup", function() {
let mockBaseApis;
let mockBaseApis: MatrixClient;
beforeEach(function() {
mockBaseApis = {};
mockBaseApis = {} as unknown as MatrixClient;
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
@ -188,8 +193,9 @@ describe("MegolmBackup", function() {
olmDevice: olmDevice,
baseApis: mockBaseApis,
roomId: ROOM_ID,
});
}) as MegolmDecryptionClass;
// @ts-ignore private field access
megolmDecryption.olmlib = mockOlmLib;
// clobber the setTimeout function to run 100x faster.
@ -239,6 +245,7 @@ describe("MegolmBackup", function() {
};
mockCrypto.cancelRoomKeyRequest = function() {};
// @ts-ignore readonly field write
mockCrypto.backupManager = {
backupGroupSession: jest.fn(),
};
@ -264,21 +271,22 @@ describe("MegolmBackup", function() {
olmDevice: olmDevice,
baseApis: client,
roomId: ROOM_ID,
});
}) as MegolmDecryptionClass;
// @ts-ignore private field access
megolmDecryption.olmlib = mockOlmLib;
return client.initCrypto()
.then(() => {
return cryptoStore.doTxn(
"readwrite",
[cryptoStore.STORE_SESSION],
[IndexedDBCryptoStore.STORE_SESSIONS],
(txn) => {
cryptoStore.addEndToEndInboundGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
groupSession.session_id(),
{
forwardingCurve25519KeyChain: undefined,
forwardingCurve25519KeyChain: undefined!,
keysClaimed: {
ed25519: "SENDER_ED25519",
},
@ -298,25 +306,25 @@ describe("MegolmBackup", function() {
});
let numCalls = 0;
return new Promise<void>((resolve, reject) => {
client.http.authedRequest = function<T>(
client.http.authedRequest = function(
method, path, queryParams, data, opts,
): Promise<T> {
): any {
++numCalls;
expect(numCalls).toBeLessThanOrEqual(1);
if (numCalls >= 2) {
// exit out of retry loop if there's something wrong
reject(new Error("authedRequest called too many timmes"));
return Promise.resolve({} as T);
return Promise.resolve({});
}
expect(method).toBe("PUT");
expect(path).toBe("/room_keys/keys");
expect(queryParams.version).toBe('1');
expect(queryParams?.version).toBe('1');
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toBeDefined();
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toHaveProperty(
groupSession.session_id(),
);
resolve();
return Promise.resolve({} as T);
return Promise.resolve({});
};
client.crypto!.backupManager.backupGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
@ -343,8 +351,9 @@ describe("MegolmBackup", function() {
olmDevice: olmDevice,
baseApis: client,
roomId: ROOM_ID,
});
}) as MegolmDecryptionClass;
// @ts-ignore private field access
megolmDecryption.olmlib = mockOlmLib;
return client.initCrypto()
@ -354,13 +363,13 @@ describe("MegolmBackup", function() {
.then(() => {
return cryptoStore.doTxn(
"readwrite",
[cryptoStore.STORE_SESSION],
[IndexedDBCryptoStore.STORE_SESSIONS],
(txn) => {
cryptoStore.addEndToEndInboundGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
groupSession.session_id(),
{
forwardingCurve25519KeyChain: undefined,
forwardingCurve25519KeyChain: undefined!,
keysClaimed: {
ed25519: "SENDER_ED25519",
},
@ -381,25 +390,25 @@ describe("MegolmBackup", function() {
});
let numCalls = 0;
return new Promise<void>((resolve, reject) => {
client.http.authedRequest = function<T>(
client.http.authedRequest = function(
method, path, queryParams, data, opts,
): Promise<T> {
): any {
++numCalls;
expect(numCalls).toBeLessThanOrEqual(1);
if (numCalls >= 2) {
// exit out of retry loop if there's something wrong
reject(new Error("authedRequest called too many timmes"));
return Promise.resolve({} as T);
return Promise.resolve({});
}
expect(method).toBe("PUT");
expect(path).toBe("/room_keys/keys");
expect(queryParams.version).toBe('1');
expect(queryParams?.version).toBe('1');
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toBeDefined();
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toHaveProperty(
groupSession.session_id(),
);
resolve();
return Promise.resolve({} as T);
return Promise.resolve({});
};
client.crypto!.backupManager.backupGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
@ -426,8 +435,9 @@ describe("MegolmBackup", function() {
olmDevice: olmDevice,
baseApis: client,
roomId: ROOM_ID,
});
}) as MegolmDecryptionClass;
// @ts-ignore private field access
megolmDecryption.olmlib = mockOlmLib;
await client.initCrypto();
@ -437,10 +447,10 @@ describe("MegolmBackup", function() {
let numCalls = 0;
await Promise.all([
new Promise<void>((resolve, reject) => {
let backupInfo;
let backupInfo: Record<string, any> | BodyInit | undefined;
client.http.authedRequest = function(
method, path, queryParams, data, opts,
) {
): any {
++numCalls;
expect(numCalls).toBeLessThanOrEqual(2);
if (numCalls === 1) {
@ -486,10 +496,7 @@ describe("MegolmBackup", function() {
const ibGroupSession = new Olm.InboundGroupSession();
ibGroupSession.create(groupSession.session_key());
const scheduler = [
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
"setProcessFunction",
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {}) as MockedObject<MatrixScheduler>;
const scheduler = makeTestScheduler();
const store = new StubStore();
const client = new MatrixClient({
baseUrl: "https://my.home.server",
@ -509,20 +516,21 @@ describe("MegolmBackup", function() {
olmDevice: olmDevice,
baseApis: client,
roomId: ROOM_ID,
});
}) as MegolmDecryptionClass;
// @ts-ignore private field access
megolmDecryption.olmlib = mockOlmLib;
await client.initCrypto();
await cryptoStore.doTxn(
"readwrite",
[cryptoStore.STORE_SESSION],
[IndexedDBCryptoStore.STORE_SESSIONS],
(txn) => {
cryptoStore.addEndToEndInboundGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
groupSession.session_id(),
{
forwardingCurve25519KeyChain: undefined,
forwardingCurve25519KeyChain: undefined!,
keysClaimed: {
ed25519: "SENDER_ED25519",
},
@ -542,26 +550,26 @@ describe("MegolmBackup", function() {
let numCalls = 0;
await new Promise<void>((resolve, reject) => {
client.http.authedRequest = function<T>(
client.http.authedRequest = function(
method, path, queryParams, data, opts,
): Promise<T> {
): any {
++numCalls;
expect(numCalls).toBeLessThanOrEqual(2);
if (numCalls >= 3) {
// exit out of retry loop if there's something wrong
reject(new Error("authedRequest called too many timmes"));
return Promise.resolve({} as T);
return Promise.resolve({});
}
expect(method).toBe("PUT");
expect(path).toBe("/room_keys/keys");
expect(queryParams.version).toBe('1');
expect(queryParams?.version).toBe('1');
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toBeDefined();
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toHaveProperty(
groupSession.session_id(),
);
if (numCalls > 1) {
resolve();
return Promise.resolve({} as T);
return Promise.resolve({});
} else {
return Promise.reject(
new Error("this is an expected failure"),
@ -579,7 +587,7 @@ describe("MegolmBackup", function() {
});
describe("restore", function() {
let client;
let client: MatrixClient;
beforeEach(function() {
client = makeTestClient(cryptoStore);
@ -590,8 +598,9 @@ describe("MegolmBackup", function() {
olmDevice: olmDevice,
baseApis: client,
roomId: ROOM_ID,
});
}) as MegolmDecryptionClass;
// @ts-ignore private field access
megolmDecryption.olmlib = mockOlmLib;
return client.initCrypto();
@ -603,7 +612,7 @@ describe("MegolmBackup", function() {
it('can restore from backup (Curve25519 version)', function() {
client.http.authedRequest = function() {
return Promise.resolve(CURVE25519_KEY_BACKUP_DATA);
return Promise.resolve<any>(CURVE25519_KEY_BACKUP_DATA);
};
return client.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
@ -620,7 +629,7 @@ describe("MegolmBackup", function() {
it('can restore from backup (AES-256 version)', function() {
client.http.authedRequest = function() {
return Promise.resolve(AES256_KEY_BACKUP_DATA);
return Promise.resolve<any>(AES256_KEY_BACKUP_DATA);
};
return client.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
@ -637,7 +646,7 @@ describe("MegolmBackup", function() {
it('can restore backup by room (Curve25519 version)', function() {
client.http.authedRequest = function() {
return Promise.resolve({
return Promise.resolve<any>({
rooms: {
[ROOM_ID]: {
sessions: {
@ -649,7 +658,7 @@ describe("MegolmBackup", function() {
};
return client.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
null, null, CURVE25519_BACKUP_INFO,
null!, null!, CURVE25519_BACKUP_INFO,
).then(() => {
return megolmDecryption.decryptEvent(ENCRYPTED_EVENT);
}).then((res) => {
@ -659,18 +668,18 @@ describe("MegolmBackup", function() {
it('has working cache functions', async function() {
const key = Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 8]);
await client.crypto.storeSessionBackupPrivateKey(key);
const result = await client.crypto.getSessionBackupPrivateKey();
expect(new Uint8Array(result)).toEqual(key);
await client.crypto!.storeSessionBackupPrivateKey(key);
const result = await client.crypto!.getSessionBackupPrivateKey();
expect(new Uint8Array(result!)).toEqual(key);
});
it('caches session backup keys as it encounters them', async function() {
const cachedNull = await client.crypto.getSessionBackupPrivateKey();
const cachedNull = await client.crypto!.getSessionBackupPrivateKey();
expect(cachedNull).toBeNull();
client.http.authedRequest = function() {
return Promise.resolve(CURVE25519_KEY_BACKUP_DATA);
return Promise.resolve<any>(CURVE25519_KEY_BACKUP_DATA);
};
await new Promise((resolve) => {
await new Promise<void>((resolve) => {
client.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
ROOM_ID,
@ -679,7 +688,7 @@ describe("MegolmBackup", function() {
{ cacheCompleteCallback: resolve },
);
});
const cachedKey = await client.crypto.getSessionBackupPrivateKey();
const cachedKey = await client.crypto!.getSessionBackupPrivateKey();
expect(cachedKey).not.toBeNull();
});
@ -688,7 +697,7 @@ describe("MegolmBackup", function() {
algorithm: "this.algorithm.does.not.exist",
});
client.http.authedRequest = function() {
return Promise.resolve(CURVE25519_KEY_BACKUP_DATA);
return Promise.resolve<any>(CURVE25519_KEY_BACKUP_DATA);
};
await expect(client.restoreKeyBackupWithRecoveryKey(
@ -702,10 +711,7 @@ describe("MegolmBackup", function() {
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 scheduler = makeTestScheduler();
const store = new StubStore();
const client = new MatrixClient({
baseUrl: "https://my.home.server",

View File

@ -18,23 +18,24 @@ limitations under the License.
import '../../olm-loader';
import anotherjson from 'another-json';
import { PkSigning } from '@matrix-org/olm';
import HttpBackend from "matrix-mock-request";
import * as olmlib from "../../../src/crypto/olmlib";
import { MatrixError } from '../../../src/http-api';
import { logger } from '../../../src/logger';
import { ICrossSigningKey, ICreateClientOpts, ISignedKey } from '../../../src/client';
import { CryptoEvent } from '../../../src/crypto';
import { CryptoEvent, IBootstrapCrossSigningOpts } from '../../../src/crypto';
import { IDevice } from '../../../src/crypto/deviceinfo';
import { TestClient } from '../../TestClient';
import { resetCrossSigningKeys } from "./crypto-utils";
const PUSH_RULES_RESPONSE = {
const PUSH_RULES_RESPONSE: Response = {
method: "GET",
path: "/pushrules/",
data: {},
};
const filterResponse = function(userId) {
const filterResponse = function(userId: string): Response {
const filterPath = "/user/" + encodeURIComponent(userId) + "/filter";
return {
method: "POST",
@ -43,7 +44,13 @@ const filterResponse = function(userId) {
};
};
function setHttpResponses(httpBackend, responses) {
interface Response {
method: 'GET' | 'PUT' | 'POST' | 'DELETE';
path: string;
data: object;
}
function setHttpResponses(httpBackend: HttpBackend, responses: Response[]) {
responses.forEach(response => {
httpBackend
.when(response.method, response.path)
@ -54,13 +61,13 @@ function setHttpResponses(httpBackend, responses) {
async function makeTestClient(
userInfo: { userId: string, deviceId: string},
options: Partial<ICreateClientOpts> = {},
keys = {},
keys: Record<string, Uint8Array> = {},
) {
function getCrossSigningKey(type) {
return keys[type];
function getCrossSigningKey(type: string) {
return keys[type] ?? null;
}
function saveCrossSigningKeys(k) {
function saveCrossSigningKeys(k: Record<string, Uint8Array>) {
Object.assign(keys, k);
}
@ -142,7 +149,9 @@ describe("Cross Signing", function() {
alice.uploadKeySignatures = async () => ({ failures: {} });
alice.setAccountData = async () => ({});
alice.getAccountDataFromServer = async <T extends {[k: string]: any}>(): Promise<T | null> => ({} as T);
const authUploadDeviceSigningKeys = async func => await func({});
const authUploadDeviceSigningKeys: IBootstrapCrossSigningOpts["authUploadDeviceSigningKeys"] = async func => {
await func({});
};
// Try bootstrap, expecting `authUploadDeviceSigningKeys` to pass
// through failure, stopping before actually applying changes.
@ -275,7 +284,7 @@ describe("Cross Signing", function() {
);
// feed sync result that includes master key, ssk, device key
const responses = [
const responses: Response[] = [
PUSH_RULES_RESPONSE,
{
method: "POST",
@ -464,7 +473,7 @@ describe("Cross Signing", function() {
});
it.skip("should trust signatures received from other devices", async function() {
const aliceKeys: Record<string, PkSigning> = {};
const aliceKeys: Record<string, Uint8Array> = {};
const { client: alice, httpBackend } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
undefined,
@ -494,8 +503,7 @@ describe("Cross Signing", function() {
});
// @ts-ignore private property
const deviceInfo = alice.crypto!.deviceList.devices["@alice:example.com"]
.Osborne2;
const deviceInfo = alice.crypto!.deviceList.devices["@alice:example.com"].Osborne2;
const aliceDevice = {
user_id: "@alice:example.com",
device_id: "Osborne2",
@ -549,7 +557,7 @@ describe("Cross Signing", function() {
// - ssk
// - master key signed by her usk (pretend that it was signed by another
// of Alice's devices)
const responses = [
const responses: Response[] = [
PUSH_RULES_RESPONSE,
{
method: "POST",
@ -853,7 +861,7 @@ describe("Cross Signing", function() {
});
it("should offer to upgrade device verifications to cross-signing", async function() {
let upgradeResolveFunc;
let upgradeResolveFunc: Function;
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },

View File

@ -1,14 +1,16 @@
import { IRecoveryKey } from '../../../src/crypto/api';
import { CrossSigningLevel } from '../../../src/crypto/CrossSigning';
import { IndexedDBCryptoStore } from '../../../src/crypto/store/indexeddb-crypto-store';
import { MatrixClient } from "../../../src";
import { CryptoEvent } from "../../../src/crypto";
// needs to be phased out and replaced with bootstrapSecretStorage,
// but that is doing too much extra stuff for it to be an easy transition.
export async function resetCrossSigningKeys(
client,
client: MatrixClient,
{ level }: { level?: CrossSigningLevel} = {},
): Promise<void> {
const crypto = client.crypto;
const crypto = client.crypto!;
const oldKeys = Object.assign({}, crypto.crossSigningInfo.keys);
try {
@ -28,7 +30,8 @@ export async function resetCrossSigningKeys(
crypto.crossSigningInfo.keys = oldKeys;
throw e;
}
crypto.emit("crossSigning.keysChanged", {});
crypto.emit(CryptoEvent.KeysChanged, {});
// @ts-ignore
await crypto.afterCrossSigningLocalKeyChange();
}

View File

@ -16,6 +16,7 @@ limitations under the License.
import '../../olm-loader';
import * as olmlib from "../../../src/crypto/olmlib";
import { IObject } from "../../../src/crypto/olmlib";
import { SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/crypto/SecretStorage";
import { MatrixEvent } from "../../../src/models/event";
import { TestClient } from '../../TestClient';
@ -23,9 +24,11 @@ import { makeTestClients } from './verification/util';
import { encryptAES } from "../../../src/crypto/aes";
import { createSecretStorageKey, resetCrossSigningKeys } from "./crypto-utils";
import { logger } from '../../../src/logger';
import { ClientEvent, ICreateClientOpts } from '../../../src/client';
import { ClientEvent, ICreateClientOpts, ICrossSigningKey, MatrixClient } from '../../../src/client';
import { ISecretStorageKeyInfo } from '../../../src/crypto/api';
import { DeviceInfo } from '../../../src/crypto/deviceinfo';
import { ISignatures } from "../../../src/@types/signed";
import { ICurve25519AuthData } from "../../../src/crypto/keybackup";
async function makeTestClient(userInfo: { userId: string, deviceId: string}, options: Partial<ICreateClientOpts> = {}) {
const client = (new TestClient(
@ -48,9 +51,15 @@ async function makeTestClient(userInfo: { userId: string, deviceId: string}, opt
// Wrapper around pkSign to return a signed object. pkSign returns the
// signature, rather than the signed object.
function sign(obj, key, userId) {
function sign<T extends IObject | ICurve25519AuthData>(obj: T, key: Uint8Array, userId: string): T & {
signatures: ISignatures;
unsigned?: object;
} {
olmlib.pkSign(obj, key, userId, '');
return obj;
return obj as T & {
signatures: ISignatures;
unsigned?: object;
};
}
describe("Secrets", function() {
@ -169,12 +178,12 @@ describe("Secrets", function() {
return [newKeyId, key];
});
let keys = {};
let keys: Record<string, Uint8Array> = {};
const alice = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{
cryptoCallbacks: {
getCrossSigningKey: t => keys[t],
getCrossSigningKey: t => Promise.resolve(keys[t]),
saveCrossSigningKeys: k => keys = k,
getSecretStorageKey: getKey,
},
@ -227,7 +236,7 @@ describe("Secrets", function() {
cryptoCallbacks: {
onSecretRequested: (userId, deviceId, requestId, secretName, deviceTrust) => {
expect(secretName).toBe("foo");
return "bar";
return Promise.resolve("bar");
},
},
},
@ -354,7 +363,7 @@ describe("Secrets", function() {
const storagePublicKey = decryption.generate_key();
const storagePrivateKey = decryption.get_private_key();
const bob = await makeTestClient(
const bob: MatrixClient = await makeTestClient(
{
userId: "@bob:example.com",
deviceId: "bob1",
@ -364,15 +373,15 @@ describe("Secrets", function() {
getSecretStorageKey: async request => {
const defaultKeyId = await bob.getDefaultSecretStorageKeyId();
expect(Object.keys(request.keys)).toEqual([defaultKeyId]);
return [defaultKeyId, storagePrivateKey];
return [defaultKeyId!, storagePrivateKey];
},
},
},
);
bob.uploadDeviceSigningKeys = async () => {};
bob.uploadKeySignatures = async () => {};
bob.setAccountData = async function(eventType, contents, callback) {
bob.uploadDeviceSigningKeys = async () => ({});
bob.uploadKeySignatures = async () => ({ failures: {} });
bob.setAccountData = async function(eventType, contents) {
const event = new MatrixEvent({
type: eventType,
content: contents,
@ -380,16 +389,19 @@ describe("Secrets", function() {
this.store.storeAccountDataEvents([
event,
]);
this.emit("accountData", event);
this.emit(ClientEvent.AccountData, event);
return {};
};
bob.crypto.backupManager.checkKeyBackup = async () => {};
bob.crypto!.backupManager.checkKeyBackup = async () => null;
const crossSigning = bob.crypto.crossSigningInfo;
const secretStorage = bob.crypto.secretStorage;
const crossSigning = bob.crypto!.crossSigningInfo;
const secretStorage = bob.crypto!.secretStorage;
// Set up cross-signing keys from scratch with specific storage key
await bob.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async func => await func({}),
authUploadDeviceSigningKeys: async func => {
await func({});
},
});
await bob.bootstrapSecretStorage({
createSecretStorageKey: async () => ({
@ -400,13 +412,15 @@ describe("Secrets", function() {
});
// Clear local cross-signing keys and read from secret storage
bob.crypto.deviceList.storeCrossSigningForUser(
bob.crypto!.deviceList.storeCrossSigningForUser(
"@bob:example.com",
crossSigning.toStorage(),
);
crossSigning.keys = {};
await bob.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async func => await func({}),
authUploadDeviceSigningKeys: async func => {
await func({});
},
});
expect(crossSigning.getId()).toBeTruthy();
@ -422,7 +436,7 @@ describe("Secrets", function() {
user_signing: USK,
self_signing: SSK,
};
const secretStorageKeys = {
const secretStorageKeys: Record<string, Uint8Array> = {
key_id: SSSSKey,
};
const alice = await makeTestClient(
@ -498,14 +512,14 @@ describe("Secrets", function() {
[`ed25519:${XSPubKey}`]: XSPubKey,
},
},
self_signing: sign({
self_signing: sign<ICrossSigningKey>({
user_id: "@alice:example.com",
usage: ["self_signing"],
keys: {
[`ed25519:${SSPubKey}`]: SSPubKey,
},
}, XSK, "@alice:example.com"),
user_signing: sign({
user_signing: sign<ICrossSigningKey>({
user_id: "@alice:example.com",
usage: ["user_signing"],
keys: {
@ -557,7 +571,7 @@ describe("Secrets", function() {
user_signing: USK,
self_signing: SSK,
};
const secretStorageKeys = {
const secretStorageKeys: Record<string, Uint8Array> = {
key_id: SSSSKey,
};
const alice = await makeTestClient(
@ -642,14 +656,14 @@ describe("Secrets", function() {
[`ed25519:${XSPubKey}`]: XSPubKey,
},
},
self_signing: sign({
self_signing: sign<ICrossSigningKey>({
user_id: "@alice:example.com",
usage: ["self_signing"],
keys: {
[`ed25519:${SSPubKey}`]: SSPubKey,
},
}, XSK, "@alice:example.com"),
user_signing: sign({
user_signing: sign<ICrossSigningKey>({
user_id: "@alice:example.com",
usage: ["user_signing"],
keys: {

View File

@ -18,12 +18,12 @@ import "../../../olm-loader";
import { makeTestClients } from './util';
import { MatrixEvent } from "../../../../src/models/event";
import { ISasEvent, SAS, SasEvent } from "../../../../src/crypto/verification/SAS";
import { DeviceInfo } from "../../../../src/crypto/deviceinfo";
import { DeviceInfo, IDevice } 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 as Verification, VerificationBase } from "../../../../src/crypto/verification/Base";
import { 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";
@ -31,8 +31,8 @@ import { TestClient } from "../../../TestClient";
const Olm = global.Olm;
let ALICE_DEVICES;
let BOB_DEVICES;
let ALICE_DEVICES: Record<string, IDevice>;
let BOB_DEVICES: Record<string, IDevice>;
describe("SAS verification", function() {
if (!global.Olm) {
@ -75,7 +75,7 @@ describe("SAS verification", function() {
let bob: TestClient;
let aliceSasEvent: ISasEvent | null;
let bobSasEvent: ISasEvent | null;
let aliceVerifier: Verification<any, any>;
let aliceVerifier: SAS;
let bobPromise: Promise<VerificationBase<any, any>>;
let clearTestClientTimeouts: () => void;
@ -95,25 +95,25 @@ describe("SAS verification", function() {
ALICE_DEVICES = {
Osborne2: {
user_id: "@alice:example.com",
device_id: "Osborne2",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Osborne2": aliceDevice.deviceEd25519Key,
"curve25519:Osborne2": aliceDevice.deviceCurve25519Key,
"ed25519:Osborne2": aliceDevice.deviceEd25519Key!,
"curve25519:Osborne2": aliceDevice.deviceCurve25519Key!,
},
verified: DeviceInfo.DeviceVerification.UNVERIFIED,
known: false,
},
};
BOB_DEVICES = {
Dynabook: {
user_id: "@bob:example.com",
device_id: "Dynabook",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Dynabook": bobDevice.deviceEd25519Key,
"curve25519:Dynabook": bobDevice.deviceCurve25519Key,
"ed25519:Dynabook": bobDevice.deviceEd25519Key!,
"curve25519:Dynabook": bobDevice.deviceCurve25519Key!,
},
verified: DeviceInfo.DeviceVerification.UNVERIFIED,
known: false,
},
};
@ -136,7 +136,7 @@ describe("SAS verification", function() {
bobPromise = new Promise<VerificationBase<any, any>>((resolve, reject) => {
bob.client.on(CryptoEvent.VerificationRequest, request => {
request.verifier!.on("show_sas", (e) => {
(<SAS>request.verifier!).on(SasEvent.ShowSas, (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
e.cancel();
} else if (!aliceSasEvent) {
@ -158,7 +158,7 @@ describe("SAS verification", function() {
aliceVerifier = alice.client.beginKeyVerification(
verificationMethods.SAS, bob.client.getUserId()!, bob.deviceId!,
);
) as SAS;
aliceVerifier.on(SasEvent.ShowSas, (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
e.cancel();
@ -413,7 +413,7 @@ 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) => {
(<SAS>request.verifier!).on(SasEvent.ShowSas, (e) => {
e.mismatch();
});
resolve(request.verifier!);
@ -443,13 +443,13 @@ describe("SAS verification", function() {
});
describe("verification in DM", function() {
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: SAS;
let bobPromise: Promise<void>;
let clearTestClientTimeouts: Function;
beforeEach(async function() {
[[alice, bob], clearTestClientTimeouts] = await makeTestClients(
@ -477,7 +477,7 @@ describe("SAS verification", function() {
);
};
alice.client.downloadKeys = () => {
return Promise.resolve();
return Promise.resolve({});
};
bob.client.crypto!.setDeviceVerification = jest.fn();
@ -495,16 +495,16 @@ describe("SAS verification", function() {
return "bob+base64+ed25519+key";
};
bob.client.downloadKeys = () => {
return Promise.resolve();
return Promise.resolve({});
};
aliceSasEvent = null;
bobSasEvent = null;
bobPromise = new Promise<void>((resolve, reject) => {
bob.client.on("crypto.verification.request", async (request) => {
const verifier = request.beginKeyVerification(SAS.NAME);
verifier.on("show_sas", (e) => {
bob.client.on(CryptoEvent.VerificationRequest, async (request) => {
const verifier = request.beginKeyVerification(SAS.NAME) as SAS;
verifier.on(SasEvent.ShowSas, (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
e.cancel();
} else if (!aliceSasEvent) {
@ -525,12 +525,10 @@ describe("SAS verification", function() {
});
});
const aliceRequest = await alice.client.requestVerificationDM(
bob.client.getUserId(), "!room_id",
);
const aliceRequest = await alice.client.requestVerificationDM(bob.client.getUserId()!, "!room_id");
await aliceRequest.waitFor(r => r.started);
aliceVerifier = aliceRequest.verifier;
aliceVerifier.on("show_sas", (e) => {
aliceVerifier = aliceRequest.verifier! as SAS;
aliceVerifier.on(SasEvent.ShowSas, (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
e.cancel();
} else if (!bobSasEvent) {

View File

@ -125,7 +125,7 @@ describe("self-verifications", () => {
expect(restoreKeyBackupWithCache).toHaveBeenCalled();
expect(result).toBeInstanceOf(Array);
expect(result[0][0]).toBe(testKeyPub);
expect(result[1][0]).toBe(testKeyPub);
expect(result![0][0]).toBe(testKeyPub);
expect(result![1][0]).toBe(testKeyPub);
});
});

View File

@ -16,13 +16,21 @@ limitations under the License.
*/
import { TestClient } from '../../../TestClient';
import { MatrixEvent } from "../../../../src/models/event";
import { IContent, MatrixEvent } from "../../../../src/models/event";
import { IRoomTimelineData } from "../../../../src/models/event-timeline-set";
import { Room, RoomEvent } from "../../../../src/models/room";
import { logger } from '../../../../src/logger';
import { MatrixClient, ClientEvent } from '../../../../src/client';
import { MatrixClient, ClientEvent, ICreateClientOpts } from '../../../../src/client';
export async function makeTestClients(userInfos, options): Promise<[TestClient[], () => void]> {
interface UserInfo {
userId: string;
deviceId: string;
}
export async function makeTestClients(
userInfos: UserInfo[],
options: Partial<ICreateClientOpts>,
): Promise<[TestClient[], () => void]> {
const clients: TestClient[] = [];
const timeouts: ReturnType<typeof setTimeout>[] = [];
const clientMap: Record<string, Record<string, MatrixClient>> = {};
@ -51,7 +59,7 @@ export async function makeTestClients(userInfos, options): Promise<[TestClient[]
}
return {};
};
const makeSendEvent = (matrixClient: MatrixClient) => (room, type, content) => {
const makeSendEvent = (matrixClient: MatrixClient) => (room: string, type: string, content: IContent) => {
// make up a unique ID as the event ID
const eventId = "$" + matrixClient.makeTxnId();
const rawEvent = {
@ -88,11 +96,12 @@ export async function makeTestClients(userInfos, options): Promise<[TestClient[]
};
for (const userInfo of userInfos) {
let keys = {};
let keys: Record<string, Uint8Array> = {};
if (!options) options = {};
if (!options.cryptoCallbacks) options.cryptoCallbacks = {};
if (!options.cryptoCallbacks.saveCrossSigningKeys) {
options.cryptoCallbacks.saveCrossSigningKeys = k => { keys = k; };
// @ts-ignore tsc getting confused by overloads
options.cryptoCallbacks.getCrossSigningKey = typ => keys[typ];
}
const testClient = new TestClient(
@ -104,6 +113,7 @@ export async function makeTestClients(userInfos, options): Promise<[TestClient[]
}
clientMap[userInfo.userId][userInfo.deviceId] = testClient.client;
testClient.client.sendToDevice = makeSendToDevice(testClient.client);
// @ts-ignore tsc getting confused by overloads
testClient.client.sendEvent = makeSendEvent(testClient.client);
clients.push(testClient);
}

View File

@ -18,7 +18,7 @@ import { VerificationRequest, READY_TYPE, START_TYPE, DONE_TYPE } from
import { InRoomChannel } from "../../../../src/crypto/verification/request/InRoomChannel";
import { ToDeviceChannel } from
"../../../../src/crypto/verification/request/ToDeviceChannel";
import { MatrixEvent } from "../../../../src/models/event";
import { IContent, MatrixEvent } from "../../../../src/models/event";
import { MatrixClient } from "../../../../src/client";
import { IVerificationChannel } from "../../../../src/crypto/verification/request/Channel";
import { VerificationBase } from "../../../../src/crypto/verification/Base";
@ -30,12 +30,12 @@ type MockClient = MatrixClient & {
function makeMockClient(userId: string, deviceId: string): MockClient {
let counter = 1;
let events: MatrixEvent[] = [];
const deviceEvents = {};
const deviceEvents: Record<string, Record<string, MatrixEvent[]>> = {};
return {
getUserId() { return userId; },
getDeviceId() { return deviceId; },
sendEvent(roomId, type, content) {
sendEvent(roomId: string, type: string, content: IContent) {
counter = counter + 1;
const eventId = `$${userId}-${deviceId}-${counter}`;
events.push(new MatrixEvent({
@ -49,7 +49,7 @@ function makeMockClient(userId: string, deviceId: string): MockClient {
return Promise.resolve({ event_id: eventId });
},
sendToDevice(type, msgMap) {
sendToDevice(type: string, msgMap: Record<string, Record<string, IContent>>) {
for (const userId of Object.keys(msgMap)) {
const deviceMap = msgMap[userId];
for (const deviceId of Object.keys(deviceMap)) {
@ -111,7 +111,7 @@ class MockVerifier extends VerificationBase<'', any> {
}
}
async handleEvent(event) {
async handleEvent(event: MatrixEvent) {
if (event.getType() === DONE_TYPE && !this._startEvent) {
await this._channel.send(DONE_TYPE, {});
}
@ -122,7 +122,7 @@ class MockVerifier extends VerificationBase<'', any> {
}
}
function makeRemoteEcho(event) {
function makeRemoteEcho(event: MatrixEvent) {
return new MatrixEvent(Object.assign({}, event.event, {
unsigned: {
transaction_id: "abc",

View File

@ -17,7 +17,7 @@ limitations under the License.
import { mocked } from "jest-mock";
import { logger } from "../../src/logger";
import { MatrixClient, ClientEvent } from "../../src/client";
import { ClientEvent, ITurnServerResponse, MatrixClient, Store } from "../../src/client";
import { Filter } from "../../src/filter";
import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE } from "../../src/models/MSC3089TreeSpace";
import {
@ -36,7 +36,16 @@ 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, MatrixError, Room } from "../../src";
import {
ContentHelpers,
EventTimeline, ICreateRoomOpts,
IRequestOpts,
MatrixError,
MatrixHttpApi,
MatrixScheduler,
Method,
Room,
} from "../../src";
import { supportsMatrixCall } from "../../src/webrtc/call";
import { makeBeaconEvent } from "../test-utils/beacon";
import {
@ -44,6 +53,9 @@ import {
POLICIES_ACCOUNT_EVENT_TYPE,
PolicyScope,
} from "../../src/models/invites-ignorer";
import { IOlmDevice } from "../../src/crypto/algorithms/megolm";
import { QueryDict } from "../../src/utils";
import { SyncState } from "../../src/sync";
jest.useFakeTimers();
@ -52,17 +64,36 @@ jest.mock("../../src/webrtc/call", () => ({
supportsMatrixCall: jest.fn(() => false),
}));
type HttpLookup = {
method: string;
path: string;
data?: Record<string, any>;
error?: object;
expectBody?: Record<string, any>;
expectQueryParams?: QueryDict;
thenCall?: Function;
};
interface Options extends ICreateRoomOpts {
_roomId?: string;
}
type WrappedRoom = Room & {
_options: Options;
_state: Map<string, any>;
};
describe("MatrixClient", function() {
const userId = "@alice:bar";
const identityServerUrl = "https://identity.server";
const identityServerDomain = "identity.server";
let client;
let store;
let scheduler;
let client: MatrixClient;
let store: Store;
let scheduler: MatrixScheduler;
const KEEP_ALIVE_PATH = "/_matrix/client/versions";
const PUSH_RULES_RESPONSE = {
const PUSH_RULES_RESPONSE: HttpLookup = {
method: "GET",
path: "/pushrules/",
data: {},
@ -70,7 +101,7 @@ describe("MatrixClient", function() {
const FILTER_PATH = "/user/" + encodeURIComponent(userId) + "/filter";
const FILTER_RESPONSE = {
const FILTER_RESPONSE: HttpLookup = {
method: "POST",
path: FILTER_PATH,
data: { filter_id: "f1lt3r" },
@ -82,29 +113,21 @@ describe("MatrixClient", function() {
rooms: {},
};
const SYNC_RESPONSE = {
const SYNC_RESPONSE: HttpLookup = {
method: "GET",
path: "/sync",
data: SYNC_DATA,
};
// 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 httpLookups: HttpLookup[] = [];
let acceptKeepalives: boolean;
let pendingLookup: {
promise: Promise<any>;
method: string;
path: string;
} | null = null;
function httpReq(method, path, qp, data, prefix) {
function httpReq(method: Method, path: string, qp?: QueryDict, data?: BodyInit, opts?: IRequestOpts) {
if (path === KEEP_ALIVE_PATH && acceptKeepalives) {
return Promise.resolve({
unstable_features: {
@ -145,7 +168,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]);
});
}
@ -184,24 +207,38 @@ describe("MatrixClient", function() {
userId: userId,
});
// FIXME: We shouldn't be yanking http like this.
client.http = [
"authedRequest", "getContentUri", "request", "uploadContent",
].reduce((r, k) => { r[k] = jest.fn(); return r; }, {});
client.http.authedRequest.mockImplementation(httpReq);
client.http.request.mockImplementation(httpReq);
client.http = ([
"authedRequest",
"getContentUri",
"request",
"uploadContent",
] as const).reduce((r, k) => {
r[k] = jest.fn();
return r;
}, {} as MatrixHttpApi<any>);
mocked(client.http.authedRequest).mockImplementation(httpReq);
mocked(client.http.request).mockImplementation(httpReq);
}
beforeEach(function() {
scheduler = [
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
scheduler = ([
"getQueueForEvent",
"queueEvent",
"removeEventFromQueue",
"setProcessFunction",
].reduce((r, k) => { r[k] = jest.fn(); return r; }, {});
store = [
] as const).reduce((r, k) => {
r[k] = jest.fn();
return r;
}, {} as MatrixScheduler);
store = ([
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
"save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom", "storeUser",
"getFilterIdByName", "setFilterIdByName", "getFilter", "storeFilter",
"getSyncAccumulator", "startup", "deleteAllData",
].reduce((r, k) => { r[k] = jest.fn(); return r; }, {});
"startup", "deleteAllData",
] as const).reduce((r, k) => {
r[k] = jest.fn();
return r;
}, {} as Store);
store.getSavedSync = jest.fn().mockReturnValue(Promise.resolve(null));
store.getSavedSyncToken = jest.fn().mockReturnValue(Promise.resolve(null));
store.setSyncData = jest.fn().mockReturnValue(Promise.resolve(null));
@ -225,7 +262,7 @@ describe("MatrixClient", function() {
// means they may call /events and then fail an expect() which will fail
// a DIFFERENT test (pollution between tests!) - we return unresolved
// promises to stop the client from continuing to run.
client.http.authedRequest.mockImplementation(function() {
mocked(client.http.authedRequest).mockImplementation(function() {
return new Promise(() => {});
});
client.stopClient();
@ -289,7 +326,7 @@ describe("MatrixClient", function() {
const txnId = client.makeTxnId();
const room = new Room(roomId, client, userId);
store.getRoom.mockReturnValue(room);
mocked(store.getRoom).mockReturnValue(room);
const rootEvent = new MatrixEvent({ event_id: threadId });
room.createThread(threadId, rootEvent, [rootEvent], false);
@ -329,7 +366,7 @@ describe("MatrixClient", function() {
};
const room = new Room(roomId, client, userId);
store.getRoom.mockReturnValue(room);
mocked(store.getRoom).mockReturnValue(room);
const rootEvent = new MatrixEvent({ event_id: threadId });
room.createThread(threadId, rootEvent, [rootEvent], false);
@ -359,7 +396,7 @@ describe("MatrixClient", function() {
const userId = "@test:example.org";
const roomId = "!room:example.org";
const roomName = "Test Tree";
const mockRoom = {};
const mockRoom = {} as unknown as Room;
const fn = jest.fn().mockImplementation((opts) => {
expect(opts).toMatchObject({
name: roomName,
@ -431,23 +468,23 @@ describe("MatrixClient", function() {
throw new Error("Unexpected event type or state key");
}
},
},
};
} as Room["currentState"],
} as unknown as Room;
client.getRoom = (getRoomId) => {
expect(getRoomId).toEqual(roomId);
return mockRoom;
};
const tree = client.unstableGetFileTreeSpace(roomId);
expect(tree).toBeDefined();
expect(tree.roomId).toEqual(roomId);
expect(tree.room).toBe(mockRoom);
expect(tree!.roomId).toEqual(roomId);
expect(tree!.room).toBe(mockRoom);
});
it("should not get (unstable) file trees if not joined", async () => {
const roomId = "!room:example.org";
const mockRoom = {
getMyMembership: () => "leave", // "not join"
};
} as unknown as Room;
client.getRoom = (getRoomId) => {
expect(getRoomId).toEqual(roomId);
return mockRoom;
@ -491,8 +528,8 @@ describe("MatrixClient", function() {
throw new Error("Unexpected event type or state key");
}
},
},
};
} as Room["currentState"],
} as unknown as Room;
client.getRoom = (getRoomId) => {
expect(getRoomId).toEqual(roomId);
return mockRoom;
@ -525,8 +562,8 @@ describe("MatrixClient", function() {
throw new Error("Unexpected event type or state key");
}
},
},
};
} as Room["currentState"],
} as unknown as Room;
client.getRoom = (getRoomId) => {
expect(getRoomId).toEqual(roomId);
return mockRoom;
@ -541,15 +578,15 @@ describe("MatrixClient", function() {
SYNC_RESPONSE,
];
const filterId = "ehfewf";
store.getFilterIdByName.mockReturnValue(filterId);
mocked(store.getFilterIdByName).mockReturnValue(filterId);
const filter = new Filter("0", filterId);
filter.setDefinition({ "room": { "timeline": { "limit": 8 } } });
store.getFilter.mockReturnValue(filter);
mocked(store.getFilter).mockReturnValue(filter);
const syncPromise = new Promise<void>((resolve, reject) => {
client.on("sync", function syncListener(state) {
client.on(ClientEvent.Sync, function syncListener(state) {
if (state === "SYNCING") {
expect(httpLookups.length).toEqual(0);
client.removeListener("sync", syncListener);
client.removeListener(ClientEvent.Sync, syncListener);
resolve();
} else if (state === "ERROR") {
reject(new Error("sync error"));
@ -567,10 +604,10 @@ describe("MatrixClient", function() {
it("should return the same sync state as emitted sync events", async function() {
const syncingPromise = new Promise<void>((resolve) => {
client.on("sync", function syncListener(state) {
client.on(ClientEvent.Sync, function syncListener(state) {
expect(state).toEqual(client.getSyncState());
if (state === "SYNCING") {
client.removeListener("sync", syncListener);
client.removeListener(ClientEvent.Sync, syncListener);
resolve();
}
});
@ -586,7 +623,7 @@ describe("MatrixClient", function() {
it("should use an existing filter if id is present in localStorage", function() {
});
it("should handle localStorage filterId missing from the server", function(done) {
function getFilterName(userId, suffix?: string) {
function getFilterName(userId: string, suffix?: string) {
// scope this on the user ID because people may login on many accounts
// and they all need to be stored!
return "FILTER_SYNC_" + userId + (suffix ? "_" + suffix : "");
@ -605,14 +642,14 @@ describe("MatrixClient", function() {
},
});
httpLookups.push(FILTER_RESPONSE);
store.getFilterIdByName.mockReturnValue(invalidFilterId);
mocked(store.getFilterIdByName).mockReturnValue(invalidFilterId);
const filterName = getFilterName(client.credentials.userId);
const filterName = getFilterName(client.credentials.userId!);
client.store.setFilterIdByName(filterName, invalidFilterId);
const filter = new Filter(client.credentials.userId);
client.getOrCreateFilter(filterName, filter).then(function(filterId) {
expect(filterId).toEqual(FILTER_RESPONSE.data.filter_id);
expect(filterId).toEqual(FILTER_RESPONSE.data?.filter_id);
done();
});
});
@ -634,13 +671,13 @@ describe("MatrixClient", function() {
httpLookups.push(FILTER_RESPONSE);
httpLookups.push(SYNC_RESPONSE);
client.on("sync", function syncListener(state) {
client.on(ClientEvent.Sync, function syncListener(state) {
if (state === "ERROR" && httpLookups.length > 0) {
expect(httpLookups.length).toEqual(2);
expect(client.retryImmediately()).toBe(true);
jest.advanceTimersByTime(1);
} else if (state === "PREPARED" && httpLookups.length === 0) {
client.removeListener("sync", syncListener);
client.removeListener(ClientEvent.Sync, syncListener);
done();
} else {
// unexpected state transition!
@ -658,7 +695,7 @@ describe("MatrixClient", function() {
method: "GET", path: "/sync", data: SYNC_DATA,
});
client.on("sync", function syncListener(state) {
client.on(ClientEvent.Sync, function syncListener(state) {
if (state === "ERROR" && httpLookups.length > 0) {
expect(httpLookups.length).toEqual(1);
expect(client.retryImmediately()).toBe(
@ -668,7 +705,7 @@ describe("MatrixClient", function() {
} else if (state === "RECONNECTING" && httpLookups.length > 0) {
jest.advanceTimersByTime(10000);
} else if (state === "SYNCING" && httpLookups.length === 0) {
client.removeListener("sync", syncListener);
client.removeListener(ClientEvent.Sync, syncListener);
done();
}
});
@ -684,13 +721,13 @@ describe("MatrixClient", function() {
httpLookups.push(FILTER_RESPONSE);
httpLookups.push(SYNC_RESPONSE);
client.on("sync", function syncListener(state) {
client.on(ClientEvent.Sync, function syncListener(state) {
if (state === "ERROR" && httpLookups.length > 0) {
expect(httpLookups.length).toEqual(3);
expect(client.retryImmediately()).toBe(true);
jest.advanceTimersByTime(1);
} else if (state === "PREPARED" && httpLookups.length === 0) {
client.removeListener("sync", syncListener);
client.removeListener(ClientEvent.Sync, syncListener);
done();
} else {
// unexpected state transition!
@ -702,8 +739,8 @@ describe("MatrixClient", function() {
});
describe("emitted sync events", function() {
function syncChecker(expectedStates, done) {
return function syncListener(state, old) {
function syncChecker(expectedStates: [string, string | null][], done: Function) {
return function syncListener(state: SyncState, old: SyncState | null) {
const expected = expectedStates.shift();
logger.log(
"'sync' curr=%s old=%s EXPECT=%s", state, old, expected,
@ -715,7 +752,7 @@ describe("MatrixClient", function() {
expect(state).toEqual(expected[0]);
expect(old).toEqual(expected[1]);
if (expectedStates.length === 0) {
client.removeListener("sync", syncListener);
client.removeListener(ClientEvent.Sync, syncListener);
done();
}
// standard retry time is 5 to 10 seconds
@ -726,7 +763,7 @@ describe("MatrixClient", function() {
it("should transition null -> PREPARED after the first /sync", function(done) {
const expectedStates: [string, string | null][] = [];
expectedStates.push(["PREPARED", null]);
client.on("sync", syncChecker(expectedStates, done));
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
client.startClient();
});
@ -738,7 +775,7 @@ describe("MatrixClient", function() {
method: "POST", path: FILTER_PATH, error: { errcode: "NOPE_NOPE_NOPE" },
});
expectedStates.push(["ERROR", null]);
client.on("sync", syncChecker(expectedStates, done));
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
client.startClient();
});
@ -768,7 +805,7 @@ describe("MatrixClient", function() {
expectedStates.push(["RECONNECTING", null]);
expectedStates.push(["ERROR", "RECONNECTING"]);
expectedStates.push(["CATCHUP", "ERROR"]);
client.on("sync", syncChecker(expectedStates, done));
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
client.startClient();
});
@ -776,7 +813,7 @@ describe("MatrixClient", function() {
const expectedStates: [string, string | null][] = [];
expectedStates.push(["PREPARED", null]);
expectedStates.push(["SYNCING", "PREPARED"]);
client.on("sync", syncChecker(expectedStates, done));
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
client.startClient();
});
@ -795,7 +832,7 @@ describe("MatrixClient", function() {
expectedStates.push(["SYNCING", "PREPARED"]);
expectedStates.push(["RECONNECTING", "SYNCING"]);
expectedStates.push(["ERROR", "RECONNECTING"]);
client.on("sync", syncChecker(expectedStates, done));
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
client.startClient();
});
@ -809,7 +846,7 @@ describe("MatrixClient", function() {
expectedStates.push(["PREPARED", null]);
expectedStates.push(["SYNCING", "PREPARED"]);
expectedStates.push(["ERROR", "SYNCING"]);
client.on("sync", syncChecker(expectedStates, done));
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
client.startClient();
});
@ -821,7 +858,7 @@ describe("MatrixClient", function() {
expectedStates.push(["PREPARED", null]);
expectedStates.push(["SYNCING", "PREPARED"]);
expectedStates.push(["SYNCING", "SYNCING"]);
client.on("sync", syncChecker(expectedStates, done));
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
client.startClient();
});
@ -845,7 +882,7 @@ describe("MatrixClient", function() {
expectedStates.push(["RECONNECTING", "SYNCING"]);
expectedStates.push(["ERROR", "RECONNECTING"]);
expectedStates.push(["ERROR", "ERROR"]);
client.on("sync", syncChecker(expectedStates, done));
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
client.startClient();
});
});
@ -914,14 +951,14 @@ describe("MatrixClient", function() {
throw new Error("Unexpected event type or state key");
}
},
},
} as Room["currentState"],
getThread: jest.fn(),
addPendingEvent: jest.fn(),
updatePendingEvent: jest.fn(),
reEmitter: {
reEmit: jest.fn(),
},
};
} as unknown as Room;
beforeEach(() => {
client.getRoom = (getRoomId) => {
@ -987,7 +1024,7 @@ describe("MatrixClient", function() {
const mockRoom = {
getMyMembership: () => "join",
updatePendingEvent: (event, status) => event.setStatus(status),
updatePendingEvent: (event: MatrixEvent, status: EventStatus) => event.setStatus(status),
currentState: {
getStateEvents: (eventType, stateKey) => {
if (eventType === EventType.RoomCreate) {
@ -1004,15 +1041,14 @@ describe("MatrixClient", function() {
throw new Error("Unexpected event type or state key");
}
},
},
};
} as Room["currentState"],
} as unknown as Room;
let event;
let event: MatrixEvent;
beforeEach(async () => {
event = new MatrixEvent({
event_id: "~" + roomId + ":" + txnId,
user_id: client.credentials.userId,
sender: client.credentials.userId,
sender: client.credentials.userId!,
room_id: roomId,
origin_server_ts: new Date().getTime(),
});
@ -1023,25 +1059,26 @@ describe("MatrixClient", function() {
return mockRoom;
};
client.crypto = { // mock crypto
encryptEvent: (event, room) => new Promise(() => {}),
encryptEvent: () => new Promise(() => {}),
stop: jest.fn(),
};
} as unknown as Crypto;
});
function assertCancelled() {
expect(event.status).toBe(EventStatus.CANCELLED);
expect(client.scheduler.removeEventFromQueue(event)).toBeFalsy();
expect(client.scheduler?.removeEventFromQueue(event)).toBeFalsy();
expect(httpLookups.filter(h => h.path.includes("/send/")).length).toBe(0);
}
it("should cancel an event which is queued", () => {
event.setStatus(EventStatus.QUEUED);
client.scheduler.queueEvent(event);
client.scheduler?.queueEvent(event);
client.cancelPendingEvent(event);
assertCancelled();
});
it("should cancel an event which is encrypting", async () => {
// @ts-ignore protected method access
client.encryptAndSendEvent(null, event);
await testUtils.emitPromise(event, "Event.status");
client.cancelPendingEvent(event);
@ -1103,7 +1140,7 @@ describe("MatrixClient", function() {
const room = {
hasPendingEvent: jest.fn().mockReturnValue(false),
addLocalEchoReceipt: jest.fn(),
};
} as unknown as Room;
const rrEvent = new MatrixEvent({ event_id: "read_event_id" });
const rpEvent = new MatrixEvent({ event_id: "read_private_event_id" });
client.getRoom = () => room;
@ -1142,7 +1179,7 @@ describe("MatrixClient", function() {
const content = makeBeaconInfoContent(100, true);
beforeEach(() => {
client.http.authedRequest.mockClear().mockResolvedValue({});
mocked(client.http.authedRequest).mockClear().mockResolvedValue({});
});
it("creates new beacon info", async () => {
@ -1150,7 +1187,7 @@ describe("MatrixClient", function() {
// event type combined
const expectedEventType = M_BEACON_INFO.name;
const [method, path, queryParams, requestContent] = client.http.authedRequest.mock.calls[0];
const [method, path, queryParams, requestContent] = mocked(client.http.authedRequest).mock.calls[0];
expect(method).toBe('PUT');
expect(path).toEqual(
`/rooms/${encodeURIComponent(roomId)}/state/` +
@ -1164,7 +1201,7 @@ describe("MatrixClient", function() {
await client.unstable_setLiveBeacon(roomId, content);
// event type combined
const [, path, , requestContent] = client.http.authedRequest.mock.calls[0];
const [, path, , requestContent] = mocked(client.http.authedRequest).mock.calls[0];
expect(path).toEqual(
`/rooms/${encodeURIComponent(roomId)}/state/` +
`${encodeURIComponent(M_BEACON_INFO.name)}/${encodeURIComponent(userId)}`,
@ -1242,7 +1279,7 @@ describe("MatrixClient", function() {
const newPassword = 'newpassword';
const passwordTest = (expectedRequestContent: any) => {
const [method, path, queryParams, requestContent] = client.http.authedRequest.mock.calls[0];
const [method, path, queryParams, requestContent] = mocked(client.http.authedRequest).mock.calls[0];
expect(method).toBe('POST');
expect(path).toEqual('/account/password');
expect(queryParams).toBeFalsy();
@ -1250,7 +1287,7 @@ describe("MatrixClient", function() {
};
beforeEach(() => {
client.http.authedRequest.mockClear().mockResolvedValue({});
mocked(client.http.authedRequest).mockClear().mockResolvedValue({});
});
it("no logout_devices specified", async () => {
@ -1289,13 +1326,13 @@ describe("MatrixClient", function() {
const response = {
aliases: ["#woop:example.org", "#another:example.org"],
};
client.http.authedRequest.mockClear().mockResolvedValue(response);
mocked(client.http.authedRequest).mockClear().mockResolvedValue(response);
const roomId = "!whatever:example.org";
const result = await client.getLocalAliases(roomId);
// Current version of the endpoint we support is v3
const [method, path, queryParams, data, opts] = client.http.authedRequest.mock.calls[0];
const [method, path, queryParams, data, opts] = mocked(client.http.authedRequest).mock.calls[0];
expect(data).toBeFalsy();
expect(method).toBe('GET');
expect(path).toEqual(`/rooms/${encodeURIComponent(roomId)}/aliases`);
@ -1352,11 +1389,11 @@ describe("MatrixClient", function() {
],
username: "1443779631:@user:example.com",
password: "JlKfBy1QwLrO20385QyAtEyIv0=",
};
} as unknown as ITurnServerResponse;
jest.spyOn(client, "turnServer").mockResolvedValue(turnServer);
const events: any[][] = [];
const onTurnServers = (...args) => events.push(args);
const onTurnServers = (...args: any[]) => events.push(args);
client.on(ClientEvent.TurnServers, onTurnServers);
expect(await client.checkTurnServers()).toBe(true);
client.off(ClientEvent.TurnServers, onTurnServers);
@ -1372,7 +1409,7 @@ describe("MatrixClient", function() {
jest.spyOn(client, "turnServer").mockRejectedValue(error);
const events: any[][] = [];
const onTurnServersError = (...args) => events.push(args);
const onTurnServersError = (...args: any[]) => events.push(args);
client.on(ClientEvent.TurnServersError, onTurnServersError);
expect(await client.checkTurnServers()).toBe(false);
client.off(ClientEvent.TurnServersError, onTurnServersError);
@ -1384,7 +1421,7 @@ describe("MatrixClient", function() {
jest.spyOn(client, "turnServer").mockRejectedValue(error);
const events: any[][] = [];
const onTurnServersError = (...args) => events.push(args);
const onTurnServersError = (...args: any[]) => events.push(args);
client.on(ClientEvent.TurnServersError, onTurnServersError);
expect(await client.checkTurnServers()).toBe(false);
client.off(ClientEvent.TurnServersError, onTurnServersError);
@ -1400,7 +1437,7 @@ describe("MatrixClient", function() {
it("is an alias for the crypto method", async () => {
client.crypto = testUtils.mock(Crypto, "Crypto");
const deviceInfos = [];
const deviceInfos: IOlmDevice[] = [];
const payload = {};
await client.encryptAndSendToDevices(deviceInfos, payload);
expect(client.crypto.encryptAndSendToDevices).toHaveBeenLastCalledWith(deviceInfos, payload);
@ -1413,7 +1450,7 @@ describe("MatrixClient", function() {
const dataStore = new Map();
client.setAccountData = function(eventType, content) {
dataStore.set(eventType, content);
return Promise.resolve();
return Promise.resolve({});
};
client.getAccountData = function(eventType) {
const data = dataStore.get(eventType);
@ -1424,9 +1461,9 @@ describe("MatrixClient", function() {
// Mockup `createRoom`/`getRoom`/`joinRoom`, including state.
const rooms = new Map();
client.createRoom = function(options = {}) {
client.createRoom = function(options: Options = {}) {
const roomId = options["_roomId"] || `!room-${rooms.size}:example.org`;
const state = new Map();
const state = new Map<string, any>();
const room = {
roomId,
_options: options,
@ -1444,24 +1481,24 @@ describe("MatrixClient", function() {
},
};
},
};
} as EventTimeline;
},
};
},
};
} as unknown as WrappedRoom;
rooms.set(roomId, room);
return Promise.resolve({ room_id: roomId });
};
client.getRoom = function(roomId) {
return rooms.get(roomId);
};
client.joinRoom = function(roomId) {
return this.getRoom(roomId) || this.createRoom({ _roomId: roomId });
client.joinRoom = async function(roomId) {
return this.getRoom(roomId)! || this.createRoom({ _roomId: roomId } as ICreateRoomOpts);
};
// Mockup state events
client.sendStateEvent = function(roomId, type, content) {
const room = this.getRoom(roomId);
const room = this.getRoom(roomId) as WrappedRoom;
const state: Map<string, any> = room._state;
let store = state.get(type);
if (!store) {
@ -1480,14 +1517,15 @@ describe("MatrixClient", function() {
return content;
},
};
return { event_id: eventId };
return Promise.resolve({ event_id: eventId });
};
client.redactEvent = function(roomId, eventId) {
const room = this.getRoom(roomId);
const room = this.getRoom(roomId) as WrappedRoom;
const state: Map<string, any> = room._state;
for (const store of state.values()) {
delete store[eventId];
delete store[eventId!];
}
return Promise.resolve({ event_id: "$" + eventId + "-" + Math.random() });
};
});
@ -1523,7 +1561,7 @@ describe("MatrixClient", function() {
roomId: "!snafu:somewhere.org",
});
expect(ruleMatch).toBeTruthy();
expect(ruleMatch.getContent()).toMatchObject({
expect(ruleMatch!.getContent()).toMatchObject({
recommendation: "m.ban",
reason: "just a test",
});
@ -1552,7 +1590,7 @@ describe("MatrixClient", function() {
roomId: "!snafu:somewhere.org",
});
expect(ruleSenderMatch).toBeTruthy();
expect(ruleSenderMatch.getContent()).toMatchObject({
expect(ruleSenderMatch!.getContent()).toMatchObject({
recommendation: "m.ban",
reason: REASON,
});
@ -1562,7 +1600,7 @@ describe("MatrixClient", function() {
roomId: "!snafu:example.org",
});
expect(ruleRoomMatch).toBeTruthy();
expect(ruleRoomMatch.getContent()).toMatchObject({
expect(ruleRoomMatch!.getContent()).toMatchObject({
recommendation: "m.ban",
reason: REASON,
});
@ -1587,7 +1625,7 @@ describe("MatrixClient", function() {
roomId: BAD_ROOM_ID,
});
expect(ruleSenderMatch).toBeTruthy();
expect(ruleSenderMatch.getContent()).toMatchObject({
expect(ruleSenderMatch!.getContent()).toMatchObject({
recommendation: "m.ban",
reason: REASON,
});
@ -1621,7 +1659,7 @@ describe("MatrixClient", function() {
roomId: "!snafu:somewhere.org",
});
expect(ruleMatch).toBeTruthy();
expect(ruleMatch.getContent()).toMatchObject({
expect(ruleMatch!.getContent()).toMatchObject({
recommendation: "m.ban",
reason: "just a test",
});
@ -1649,13 +1687,13 @@ describe("MatrixClient", function() {
roomId: "!snafu:somewhere.org",
});
expect(ruleMatch).toBeTruthy();
expect(ruleMatch.getContent()).toMatchObject({
expect(ruleMatch!.getContent()).toMatchObject({
recommendation: "m.ban",
reason: "just a test",
});
// After removing the invite, we shouldn't reject it anymore.
await client.ignoredInvites.removeRule(ruleMatch);
await client.ignoredInvites.removeRule(ruleMatch as MatrixEvent);
const ruleMatch2 = await client.ignoredInvites.getRuleForInvite({
sender: "@foobar:example.org",
roomId: "!snafu:somewhere.org",
@ -1669,10 +1707,10 @@ describe("MatrixClient", function() {
// Make sure that everything is initialized.
await client.ignoredInvites.getOrCreateSourceRooms();
await client.joinRoom(NEW_SOURCE_ROOM_ID);
const newSourceRoom = client.getRoom(NEW_SOURCE_ROOM_ID);
const newSourceRoom = client.getRoom(NEW_SOURCE_ROOM_ID) as WrappedRoom;
// Fetch the list of sources and check that we do not have the new room yet.
const policies = await client.getAccountData(POLICIES_ACCOUNT_EVENT_TYPE.name).getContent();
const policies = await client.getAccountData(POLICIES_ACCOUNT_EVENT_TYPE.name)!.getContent();
expect(policies).toBeTruthy();
const ignoreInvites = policies[IGNORE_INVITES_ACCOUNT_EVENT_KEY.name];
expect(ignoreInvites).toBeTruthy();
@ -1686,7 +1724,7 @@ describe("MatrixClient", function() {
expect(added2).toBe(false);
// Fetch the list of sources and check that we have added the new room.
const policies2 = await client.getAccountData(POLICIES_ACCOUNT_EVENT_TYPE.name).getContent();
const policies2 = await client.getAccountData(POLICIES_ACCOUNT_EVENT_TYPE.name)!.getContent();
expect(policies2).toBeTruthy();
const ignoreInvites2 = policies2[IGNORE_INVITES_ACCOUNT_EVENT_KEY.name];
expect(ignoreInvites2).toBeTruthy();
@ -1698,7 +1736,7 @@ describe("MatrixClient", function() {
// Check where it shows up.
const targetRoomId = ignoreInvites2.target;
const targetRoom = client.getRoom(targetRoomId);
const targetRoom = client.getRoom(targetRoomId) as WrappedRoom;
expect(targetRoom._state.get(PolicyScope.User)[eventId]).toBeTruthy();
expect(newSourceRoom._state.get(PolicyScope.User)?.[eventId]).toBeFalsy();
});

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient } from "../../../src";
import { IContent, MatrixClient } from "../../../src";
import { Room } from "../../../src/models/room";
import { MatrixEvent } from "../../../src/models/event";
import { EventType, MsgType, UNSTABLE_MSC3089_BRANCH, UNSTABLE_MSC3089_LEAF } from "../../../src/@types/event";
@ -33,7 +33,7 @@ describe("MSC3089TreeSpace", () => {
const roomId = "!tree:localhost";
const targetUser = "@target:example.org";
let powerLevels;
let powerLevels: MatrixEvent;
beforeEach(() => {
// TODO: Use utility functions to create test rooms and clients
@ -480,7 +480,7 @@ describe("MSC3089TreeSpace", () => {
const staticDomain = "static.example.org";
function addSubspace(roomId: string, createTs?: number, order?: string) {
const content = {
const content: IContent = {
via: [staticDomain],
};
if (order) content['order'] = order;

View File

@ -121,7 +121,7 @@ describe('MatrixEvent', () => {
});
describe(".attemptDecryption", () => {
let encryptedEvent;
let encryptedEvent: MatrixEvent;
const eventId = 'test_encrypted_event';
beforeEach(() => {
@ -155,7 +155,7 @@ describe('MatrixEvent', () => {
},
});
}),
};
} as unknown as Crypto;
await encryptedEvent.attemptDecryption(crypto);

View File

@ -39,7 +39,7 @@ let threadEvent: MatrixEvent;
const ROOM_ID = "!roomId:example.org";
let THREAD_ID: string;
function mkPushAction(notify, highlight): IActionsObject {
function mkPushAction(notify: boolean, highlight: boolean): IActionsObject {
return {
notify,
tweaks: {

View File

@ -70,7 +70,7 @@ const roomEvent = utils.mkEvent({
},
});
function mockServerSideSupport(client, serverSideSupport: ServerSupport) {
function mockServerSideSupport(client: MatrixClient, serverSideSupport: ServerSupport) {
client.canSupport.set(Feature.ThreadUnreadNotifications, serverSideSupport);
}

View File

@ -20,7 +20,7 @@ let wallTime = 1234567890;
jest.useFakeTimers().setSystemTime(wallTime);
describe("realtime-callbacks", function() {
function tick(millis) {
function tick(millis: number): void {
wallTime += millis;
jest.advanceTimersByTime(millis);
}

View File

@ -87,7 +87,7 @@ describe("Rendezvous", function() {
});
let httpBackend: MockHttpBackend;
let fetchFn: typeof global.fetchFn;
let fetchFn: typeof global.fetch;
let transports: DummyTransport<any, MSC3903ECDHPayload>[];
beforeEach(function() {

View File

@ -373,37 +373,36 @@ describe("RoomMember", function() {
expect(member.events.member).toEqual(joinEvent);
});
it("should set 'name' based on user_id, displayname and room state",
function() {
const roomState = {
getStateEvents: function(type) {
if (type !== "m.room.member") {
return [];
}
return [
utils.mkMembership({
event: true, mship: "join", room: roomId,
user: userB,
}),
utils.mkMembership({
event: true, mship: "join", room: roomId,
user: userC, name: "Alice",
}),
joinEvent,
];
},
getUserIdsWithDisplayName: function(displayName) {
return [userA, userC];
},
} as unknown as RoomState;
expect(member.name).toEqual(userA); // default = user_id
member.setMembershipEvent(joinEvent);
expect(member.name).toEqual("Alice"); // prefer displayname
member.setMembershipEvent(joinEvent, roomState);
expect(member.name).not.toEqual("Alice"); // it should disambig.
// user_id should be there somewhere
expect(member.name.indexOf(userA)).not.toEqual(-1);
});
it("should set 'name' based on user_id, displayname and room state", function() {
const roomState = {
getStateEvents: function(type: string) {
if (type !== "m.room.member") {
return [];
}
return [
utils.mkMembership({
event: true, mship: "join", room: roomId,
user: userB,
}),
utils.mkMembership({
event: true, mship: "join", room: roomId,
user: userC, name: "Alice",
}),
joinEvent,
];
},
getUserIdsWithDisplayName: function(displayName: string) {
return [userA, userC];
},
} as unknown as RoomState;
expect(member.name).toEqual(userA); // default = user_id
member.setMembershipEvent(joinEvent);
expect(member.name).toEqual("Alice"); // prefer displayname
member.setMembershipEvent(joinEvent, roomState);
expect(member.name).not.toEqual("Alice"); // it should disambig.
// user_id should be there somewhere
expect(member.name.indexOf(userA)).not.toEqual(-1);
});
it("should emit 'RoomMember.membership' if the membership changes", function() {
let emitCount = 0;
@ -455,7 +454,7 @@ describe("RoomMember", function() {
});
const roomState = {
getStateEvents: function(type) {
getStateEvents: function(type: string) {
if (type !== "m.room.member") {
return [];
}
@ -467,7 +466,7 @@ describe("RoomMember", function() {
joinEvent,
];
},
getUserIdsWithDisplayName: function(displayName) {
getUserIdsWithDisplayName: function(displayName: string) {
return [userA, userC];
},
} as unknown as RoomState;

View File

@ -19,27 +19,32 @@ limitations under the License.
* @module client
*/
import { mocked } from "jest-mock";
import * as utils from "../test-utils/test-utils";
import { emitPromise } from "../test-utils/test-utils";
import {
Direction,
DuplicateStrategy,
EventStatus,
EventTimelineSet,
EventType, IStateEventWithRoomId,
EventType, IContent,
IStateEventWithRoomId,
JoinRule,
MatrixEvent,
MatrixEventEvent,
PendingEventOrdering,
RelationType,
RoomEvent,
RoomMember,
} from "../../src";
import { EventTimeline } from "../../src/models/event-timeline";
import { NotificationCountType, Room } from "../../src/models/room";
import { RoomState } from "../../src/models/room-state";
import { UNSTABLE_ELEMENT_FUNCTIONAL_USERS } from "../../src/@types/event";
import { TestClient } from "../TestClient";
import { emitPromise } from "../test-utils/test-utils";
import { ReceiptType, WrappedReceipt } from "../../src/@types/read_receipts";
import { FeatureSupport, Thread, ThreadEvent, THREAD_RELATION_TYPE } from "../../src/models/thread";
import { FeatureSupport, Thread, THREAD_RELATION_TYPE, ThreadEvent } from "../../src/models/thread";
import { Crypto } from "../../src/crypto";
describe("Room", function() {
@ -48,7 +53,7 @@ describe("Room", function() {
const userB = "@bertha:bar";
const userC = "@clarissa:bar";
const userD = "@dorothy:bar";
let room;
let room: Room;
const mkMessage = () => utils.mkMessage({
event: true,
@ -131,13 +136,16 @@ describe("Room", function() {
beforeEach(function() {
room = new Room(roomId, new TestClient(userA, "device").client, userA);
// mock RoomStates
// @ts-ignore
room.oldState = room.getLiveTimeline().startState = utils.mock(RoomState, "oldState");
// @ts-ignore
room.currentState = room.getLiveTimeline().endState = utils.mock(RoomState, "currentState");
});
describe('getCreator', () => {
it("should return the creator from m.room.create", function() {
room.currentState.getStateEvents.mockImplementation(function(type, key) {
// @ts-ignore - mocked doesn't handle overloads sanely
mocked(room.currentState.getStateEvents).mockImplementation(function(type, key) {
if (type === EventType.RoomCreate && key === "") {
return utils.mkEvent({
event: true,
@ -160,7 +168,8 @@ describe("Room", function() {
const hsUrl = "https://my.home.server";
it("should return the URL from m.room.avatar preferentially", function() {
room.currentState.getStateEvents.mockImplementation(function(type, key) {
// @ts-ignore - mocked doesn't handle overloads sanely
mocked(room.currentState.getStateEvents).mockImplementation(function(type, key) {
if (type === EventType.RoomAvatar && key === "") {
return utils.mkEvent({
event: true,
@ -174,10 +183,10 @@ describe("Room", function() {
});
}
});
const url = room.getAvatarUrl(hsUrl);
const url = room.getAvatarUrl(hsUrl, 100, 100, "scale");
// we don't care about how the mxc->http conversion is done, other
// than it contains the mxc body.
expect(url.indexOf("flibble/wibble")).not.toEqual(-1);
expect(url?.indexOf("flibble/wibble")).not.toEqual(-1);
});
it("should return nothing if there is no m.room.avatar and allowDefault=false",
@ -189,12 +198,12 @@ describe("Room", function() {
describe("getMember", function() {
beforeEach(function() {
room.currentState.getMember.mockImplementation(function(userId) {
mocked(room.currentState.getMember).mockImplementation(function(userId) {
return {
"@alice:bar": {
userId: userA,
roomId: roomId,
},
} as unknown as RoomMember,
}[userId] || null;
});
});
@ -222,11 +231,13 @@ describe("Room", function() {
it("Make sure legacy overload passing options directly as parameters still works", () => {
expect(() => room.addLiveEvents(events, DuplicateStrategy.Replace, false)).not.toThrow();
expect(() => room.addLiveEvents(events, DuplicateStrategy.Ignore, true)).not.toThrow();
// @ts-ignore
expect(() => room.addLiveEvents(events, "shouldfailbecauseinvalidduplicatestrategy", false)).toThrow();
});
it("should throw if duplicateStrategy isn't 'replace' or 'ignore'", function() {
expect(function() {
// @ts-ignore
room.addLiveEvents(events, {
duplicateStrategy: "foo",
});
@ -255,6 +266,7 @@ describe("Room", function() {
dupe.event.event_id = events[0].getId();
room.addLiveEvents(events);
expect(room.timeline[0]).toEqual(events[0]);
// @ts-ignore
room.addLiveEvents([dupe], {
duplicateStrategy: "ignore",
});
@ -263,7 +275,7 @@ describe("Room", function() {
it("should emit 'Room.timeline' events", function() {
let callCount = 0;
room.on("Room.timeline", function(event, emitRoom, toStart) {
room.on(RoomEvent.Timeline, function(event, emitRoom, toStart) {
callCount += 1;
expect(room.timeline.length).toEqual(callCount);
expect(event).toEqual(events[callCount - 1]);
@ -306,8 +318,8 @@ describe("Room", function() {
userId: userA,
membership: "join",
name: "Alice",
};
room.currentState.getSentinelMember.mockImplementation(function(uid) {
} as unknown as RoomMember;
mocked(room.currentState.getSentinelMember).mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
@ -331,27 +343,25 @@ describe("Room", function() {
const remoteEventId = remoteEvent.getId();
let callCount = 0;
room.on("Room.localEchoUpdated",
function(event, emitRoom, oldEventId, oldStatus) {
switch (callCount) {
case 0:
expect(event.getId()).toEqual(localEventId);
expect(event.status).toEqual(EventStatus.SENDING);
expect(emitRoom).toEqual(room);
expect(oldEventId).toBeUndefined();
expect(oldStatus).toBeUndefined();
break;
case 1:
expect(event.getId()).toEqual(remoteEventId);
expect(event.status).toBeNull();
expect(emitRoom).toEqual(room);
expect(oldEventId).toEqual(localEventId);
expect(oldStatus).toBe(EventStatus.SENDING);
break;
}
callCount += 1;
},
);
room.on(RoomEvent.LocalEchoUpdated, (event, emitRoom, oldEventId, oldStatus) => {
switch (callCount) {
case 0:
expect(event.getId()).toEqual(localEventId);
expect(event.status).toEqual(EventStatus.SENDING);
expect(emitRoom).toEqual(room);
expect(oldEventId).toBeUndefined();
expect(oldStatus).toBeUndefined();
break;
case 1:
expect(event.getId()).toEqual(remoteEventId);
expect(event.status).toBeNull();
expect(emitRoom).toEqual(room);
expect(oldEventId).toEqual(localEventId);
expect(oldStatus).toBe(EventStatus.SENDING);
break;
}
callCount += 1;
});
// first add the local echo
room.addPendingEvent(localEvent, "TXN_ID");
@ -367,7 +377,7 @@ describe("Room", function() {
it("should be able to update local echo without a txn ID (/send then /sync)", function() {
const eventJson = utils.mkMessage({
room: roomId, user: userA, event: false,
}) as object;
});
delete eventJson["txn_id"];
delete eventJson["event_id"];
const localEvent = new MatrixEvent(Object.assign({ event_id: "$temp" }, eventJson));
@ -398,7 +408,7 @@ describe("Room", function() {
it("should be able to update local echo without a txn ID (/sync then /send)", function() {
const eventJson = utils.mkMessage({
room: roomId, user: userA, event: false,
}) as object;
});
delete eventJson["txn_id"];
delete eventJson["event_id"];
const txnId = "My_txn_id";
@ -483,7 +493,7 @@ describe("Room", function() {
it("should emit 'Room.timeline' events when added to the start",
function() {
let callCount = 0;
room.on("Room.timeline", function(event, emitRoom, toStart) {
room.on(RoomEvent.Timeline, function(event, emitRoom, toStart) {
callCount += 1;
expect(room.timeline.length).toEqual(callCount);
expect(event).toEqual(events[callCount - 1]);
@ -501,19 +511,19 @@ describe("Room", function() {
userId: userA,
membership: "join",
name: "Alice",
};
} as unknown as RoomMember;
const oldSentinel = {
userId: userA,
membership: "join",
name: "Old Alice",
};
room.currentState.getSentinelMember.mockImplementation(function(uid) {
} as unknown as RoomMember;
mocked(room.currentState.getSentinelMember).mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
room.oldState.getSentinelMember.mockImplementation(function(uid) {
mocked(room.oldState.getSentinelMember).mockImplementation(function(uid) {
if (uid === userA) {
return oldSentinel;
}
@ -539,19 +549,19 @@ describe("Room", function() {
userId: userA,
membership: "join",
name: "Alice",
};
} as unknown as RoomMember;
const oldSentinel = {
userId: userA,
membership: "join",
name: "Old Alice",
};
room.currentState.getSentinelMember.mockImplementation(function(uid) {
} as unknown as RoomMember;
mocked(room.currentState.getSentinelMember).mockImplementation(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
room.oldState.getSentinelMember.mockImplementation(function(uid) {
mocked(room.oldState.getSentinelMember).mockImplementation(function(uid) {
if (uid === userA) {
return oldSentinel;
}
@ -599,7 +609,7 @@ describe("Room", function() {
});
});
const resetTimelineTests = function(timelineSupport) {
const resetTimelineTests = function(timelineSupport: boolean) {
let events: MatrixEvent[];
beforeEach(function() {
@ -630,8 +640,8 @@ describe("Room", function() {
const oldState = room.getLiveTimeline().getState(EventTimeline.BACKWARDS);
const newState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
expect(room.getLiveTimeline().getEvents().length).toEqual(1);
expect(oldState.getStateEvents(EventType.RoomName, "")).toEqual(events[1]);
expect(newState.getStateEvents(EventType.RoomName, "")).toEqual(events[2]);
expect(oldState?.getStateEvents(EventType.RoomName, "")).toEqual(events[1]);
expect(newState?.getStateEvents(EventType.RoomName, "")).toEqual(events[2]);
});
it("should reset the legacy timeline fields", function() {
@ -669,17 +679,14 @@ describe("Room", function() {
expect(currentStateUpdateEmitCount).toEqual(timelineSupport ? 1 : 0);
});
it("should emit Room.timelineReset event and set the correct " +
"pagination token", function() {
it("should emit Room.timelineReset event and set the correct pagination token", function() {
let callCount = 0;
room.on("Room.timelineReset", function(emitRoom) {
room.on(RoomEvent.TimelineReset, function(emitRoom) {
callCount += 1;
expect(emitRoom).toEqual(room);
// make sure that the pagination token has been set before the
// event is emitted.
const tok = emitRoom.getLiveTimeline()
.getPaginationToken(EventTimeline.BACKWARDS);
// make sure that the pagination token has been set before the event is emitted.
const tok = emitRoom?.getLiveTimeline().getPaginationToken(EventTimeline.BACKWARDS);
expect(tok).toEqual("pagToken");
});
@ -693,7 +700,7 @@ describe("Room", function() {
const firstLiveTimeline = room.getLiveTimeline();
room.resetLiveTimeline('sometoken', 'someothertoken');
const tl = room.getTimelineForEvent(events[0].getId());
const tl = room.getTimelineForEvent(events[0].getId()!);
expect(tl).toBe(timelineSupport ? firstLiveTimeline : null);
});
};
@ -721,30 +728,25 @@ describe("Room", function() {
it("should handle events in the same timeline", function() {
room.addLiveEvents(events);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!,
events[1].getId()))
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!, events[1].getId()!))
.toBeLessThan(0);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[2].getId()!,
events[1].getId()))
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[2].getId()!, events[1].getId()!))
.toBeGreaterThan(0);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!,
events[1].getId()))
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!, events[1].getId()!))
.toEqual(0);
});
it("should handle events in adjacent timelines", function() {
const oldTimeline = room.addTimeline();
oldTimeline.setNeighbouringTimeline(room.getLiveTimeline(), 'f');
room.getLiveTimeline().setNeighbouringTimeline(oldTimeline, 'b');
oldTimeline.setNeighbouringTimeline(room.getLiveTimeline(), Direction.Forward);
room.getLiveTimeline().setNeighbouringTimeline(oldTimeline, Direction.Backward);
room.addEventsToTimeline([events[0]], false, oldTimeline);
room.addLiveEvents([events[1]]);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!,
events[1].getId()))
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!, events[1].getId()!))
.toBeLessThan(0);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!,
events[0].getId()))
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!, events[0].getId()!))
.toBeGreaterThan(0);
});
@ -754,11 +756,9 @@ describe("Room", function() {
room.addEventsToTimeline([events[0]], false, oldTimeline);
room.addLiveEvents([events[1]]);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!,
events[1].getId()))
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!, events[1].getId()!))
.toBe(null);
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!,
events[0].getId()))
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!, events[0].getId()!))
.toBe(null);
});
@ -769,21 +769,21 @@ describe("Room", function() {
.compareEventOrdering(events[0].getId()!, "xxx"))
.toBe(null);
expect(room.getUnfilteredTimelineSet()
.compareEventOrdering("xxx", events[0].getId()))
.compareEventOrdering("xxx", events[0].getId()!))
.toBe(null);
expect(room.getUnfilteredTimelineSet()
.compareEventOrdering(events[0].getId()!, events[0].getId()))
.compareEventOrdering(events[0].getId()!, events[0].getId()!))
.toBe(0);
});
});
describe("getJoinedMembers", function() {
it("should return members whose membership is 'join'", function() {
room.currentState.getMembers.mockImplementation(function() {
mocked(room.currentState.getMembers).mockImplementation(function() {
return [
{ userId: "@alice:bar", membership: "join" },
{ userId: "@bob:bar", membership: "invite" },
{ userId: "@cleo:bar", membership: "leave" },
{ userId: "@alice:bar", membership: "join" } as unknown as RoomMember,
{ userId: "@bob:bar", membership: "invite" } as unknown as RoomMember,
{ userId: "@cleo:bar", membership: "leave" } as unknown as RoomMember,
];
});
const res = room.getJoinedMembers();
@ -792,9 +792,9 @@ describe("Room", function() {
});
it("should return an empty list if no membership is 'join'", function() {
room.currentState.getMembers.mockImplementation(function() {
mocked(room.currentState.getMembers).mockImplementation(function() {
return [
{ userId: "@bob:bar", membership: "invite" },
{ userId: "@bob:bar", membership: "invite" } as unknown as RoomMember,
];
});
const res = room.getJoinedMembers();
@ -805,41 +805,41 @@ describe("Room", function() {
describe("hasMembershipState", function() {
it("should return true for a matching userId and membership",
function() {
room.currentState.getMember.mockImplementation(function(userId) {
mocked(room.currentState.getMember).mockImplementation(function(userId) {
return {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
"@bob:bar": { userId: "@bob:bar", membership: "invite" },
}[userId];
}[userId] as unknown as RoomMember;
});
expect(room.hasMembershipState("@bob:bar", "invite")).toBe(true);
});
it("should return false if match membership but no match userId",
function() {
room.currentState.getMember.mockImplementation(function(userId) {
mocked(room.currentState.getMember).mockImplementation(function(userId) {
return {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
}[userId];
}[userId] as unknown as RoomMember;
});
expect(room.hasMembershipState("@bob:bar", "join")).toBe(false);
});
it("should return false if match userId but no match membership",
function() {
room.currentState.getMember.mockImplementation(function(userId) {
mocked(room.currentState.getMember).mockImplementation(function(userId) {
return {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
}[userId];
}[userId] as unknown as RoomMember;
});
expect(room.hasMembershipState("@alice:bar", "ban")).toBe(false);
});
it("should return false if no match membership or userId",
function() {
room.currentState.getMember.mockImplementation(function(userId) {
mocked(room.currentState.getMember).mockImplementation(function(userId) {
return {
"@alice:bar": { userId: "@alice:bar", membership: "join" },
}[userId];
}[userId] as unknown as RoomMember;
});
expect(room.hasMembershipState("@bob:bar", "invite")).toBe(false);
});
@ -1193,8 +1193,8 @@ describe("Room", function() {
event: true,
});
function mkReceipt(roomId: string, records) {
const content = {};
function mkReceipt(roomId: string, records: Array<ReturnType<typeof mkRecord>>) {
const content: IContent = {};
records.forEach(function(r) {
if (!content[r.eventId]) {
content[r.eventId] = {};
@ -1241,7 +1241,7 @@ describe("Room", function() {
it("should emit an event when a receipt is added",
function() {
const listener = jest.fn();
room.on("Room.receipt", listener);
room.on(RoomEvent.Receipt, listener);
const ts = 13787898424;
@ -1448,7 +1448,7 @@ describe("Room", function() {
});
describe("tags", function() {
function mkTags(roomId, tags) {
function mkTags(roomId: string, tags: object) {
const content = { "tags": tags };
return new MatrixEvent({
content: content,
@ -1470,7 +1470,7 @@ describe("Room", function() {
"received on the event stream",
function() {
const listener = jest.fn();
room.on("Room.tags", listener);
room.on(RoomEvent.Tags, listener);
const tags = { "m.foo": { "order": 0.5 } };
const event = mkTags(roomId, tags);
@ -1642,11 +1642,14 @@ describe("Room", function() {
});
describe("loadMembersIfNeeded", function() {
function createClientMock(serverResponse, storageResponse: MatrixEvent[] | Error | null = null) {
function createClientMock(
serverResponse: Error | MatrixEvent[],
storageResponse: MatrixEvent[] | Error | null = null,
) {
return {
getEventMapper: function() {
// events should already be MatrixEvents
return function(event) {return event;};
return function(event: MatrixEvent) {return event;};
},
isCryptoEnabled() {
return true;
@ -1671,7 +1674,7 @@ describe("Room", function() {
return Promise.resolve(this.storageResponse);
}
},
setOutOfBandMembers: function(roomId, memberEvents) {
setOutOfBandMembers: function(roomId: string, memberEvents: IStateEventWithRoomId[]) {
this.storedMembers = memberEvents;
return Promise.resolve();
},
@ -2170,7 +2173,7 @@ describe("Room", function() {
},
});
room.createThread("$000", undefined, [eventWithoutARootEvent]);
room.createThread("$000", undefined, [eventWithoutARootEvent], false);
const rootEvent = new MatrixEvent({
event_id: "$666",
@ -2188,7 +2191,7 @@ describe("Room", function() {
},
});
expect(() => room.createThread(rootEvent.getId()!, rootEvent, [])).not.toThrow();
expect(() => room.createThread(rootEvent.getId()!, rootEvent, [], false)).not.toThrow();
});
it("creating thread from edited event should not conflate old versions of the event", () => {
@ -2406,8 +2409,6 @@ describe("Room", function() {
Thread.setServerSideListSupport(FeatureSupport.Stable);
room.client.createThreadListMessagesRequest = () => Promise.resolve({
start: null,
end: null,
chunk: [],
state: [],
});
@ -2761,7 +2762,7 @@ describe("Room", function() {
});
describe("thread notifications", () => {
let room;
let room: Room;
beforeEach(() => {
const client = new TestClient(userA).client;

View File

@ -1,18 +1,19 @@
// This file had a function whose name is all caps, which displeases eslint
/* eslint new-cap: "off" */
import { defer } from '../../src/utils';
import { defer, IDeferred } from '../../src/utils';
import { MatrixError } from "../../src/http-api";
import { MatrixScheduler } from "../../src/scheduler";
import * as utils from "../test-utils/test-utils";
import { MatrixEvent } from "../../src";
jest.useFakeTimers();
describe("MatrixScheduler", function() {
let scheduler;
let retryFn;
let queueFn;
let deferred;
let scheduler: MatrixScheduler<Record<string, boolean>>;
let retryFn: Function | null;
let queueFn: ((event: MatrixEvent) => string | null) | null;
let deferred: IDeferred<Record<string, boolean>>;
const roomId = "!foo:bar";
const eventA = utils.mkMessage({
user: "@alice:bar", room: roomId, event: true,
@ -65,8 +66,8 @@ describe("MatrixScheduler", function() {
deferB.resolve({ b: true });
deferA.resolve({ a: true });
const [a, b] = await abPromise;
expect(a.a).toEqual(true);
expect(b.b).toEqual(true);
expect(a!.a).toEqual(true);
expect(b!.b).toEqual(true);
});
it("should invoke the retryFn on failure and wait the amount of time specified",
@ -92,6 +93,7 @@ describe("MatrixScheduler", function() {
return new Promise(() => {});
}
expect(procCount).toBeLessThan(3);
return new Promise(() => {});
});
scheduler.queueEvent(eventA);
@ -119,8 +121,8 @@ describe("MatrixScheduler", function() {
return "yep";
};
const deferA = defer();
const deferB = defer();
const deferA = defer<Record<string, boolean>>();
const deferB = defer<Record<string, boolean>>();
let procCount = 0;
scheduler.setProcessFunction(function(ev) {
procCount += 1;
@ -132,6 +134,7 @@ describe("MatrixScheduler", function() {
return deferB.promise;
}
expect(procCount).toBeLessThan(3);
return new Promise<Record<string, boolean>>(() => {});
});
const globalA = scheduler.queueEvent(eventA);
@ -159,7 +162,7 @@ describe("MatrixScheduler", function() {
const eventC = utils.mkMessage({ user: "@a:bar", room: roomId, event: true });
const eventD = utils.mkMessage({ user: "@b:bar", room: roomId, event: true });
const buckets = {};
const buckets: Record<string, string> = {};
buckets[eventA.getId()!] = "queue_A";
buckets[eventD.getId()!] = "queue_A";
buckets[eventB.getId()!] = "queue_B";
@ -169,13 +172,13 @@ describe("MatrixScheduler", function() {
return 0;
};
queueFn = function(event) {
return buckets[event.getId()];
return buckets[event.getId()!];
};
const expectOrder = [
eventA.getId(), eventB.getId(), eventD.getId(),
];
const deferA = defer<void>();
const deferA = defer<Record<string, boolean>>();
scheduler.setProcessFunction(function(event) {
const id = expectOrder.shift();
expect(id).toEqual(event.getId());
@ -191,7 +194,7 @@ describe("MatrixScheduler", function() {
// wait a bit then resolve A and we should get D (not C) next.
setTimeout(function() {
deferA.resolve();
deferA.resolve({});
}, 1000);
jest.advanceTimersByTime(1000);
});
@ -210,7 +213,7 @@ describe("MatrixScheduler", function() {
};
const prom = scheduler.queueEvent(eventA);
expect(prom).toBeTruthy();
expect(prom.then).toBeTruthy();
expect(prom!.then).toBeTruthy();
});
});
@ -237,15 +240,15 @@ describe("MatrixScheduler", function() {
scheduler.queueEvent(eventA);
scheduler.queueEvent(eventB);
const queue = scheduler.getQueueForEvent(eventA);
expect(queue.length).toEqual(2);
expect(queue).toHaveLength(2);
expect(queue).toEqual([eventA, eventB]);
// modify the queue
const eventC = utils.mkMessage(
{ user: "@a:bar", room: roomId, event: true },
);
queue.push(eventC);
queue!.push(eventC);
const queueAgain = scheduler.getQueueForEvent(eventA);
expect(queueAgain.length).toEqual(2);
expect(queueAgain).toHaveLength(2);
});
it("should return a list of events in the queue and modifications to" +
@ -255,10 +258,10 @@ describe("MatrixScheduler", function() {
};
scheduler.queueEvent(eventA);
scheduler.queueEvent(eventB);
const queue = scheduler.getQueueForEvent(eventA);
queue[1].event.content.body = "foo";
const queueAgain = scheduler.getQueueForEvent(eventA);
expect(queueAgain[1].event.content.body).toEqual("foo");
const queue = scheduler.getQueueForEvent(eventA)!;
queue[1].event.content!.body = "foo";
const queueAgain = scheduler.getQueueForEvent(eventA)!;
expect(queueAgain[1].event.content?.body).toEqual("foo");
});
});

View File

@ -16,7 +16,8 @@ limitations under the License.
*/
import { ReceiptType } from "../../src/@types/read_receipts";
import { SyncAccumulator } from "../../src/sync-accumulator";
import { IJoinedRoom, ISyncResponse, SyncAccumulator } from "../../src/sync-accumulator";
import { IRoomSummary } from "../../src";
// The event body & unsigned object get frozen to assert that they don't get altered
// by the impl
@ -55,10 +56,10 @@ const RES_WITH_AGE = {
},
},
},
};
} as unknown as ISyncResponse;
describe("SyncAccumulator", function() {
let sa;
let sa: SyncAccumulator;
beforeEach(function() {
sa = new SyncAccumulator({
@ -98,7 +99,7 @@ describe("SyncAccumulator", function() {
},
},
},
};
} as unknown as ISyncResponse;
sa.accumulate(res);
const output = sa.getJSON();
expect(output.nextBatch).toEqual(res.next_batch);
@ -223,7 +224,6 @@ describe("SyncAccumulator", function() {
content: {
user_ids: ["@alice:localhost"],
},
room_id: "!foo:bar",
}],
},
});
@ -281,12 +281,12 @@ describe("SyncAccumulator", function() {
account_data: {
events: [acc1],
},
});
} as unknown as ISyncResponse);
sa.accumulate({
account_data: {
events: [acc2],
},
});
} as unknown as ISyncResponse);
expect(
sa.getJSON().accountData.length,
).toEqual(1);
@ -422,7 +422,7 @@ describe("SyncAccumulator", function() {
});
describe("summary field", function() {
function createSyncResponseWithSummary(summary) {
function createSyncResponseWithSummary(summary: IRoomSummary): ISyncResponse {
return {
next_batch: "abc",
rooms: {
@ -444,7 +444,7 @@ describe("SyncAccumulator", function() {
},
},
},
};
} as unknown as ISyncResponse;
}
afterEach(() => {
@ -487,8 +487,8 @@ describe("SyncAccumulator", function() {
jest.spyOn(global.Date, 'now').mockReturnValue(startingTs + delta);
const output = sa.getJSON();
expect(output.roomsData.join["!foo:bar"].timeline.events[0].unsigned.age).toEqual(
RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0].unsigned.age + delta,
expect(output.roomsData.join["!foo:bar"].timeline.events[0].unsigned?.age).toEqual(
RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0].unsigned!.age! + delta,
);
expect(Object.keys(output.roomsData.join["!foo:bar"].timeline.events[0])).toEqual(
Object.keys(RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0]),
@ -507,12 +507,12 @@ describe("SyncAccumulator", function() {
sa.accumulate(RES_WITH_AGE);
const output = sa.getJSON();
expect(output.roomsData.join["!foo:bar"]
.unread_thread_notifications["$143273582443PhrSn:example.org"]).not.toBeUndefined();
.unread_thread_notifications!["$143273582443PhrSn:example.org"]).not.toBeUndefined();
});
});
});
function syncSkeleton(joinObj) {
function syncSkeleton(joinObj: Partial<IJoinedRoom>): ISyncResponse {
joinObj = joinObj || {};
return {
next_batch: "abc",
@ -521,11 +521,12 @@ function syncSkeleton(joinObj) {
"!foo:bar": joinObj,
},
},
};
} as unknown as ISyncResponse;
}
function msg(localpart, text) {
function msg(localpart: string, text: string) {
return {
event_id: "$" + Math.random(),
content: {
body: text,
},
@ -535,8 +536,9 @@ function msg(localpart, text) {
};
}
function member(localpart, membership) {
function member(localpart: string, membership: string) {
return {
event_id: "$" + Math.random(),
content: {
membership: membership,
},

View File

@ -455,7 +455,11 @@ describe("utils", function() {
describe("recursivelyAssign", () => {
it("doesn't override with null/undefined", () => {
const result = utils.recursivelyAssign(
const result = utils.recursivelyAssign<{
string: string;
object: object;
float: number;
}, {}>(
{
string: "Hello world",
object: {},
@ -475,7 +479,11 @@ describe("utils", function() {
});
it("assigns recursively", () => {
const result = utils.recursivelyAssign(
const result = utils.recursivelyAssign<{
number: number;
object: object;
thing: string | object;
}, {}>(
{
number: 42,
object: {

View File

@ -933,7 +933,7 @@ describe('Call', function() {
await fakeIncomingCall(client, call, "1");
});
const untilEventSent = async (...args) => {
const untilEventSent = async (...args: any[]) => {
const maxTries = 20;
for (let tries = 0; tries < maxTries; ++tries) {
@ -971,7 +971,7 @@ describe('Call', function() {
});
describe("ICE candidate sending", () => {
let mockPeerConn;
let mockPeerConn: MockRTCPeerConnection;
const fakeCandidateString = "here is a fake candidate!";
const fakeCandidateEvent = {
candidate: {
@ -1086,7 +1086,7 @@ describe('Call', function() {
});
describe("Screen sharing", () => {
const waitNegotiateFunc = resolve => {
const waitNegotiateFunc = (resolve: Function): void => {
mockSendEvent.mockImplementationOnce(() => {
// Note that the peer connection here is a dummy one and always returns
// dummy SDP, so there's not much point returning the content: the SDP will

View File

@ -42,7 +42,7 @@ export type ReceiptCache = {[eventId: string]: CachedReceipt[]};
export interface ReceiptContent {
[eventId: string]: {
[key in ReceiptType]: {
[key in ReceiptType | string]: {
[userId: string]: Receipt;
};
};

View File

@ -49,7 +49,7 @@ interface WellKnownConfig extends Omit<IWellKnownConfig, "error"> {
error?: IWellKnownConfig["error"] | null;
}
interface ClientConfig {
interface ClientConfig extends Omit<IClientWellKnown, "m.homeserver" | "m.identity_server"> {
"m.homeserver": WellKnownConfig;
"m.identity_server": WellKnownConfig;
}
@ -131,7 +131,7 @@ export class AutoDiscovery {
* configuration, which may include error states. Rejects on unexpected
* failure, not when verification fails.
*/
public static async fromDiscoveryConfig(wellknown: any): Promise<ClientConfig> {
public static async fromDiscoveryConfig(wellknown: IClientWellKnown): Promise<ClientConfig> {
// Step 1 is to get the config, which is provided to us here.
// We default to an error state to make the first few checks easier to
@ -185,7 +185,7 @@ export class AutoDiscovery {
const hsVersions = await this.fetchWellKnownObject(
`${hsUrl}/_matrix/client/versions`,
);
if (!hsVersions || !hsVersions.raw["versions"]) {
if (!hsVersions || !hsVersions.raw?.["versions"]) {
logger.error("Invalid /versions response");
clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID_HOMESERVER;
@ -219,9 +219,7 @@ export class AutoDiscovery {
// Step 5a: Make sure the URL is valid *looking*. We'll make sure it
// points to an identity server in Step 5b.
isUrl = this.sanitizeWellKnownUrl(
wellknown["m.identity_server"]["base_url"],
);
isUrl = this.sanitizeWellKnownUrl(wellknown["m.identity_server"]["base_url"]);
if (!isUrl) {
logger.error("Invalid base_url for m.identity_server");
failingClientConfig["m.identity_server"].error =
@ -234,7 +232,7 @@ export class AutoDiscovery {
const isResponse = await this.fetchWellKnownObject(
`${isUrl}/_matrix/identity/api/v1`,
);
if (!isResponse || !isResponse.raw || isResponse.action !== AutoDiscoveryAction.SUCCESS) {
if (!isResponse?.raw || isResponse.action !== AutoDiscoveryAction.SUCCESS) {
logger.error("Invalid /api/v1 response");
failingClientConfig["m.identity_server"].error =
AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER;
@ -259,14 +257,16 @@ export class AutoDiscovery {
// Step 7: Copy any other keys directly into the clientConfig. This is for
// things like custom configuration of services.
Object.keys(wellknown).forEach((k) => {
Object.keys(wellknown).forEach((k: keyof IClientWellKnown) => {
if (k === "m.homeserver" || k === "m.identity_server") {
// Only copy selected parts of the config to avoid overwriting
// properties computed by the validation logic above.
const notProps = ["error", "state", "base_url"];
for (const prop of Object.keys(wellknown[k])) {
for (const prop of Object.keys(wellknown[k]!)) {
if (notProps.includes(prop)) continue;
clientConfig[k][prop] = wellknown[k][prop];
type Prop = Exclude<keyof IWellKnownConfig, "error" | "state" | "base_url">;
// @ts-ignore - ts gets unhappy as we're mixing types here
clientConfig[k][prop as Prop] = wellknown[k]![prop as Prop];
}
} else {
// Just copy the whole thing over otherwise
@ -347,7 +347,7 @@ export class AutoDiscovery {
}
// Step 2: Validate and parse the config
return AutoDiscovery.fromDiscoveryConfig(wellknown.raw);
return AutoDiscovery.fromDiscoveryConfig(wellknown.raw!);
}
/**
@ -378,7 +378,7 @@ export class AutoDiscovery {
* @return {string|boolean} The sanitized URL or a falsey value if the URL is invalid.
* @private
*/
private static sanitizeWellKnownUrl(url: string): string | false {
private static sanitizeWellKnownUrl(url?: string | null): string | false {
if (!url) return false;
try {

View File

@ -16,27 +16,28 @@ limitations under the License.
import * as matrixcs from "./matrix";
type BrowserMatrix = typeof matrixcs;
declare global {
/* eslint-disable no-var, camelcase */
var __js_sdk_entrypoint: boolean;
var matrixcs: BrowserMatrix;
/* eslint-enable no-var */
}
if (global.__js_sdk_entrypoint) {
throw new Error("Multiple matrix-js-sdk entrypoints detected!");
}
global.__js_sdk_entrypoint = true;
// just *accessing* indexedDB throws an exception in firefox with
// indexeddb disabled.
let indexedDB;
// just *accessing* indexedDB throws an exception in firefox with indexeddb disabled.
let indexedDB: IDBFactory | undefined;
try {
indexedDB = global.indexedDB;
} catch (e) {}
// if our browser (appears to) support indexeddb, use an indexeddb crypto store.
if (indexedDB) {
matrixcs.setCryptoStoreFactory(
function() {
return new matrixcs.IndexedDBCryptoStore(
indexedDB, "matrix-js-sdk:crypto",
);
},
);
matrixcs.setCryptoStoreFactory(() => new matrixcs.IndexedDBCryptoStore(indexedDB!, "matrix-js-sdk:crypto"));
}
// We export 3 things to make browserify happy as well as downstream projects.

View File

@ -530,7 +530,7 @@ export interface IPreviewUrlResponse {
"matrix:image:size"?: number;
}
interface ITurnServerResponse {
export interface ITurnServerResponse {
uris: string[];
username: string;
password: string;
@ -561,7 +561,7 @@ export interface IClientWellKnown {
}
export interface IWellKnownConfig {
raw?: any; // todo typings
raw?: IClientWellKnown;
action?: AutoDiscoveryAction;
reason?: string;
error?: Error | string;
@ -605,10 +605,10 @@ interface ITagMetadata {
}
interface IMessagesResponse {
start: string;
end: string;
start?: string;
end?: string;
chunk: IRoomEvent[];
state: IStateEvent[];
state?: IStateEvent[];
}
interface IThreadedMessagesResponse {
@ -635,6 +635,17 @@ export interface IUploadKeysRequest {
"org.matrix.msc2732.fallback_keys"?: Record<string, IOneTimeKey>;
}
export interface IQueryKeysRequest {
device_keys: { [userId: string]: string[] };
timeout?: number;
token?: string;
}
export interface IClaimKeysRequest {
one_time_keys: { [userId: string]: { [deviceId: string]: string } };
timeout?: number;
}
export interface IOpenIDToken {
access_token: string;
token_type: "Bearer" | string;
@ -1744,13 +1755,15 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
}
return this.http.authedRequest<{
type Response = {
capabilities?: ICapabilities;
}>(Method.Get, "/capabilities").catch((e: Error): void => {
};
return this.http.authedRequest<Response>(Method.Get, "/capabilities").catch((e: Error): Response => {
// We swallow errors because we need a default object anyhow
logger.error(e);
return {};
}).then((r = {}) => {
const capabilities: ICapabilities = r["capabilities"] || {};
const capabilities = r["capabilities"] || {};
// If the capabilities missed the cache, cache it for a shorter amount
// of time to try and refresh them later.
@ -3229,28 +3242,28 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
targetRoomId: undefined,
targetSessionId: undefined,
backupInfo: IKeyBackupInfo,
opts: IKeyBackupRestoreOpts,
opts?: IKeyBackupRestoreOpts,
): Promise<IKeyBackupRestoreResult>;
public restoreKeyBackupWithRecoveryKey(
recoveryKey: string,
targetRoomId: string,
targetSessionId: undefined,
backupInfo: IKeyBackupInfo,
opts: IKeyBackupRestoreOpts,
opts?: IKeyBackupRestoreOpts,
): Promise<IKeyBackupRestoreResult>;
public restoreKeyBackupWithRecoveryKey(
recoveryKey: string,
targetRoomId: string,
targetSessionId: string,
backupInfo: IKeyBackupInfo,
opts: IKeyBackupRestoreOpts,
opts?: IKeyBackupRestoreOpts,
): Promise<IKeyBackupRestoreResult>;
public restoreKeyBackupWithRecoveryKey(
recoveryKey: string,
targetRoomId: string | undefined,
targetSessionId: string | undefined,
backupInfo: IKeyBackupInfo,
opts: IKeyBackupRestoreOpts,
opts?: IKeyBackupRestoreOpts,
): Promise<IKeyBackupRestoreResult> {
const privKey = decodeRecoveryKey(recoveryKey);
return this.restoreKeyBackup(privKey, targetRoomId!, targetSessionId!, backupInfo, opts);
@ -3442,7 +3455,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
const deviceInfos = await this.crypto.downloadKeys(userIds);
const devicesByUser = {};
const devicesByUser: Record<string, DeviceInfo[]> = {};
for (const [userId, devices] of Object.entries(deviceInfos)) {
devicesByUser[userId] = Object.values(devices);
}
@ -3616,7 +3629,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
public setIgnoredUsers(userIds: string[]): Promise<{}> {
const content = { ignored_users: {} };
const content = { ignored_users: {} as Record<string, object> };
userIds.forEach((u) => {
content.ignored_users[u] = {};
});
@ -3910,16 +3923,25 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
): Promise<ISendEventResponse>;
public sendEvent(
roomId: string,
threadId: string | null,
eventType: string | IContent,
content?: IContent | string,
txnId?: string,
threadIdOrEventType: string | null,
eventTypeOrContent: string | IContent,
contentOrTxnId?: IContent | string,
txnIdOrVoid?: string,
): Promise<ISendEventResponse> {
if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {
txnId = content as string;
content = eventType as IContent;
eventType = threadId;
let threadId: string | null;
let eventType: string;
let content: IContent;
let txnId: string | undefined;
if (!threadIdOrEventType?.startsWith(EVENT_ID_PREFIX) && threadIdOrEventType !== null) {
txnId = contentOrTxnId as string;
content = eventTypeOrContent as IContent;
eventType = threadIdOrEventType;
threadId = null;
} else {
txnId = txnIdOrVoid;
content = contentOrTxnId as IContent;
eventType = eventTypeOrContent as string;
threadId = threadIdOrEventType;
}
// If we expect that an event is part of a thread but is missing the relation
@ -4280,7 +4302,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
let eventType: string = EventType.RoomMessage;
let sendContent: IContent = content as IContent;
const makeContentExtensible = (content: IContent = {}, recurse = true): IPartialEvent<object> | undefined => {
let newEvent: IPartialEvent<object> | undefined;
let newEvent: IPartialEvent<IContent> | undefined;
if (content['msgtype'] === MsgType.Text) {
newEvent = MessageEvent.from(content['body'], content['formatted_body']).serialize();
@ -5262,11 +5284,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
room.addEventsToTimeline(timelineEvents, true, room.getLiveTimeline());
this.processThreadEvents(room, threadedEvents, true);
room.oldState.paginationToken = res.end;
room.oldState.paginationToken = res.end ?? null;
if (res.chunk.length === 0) {
room.oldState.paginationToken = null;
}
this.store.storeEvents(room, matrixEvents, res.end, true);
this.store.storeEvents(room, matrixEvents, res.end ?? null, true);
delete this.ongoingScrollbacks[room.roomId];
resolve(room);
}).catch((err) => {
@ -6261,7 +6283,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @param {string} roomId the id of the room.
* @return {object} the rule or undefined.
*/
public getRoomPushRule(scope: string, roomId: string): IPushRule | undefined {
public getRoomPushRule(scope: "global" | "device", roomId: string): IPushRule | undefined {
// There can be only room-kind push rule per room
// and its id is the room id.
if (this.pushRules) {
@ -6282,7 +6304,7 @@ 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> | undefined {
public setRoomMutePushRule(scope: "global" | "device", roomId: string, mute: boolean): Promise<void> | undefined {
let promise: Promise<unknown> | undefined;
let hasDontNotifyRule = false;
@ -6818,7 +6840,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
.filter(([key, value]) => {
return primTypes.includes(typeof value);
})
.reduce((obj, [key, value]) => {
.reduce<Record<string, any>>((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {});
@ -7841,7 +7863,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
$roomId: roomId,
});
const content = {
const content: IContent = {
[ReceiptType.FullyRead]: rmEventId,
[ReceiptType.Read]: rrEventId,
};
@ -8540,7 +8562,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* an error response ({@link module:http-api.MatrixError}).
*/
public downloadKeysForUsers(userIds: string[], opts: { token?: string } = {}): Promise<IDownloadKeyResult> {
const content: any = {
const content: IQueryKeysRequest = {
device_keys: {},
};
if ('token' in opts) {
@ -8582,7 +8604,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
queries[userId] = query;
query[deviceId] = keyAlgorithm;
}
const content: any = { one_time_keys: queries };
const content: IClaimKeysRequest = { one_time_keys: queries };
if (timeout) {
content.timeout = timeout;
}
@ -8874,7 +8896,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
const med = p[1].toLowerCase();
const unhashed = `${addr} ${med}`;
// Map the unhashed values to a known (case-sensitive) address. We use
// the case sensitive version because the caller might be expecting that.
// the case-sensitive version because the caller might be expecting that.
localMapping[unhashed] = p[0];
return unhashed;
});
@ -8883,12 +8905,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
throw new Error("Unsupported identity server: unknown hash algorithm");
}
const response = await this.http.idServerRequest(
Method.Post, "/lookup",
params, IdentityPrefix.V2, identityAccessToken,
);
const response = await this.http.idServerRequest<{
mappings: { [address: string]: string };
}>(Method.Post, "/lookup", params, IdentityPrefix.V2, identityAccessToken);
if (!response || !response['mappings']) return []; // no results
if (!response?.['mappings']) return []; // no results
const foundAddresses: { address: string, mxid: string }[] = [];
for (const hashed of Object.keys(response['mappings'])) {
@ -9028,7 +9049,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
messages: contentMap,
};
const targets = Object.keys(contentMap).reduce((obj, key) => {
const targets = Object.keys(contentMap).reduce<Record<string, string[]>>((obj, key) => {
obj[key] = Object.keys(contentMap[key]);
return obj;
}, {});

View File

@ -84,6 +84,7 @@ export class CrossSigningInfo {
const res = new CrossSigningInfo(userId);
for (const prop in obj) {
if (obj.hasOwnProperty(prop)) {
// @ts-ignore - ts doesn't like this and nor should we
res[prop] = obj[prop];
}
}

View File

@ -35,7 +35,7 @@ import { IAccountDataClient } from "./SecretStorage";
interface ICrossSigningKeys {
authUpload: IBootstrapCrossSigningOpts["authUploadDeviceSigningKeys"];
keys: Record<string, ICrossSigningKey>;
keys: Record<"master" | "self_signing" | "user_signing", ICrossSigningKey>;
}
/**
@ -209,7 +209,7 @@ export class EncryptionSetupOperation {
if (this.crossSigningKeys) {
const keys: Partial<CrossSigningKeys> = {};
for (const [name, key] of Object.entries(this.crossSigningKeys.keys)) {
keys[name + "_key"] = key;
keys[((name as keyof ICrossSigningKeys["keys"]) + "_key" as keyof CrossSigningKeys)] = key;
}
// We must only call `uploadDeviceSigningKeys` from inside this auth

View File

@ -23,11 +23,19 @@ import { CryptoStore, IProblem, ISessionInfo, IWithheld } from "./store/base";
import { IOlmDevice, IOutboundGroupSessionKey } from "./algorithms/megolm";
import { IMegolmSessionData } from "./index";
import { OlmGroupSessionExtraData } from "../@types/crypto";
import { IMessage } from "./algorithms/olm";
// The maximum size of an event is 65K, and we base64 the content, so this is a
// reasonable approximation to the biggest plaintext we can encrypt.
const MAX_PLAINTEXT_LENGTH = 65536 * 3 / 4;
export class PayloadTooLargeError extends Error {
public readonly data = {
errcode: "M_TOO_LARGE",
error: "Payload too large for encrypted message",
};
}
function checkPayloadLength(payloadString: string): void {
if (payloadString === undefined) {
throw new Error("payloadString undefined");
@ -40,15 +48,8 @@ function checkPayloadLength(payloadString: string): void {
// Note that even if we manage to do the encryption, the message send may fail,
// because by the time we've wrapped the ciphertext in the event object, it may
// exceed 65K. But at least we won't just fail with "abort()" in that case.
const err = new Error("Message too long (" + payloadString.length + " bytes). " +
"The maximum for an encrypted message is " +
MAX_PLAINTEXT_LENGTH + " bytes.");
// TODO: [TypeScript] We should have our own error types
err["data"] = {
errcode: "M_TOO_LARGE",
error: "Payload too large for encrypted message",
};
throw err;
throw new PayloadTooLargeError(`Message too long (${payloadString.length} bytes). ` +
`The maximum for an encrypted message is ${MAX_PLAINTEXT_LENGTH} bytes.`);
}
}
@ -126,6 +127,8 @@ interface IInboundGroupSessionKey {
}
/* eslint-enable camelcase */
type OneTimeKeys = { curve25519: { [keyId: string]: string } };
/**
* Manages the olm cryptography functions. Each OlmDevice has a single
* OlmAccount and a number of OlmSessions.
@ -452,7 +455,7 @@ export class OlmDevice {
* @return {Promise<string>} base64-encoded signature
*/
public async sign(message: string): Promise<string> {
let result;
let result: string;
await this.cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
@ -460,7 +463,7 @@ export class OlmDevice {
result = account.sign(message);
});
});
return result;
return result!;
}
/**
@ -470,8 +473,8 @@ export class OlmDevice {
* <tt>curve25519</tt>, which is itself an object mapping key id to Curve25519
* key.
*/
public async getOneTimeKeys(): Promise<{ curve25519: { [keyId: string]: string } }> {
let result;
public async getOneTimeKeys(): Promise<OneTimeKeys> {
let result: OneTimeKeys;
await this.cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
@ -481,7 +484,7 @@ export class OlmDevice {
},
);
return result;
return result!;
}
/**
@ -821,10 +824,10 @@ export class OlmDevice {
theirDeviceIdentityKey: string,
sessionId: string,
payloadString: string,
): Promise<string> {
): Promise<IMessage> {
checkPayloadLength(payloadString);
let res;
let res: IMessage;
await this.cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
(txn) => {
@ -840,7 +843,7 @@ export class OlmDevice {
},
logger.withPrefix("[encryptMessage]"),
);
return res;
return res!;
}
/**
@ -860,7 +863,7 @@ export class OlmDevice {
messageType: number,
ciphertext: string,
): Promise<string> {
let payloadString;
let payloadString: string;
await this.cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
(txn) => {
@ -877,7 +880,7 @@ export class OlmDevice {
},
logger.withPrefix("[decryptMessage]"),
);
return payloadString;
return payloadString!;
}
/**
@ -902,7 +905,7 @@ export class OlmDevice {
return false;
}
let matches;
let matches: boolean;
await this.cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_SESSIONS],
(txn) => {
@ -912,7 +915,7 @@ export class OlmDevice {
},
logger.withPrefix("[matchesSession]"),
);
return matches;
return matches!;
}
public async recordSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void> {
@ -1293,7 +1296,7 @@ export class OlmDevice {
result = null;
return;
}
let res;
let res: ReturnType<InboundGroupSession["decrypt"]>;
try {
res = session.decrypt(body);
} catch (e) {
@ -1313,8 +1316,8 @@ export class OlmDevice {
let plaintext: string = res.plaintext;
if (plaintext === undefined) {
// Compatibility for older olm versions.
plaintext = res;
// @ts-ignore - Compatibility for older olm versions.
plaintext = res as string;
} else {
// Check if we have seen this message index before to detect replay attacks.
// If the event ID and timestamp are specified, and the match the event ID
@ -1555,7 +1558,7 @@ export class OlmDevice {
}
}
export const WITHHELD_MESSAGES = {
export const WITHHELD_MESSAGES: Record<string, string> = {
"m.unverified": "The sender has disabled encrypting to unverified devices.",
"m.blacklisted": "The sender has blocked you.",
"m.unauthorised": "You are not authorised to read the message.",

View File

@ -18,10 +18,9 @@ import { v4 as uuidv4 } from 'uuid';
import { logger } from '../logger';
import * as olmlib from './olmlib';
import { encodeBase64 } from './olmlib';
import { randomString } from '../randomstring';
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from './aes';
import { ICryptoCallbacks } from ".";
import { ICryptoCallbacks, IEncryptedContent } from ".";
import { IContent, MatrixEvent } from "../models/event";
import { ClientEvent, ClientEventHandlerMap, MatrixClient } from "../client";
import { IAddSecretStorageKeyOpts, ISecretStorageKeyInfo } from './api';
@ -61,8 +60,7 @@ interface IDecryptors {
interface ISecretInfo {
encrypted: {
// eslint-disable-next-line camelcase
key_id: IEncryptedPayload;
[keyId: string]: IEncryptedPayload;
};
}
@ -318,23 +316,11 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
`the keys it is encrypted with are for a supported algorithm`);
}
let keyId: string;
let decryption;
try {
// fetch private key from app
[keyId, decryption] = await this.getSecretStorageKey(keys, name);
// fetch private key from app
const [keyId, decryption] = await this.getSecretStorageKey(keys, name);
const encInfo = secretInfo.encrypted[keyId];
const encInfo = secretInfo.encrypted[keyId];
// We don't actually need the decryption object if it's a passthrough
// since we just want to return the key itself. It must be base64
// encoded, since this is how a key would normally be stored.
if (encInfo.passthrough) return encodeBase64(decryption.get_private_key());
return decryption.decrypt(encInfo);
} finally {
if (decryption && decryption.free) decryption.free();
}
return decryption.decrypt(encInfo);
}
/**
@ -351,7 +337,7 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer<ISecretInfo>(name);
if (!secretInfo?.encrypted) return null;
const ret = {};
const ret: Record<string, ISecretStorageKeyInfo> = {};
// filter secret encryption keys with supported algorithm
for (const keyId of Object.keys(secretInfo.encrypted)) {
@ -391,7 +377,7 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
requesting_device_id: this.baseApis.deviceId,
request_id: requestId,
};
const toDevice = {};
const toDevice: Record<string, typeof cancelData> = {};
for (const device of devices) {
toDevice[device] = cancelData;
}
@ -412,7 +398,7 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
request_id: requestId,
[ToDeviceMessageId]: uuidv4(),
};
const toDevice = {};
const toDevice: Record<string, typeof requestData> = {};
for (const device of devices) {
toDevice[device] = requestData;
}
@ -490,9 +476,9 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
secret: secret,
},
};
const encryptedContent = {
const encryptedContent: IEncryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this.baseApis.crypto!.olmDevice.deviceCurve25519Key,
sender_key: this.baseApis.crypto!.olmDevice.deviceCurve25519Key!,
ciphertext: {},
[ToDeviceMessageId]: uuidv4(),
};

View File

@ -23,8 +23,14 @@ limitations under the License.
import { MatrixClient } from "../../client";
import { Room } from "../../models/room";
import { OlmDevice } from "../OlmDevice";
import { MatrixEvent, RoomMember } from "../../matrix";
import { Crypto, IEventDecryptionResult, IMegolmSessionData, IncomingRoomKeyRequest } from "..";
import { IContent, MatrixEvent, RoomMember } from "../../matrix";
import {
Crypto,
IEncryptedContent,
IEventDecryptionResult,
IMegolmSessionData,
IncomingRoomKeyRequest,
} from "..";
import { DeviceInfo } from "../deviceinfo";
import { IRoomEncryption } from "../RoomList";
@ -108,7 +114,7 @@ export abstract class EncryptionAlgorithm {
*
* @return {Promise} Promise which resolves to the new event body
*/
public abstract encryptMessage(room: Room, eventType: string, content: object): Promise<object>;
public abstract encryptMessage(room: Room, eventType: string, content: IContent): Promise<IEncryptedContent>;
/**
* Called when the membership of a member of the room changes.

View File

@ -38,9 +38,15 @@ import { Room } from '../../models/room';
import { DeviceInfo } from "../deviceinfo";
import { IOlmSessionResult } from "../olmlib";
import { DeviceInfoMap } from "../DeviceList";
import { MatrixEvent } from "../../models/event";
import { IContent, MatrixEvent } from "../../models/event";
import { EventType, MsgType, ToDeviceMessageId } from '../../@types/event';
import { IEncryptedContent, IEventDecryptionResult, IMegolmSessionData, IncomingRoomKeyRequest } from "../index";
import {
IMegolmEncryptedContent,
IEventDecryptionResult,
IMegolmSessionData,
IncomingRoomKeyRequest,
IEncryptedContent,
} from "../index";
import { RoomKeyRequestState } from '../OutgoingRoomKeyRequestManager';
import { OlmGroupSessionExtraData } from "../../@types/crypto";
import { MatrixError } from "../../http-api";
@ -228,7 +234,7 @@ class OutboundSessionInfo {
* @param {object} params parameters, as per
* {@link module:crypto/algorithms/EncryptionAlgorithm}
*/
class MegolmEncryption extends EncryptionAlgorithm {
export class MegolmEncryption extends EncryptionAlgorithm {
// the most recent attempt to set up a session. This is used to serialise
// the session setups, so that we have a race-free view of which session we
// are using, and which devices we have shared the keys with. It resolves
@ -761,9 +767,9 @@ class MegolmEncryption extends EncryptionAlgorithm {
},
};
const encryptedContent = {
const encryptedContent: IEncryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this.olmDevice.deviceCurve25519Key,
sender_key: this.olmDevice.deviceCurve25519Key!,
ciphertext: {},
[ToDeviceMessageId]: uuidv4(),
};
@ -1010,7 +1016,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
*
* @return {Promise} Promise which resolves to the new event body
*/
public async encryptMessage(room: Room, eventType: string, content: object): Promise<object> {
public async encryptMessage(room: Room, eventType: string, content: IContent): Promise<IMegolmEncryptedContent> {
logger.log(`Starting to encrypt event for ${this.roomId}`);
if (this.encryptionPreparation != null) {
@ -1045,12 +1051,10 @@ class MegolmEncryption extends EncryptionAlgorithm {
content: content,
};
const ciphertext = this.olmDevice.encryptGroupMessage(
session.sessionId, JSON.stringify(payloadJson),
);
const encryptedContent = {
const ciphertext = this.olmDevice.encryptGroupMessage(session.sessionId, JSON.stringify(payloadJson));
const encryptedContent: IEncryptedContent = {
algorithm: olmlib.MEGOLM_ALGORITHM,
sender_key: this.olmDevice.deviceCurve25519Key,
sender_key: this.olmDevice.deviceCurve25519Key!,
ciphertext: ciphertext,
session_id: session.sessionId,
// Include our device ID so that recipients can send us a
@ -1064,7 +1068,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
return encryptedContent;
}
private isVerificationEvent(eventType: string, content: object): boolean {
private isVerificationEvent(eventType: string, content: IContent): boolean {
switch (eventType) {
case EventType.KeyVerificationCancel:
case EventType.KeyVerificationDone:
@ -1227,7 +1231,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
* @param {object} params parameters, as per
* {@link module:crypto/algorithms/DecryptionAlgorithm}
*/
class MegolmDecryption extends DecryptionAlgorithm {
export class MegolmDecryption extends DecryptionAlgorithm {
// events which we couldn't decrypt due to unknown sessions /
// indexes, or which we could only decrypt with untrusted keys:
// map from senderKey|sessionId to Set of MatrixEvents
@ -1670,9 +1674,9 @@ class MegolmDecryption extends DecryptionAlgorithm {
await olmlib.ensureOlmSessionsForDevices(
this.olmDevice, this.baseApis, { [sender]: [device] }, false,
);
const encryptedContent = {
const encryptedContent: IEncryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this.olmDevice.deviceCurve25519Key,
sender_key: this.olmDevice.deviceCurve25519Key!,
ciphertext: {},
[ToDeviceMessageId]: uuidv4(),
};
@ -1752,9 +1756,9 @@ class MegolmDecryption extends DecryptionAlgorithm {
body.room_id, body.sender_key, body.session_id,
);
}).then((payload) => {
const encryptedContent = {
const encryptedContent: IEncryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this.olmDevice.deviceCurve25519Key,
sender_key: this.olmDevice.deviceCurve25519Key!,
ciphertext: {},
[ToDeviceMessageId]: uuidv4(),
};

View File

@ -30,13 +30,13 @@ import {
registerAlgorithm,
} from "./base";
import { Room } from '../../models/room';
import { MatrixEvent } from "../../models/event";
import { IEventDecryptionResult } from "../index";
import { IContent, MatrixEvent } from "../../models/event";
import { IEncryptedContent, IEventDecryptionResult, IOlmEncryptedContent } from "../index";
import { IInboundSession } from "../OlmDevice";
const DeviceVerification = DeviceInfo.DeviceVerification;
interface IMessage {
export interface IMessage {
type: number;
body: string;
}
@ -91,7 +91,7 @@ class OlmEncryption extends EncryptionAlgorithm {
*
* @return {Promise} Promise which resolves to the new event body
*/
public async encryptMessage(room: Room, eventType: string, content: object): Promise<object> {
public async encryptMessage(room: Room, eventType: string, content: IContent): Promise<IOlmEncryptedContent> {
// pick the list of recipients based on the membership list.
//
// TODO: there is a race condition here! What if a new user turns up
@ -111,9 +111,9 @@ class OlmEncryption extends EncryptionAlgorithm {
content: content,
};
const encryptedContent = {
const encryptedContent: IEncryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this.olmDevice.deviceCurve25519Key,
sender_key: this.olmDevice.deviceCurve25519Key!,
ciphertext: {},
};

View File

@ -93,7 +93,7 @@ interface BackupAlgorithmClass {
interface BackupAlgorithm {
untrusted: boolean;
encryptSession(data: Record<string, any>): Promise<any>;
encryptSession(data: Record<string, any>): Promise<Curve25519SessionData | IEncryptedPayload>;
decryptSessions(ciphertexts: Record<string, IKeyBackupSession>): Promise<IMegolmSessionData[]>;
authData: AuthData;
keyMatches(key: ArrayLike<number>): Promise<boolean>;
@ -663,7 +663,7 @@ export class Curve25519 implements BackupAlgorithm {
public get untrusted(): boolean { return true; }
public async encryptSession(data: Record<string, any>): Promise<any> {
public async encryptSession(data: Record<string, any>): Promise<Curve25519SessionData> {
const plainText: Record<string, any> = Object.assign({}, data);
delete plainText.session_id;
delete plainText.room_id;
@ -788,7 +788,7 @@ export class Aes256 implements BackupAlgorithm {
public get untrusted(): boolean { return false; }
public encryptSession(data: Record<string, any>): Promise<any> {
public encryptSession(data: Record<string, any>): Promise<IEncryptedPayload> {
const plainText: Record<string, any> = Object.assign({}, data);
delete plainText.session_id;
delete plainText.room_id;

View File

@ -258,7 +258,7 @@ export class DehydrationManager {
}
logger.log("Preparing fallback keys");
const fallbackKeys = {};
const fallbackKeys: Record<string, IOneTimeKey> = {};
for (const [keyId, key] of Object.entries(fallbacks.curve25519)) {
const k: IOneTimeKey = { key, fallback: true };
const signature = account.sign(anotherjson.stringify(k));

View File

@ -72,7 +72,8 @@ export class DeviceInfo {
const res = new DeviceInfo(deviceId);
for (const prop in obj) {
if (obj.hasOwnProperty(prop)) {
res[prop] = obj[prop];
// @ts-ignore - this is messy and typescript doesn't like it
res[prop as keyof IDevice] = obj[prop as keyof IDevice];
}
}
return res;

View File

@ -88,6 +88,9 @@ import { CryptoStore } from "./store/base";
import { IVerificationChannel } from "./verification/request/Channel";
import { TypedEventEmitter } from "../models/typed-event-emitter";
import { IContent } from "../models/event";
import { ISyncResponse } from "../sync-accumulator";
import { ISignatures } from "../@types/signed";
import { IMessage } from "./algorithms/olm";
const DeviceVerification = DeviceInfo.DeviceVerification;
@ -100,7 +103,7 @@ const defaultVerificationMethods = {
// to start.
[SHOW_QR_CODE_METHOD]: IllegalMethod,
[SCAN_QR_CODE_METHOD]: IllegalMethod,
};
} as const;
/**
* verification method names
@ -109,7 +112,7 @@ const defaultVerificationMethods = {
export const verificationMethods = {
RECIPROCATE_QR_CODE: ReciprocateQRCode.NAME,
SAS: SASVerification.NAME,
};
} as const;
export type VerificationMethod = keyof typeof verificationMethods | string;
@ -200,18 +203,13 @@ interface IUserOlmSession {
}[];
}
interface ISyncDeviceLists {
changed: string[];
left: string[];
}
export interface IRoomKeyRequestRecipient {
userId: string;
deviceId: string;
}
interface ISignableObject {
signatures?: object;
signatures?: ISignatures;
unsigned?: object;
}
@ -231,14 +229,25 @@ export interface IRequestsMap {
}
/* eslint-disable camelcase */
export interface IEncryptedContent {
algorithm: string;
export interface IOlmEncryptedContent {
algorithm: typeof olmlib.OLM_ALGORITHM;
sender_key: string;
ciphertext: Record<string, string>;
[ToDeviceMessageId]: string;
ciphertext: Record<string, IMessage>;
[ToDeviceMessageId]?: string;
}
export interface IMegolmEncryptedContent {
algorithm: typeof olmlib.MEGOLM_ALGORITHM;
sender_key: string;
session_id: string;
device_id: string;
ciphertext: string;
[ToDeviceMessageId]?: string;
}
/* eslint-enable camelcase */
export type IEncryptedContent = IOlmEncryptedContent | IMegolmEncryptedContent;
export enum CryptoEvent {
DeviceVerificationChanged = "deviceVerificationChanged",
UserTrustStatusChanged = "userTrustStatusChanged",
@ -382,7 +391,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
private readonly clientStore: IStore,
public readonly cryptoStore: CryptoStore,
private readonly roomList: RoomList,
verificationMethods: Array<keyof typeof defaultVerificationMethods | typeof VerificationBase>,
verificationMethods: Array<VerificationMethod | (typeof VerificationBase & { NAME: string })>,
) {
super();
this.reEmitter = new TypedReEmitter(this);
@ -2947,7 +2956,10 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* @param {Object} syncDeviceLists device_lists field from /sync, or response from
* /keys/changes
*/
public async handleDeviceListChanges(syncData: ISyncStateData, syncDeviceLists: ISyncDeviceLists): Promise<void> {
public async handleDeviceListChanges(
syncData: ISyncStateData,
syncDeviceLists: Required<ISyncResponse>["device_lists"],
): Promise<void> {
// Initial syncs don't have device change lists. We'll either get the complete list
// of changes for the interval or will have invalidated everything in willProcessSync
if (!syncData.oldSyncToken) return;
@ -3087,15 +3099,14 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
* @param {Object} deviceLists device_lists field from /sync, or response from
* /keys/changes
*/
private async evalDeviceListChanges(deviceLists: ISyncDeviceLists): Promise<void> {
if (deviceLists.changed && Array.isArray(deviceLists.changed)) {
private async evalDeviceListChanges(deviceLists: Required<ISyncResponse>["device_lists"]): Promise<void> {
if (Array.isArray(deviceLists?.changed)) {
deviceLists.changed.forEach((u) => {
this.deviceList.invalidateUserDeviceList(u);
});
}
if (deviceLists.left && Array.isArray(deviceLists.left) &&
deviceLists.left.length) {
if (Array.isArray(deviceLists?.left) && deviceLists.left.length) {
// Check we really don't share any rooms with these users
// any more: the server isn't required to give us the
// exact correct set.
@ -3515,9 +3526,9 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
// same order we sent them, the other end will get this first, set up the new session,
// then get the keyshare request and send the key over this new session (because it
// is the session it has most recently received a message on).
const encryptedContent = {
const encryptedContent: IEncryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this.olmDevice.deviceCurve25519Key,
sender_key: this.olmDevice.deviceCurve25519Key!,
ciphertext: {},
[ToDeviceMessageId]: uuidv4(),
};
@ -3842,7 +3853,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
*
* @param {Object} obj Object to which we will add a 'signatures' property
*/
public async signObject(obj: object & ISignableObject): Promise<void> {
public async signObject<T extends ISignableObject & object>(obj: T): Promise<void> {
const sigs = obj.signatures || {};
const unsigned = obj.unsigned;

View File

@ -31,6 +31,7 @@ import { IClaimOTKsResult, MatrixClient } from "../client";
import { ISignatures } from "../@types/signed";
import { MatrixEvent } from "../models/event";
import { EventType } from "../@types/event";
import { IMessage } from "./algorithms/olm";
enum Algorithm {
Olm = "m.olm.v1.curve25519-aes-sha2",
@ -75,7 +76,7 @@ export interface IOlmSessionResult {
* has been encrypted into `resultsObject`
*/
export async function encryptMessageForDevice(
resultsObject: Record<string, string>,
resultsObject: Record<string, IMessage>,
ourUserId: string,
ourDeviceId: string | undefined,
olmDevice: OlmDevice,
@ -124,6 +125,7 @@ export async function encryptMessageForDevice(
recipient_keys: {
"ed25519": recipientDevice.getFingerprint(),
},
...payloadFields,
};
// TODO: technically, a bunch of that stuff only needs to be included for
@ -131,11 +133,7 @@ export async function encryptMessageForDevice(
// involved in the session. If we're looking to reduce data transfer in the
// future, we could elide them for subsequent messages.
Object.assign(payload, payloadFields);
resultsObject[deviceKey] = await olmDevice.encryptMessage(
deviceKey, sessionId, JSON.stringify(payload),
);
resultsObject[deviceKey] = await olmDevice.encryptMessage(deviceKey, sessionId, JSON.stringify(payload));
}
interface IExistingOlmSession {
@ -495,7 +493,7 @@ export async function verifySignature(
* @param {string} pubKey The public key (ignored if key is a seed)
* @returns {string} the signature for the object
*/
export function pkSign(obj: IObject, key: PkSigning, userId: string, pubKey: string): string {
export function pkSign(obj: object & IObject, key: Uint8Array | PkSigning, userId: string, pubKey: string): string {
let createdKey = false;
if (key instanceof Uint8Array) {
const keyObj = new global.Olm.PkSigning();

View File

@ -69,7 +69,7 @@ export interface CryptoStore {
deleteOutgoingRoomKeyRequest(requestId: string, expectedState: number): Promise<OutgoingRoomKeyRequest | null>;
// Olm Account
getAccount(txn: unknown, func: (accountPickle: string | null) => void);
getAccount(txn: unknown, func: (accountPickle: string | null) => void): void;
storeAccount(txn: unknown, accountPickle: string): void;
getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey> | null) => void): void;
getSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(

View File

@ -338,20 +338,20 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
}
public unmarkSessionsNeedingBackup(sessions: ISession[]): Promise<void> {
const sessionsNeedingBackup
= getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
const sessionsNeedingBackup = getJsonItem<{
[senderKeySessionId: string]: string;
}>(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
for (const session of sessions) {
delete sessionsNeedingBackup[session.senderKey + '/' + session.sessionId];
}
setJsonItem(
this.store, KEY_SESSIONS_NEEDING_BACKUP, sessionsNeedingBackup,
);
setJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP, sessionsNeedingBackup);
return Promise.resolve();
}
public markSessionsNeedingBackup(sessions: ISession[]): Promise<void> {
const sessionsNeedingBackup
= getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
const sessionsNeedingBackup = getJsonItem<{
[senderKeySessionId: string]: boolean;
}>(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
for (const session of sessions) {
sessionsNeedingBackup[session.senderKey + '/' + session.sessionId] = true;
}

View File

@ -283,21 +283,21 @@ export class QRCodeData {
private static generateBuffer(qrData: IQrData): Buffer {
let buf = Buffer.alloc(0); // we'll concat our way through life
const appendByte = (b): void => {
const appendByte = (b: number): void => {
const tmpBuf = Buffer.from([b]);
buf = Buffer.concat([buf, tmpBuf]);
};
const appendInt = (i): void => {
const appendInt = (i: number): void => {
const tmpBuf = Buffer.alloc(2);
tmpBuf.writeInt16BE(i, 0);
buf = Buffer.concat([buf, tmpBuf]);
};
const appendStr = (s, enc, withLengthPrefix = true): void => {
const appendStr = (s: string, enc: BufferEncoding, withLengthPrefix = true): void => {
const tmpBuf = Buffer.from(s, enc);
if (withLengthPrefix) appendInt(tmpBuf.byteLength);
buf = Buffer.concat([buf, tmpBuf]);
};
const appendEncBase64 = (b64): void => {
const appendEncBase64 = (b64: string): void => {
const b = decodeBase64(b64);
const tmpBuf = Buffer.from(b);
buf = Buffer.concat([buf, tmpBuf]);
@ -307,7 +307,7 @@ export class QRCodeData {
appendStr(qrData.prefix, "ascii", false);
appendByte(qrData.version);
appendByte(qrData.mode);
appendStr(qrData.transactionId, "utf-8");
appendStr(qrData.transactionId!, "utf-8");
appendEncBase64(qrData.firstKeyB64);
appendEncBase64(qrData.secondKeyB64);
appendEncBase64(qrData.secretB64);

View File

@ -140,7 +140,7 @@ function generateEmojiSas(sasBytes: number[]): EmojiMapping[] {
const sasGenerators = {
decimal: generateDecimalSas,
emoji: generateEmojiSas,
};
} as const;
export interface IGeneratedSas {
decimal?: [number, number, number];
@ -154,11 +154,12 @@ export interface ISasEvent {
mismatch(): void;
}
function generateSas(sasBytes: number[], methods: string[]): IGeneratedSas {
function generateSas(sasBytes: Uint8Array, methods: string[]): IGeneratedSas {
const sas: IGeneratedSas = {};
for (const method of methods) {
if (method in sasGenerators) {
sas[method] = sasGenerators[method](sasBytes);
// @ts-ignore - ts doesn't like us mixing types like this
sas[method] = sasGenerators[method](Array.from(sasBytes));
}
}
return sas;
@ -168,15 +169,14 @@ const macMethods = {
"hkdf-hmac-sha256": "calculate_mac",
"org.matrix.msc3783.hkdf-hmac-sha256": "calculate_mac_fixed_base64",
"hmac-sha256": "calculate_mac_long_kdf",
};
} as const;
type Method = keyof typeof macMethods;
type MacMethod = keyof typeof macMethods;
function calculateMAC(olmSAS: OlmSAS, method: Method) {
return function(...args): string {
const macFunction = olmSAS[macMethods[method]];
const mac: string = macFunction.apply(olmSAS, args);
logger.log("SAS calculateMAC:", method, args, mac);
function calculateMAC(olmSAS: OlmSAS, method: MacMethod) {
return function(input: string, info: string): string {
const mac = olmSAS[macMethods[method]](input, info);
logger.log("SAS calculateMAC:", method, [input, info], mac);
return mac;
};
}
@ -202,15 +202,17 @@ const calculateKeyAgreement = {
+ sas.channel.transactionId;
return olmSAS.generate_bytes(sasInfo, bytes);
},
};
} as const;
type KeyAgreement = keyof typeof calculateKeyAgreement;
/* lists of algorithms/methods that are supported. The key agreement, hashes,
* and MAC lists should be sorted in order of preference (most preferred
* first).
*/
const KEY_AGREEMENT_LIST = ["curve25519-hkdf-sha256", "curve25519"];
const KEY_AGREEMENT_LIST: KeyAgreement[] = ["curve25519-hkdf-sha256", "curve25519"];
const HASHES_LIST = ["sha256"];
const MAC_LIST: Method[] = ["org.matrix.msc3783.hkdf-hmac-sha256", "hkdf-hmac-sha256", "hmac-sha256"];
const MAC_LIST: MacMethod[] = ["org.matrix.msc3783.hkdf-hmac-sha256", "hkdf-hmac-sha256", "hmac-sha256"];
const SAS_LIST = Object.keys(sasGenerators);
const KEY_AGREEMENT_SET = new Set(KEY_AGREEMENT_LIST);
@ -299,10 +301,10 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
}
private async verifyAndCheckMAC(
keyAgreement: string,
keyAgreement: KeyAgreement,
sasMethods: string[],
olmSAS: OlmSAS,
macMethod: Method,
macMethod: MacMethod,
): Promise<void> {
const sasBytes = calculateKeyAgreement[keyAgreement](this, olmSAS, 6);
const verifySAS = new Promise<void>((resolve, reject) => {
@ -354,7 +356,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
throw new SwitchStartEventError(this.startEvent);
}
let e;
let e: MatrixEvent;
try {
e = await this.waitForEvent(EventType.KeyVerificationAccept);
} finally {
@ -445,8 +447,8 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
}
}
private sendMAC(olmSAS: OlmSAS, method: Method): Promise<void> {
const mac = {};
private sendMAC(olmSAS: OlmSAS, method: MacMethod): Promise<void> {
const mac: Record<string, string> = {};
const keyList: string[] = [];
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
+ this.baseApis.getUserId() + this.baseApis.deviceId
@ -455,7 +457,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
const deviceKeyId = `ed25519:${this.baseApis.deviceId}`;
mac[deviceKeyId] = calculateMAC(olmSAS, method)(
this.baseApis.getDeviceEd25519Key(),
this.baseApis.getDeviceEd25519Key()!,
baseInfo + deviceKeyId,
);
keyList.push(deviceKeyId);
@ -477,7 +479,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
return this.send(EventType.KeyVerificationMac, { mac, keys });
}
private async checkMAC(olmSAS: OlmSAS, content: IContent, method: Method): Promise<void> {
private async checkMAC(olmSAS: OlmSAS, content: IContent, method: MacMethod): Promise<void> {
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
+ this.userId + this.deviceId
+ this.baseApis.getUserId() + this.baseApis.deviceId

View File

@ -148,17 +148,17 @@ export class FilterComponent {
"types": function(v: string): boolean {
return matchesWildcard(eventType, v);
},
};
} as const;
for (const name in literalKeys) {
const matchFunc = literalKeys[name];
const matchFunc = literalKeys[<keyof typeof literalKeys>name];
const notName = "not_" + name;
const disallowedValues: string[] = this.filterJson[notName];
const disallowedValues = this.filterJson[<`not_${keyof typeof literalKeys}`>notName];
if (disallowedValues?.some(matchFunc)) {
return false;
}
const allowedValues: string[] = this.filterJson[name];
const allowedValues = this.filterJson[name as keyof typeof literalKeys];
if (allowedValues && !allowedValues.some(matchFunc)) {
return false;
}

View File

@ -31,8 +31,8 @@ import { MatrixEvent } from "./models/event";
* @param {string} keyNesting
* @param {*} val
*/
function setProp(obj: object, keyNesting: string, val: any): void {
const nestedKeys = keyNesting.split(".");
function setProp(obj: Record<string, any>, keyNesting: string, val: any): void {
const nestedKeys = keyNesting.split(".") as [keyof typeof obj];
let currentObj = obj;
for (let i = 0; i < (nestedKeys.length - 1); i++) {
if (!currentObj[nestedKeys[i]]) {
@ -70,8 +70,7 @@ interface IStateFilter extends IRoomEventFilter {}
interface IRoomFilter {
not_rooms?: string[];
rooms?: string[];
ephemeral?: IRoomEventFilter;
rooms?: string[];ephemeral?: IRoomEventFilter;
include_leave?: boolean;
state?: IStateFilter;
timeline?: IRoomEventFilter;

View File

@ -318,19 +318,19 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
// 'membership' at the event level (rather than the content level) is a legacy
// field that Element never otherwise looks at, but it will still take up a lot
// of space if we don't intern it.
["state_key", "type", "sender", "room_id", "membership"].forEach((prop) => {
(["state_key", "type", "sender", "room_id", "membership"] as const).forEach((prop) => {
if (typeof event[prop] !== "string") return;
event[prop] = internaliseString(event[prop]);
event[prop] = internaliseString(event[prop]!);
});
["membership", "avatar_url", "displayname"].forEach((prop) => {
(["membership", "avatar_url", "displayname"] as const).forEach((prop) => {
if (typeof event.content?.[prop] !== "string") return;
event.content[prop] = internaliseString(event.content[prop]);
event.content[prop] = internaliseString(event.content[prop]!);
});
["rel_type"].forEach((prop) => {
(["rel_type"] as const).forEach((prop) => {
if (typeof event.content?.["m.relates_to"]?.[prop] !== "string") return;
event.content["m.relates_to"][prop] = internaliseString(event.content["m.relates_to"][prop]);
event.content["m.relates_to"][prop] = internaliseString(event.content["m.relates_to"][prop]!);
});
this.txnId = event.txn_id;
@ -1120,7 +1120,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
for (const key in this.event) {
if (this.event.hasOwnProperty(key) && !REDACT_KEEP_KEYS.has(key)) {
delete this.event[key];
delete this.event[key as keyof IEvent];
}
}
@ -1129,7 +1129,9 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
this.clearEvent = undefined;
}
const keeps = REDACT_KEEP_CONTENT_MAP[this.getType()] || {};
const keeps = this.getType() in REDACT_KEEP_CONTENT_MAP
? REDACT_KEEP_CONTENT_MAP[this.getType() as keyof typeof REDACT_KEEP_CONTENT_MAP]
: {};
const content = this.getContent();
for (const key in content) {
if (content.hasOwnProperty(key) && !keeps[key]) {
@ -1212,7 +1214,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
*
* @returns {object} The redaction event JSON, or an empty object
*/
public getRedactionEvent(): object | null {
public getRedactionEvent(): IEvent | {} | null {
if (!this.isRedacted()) return null;
if (this.clearEvent?.unsigned) {
@ -1512,7 +1514,8 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
const ev = new MatrixEvent(JSON.parse(JSON.stringify(this.event)));
for (const [p, v] of Object.entries(this)) {
if (p !== "event") { // exclude the thing we just cloned
ev[p] = v;
// @ts-ignore - XXX: this is just nasty
ev[p as keyof MatrixEvent] = v;
}
}
return ev;
@ -1612,7 +1615,7 @@ const REDACT_KEEP_KEYS = new Set([
]);
// a map from state event type to the .content keys we keep when an event is redacted
const REDACT_KEEP_CONTENT_MAP = {
const REDACT_KEEP_CONTENT_MAP: Record<string, Record<string, 1>> = {
[EventType.RoomMember]: { 'membership': 1 },
[EventType.RoomCreate]: { 'creator': 1 },
[EventType.RoomJoinRules]: { 'join_rule': 1 },
@ -1621,7 +1624,7 @@ const REDACT_KEEP_CONTENT_MAP = {
'kick': 1, 'redact': 1, 'state_default': 1,
'users': 1, 'users_default': 1,
},
};
} as const;
/**
* Fires when an event is decrypted

View File

@ -17,7 +17,7 @@ limitations under the License.
import { UnstableValue } from "matrix-events-sdk";
import { MatrixClient } from "../client";
import { MatrixEvent } from "./event";
import { IContent, MatrixEvent } from "./event";
import { EventTimeline } from "./event-timeline";
import { Preset } from "../@types/partials";
import { globToRegexp } from "../utils";
@ -262,7 +262,7 @@ export class IgnoredInvites {
*/
public async getOrCreateSourceRooms(): Promise<Room[]> {
const ignoreInvitesPolicies = this.getIgnoreInvitesPolicies();
let sources = ignoreInvitesPolicies.sources;
let sources: string[] = ignoreInvitesPolicies.sources;
// Validate `sources`. If it is invalid, trash out the current `sources`
// and create a new list of sources from `target`.
@ -272,11 +272,11 @@ export class IgnoredInvites {
hasChanges = true;
sources = [];
}
let sourceRooms: Room[] = sources
let sourceRooms = sources
// `sources` could contain non-string / invalid room ids
.filter(roomId => typeof roomId === "string")
.map(roomId => this.client.getRoom(roomId))
.filter(room => !!room);
.filter(room => !!room) as Room[];
if (sourceRooms.length != sources.length) {
hasChanges = true;
}
@ -327,7 +327,7 @@ export class IgnoredInvites {
*/
private getPoliciesAndIgnoreInvitesPolicies():
{policies: {[key: string]: any}, ignoreInvitesPolicies: {[key: string]: any}} {
let policies = {};
let policies: IContent = {};
for (const key of [POLICIES_ACCOUNT_EVENT_TYPE.name, POLICIES_ACCOUNT_EVENT_TYPE.altName]) {
if (!key) {
continue;

View File

@ -22,7 +22,7 @@ import { RoomMember } from "./room-member";
import { logger } from '../logger';
import * as utils from "../utils";
import { EventType, UNSTABLE_MSC2716_MARKER } from "../@types/event";
import { MatrixEvent, MatrixEventEvent } from "./event";
import { IEvent, MatrixEvent, MatrixEventEvent } from "./event";
import { MatrixClient } from "../client";
import { GuestAccess, HistoryVisibility, IJoinRuleEventContent, JoinRule } from "../@types/partials";
import { TypedEventEmitter } from "./typed-event-emitter";
@ -53,6 +53,20 @@ enum OobStatus {
Finished,
}
export interface IPowerLevelsContent {
users?: Record<string, number>;
events?: Record<string, number>;
// eslint-disable-next-line camelcase
users_default?: number;
// eslint-disable-next-line camelcase
events_default?: number;
// eslint-disable-next-line camelcase
state_default?: number;
ban?: number;
kick?: number;
redact?: number;
}
export enum RoomStateEvent {
Events = "RoomState.events",
Members = "RoomState.members",
@ -499,7 +513,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
const beacon = this.beacons.get(beaconIdentifier)!;
if (event.isRedacted()) {
if (beacon.beaconInfoId === event.getRedactionEvent()?.['redacts']) {
if (beacon.beaconInfoId === (<IEvent>event.getRedactionEvent())?.redacts) {
beacon.destroy();
this.beacons.delete(beaconIdentifier);
}
@ -724,17 +738,17 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
* @param {number} powerLevel The power level of the member
* @return {boolean} true if the given power level is sufficient
*/
public hasSufficientPowerLevelFor(action: string, powerLevel: number): boolean {
public hasSufficientPowerLevelFor(action: "ban" | "kick" | "redact", powerLevel: number): boolean {
const powerLevelsEvent = this.getStateEvents(EventType.RoomPowerLevels, "");
let powerLevels = {};
let powerLevels: IPowerLevelsContent = {};
if (powerLevelsEvent) {
powerLevels = powerLevelsEvent.getContent();
}
let requiredLevel = 50;
if (utils.isNumber(powerLevels[action])) {
requiredLevel = powerLevels[action];
requiredLevel = powerLevels[action]!;
}
return powerLevel >= requiredLevel;
@ -807,8 +821,8 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
private maySendEventOfType(eventType: EventType | string, userId: string, state: boolean): boolean {
const powerLevelsEvent = this.getStateEvents(EventType.RoomPowerLevels, '');
let powerLevels;
let eventsLevels = {};
let powerLevels: IPowerLevelsContent;
let eventsLevels: Record<EventType | string, number> = {};
let stateDefault = 0;
let eventsDefault = 0;
@ -818,20 +832,20 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
eventsLevels = powerLevels.events || {};
if (Number.isSafeInteger(powerLevels.state_default)) {
stateDefault = powerLevels.state_default;
stateDefault = powerLevels.state_default!;
} else {
stateDefault = 50;
}
const userPowerLevel = powerLevels.users && powerLevels.users[userId];
if (Number.isSafeInteger(userPowerLevel)) {
powerLevel = userPowerLevel;
powerLevel = userPowerLevel!;
} else if (Number.isSafeInteger(powerLevels.users_default)) {
powerLevel = powerLevels.users_default;
powerLevel = powerLevels.users_default!;
}
if (Number.isSafeInteger(powerLevels.events_default)) {
eventsDefault = powerLevels.events_default;
eventsDefault = powerLevels.events_default!;
}
}
@ -877,7 +891,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
*/
public getJoinRule(): JoinRule {
const joinRuleEvent = this.getStateEvents(EventType.RoomJoinRules, "");
const joinRuleContent = joinRuleEvent?.getContent<IJoinRuleEventContent>() ?? {};
const joinRuleContent: Partial<IJoinRuleEventContent> = joinRuleEvent?.getContent() ?? {};
return joinRuleContent["join_rule"] || JoinRule.Invite;
}

View File

@ -20,8 +20,8 @@ limitations under the License.
export interface IRoomSummary {
"m.heroes": string[];
"m.joined_member_count": number;
"m.invited_member_count": number;
"m.joined_member_count"?: number;
"m.invited_member_count"?: number;
}
interface IInfo {

View File

@ -1312,10 +1312,10 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
const joinedCount = summary["m.joined_member_count"];
const invitedCount = summary["m.invited_member_count"];
if (Number.isInteger(joinedCount)) {
this.currentState.setJoinedMemberCount(joinedCount);
this.currentState.setJoinedMemberCount(joinedCount!);
}
if (Number.isInteger(invitedCount)) {
this.currentState.setInvitedMemberCount(invitedCount);
this.currentState.setInvitedMemberCount(invitedCount!);
}
if (Array.isArray(heroes)) {
// be cautious about trusting server values,
@ -1826,7 +1826,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
timelineSet.getFilter(),
);
timelineSet.getLiveTimeline().setPaginationToken(end, Direction.Backward);
timelineSet.getLiveTimeline().setPaginationToken(end ?? null, Direction.Backward);
if (!events.length) return;

View File

@ -518,13 +518,13 @@ export class PushProcessor {
* @return {object} The push rule, or null if no such rule was found
*/
public getPushRuleById(ruleId: string): IPushRule | null {
for (const scope of ['global']) {
for (const scope of ['global'] as const) {
if (this.client.pushRules?.[scope] === undefined) continue;
for (const kind of RULEKINDS_IN_ORDER) {
if (this.client.pushRules[scope][kind] === undefined) continue;
for (const rule of this.client.pushRules[scope][kind]) {
for (const rule of this.client.pushRules[scope][kind]!) {
if (rule.rule_id === ruleId) return rule;
}
}

View File

@ -97,7 +97,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
* @see module:scheduler~queueAlgorithm
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
public static QUEUE_MESSAGES(event: MatrixEvent): "message" | null {
public static QUEUE_MESSAGES(event: MatrixEvent): string | null {
// enqueue messages or events that associate with another event (redactions and relations)
if (event.getType() === EventType.RoomMessage || event.hasAssociation()) {
// put these events in the 'message' queue.

View File

@ -22,7 +22,7 @@ import { ClientEvent, IStoredClientOpts, MatrixClient, PendingEventOrdering } fr
import { ISyncStateData, SyncState, _createAndReEmitRoom } from "./sync";
import { MatrixEvent } from "./models/event";
import { Crypto } from "./crypto";
import { IMinimalEvent, IRoomEvent, IStateEvent, IStrippedState } from "./sync-accumulator";
import { IMinimalEvent, IRoomEvent, IStateEvent, IStrippedState, ISyncResponse } from "./sync-accumulator";
import { MatrixError } from "./http-api";
import {
Extension,
@ -44,7 +44,18 @@ import { RoomMemberEvent } from "./models/room-member";
// keepAlive is successful but the server /sync fails.
const FAILED_SYNC_ERROR_THRESHOLD = 3;
class ExtensionE2EE implements Extension {
type ExtensionE2EERequest = {
enabled: boolean;
};
type ExtensionE2EEResponse = Pick<ISyncResponse,
"device_lists" |
"device_one_time_keys_count" |
"device_unused_fallback_key_types" |
"org.matrix.msc2732.device_unused_fallback_key_types"
>;
class ExtensionE2EE implements Extension<ExtensionE2EERequest, ExtensionE2EEResponse> {
public constructor(private readonly crypto: Crypto) {}
public name(): string {
@ -55,7 +66,7 @@ class ExtensionE2EE implements Extension {
return ExtensionState.PreProcess;
}
public onRequest(isInitial: boolean): object | undefined {
public onRequest(isInitial: boolean): ExtensionE2EERequest | undefined {
if (!isInitial) {
return undefined;
}
@ -64,7 +75,7 @@ class ExtensionE2EE implements Extension {
};
}
public async onResponse(data: object): Promise<void> {
public async onResponse(data: ExtensionE2EEResponse): Promise<void> {
// Handle device list updates
if (data["device_lists"]) {
await this.crypto.handleDeviceListChanges({
@ -92,7 +103,18 @@ class ExtensionE2EE implements Extension {
}
}
class ExtensionToDevice implements Extension {
type ExtensionToDeviceRequest = {
since?: string;
limit?: number;
enabled?: boolean;
};
type ExtensionToDeviceResponse = {
events: Required<ISyncResponse>["to_device"]["events"];
next_batch: string | null;
};
class ExtensionToDevice implements Extension<ExtensionToDeviceRequest, ExtensionToDeviceResponse> {
private nextBatch: string | null = null;
public constructor(private readonly client: MatrixClient) {}
@ -105,8 +127,8 @@ class ExtensionToDevice implements Extension {
return ExtensionState.PreProcess;
}
public onRequest(isInitial: boolean): object {
const extReq = {
public onRequest(isInitial: boolean): ExtensionToDeviceRequest {
const extReq: ExtensionToDeviceRequest = {
since: this.nextBatch !== null ? this.nextBatch : undefined,
};
if (isInitial) {
@ -116,11 +138,10 @@ class ExtensionToDevice implements Extension {
return extReq;
}
public async onResponse(data: object): Promise<void> {
public async onResponse(data: ExtensionToDeviceResponse): Promise<void> {
const cancelledKeyVerificationTxns: string[] = [];
data["events"] = data["events"] || [];
data["events"]
.map(this.client.getEventMapper())
data.events
?.map(this.client.getEventMapper())
.map((toDeviceEvent) => { // map is a cheap inline forEach
// We want to flag m.key.verification.start events as cancelled
// if there's an accompanying m.key.verification.cancel event, so
@ -165,11 +186,20 @@ class ExtensionToDevice implements Extension {
},
);
this.nextBatch = data["next_batch"];
this.nextBatch = data.next_batch;
}
}
class ExtensionAccountData implements Extension {
type ExtensionAccountDataRequest = {
enabled: boolean;
};
type ExtensionAccountDataResponse = {
global: IMinimalEvent[];
rooms: Record<string, IMinimalEvent[]>;
};
class ExtensionAccountData implements Extension<ExtensionAccountDataRequest, ExtensionAccountDataResponse> {
public constructor(private readonly client: MatrixClient) {}
public name(): string {
@ -180,7 +210,7 @@ class ExtensionAccountData implements Extension {
return ExtensionState.PostProcess;
}
public onRequest(isInitial: boolean): object | undefined {
public onRequest(isInitial: boolean): ExtensionAccountDataRequest | undefined {
if (!isInitial) {
return undefined;
}
@ -189,7 +219,7 @@ class ExtensionAccountData implements Extension {
};
}
public onResponse(data: {global: object[], rooms: Record<string, object[]>}): void {
public onResponse(data: ExtensionAccountDataResponse): void {
if (data.global && data.global.length > 0) {
this.processGlobalAccountData(data.global);
}
@ -208,9 +238,9 @@ class ExtensionAccountData implements Extension {
}
}
private processGlobalAccountData(globalAccountData: object[]): void {
private processGlobalAccountData(globalAccountData: IMinimalEvent[]): void {
const events = mapEvents(this.client, undefined, globalAccountData);
const prevEventsMap = events.reduce((m, c) => {
const prevEventsMap = events.reduce<Record<string, MatrixEvent | undefined>>((m, c) => {
m[c.getType()] = this.client.store.getAccountData(c.getType());
return m;
}, {});
@ -233,7 +263,15 @@ class ExtensionAccountData implements Extension {
}
}
class ExtensionTyping implements Extension {
type ExtensionTypingRequest = {
enabled: boolean;
};
type ExtensionTypingResponse = {
rooms: Record<string, IMinimalEvent>;
};
class ExtensionTyping implements Extension<ExtensionTypingRequest, ExtensionTypingResponse> {
public constructor(private readonly client: MatrixClient) {}
public name(): string {
@ -244,7 +282,7 @@ class ExtensionTyping implements Extension {
return ExtensionState.PostProcess;
}
public onRequest(isInitial: boolean): object | undefined {
public onRequest(isInitial: boolean): ExtensionTypingRequest | undefined {
if (!isInitial) {
return undefined; // don't send a JSON object for subsequent requests, we don't need to.
}
@ -253,20 +291,26 @@ class ExtensionTyping implements Extension {
};
}
public onResponse(data: {rooms: Record<string, IMinimalEvent>}): void {
if (!data || !data.rooms) {
public onResponse(data: ExtensionTypingResponse): void {
if (!data?.rooms) {
return;
}
for (const roomId in data.rooms) {
processEphemeralEvents(
this.client, roomId, [data.rooms[roomId]],
);
processEphemeralEvents(this.client, roomId, [data.rooms[roomId]]);
}
}
}
class ExtensionReceipts implements Extension {
type ExtensionReceiptsRequest = {
enabled: boolean;
};
type ExtensionReceiptsResponse = {
rooms: Record<string, IMinimalEvent>;
};
class ExtensionReceipts implements Extension<ExtensionReceiptsRequest, ExtensionReceiptsResponse> {
public constructor(private readonly client: MatrixClient) {}
public name(): string {
@ -277,7 +321,7 @@ class ExtensionReceipts implements Extension {
return ExtensionState.PostProcess;
}
public onRequest(isInitial: boolean): object | undefined {
public onRequest(isInitial: boolean): ExtensionReceiptsRequest | undefined {
if (isInitial) {
return {
enabled: true,
@ -286,8 +330,8 @@ class ExtensionReceipts implements Extension {
return undefined; // don't send a JSON object for subsequent requests, we don't need to.
}
public onResponse(data: {rooms: Record<string, IMinimalEvent>}): void {
if (!data || !data.rooms) {
public onResponse(data: ExtensionReceiptsResponse): void {
if (!data?.rooms) {
return;
}
@ -336,7 +380,7 @@ export class SlidingSyncSdk {
this.slidingSync.on(SlidingSyncEvent.Lifecycle, this.onLifecycle.bind(this));
this.slidingSync.on(SlidingSyncEvent.RoomData, this.onRoomData.bind(this));
const extensions: Extension[] = [
const extensions: Extension<any, any>[] = [
new ExtensionToDevice(this.client),
new ExtensionAccountData(this.client),
new ExtensionTyping(this.client),
@ -533,7 +577,7 @@ export class SlidingSyncSdk {
// room::decryptCriticalEvent is in charge of decrypting all the events
// required for a client to function properly
let timelineEvents = mapEvents(this.client, room.roomId, roomData.timeline, false);
const ephemeralEvents = []; // TODO this.mapSyncEventsFormat(joinObj.ephemeral);
const ephemeralEvents: MatrixEvent[] = []; // TODO this.mapSyncEventsFormat(joinObj.ephemeral);
// TODO: handle threaded / beacon events
@ -967,12 +1011,14 @@ function ensureNameEvent(client: MatrixClient, roomId: string, roomData: MSC3575
return roomData;
}
type TaggedEvent = (IStrippedState | IRoomEvent | IStateEvent | IMinimalEvent) & { room_id?: string };
// 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 | 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;
return (events as TaggedEvent[]).map(function(e) {
e.room_id = roomId;
return mapper(e);
});
}

View File

@ -139,7 +139,7 @@ export interface MSC3575SlidingSyncResponse {
txn_id?: string;
lists: ListResponse[];
rooms: Record<string, MSC3575RoomData>;
extensions: object;
extensions: Record<string, object>;
}
export enum SlidingSyncState {
@ -265,7 +265,7 @@ export enum ExtensionState {
/**
* An interface that must be satisfied to register extensions
*/
export interface Extension {
export interface Extension<Req extends {}, Res extends {}> {
/**
* The extension name to go under 'extensions' in the request body.
* @returns The JSON key.
@ -277,12 +277,12 @@ 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 | undefined;
onRequest(isInitial: boolean): Req | undefined;
/**
* A function which is called when there is response JSON under this extension.
* @param data The response JSON under the extension name.
*/
onResponse(data: object);
onResponse(data: Res): void;
/**
* Controls when onResponse should be called.
* @returns The state when it should be called.
@ -353,7 +353,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
// a defer to resolve/reject depending on whether they were successfully sent or not.
private txnIdDefers: (IDeferred<string> & { txnId: string})[] = [];
// map of extension name to req/resp handler
private extensions: Record<string, Extension> = {};
private extensions: Record<string, Extension<any, any>> = {};
private desiredRoomSubscriptions = new Set<string>(); // the *desired* room subscriptions
private confirmedRoomSubscriptions = new Set<string>();
@ -519,22 +519,22 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
* Register an extension to send with the /sync request.
* @param ext The extension to register.
*/
public registerExtension(ext: Extension): void {
public registerExtension(ext: Extension<any, any>): void {
if (this.extensions[ext.name()]) {
throw new Error(`registerExtension: ${ext.name()} already exists as an extension`);
}
this.extensions[ext.name()] = ext;
}
private getExtensionRequest(isInitial: boolean): object {
const ext = {};
private getExtensionRequest(isInitial: boolean): Record<string, object | undefined> {
const ext: Record<string, object | undefined> = {};
Object.keys(this.extensions).forEach((extName) => {
ext[extName] = this.extensions[extName].onRequest(isInitial);
});
return ext;
}
private onPreExtensionsResponse(ext: object): void {
private onPreExtensionsResponse(ext: Record<string, object>): void {
Object.keys(ext).forEach((extName) => {
if (this.extensions[extName].when() == ExtensionState.PreProcess) {
this.extensions[extName].onResponse(ext[extName]);
@ -542,7 +542,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
});
}
private onPostExtensionsResponse(ext: object): void {
private onPostExtensionsResponse(ext: Record<string, object>): void {
Object.keys(ext).forEach((extName) => {
if (this.extensions[extName].when() == ExtensionState.PostProcess) {
this.extensions[extName].onResponse(ext[extName]);

View File

@ -123,7 +123,7 @@ export interface IStore {
* @param {string} token The token associated with these events.
* @param {boolean} toStart True if these are paginated results.
*/
storeEvents(room: Room, events: MatrixEvent[], token: string, toStart: boolean): void;
storeEvents(room: Room, events: MatrixEvent[], token: string | null, toStart: boolean): void;
/**
* Store a filter.

View File

@ -315,10 +315,10 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
const minStateKeyProm = reqAsCursorPromise(
roomIndex.openKeyCursor(roomRange, "next"),
).then((cursor) => cursor && cursor.primaryKey[1]);
).then((cursor) => (<IDBValidKey[]>cursor?.primaryKey)[1]);
const maxStateKeyProm = reqAsCursorPromise(
roomIndex.openKeyCursor(roomRange, "prev"),
).then((cursor) => cursor && cursor.primaryKey[1]);
).then((cursor) => (<IDBValidKey[]>cursor?.primaryKey)[1]);
const [minStateKey, maxStateKey] = await Promise.all(
[minStateKeyProm, maxStateKeyProm]);

View File

@ -217,7 +217,7 @@ export class MemoryStore implements IStore {
* @param {string} token The token associated with these events.
* @param {boolean} toStart True if these are paginated results.
*/
public storeEvents(room: Room, events: MatrixEvent[], token: string, toStart: boolean): void {
public storeEvents(room: Room, events: MatrixEvent[], token: string | null, toStart: boolean): void {
// no-op because they've already been added to the room instance.
}

View File

@ -139,7 +139,7 @@ export class StubStore implements IStore {
* @param {string} token The token associated with these events.
* @param {boolean} toStart True if these are paginated results.
*/
public storeEvents(room: Room, events: MatrixEvent[], token: string, toStart: boolean): void {}
public storeEvents(room: Room, events: MatrixEvent[], token: string | null, toStart: boolean): void {}
/**
* Store a filter.

View File

@ -116,7 +116,7 @@ interface IAccountData {
events: IMinimalEvent[];
}
interface IToDeviceEvent {
export interface IToDeviceEvent {
content: IContent;
sender: string;
type: string;
@ -127,8 +127,8 @@ interface IToDevice {
}
interface IDeviceLists {
changed: string[];
left: string[];
changed?: string[];
left?: string[];
}
export interface ISyncResponse {
@ -139,6 +139,9 @@ export interface ISyncResponse {
to_device?: IToDevice;
device_lists?: IDeviceLists;
device_one_time_keys_count?: Record<string, number>;
device_unused_fallback_key_types?: string[];
"org.matrix.msc2732.device_unused_fallback_key_types"?: string[];
}
/* eslint-enable camelcase */
@ -182,6 +185,12 @@ export interface ISyncData {
roomsData: IRooms;
}
type TaggedEvent = IRoomEvent & { _localTs?: number };
function isTaggedEvent(event: IRoomEvent): event is TaggedEvent {
return "_localTs" in event && event["_localTs"] !== undefined;
}
/**
* The purpose of this class is to accumulate /sync responses such that a
* complete "initial" JSON response can be returned which accurately represents
@ -473,35 +482,31 @@ export class SyncAccumulator {
// - existing state which didn't come down /sync.
// - State events under the 'state' key.
// - State events in the 'timeline'.
if (data.state && data.state.events) {
data.state.events.forEach((e) => {
setState(currentData._currentState, e);
});
}
if (data.timeline && data.timeline.events) {
data.timeline.events.forEach((e, index) => {
// this nops if 'e' isn't a state event
setState(currentData._currentState, e);
// append the event to the timeline. The back-pagination token
// corresponds to the first event in the timeline
let transformedEvent: IRoomEvent & { _localTs?: number };
if (!fromDatabase) {
transformedEvent = Object.assign({}, e);
if (transformedEvent.unsigned !== undefined) {
transformedEvent.unsigned = Object.assign({}, transformedEvent.unsigned);
}
const age = e.unsigned ? e.unsigned.age : e.age;
if (age !== undefined) transformedEvent._localTs = Date.now() - age;
} else {
transformedEvent = e;
data.state?.events?.forEach((e) => {
setState(currentData._currentState, e);
});
data.timeline?.events?.forEach((e, index) => {
// this nops if 'e' isn't a state event
setState(currentData._currentState, e);
// append the event to the timeline. The back-pagination token
// corresponds to the first event in the timeline
let transformedEvent: TaggedEvent;
if (!fromDatabase) {
transformedEvent = Object.assign({}, e);
if (transformedEvent.unsigned !== undefined) {
transformedEvent.unsigned = Object.assign({}, transformedEvent.unsigned);
}
const age = e.unsigned ? e.unsigned.age : e.age;
if (age !== undefined) transformedEvent._localTs = Date.now() - age;
} else {
transformedEvent = e;
}
currentData._timeline.push({
event: transformedEvent,
token: index === 0 ? (data.timeline.prev_batch ?? null) : null,
});
currentData._timeline.push({
event: transformedEvent,
token: index === 0 ? (data.timeline.prev_batch ?? null) : null,
});
}
});
// attempt to prune the timeline by jumping between events which have
// pagination tokens.
@ -581,7 +586,7 @@ export class SyncAccumulator {
room_id: roomId,
content: {
// $event_id: { "m.read": { $user_id: $json } }
},
} as IContent,
};
for (const [userId, receiptData] of Object.entries(roomData._readReceipts)) {
@ -626,8 +631,8 @@ export class SyncAccumulator {
}
let transformedEvent: (IRoomEvent | IStateEvent) & { _localTs?: number };
if (!forDatabase && msgData.event["_localTs"]) {
// This means we have to copy each event so we can fix it up to
if (!forDatabase && isTaggedEvent(msgData.event)) {
// This means we have to copy each event, so we can fix it up to
// set a correct 'age' parameter whilst keeping the local timestamp
// on our stored event. If this turns out to be a bottleneck, it could
// be optimised either by doing this in the main process after the data
@ -641,7 +646,7 @@ export class SyncAccumulator {
}
delete transformedEvent._localTs;
transformedEvent.unsigned = transformedEvent.unsigned || {};
transformedEvent.unsigned.age = Date.now() - msgData.event["_localTs"];
transformedEvent.unsigned.age = Date.now() - msgData.event._localTs!;
} else {
transformedEvent = msgData.event;
}

View File

@ -106,7 +106,7 @@ function getFilterName(userId: string, suffix?: string): string {
return `FILTER_SYNC_${userId}` + (suffix ? "_" + suffix : "");
}
function debuglog(...params): void {
function debuglog(...params: any[]): void {
if (!DEBUG) return;
logger.log(...params);
}
@ -1093,7 +1093,7 @@ export class SyncApi {
// handle non-room account_data
if (Array.isArray(data.account_data?.events)) {
const events = data.account_data.events.map(client.getEventMapper());
const prevEventsMap = events.reduce((m, c) => {
const prevEventsMap = events.reduce<Record<string, MatrixEvent | undefined>>((m, c) => {
m[c.getType()!] = client.store.getAccountData(c.getType());
return m;
}, {});
@ -1473,12 +1473,13 @@ export class SyncApi {
this.opts.crypto.updateOneTimeKeyCount(currentCount);
}
if (this.opts.crypto &&
(data["device_unused_fallback_key_types"] ||
data["org.matrix.msc2732.device_unused_fallback_key_types"])) {
(data.device_unused_fallback_key_types ||
data["org.matrix.msc2732.device_unused_fallback_key_types"])
) {
// The presence of device_unused_fallback_key_types indicates that the
// server supports fallback keys. If there's no unused
// signed_curve25519 fallback key we need a new one.
const unusedFallbackKeys = data["device_unused_fallback_key_types"] ||
const unusedFallbackKeys = data.device_unused_fallback_key_types ||
data["org.matrix.msc2732.device_unused_fallback_key_types"];
this.opts.crypto.setNeedsNewFallback(
Array.isArray(unusedFallbackKeys) &&
@ -1607,9 +1608,10 @@ export class SyncApi {
return [];
}
const mapper = this.client.getEventMapper({ decrypt });
return (obj.events as Array<IStrippedState | IRoomEvent | IStateEvent | IMinimalEvent>).map(function(e) {
type TaggedEvent = (IStrippedState | IRoomEvent | IStateEvent | IMinimalEvent) & { room_id?: string };
return (obj.events as TaggedEvent[]).map(function(e) {
if (room) {
e["room_id"] = room.roomId;
e.room_id = room.roomId;
}
return mapper(e);
});

View File

@ -316,7 +316,7 @@ export function deepSortedObjectEntries(obj: any): [string, any][] {
* @param {*} value the value to test
* @return {boolean} whether or not value is a finite number without type-coercion
*/
export function isNumber(value: any): boolean {
export function isNumber(value: any): value is number {
return typeof value === 'number' && isFinite(value);
}
@ -428,8 +428,8 @@ export interface IDeferred<T> {
// Returns a Deferred
export function defer<T = void>(): IDeferred<T> {
let resolve;
let reject;
let resolve!: IDeferred<T>["resolve"];
let reject!: IDeferred<T>["reject"];
const promise = new Promise<T>((_resolve, _reject) => {
resolve = _resolve;
@ -665,18 +665,22 @@ export function compare(a: string, b: string): number {
* @param {Object} source
* @returns the target object
*/
export function recursivelyAssign(target: Object, source: Object, ignoreNullish = false): any {
export function recursivelyAssign<T1 extends T2, T2 extends Record<string, any>>(
target: T1,
source: T2,
ignoreNullish = false,
): T1 & T2 {
for (const [sourceKey, sourceValue] of Object.entries(source)) {
if (target[sourceKey] instanceof Object && sourceValue) {
recursivelyAssign(target[sourceKey], sourceValue);
continue;
}
if ((sourceValue !== null && sourceValue !== undefined) || !ignoreNullish) {
target[sourceKey] = sourceValue;
target[sourceKey as keyof T1] = sourceValue;
continue;
}
}
return target;
return target as T1 & T2;
}
function getContentTimestampWithFallback(event: MatrixEvent): number {

View File

@ -27,7 +27,7 @@ import { parse as parseSdp, write as writeSdp } from "sdp-transform";
import { logger } from '../logger';
import * as utils from '../utils';
import { MatrixEvent } from '../models/event';
import { IContent, MatrixEvent } from '../models/event';
import { EventType, ToDeviceMessageId } from '../@types/event';
import { RoomMember } from '../models/room-member';
import { randomString } from '../randomstring';
@ -1101,7 +1101,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
this.terminate(CallParty.Local, reason, !suppressEvent);
// We don't want to send hangup here if we didn't even get to sending an invite
if ([CallState.Fledgling, CallState.WaitLocalMedia].includes(this.state)) return;
const content = {};
const content: IContent = {};
// Don't send UserHangup reason to older clients
if ((this.opponentVersion && this.opponentVersion !== 0) || reason !== CallErrorCode.UserHangup) {
content["reason"] = reason;

View File

@ -5,7 +5,6 @@
"esModuleInterop": true,
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": false,
"noUnusedLocals": true,
"noEmit": true,
"declaration": true,

View File

@ -1423,9 +1423,9 @@
dependencies:
lodash "^4.17.21"
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.13.tgz":
version "3.2.13"
resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.13.tgz#0109fde93bcc61def851f79826c9384c073b5175"
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz":
version "3.2.14"
resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz#acd96c00a881d0f462e1f97a56c73742c8dbc984"
"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3":
version "2.1.8-no-fsevents.3"