1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-08-07 23:02:56 +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

@@ -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