You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-30 04:23:07 +03:00
Enable noImplicitAny (#2895)
* Stash noImplicitAny work * Enable noImplicitAny * Update olm * Fun * Fix msgid stuff * Fix tests * Attempt to fix Browserify
This commit is contained in:
committed by
GitHub
parent
6f81371e61
commit
8d018f9c2d
35
.github/workflows/static_analysis.yml
vendored
35
.github/workflows/static_analysis.yml
vendored
@ -72,38 +72,3 @@ jobs:
|
||||
path: _docs
|
||||
# We'll only use this in a workflow_run, then we're done with it
|
||||
retention-days: 1
|
||||
|
||||
tsc-strict:
|
||||
name: Typescript Strict Error Checker
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: read
|
||||
checks: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Get diff lines
|
||||
id: diff
|
||||
uses: Equip-Collaboration/diff-line-numbers@v1.0.0
|
||||
with:
|
||||
include: '["\\.tsx?$"]'
|
||||
|
||||
- name: Detecting files changed
|
||||
id: files
|
||||
uses: futuratrepadeira/changed-files@v4.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
pattern: '^.*\.tsx?$'
|
||||
|
||||
- uses: t3chguy/typescript-check-action@main
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
use-check: false
|
||||
check-fail-mode: added
|
||||
output-behaviour: annotate
|
||||
ts-extra-args: '--noImplicitAny'
|
||||
files-changed: ${{ steps.files.outputs.files_updated }}
|
||||
files-added: ${{ steps.files.outputs.files_created }}
|
||||
files-deleted: ${{ steps.files.outputs.files_deleted }}
|
||||
line-numbers: ${{ steps.diff.outputs.lineNumbers }}
|
||||
|
@ -14,7 +14,7 @@
|
||||
"build:dev": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:types",
|
||||
"build:types": "tsc -p tsconfig-build.json --emitDeclarationOnly",
|
||||
"build:compile": "babel -d lib --verbose --extensions \".ts,.js\" src",
|
||||
"build:compile-browser": "mkdirp dist && browserify -d src/browser-index.js -p [ tsify -p ./tsconfig-build.json ] -t [ babelify --sourceMaps=inline --presets [ @babel/preset-env @babel/preset-typescript ] ] | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js",
|
||||
"build:compile-browser": "mkdirp dist && browserify -d src/browser-index.ts -p [ tsify -p ./tsconfig-build.json ] -t [ babelify --sourceMaps=inline --presets [ @babel/preset-env @babel/preset-typescript ] ] | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js",
|
||||
"build:minify-browser": "terser dist/browser-matrix.js --compress --mangle --source-map --output dist/browser-matrix.min.js",
|
||||
"gendoc": "typedoc",
|
||||
"lint": "yarn lint:types && yarn lint:js",
|
||||
@ -33,9 +33,9 @@
|
||||
"matrix-org"
|
||||
],
|
||||
"main": "./src/index.ts",
|
||||
"browser": "./lib/browser-index.js",
|
||||
"browser": "./lib/browser-index.ts",
|
||||
"matrix_src_main": "./src/index.ts",
|
||||
"matrix_src_browser": "./src/browser-index.js",
|
||||
"matrix_src_browser": "./src/browser-index.ts",
|
||||
"matrix_lib_main": "./lib/index.js",
|
||||
"matrix_lib_typings": "./lib/index.d.ts",
|
||||
"author": "matrix.org",
|
||||
@ -80,7 +80,7 @@
|
||||
"@babel/preset-typescript": "^7.12.7",
|
||||
"@babel/register": "^7.12.10",
|
||||
"@casualbot/jest-sonar-reporter": "^2.2.5",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.13.tgz",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz",
|
||||
"@types/bs58": "^4.0.1",
|
||||
"@types/content-type": "^1.1.5",
|
||||
"@types/domexception": "^4.0.0",
|
||||
|
@ -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;
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -65,16 +65,17 @@ describe("Browserify Test", function() {
|
||||
const syncData = {
|
||||
next_batch: "batch1",
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
syncData.rooms.join[ROOM_ID] = {
|
||||
join: {
|
||||
[ROOM_ID]: {
|
||||
timeline: {
|
||||
events: [
|
||||
event,
|
||||
],
|
||||
limited: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
httpBackend.when("GET", "/sync").respond(200, syncData);
|
||||
|
@ -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({
|
||||
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();
|
||||
|
@ -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,11 +274,7 @@ async function recvMessage(
|
||||
next_batch: "x",
|
||||
rooms: {
|
||||
join: {
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
syncData.rooms.join[roomId] = {
|
||||
[roomId]: {
|
||||
timeline: {
|
||||
events: [
|
||||
testUtils.mkEvent({
|
||||
@ -291,6 +285,9 @@ async function recvMessage(
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
httpBackend.when("GET", "/sync").respond(200, syncData);
|
||||
|
||||
@ -335,10 +332,8 @@ function firstSync(testClient: TestClient): Promise<void> {
|
||||
const syncData = {
|
||||
next_batch: "x",
|
||||
rooms: {
|
||||
join: { },
|
||||
},
|
||||
};
|
||||
syncData.rooms.join[roomId] = {
|
||||
join: {
|
||||
[roomId]: {
|
||||
state: {
|
||||
events: [
|
||||
testUtils.mkMembership({
|
||||
@ -354,6 +349,9 @@ function firstSync(testClient: TestClient): Promise<void> {
|
||||
timeline: {
|
||||
events: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
testClient.httpBackend.when("GET", "/sync").respond(200, syncData);
|
||||
@ -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,11 +513,7 @@ describe("MatrixClient crypto", () => {
|
||||
next_batch: "x",
|
||||
rooms: {
|
||||
join: {
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
syncData.rooms.join[roomId] = {
|
||||
[roomId]: {
|
||||
timeline: {
|
||||
events: [
|
||||
testUtils.mkEvent({
|
||||
@ -530,6 +524,9 @@ describe("MatrixClient crypto", () => {
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
bobTestClient.httpBackend.when("GET", "/sync").respond(200, syncData);
|
||||
|
||||
@ -607,10 +604,8 @@ describe("MatrixClient crypto", () => {
|
||||
const syncData = {
|
||||
next_batch: '2',
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
syncData.rooms.join[roomId] = {
|
||||
join: {
|
||||
[roomId]: {
|
||||
state: {
|
||||
events: [
|
||||
testUtils.mkEvent({
|
||||
@ -622,6 +617,9 @@ describe("MatrixClient crypto", () => {
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
aliTestClient.httpBackend.when('GET', '/sync').respond(
|
||||
|
@ -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,10 +1569,8 @@ describe("MatrixClient event timelines", function() {
|
||||
const syncData = {
|
||||
next_batch: "batch1",
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
syncData.rooms.join[roomId] = {
|
||||
join: {
|
||||
[roomId]: {
|
||||
timeline: {
|
||||
events: [
|
||||
event,
|
||||
@ -1580,6 +1578,9 @@ describe("MatrixClient event timelines", function() {
|
||||
],
|
||||
limited: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
httpBackend.when("GET", "/sync").respond(200, syncData);
|
||||
|
||||
@ -1595,10 +1596,8 @@ describe("MatrixClient event timelines", function() {
|
||||
const sync2 = {
|
||||
next_batch: "batch2",
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
sync2.rooms.join[roomId] = {
|
||||
join: {
|
||||
[roomId]: {
|
||||
timeline: {
|
||||
events: [
|
||||
utils.mkMessage({
|
||||
@ -1608,6 +1607,9 @@ describe("MatrixClient event timelines", function() {
|
||||
limited: true,
|
||||
prev_batch: "newerTok",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
httpBackend.when("GET", "/sync").respond(200, sync2);
|
||||
|
||||
|
@ -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]);
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,11 +524,7 @@ describe("MatrixClient syncing", () => {
|
||||
const syncData = {
|
||||
rooms: {
|
||||
join: {
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
syncData.rooms.join[roomOne] = {
|
||||
[roomOne]: {
|
||||
timeline: {
|
||||
events: [
|
||||
utils.mkMessage({
|
||||
@ -558,8 +554,8 @@ describe("MatrixClient syncing", () => {
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
syncData.rooms.join[roomTwo] = {
|
||||
},
|
||||
[roomTwo]: {
|
||||
timeline: {
|
||||
events: [
|
||||
utils.mkMessage({
|
||||
@ -584,17 +580,15 @@ describe("MatrixClient syncing", () => {
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const nextSyncData = {
|
||||
rooms: {
|
||||
join: {
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
nextSyncData.rooms.join[roomOne] = {
|
||||
[roomOne]: {
|
||||
state: {
|
||||
events: [
|
||||
utils.mkEvent({
|
||||
@ -603,9 +597,8 @@ describe("MatrixClient syncing", () => {
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
nextSyncData.rooms.join[roomTwo] = {
|
||||
},
|
||||
[roomTwo]: {
|
||||
timeline: {
|
||||
events: [
|
||||
utils.mkMessage({
|
||||
@ -621,6 +614,9 @@ describe("MatrixClient syncing", () => {
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
it("should continually recalculate the right room name.", () => {
|
||||
@ -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,10 +736,8 @@ describe("MatrixClient syncing", () => {
|
||||
const normalFirstSync = {
|
||||
next_batch: "batch_token",
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
normalFirstSync.rooms.join[roomOne] = {
|
||||
join: {
|
||||
[roomOne]: {
|
||||
timeline: {
|
||||
events: [normalMessageEvent],
|
||||
prev_batch: "pagTok",
|
||||
@ -753,15 +745,16 @@ describe("MatrixClient syncing", () => {
|
||||
state: {
|
||||
events: [roomCreateEvent],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const nextSyncData = {
|
||||
next_batch: "batch_token",
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
nextSyncData.rooms.join[roomOne] = {
|
||||
join: {
|
||||
[roomOne]: {
|
||||
timeline: {
|
||||
events: [
|
||||
// In subsequent syncs, a marker event in timeline
|
||||
@ -783,6 +776,9 @@ describe("MatrixClient syncing", () => {
|
||||
],
|
||||
prev_batch: "pagTok",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Ensure the marker is being sent by someone who is not the room creator
|
||||
@ -831,10 +827,8 @@ describe("MatrixClient syncing", () => {
|
||||
const normalFirstSync = {
|
||||
next_batch: "batch_token",
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
normalFirstSync.rooms.join[roomOne] = {
|
||||
join: {
|
||||
[roomOne]: {
|
||||
timeline: {
|
||||
events: [normalMessageEvent],
|
||||
prev_batch: "pagTok",
|
||||
@ -842,6 +836,9 @@ describe("MatrixClient syncing", () => {
|
||||
state: {
|
||||
events: [roomCreateEvent],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
it('no marker event in sync response '+
|
||||
@ -849,10 +846,8 @@ describe("MatrixClient syncing", () => {
|
||||
const syncData = {
|
||||
next_batch: "batch_token",
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
syncData.rooms.join[roomOne] = {
|
||||
join: {
|
||||
[roomOne]: {
|
||||
timeline: {
|
||||
events: [normalMessageEvent],
|
||||
prev_batch: "pagTok",
|
||||
@ -860,6 +855,9 @@ describe("MatrixClient syncing", () => {
|
||||
state: {
|
||||
events: [roomCreateEvent],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
httpBackend!.when("GET", "/sync").respond(200, syncData);
|
||||
@ -879,10 +877,8 @@ describe("MatrixClient syncing", () => {
|
||||
const syncData = {
|
||||
next_batch: "batch_token",
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
syncData.rooms.join[roomOne] = {
|
||||
join: {
|
||||
[roomOne]: {
|
||||
timeline: {
|
||||
events: [markerEventFromRoomCreator],
|
||||
prev_batch: "pagTok",
|
||||
@ -890,6 +886,9 @@ describe("MatrixClient syncing", () => {
|
||||
state: {
|
||||
events: [roomCreateEvent],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
httpBackend!.when("GET", "/sync").respond(200, syncData);
|
||||
@ -909,10 +908,8 @@ describe("MatrixClient syncing", () => {
|
||||
const syncData = {
|
||||
next_batch: "batch_token",
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
syncData.rooms.join[roomOne] = {
|
||||
join: {
|
||||
[roomOne]: {
|
||||
timeline: {
|
||||
events: [normalMessageEvent],
|
||||
prev_batch: "pagTok",
|
||||
@ -923,6 +920,9 @@ describe("MatrixClient syncing", () => {
|
||||
markerEventFromRoomCreator,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
httpBackend!.when("GET", "/sync").respond(200, syncData);
|
||||
@ -942,10 +942,8 @@ describe("MatrixClient syncing", () => {
|
||||
const nextSyncData = {
|
||||
next_batch: "batch_token",
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
nextSyncData.rooms.join[roomOne] = {
|
||||
join: {
|
||||
[roomOne]: {
|
||||
timeline: {
|
||||
events: [
|
||||
// In subsequent syncs, a marker event in timeline
|
||||
@ -954,6 +952,9 @@ describe("MatrixClient syncing", () => {
|
||||
],
|
||||
prev_batch: "pagTok",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const markerEventId = nextSyncData.rooms.join[roomOne].timeline.events[0].event_id;
|
||||
@ -993,10 +994,8 @@ describe("MatrixClient syncing", () => {
|
||||
const nextSyncData = {
|
||||
next_batch: "batch_token",
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
nextSyncData.rooms.join[roomOne] = {
|
||||
join: {
|
||||
[roomOne]: {
|
||||
timeline: {
|
||||
events: [
|
||||
utils.mkMessage({
|
||||
@ -1012,6 +1011,9 @@ describe("MatrixClient syncing", () => {
|
||||
markerEventFromRoomCreator,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
httpBackend!.when("GET", "/sync").respond(200, normalFirstSync);
|
||||
@ -1095,10 +1097,8 @@ describe("MatrixClient syncing", () => {
|
||||
const limitedSyncData = {
|
||||
next_batch: "batch_token",
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
limitedSyncData.rooms.join[roomOne] = {
|
||||
join: {
|
||||
[roomOne]: {
|
||||
timeline: {
|
||||
events: [
|
||||
utils.mkMessage({
|
||||
@ -1109,6 +1109,9 @@ describe("MatrixClient syncing", () => {
|
||||
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,10 +1205,8 @@ describe("MatrixClient syncing", () => {
|
||||
const syncData = {
|
||||
next_batch: "batch_token",
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
syncData.rooms.join[roomOne] = {
|
||||
join: {
|
||||
[roomOne]: {
|
||||
timeline: {
|
||||
events: [
|
||||
utils.mkMessage({
|
||||
@ -1214,6 +1215,9 @@ describe("MatrixClient syncing", () => {
|
||||
],
|
||||
prev_batch: "pagTok",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
httpBackend!.when("GET", "/sync").respond(200, syncData);
|
||||
@ -1229,10 +1233,8 @@ describe("MatrixClient syncing", () => {
|
||||
const syncData = {
|
||||
next_batch: "batch_token",
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
syncData.rooms.join[roomTwo] = {
|
||||
join: {
|
||||
[roomTwo]: {
|
||||
timeline: {
|
||||
events: [
|
||||
utils.mkMessage({
|
||||
@ -1241,6 +1243,9 @@ describe("MatrixClient syncing", () => {
|
||||
],
|
||||
prev_batch: "roomtwotok",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
httpBackend!.when("GET", "/sync").respond(200, syncData);
|
||||
@ -1261,10 +1266,8 @@ describe("MatrixClient syncing", () => {
|
||||
const syncData = {
|
||||
next_batch: "batch_token",
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
syncData.rooms.join[roomOne] = {
|
||||
join: {
|
||||
[roomOne]: {
|
||||
timeline: {
|
||||
events: [
|
||||
utils.mkMessage({
|
||||
@ -1274,6 +1277,9 @@ describe("MatrixClient syncing", () => {
|
||||
limited: true,
|
||||
prev_batch: "newerTok",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
httpBackend!.when("GET", "/sync").respond(200, syncData);
|
||||
|
||||
@ -1304,11 +1310,10 @@ describe("MatrixClient syncing", () => {
|
||||
const syncData = {
|
||||
rooms: {
|
||||
join: {
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
syncData.rooms.join[roomOne] = {
|
||||
[roomOne]: {
|
||||
ephemeral: {
|
||||
events: [],
|
||||
} as IEphemeral,
|
||||
timeline: {
|
||||
events: [
|
||||
utils.mkMessage({
|
||||
@ -1340,6 +1345,9 @@ describe("MatrixClient syncing", () => {
|
||||
},
|
||||
}),
|
||||
],
|
||||
} as Partial<IJoinedRoom>,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -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,11 +1516,8 @@ describe("MatrixClient syncing", () => {
|
||||
const syncData = {
|
||||
next_batch: "batch_token",
|
||||
rooms: {
|
||||
leave: {},
|
||||
},
|
||||
};
|
||||
|
||||
syncData.rooms.leave[roomTwo] = {
|
||||
leave: {
|
||||
[roomTwo]: {
|
||||
timeline: {
|
||||
events: [
|
||||
utils.mkMessage({
|
||||
@ -1522,6 +1526,9 @@ describe("MatrixClient syncing", () => {
|
||||
],
|
||||
prev_batch: "pagTok",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
httpBackend!.when("POST", "/filter").respond(200, {
|
||||
|
@ -126,13 +126,14 @@ describe("megolm key backups", function() {
|
||||
const syncResponse = {
|
||||
next_batch: 1,
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
syncResponse.rooms.join[ROOM_ID] = {
|
||||
join: {
|
||||
[ROOM_ID]: {
|
||||
timeline: {
|
||||
events: [ENCRYPTED_EVENT],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return aliceTestClient!.start().then(() => {
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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 = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -232,7 +232,7 @@ describe.each([
|
||||
return store;
|
||||
}],
|
||||
])("CrossSigning > createCryptoStoreCacheCallbacks [%s]", function(name, dbFactory) {
|
||||
let store;
|
||||
let store: IndexedDBCryptoStore;
|
||||
|
||||
beforeAll(() => {
|
||||
store = dbFactory();
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
));
|
||||
|
@ -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",
|
||||
|
@ -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" },
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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: {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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: {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -373,10 +373,9 @@ describe("RoomMember", function() {
|
||||
expect(member.events.member).toEqual(joinEvent);
|
||||
});
|
||||
|
||||
it("should set 'name' based on user_id, displayname and room state",
|
||||
function() {
|
||||
it("should set 'name' based on user_id, displayname and room state", function() {
|
||||
const roomState = {
|
||||
getStateEvents: function(type) {
|
||||
getStateEvents: function(type: string) {
|
||||
if (type !== "m.room.member") {
|
||||
return [];
|
||||
}
|
||||
@ -392,7 +391,7 @@ describe("RoomMember", function() {
|
||||
joinEvent,
|
||||
];
|
||||
},
|
||||
getUserIdsWithDisplayName: function(displayName) {
|
||||
getUserIdsWithDisplayName: function(displayName: string) {
|
||||
return [userA, userC];
|
||||
},
|
||||
} as unknown as RoomState;
|
||||
@ -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;
|
||||
|
@ -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,8 +343,7 @@ describe("Room", function() {
|
||||
const remoteEventId = remoteEvent.getId();
|
||||
|
||||
let callCount = 0;
|
||||
room.on("Room.localEchoUpdated",
|
||||
function(event, emitRoom, oldEventId, oldStatus) {
|
||||
room.on(RoomEvent.LocalEchoUpdated, (event, emitRoom, oldEventId, oldStatus) => {
|
||||
switch (callCount) {
|
||||
case 0:
|
||||
expect(event.getId()).toEqual(localEventId);
|
||||
@ -350,8 +361,7 @@ describe("Room", function() {
|
||||
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;
|
||||
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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: {
|
||||
|
@ -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
|
||||
|
@ -42,7 +42,7 @@ export type ReceiptCache = {[eventId: string]: CachedReceipt[]};
|
||||
|
||||
export interface ReceiptContent {
|
||||
[eventId: string]: {
|
||||
[key in ReceiptType]: {
|
||||
[key in ReceiptType | string]: {
|
||||
[userId: string]: Receipt;
|
||||
};
|
||||
};
|
||||
|
@ -49,7 +49,7 @@ interface WellKnownConfig extends Omit<IWellKnownConfig, "error"> {
|
||||
error?: IWellKnownConfig["error"] | null;
|
||||
}
|
||||
|
||||
interface ClientConfig {
|
||||
interface ClientConfig extends Omit<IClientWellKnown, "m.homeserver" | "m.identity_server"> {
|
||||
"m.homeserver": WellKnownConfig;
|
||||
"m.identity_server": WellKnownConfig;
|
||||
}
|
||||
@ -131,7 +131,7 @@ export class AutoDiscovery {
|
||||
* configuration, which may include error states. Rejects on unexpected
|
||||
* failure, not when verification fails.
|
||||
*/
|
||||
public static async fromDiscoveryConfig(wellknown: any): Promise<ClientConfig> {
|
||||
public static async fromDiscoveryConfig(wellknown: IClientWellKnown): Promise<ClientConfig> {
|
||||
// Step 1 is to get the config, which is provided to us here.
|
||||
|
||||
// We default to an error state to make the first few checks easier to
|
||||
@ -185,7 +185,7 @@ export class AutoDiscovery {
|
||||
const hsVersions = await this.fetchWellKnownObject(
|
||||
`${hsUrl}/_matrix/client/versions`,
|
||||
);
|
||||
if (!hsVersions || !hsVersions.raw["versions"]) {
|
||||
if (!hsVersions || !hsVersions.raw?.["versions"]) {
|
||||
logger.error("Invalid /versions response");
|
||||
clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID_HOMESERVER;
|
||||
|
||||
@ -219,9 +219,7 @@ export class AutoDiscovery {
|
||||
|
||||
// Step 5a: Make sure the URL is valid *looking*. We'll make sure it
|
||||
// points to an identity server in Step 5b.
|
||||
isUrl = this.sanitizeWellKnownUrl(
|
||||
wellknown["m.identity_server"]["base_url"],
|
||||
);
|
||||
isUrl = this.sanitizeWellKnownUrl(wellknown["m.identity_server"]["base_url"]);
|
||||
if (!isUrl) {
|
||||
logger.error("Invalid base_url for m.identity_server");
|
||||
failingClientConfig["m.identity_server"].error =
|
||||
@ -234,7 +232,7 @@ export class AutoDiscovery {
|
||||
const isResponse = await this.fetchWellKnownObject(
|
||||
`${isUrl}/_matrix/identity/api/v1`,
|
||||
);
|
||||
if (!isResponse || !isResponse.raw || isResponse.action !== AutoDiscoveryAction.SUCCESS) {
|
||||
if (!isResponse?.raw || isResponse.action !== AutoDiscoveryAction.SUCCESS) {
|
||||
logger.error("Invalid /api/v1 response");
|
||||
failingClientConfig["m.identity_server"].error =
|
||||
AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER;
|
||||
@ -259,14 +257,16 @@ export class AutoDiscovery {
|
||||
|
||||
// Step 7: Copy any other keys directly into the clientConfig. This is for
|
||||
// things like custom configuration of services.
|
||||
Object.keys(wellknown).forEach((k) => {
|
||||
Object.keys(wellknown).forEach((k: keyof IClientWellKnown) => {
|
||||
if (k === "m.homeserver" || k === "m.identity_server") {
|
||||
// Only copy selected parts of the config to avoid overwriting
|
||||
// properties computed by the validation logic above.
|
||||
const notProps = ["error", "state", "base_url"];
|
||||
for (const prop of Object.keys(wellknown[k])) {
|
||||
for (const prop of Object.keys(wellknown[k]!)) {
|
||||
if (notProps.includes(prop)) continue;
|
||||
clientConfig[k][prop] = wellknown[k][prop];
|
||||
type Prop = Exclude<keyof IWellKnownConfig, "error" | "state" | "base_url">;
|
||||
// @ts-ignore - ts gets unhappy as we're mixing types here
|
||||
clientConfig[k][prop as Prop] = wellknown[k]![prop as Prop];
|
||||
}
|
||||
} else {
|
||||
// Just copy the whole thing over otherwise
|
||||
@ -347,7 +347,7 @@ export class AutoDiscovery {
|
||||
}
|
||||
|
||||
// Step 2: Validate and parse the config
|
||||
return AutoDiscovery.fromDiscoveryConfig(wellknown.raw);
|
||||
return AutoDiscovery.fromDiscoveryConfig(wellknown.raw!);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -378,7 +378,7 @@ export class AutoDiscovery {
|
||||
* @return {string|boolean} The sanitized URL or a falsey value if the URL is invalid.
|
||||
* @private
|
||||
*/
|
||||
private static sanitizeWellKnownUrl(url: string): string | false {
|
||||
private static sanitizeWellKnownUrl(url?: string | null): string | false {
|
||||
if (!url) return false;
|
||||
|
||||
try {
|
||||
|
@ -16,27 +16,28 @@ limitations under the License.
|
||||
|
||||
import * as matrixcs from "./matrix";
|
||||
|
||||
type BrowserMatrix = typeof matrixcs;
|
||||
declare global {
|
||||
/* eslint-disable no-var, camelcase */
|
||||
var __js_sdk_entrypoint: boolean;
|
||||
var matrixcs: BrowserMatrix;
|
||||
/* eslint-enable no-var */
|
||||
}
|
||||
|
||||
if (global.__js_sdk_entrypoint) {
|
||||
throw new Error("Multiple matrix-js-sdk entrypoints detected!");
|
||||
}
|
||||
global.__js_sdk_entrypoint = true;
|
||||
|
||||
// just *accessing* indexedDB throws an exception in firefox with
|
||||
// indexeddb disabled.
|
||||
let indexedDB;
|
||||
// just *accessing* indexedDB throws an exception in firefox with indexeddb disabled.
|
||||
let indexedDB: IDBFactory | undefined;
|
||||
try {
|
||||
indexedDB = global.indexedDB;
|
||||
} catch (e) {}
|
||||
|
||||
// if our browser (appears to) support indexeddb, use an indexeddb crypto store.
|
||||
if (indexedDB) {
|
||||
matrixcs.setCryptoStoreFactory(
|
||||
function() {
|
||||
return new matrixcs.IndexedDBCryptoStore(
|
||||
indexedDB, "matrix-js-sdk:crypto",
|
||||
);
|
||||
},
|
||||
);
|
||||
matrixcs.setCryptoStoreFactory(() => new matrixcs.IndexedDBCryptoStore(indexedDB!, "matrix-js-sdk:crypto"));
|
||||
}
|
||||
|
||||
// We export 3 things to make browserify happy as well as downstream projects.
|
@ -530,7 +530,7 @@ export interface IPreviewUrlResponse {
|
||||
"matrix:image:size"?: number;
|
||||
}
|
||||
|
||||
interface ITurnServerResponse {
|
||||
export interface ITurnServerResponse {
|
||||
uris: string[];
|
||||
username: string;
|
||||
password: string;
|
||||
@ -561,7 +561,7 @@ export interface IClientWellKnown {
|
||||
}
|
||||
|
||||
export interface IWellKnownConfig {
|
||||
raw?: any; // todo typings
|
||||
raw?: IClientWellKnown;
|
||||
action?: AutoDiscoveryAction;
|
||||
reason?: string;
|
||||
error?: Error | string;
|
||||
@ -605,10 +605,10 @@ interface ITagMetadata {
|
||||
}
|
||||
|
||||
interface IMessagesResponse {
|
||||
start: string;
|
||||
end: string;
|
||||
start?: string;
|
||||
end?: string;
|
||||
chunk: IRoomEvent[];
|
||||
state: IStateEvent[];
|
||||
state?: IStateEvent[];
|
||||
}
|
||||
|
||||
interface IThreadedMessagesResponse {
|
||||
@ -635,6 +635,17 @@ export interface IUploadKeysRequest {
|
||||
"org.matrix.msc2732.fallback_keys"?: Record<string, IOneTimeKey>;
|
||||
}
|
||||
|
||||
export interface IQueryKeysRequest {
|
||||
device_keys: { [userId: string]: string[] };
|
||||
timeout?: number;
|
||||
token?: string;
|
||||
}
|
||||
|
||||
export interface IClaimKeysRequest {
|
||||
one_time_keys: { [userId: string]: { [deviceId: string]: string } };
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export interface IOpenIDToken {
|
||||
access_token: string;
|
||||
token_type: "Bearer" | string;
|
||||
@ -1744,13 +1755,15 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
}
|
||||
|
||||
return this.http.authedRequest<{
|
||||
type Response = {
|
||||
capabilities?: ICapabilities;
|
||||
}>(Method.Get, "/capabilities").catch((e: Error): void => {
|
||||
};
|
||||
return this.http.authedRequest<Response>(Method.Get, "/capabilities").catch((e: Error): Response => {
|
||||
// We swallow errors because we need a default object anyhow
|
||||
logger.error(e);
|
||||
return {};
|
||||
}).then((r = {}) => {
|
||||
const capabilities: ICapabilities = r["capabilities"] || {};
|
||||
const capabilities = r["capabilities"] || {};
|
||||
|
||||
// If the capabilities missed the cache, cache it for a shorter amount
|
||||
// of time to try and refresh them later.
|
||||
@ -3229,28 +3242,28 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
targetRoomId: undefined,
|
||||
targetSessionId: undefined,
|
||||
backupInfo: IKeyBackupInfo,
|
||||
opts: IKeyBackupRestoreOpts,
|
||||
opts?: IKeyBackupRestoreOpts,
|
||||
): Promise<IKeyBackupRestoreResult>;
|
||||
public restoreKeyBackupWithRecoveryKey(
|
||||
recoveryKey: string,
|
||||
targetRoomId: string,
|
||||
targetSessionId: undefined,
|
||||
backupInfo: IKeyBackupInfo,
|
||||
opts: IKeyBackupRestoreOpts,
|
||||
opts?: IKeyBackupRestoreOpts,
|
||||
): Promise<IKeyBackupRestoreResult>;
|
||||
public restoreKeyBackupWithRecoveryKey(
|
||||
recoveryKey: string,
|
||||
targetRoomId: string,
|
||||
targetSessionId: string,
|
||||
backupInfo: IKeyBackupInfo,
|
||||
opts: IKeyBackupRestoreOpts,
|
||||
opts?: IKeyBackupRestoreOpts,
|
||||
): Promise<IKeyBackupRestoreResult>;
|
||||
public restoreKeyBackupWithRecoveryKey(
|
||||
recoveryKey: string,
|
||||
targetRoomId: string | undefined,
|
||||
targetSessionId: string | undefined,
|
||||
backupInfo: IKeyBackupInfo,
|
||||
opts: IKeyBackupRestoreOpts,
|
||||
opts?: IKeyBackupRestoreOpts,
|
||||
): Promise<IKeyBackupRestoreResult> {
|
||||
const privKey = decodeRecoveryKey(recoveryKey);
|
||||
return this.restoreKeyBackup(privKey, targetRoomId!, targetSessionId!, backupInfo, opts);
|
||||
@ -3442,7 +3455,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
}
|
||||
|
||||
const deviceInfos = await this.crypto.downloadKeys(userIds);
|
||||
const devicesByUser = {};
|
||||
const devicesByUser: Record<string, DeviceInfo[]> = {};
|
||||
for (const [userId, devices] of Object.entries(deviceInfos)) {
|
||||
devicesByUser[userId] = Object.values(devices);
|
||||
}
|
||||
@ -3616,7 +3629,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public setIgnoredUsers(userIds: string[]): Promise<{}> {
|
||||
const content = { ignored_users: {} };
|
||||
const content = { ignored_users: {} as Record<string, object> };
|
||||
userIds.forEach((u) => {
|
||||
content.ignored_users[u] = {};
|
||||
});
|
||||
@ -3910,16 +3923,25 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
): Promise<ISendEventResponse>;
|
||||
public sendEvent(
|
||||
roomId: string,
|
||||
threadId: string | null,
|
||||
eventType: string | IContent,
|
||||
content?: IContent | string,
|
||||
txnId?: string,
|
||||
threadIdOrEventType: string | null,
|
||||
eventTypeOrContent: string | IContent,
|
||||
contentOrTxnId?: IContent | string,
|
||||
txnIdOrVoid?: string,
|
||||
): Promise<ISendEventResponse> {
|
||||
if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {
|
||||
txnId = content as string;
|
||||
content = eventType as IContent;
|
||||
eventType = threadId;
|
||||
let threadId: string | null;
|
||||
let eventType: string;
|
||||
let content: IContent;
|
||||
let txnId: string | undefined;
|
||||
if (!threadIdOrEventType?.startsWith(EVENT_ID_PREFIX) && threadIdOrEventType !== null) {
|
||||
txnId = contentOrTxnId as string;
|
||||
content = eventTypeOrContent as IContent;
|
||||
eventType = threadIdOrEventType;
|
||||
threadId = null;
|
||||
} else {
|
||||
txnId = txnIdOrVoid;
|
||||
content = contentOrTxnId as IContent;
|
||||
eventType = eventTypeOrContent as string;
|
||||
threadId = threadIdOrEventType;
|
||||
}
|
||||
|
||||
// If we expect that an event is part of a thread but is missing the relation
|
||||
@ -4280,7 +4302,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
let eventType: string = EventType.RoomMessage;
|
||||
let sendContent: IContent = content as IContent;
|
||||
const makeContentExtensible = (content: IContent = {}, recurse = true): IPartialEvent<object> | undefined => {
|
||||
let newEvent: IPartialEvent<object> | undefined;
|
||||
let newEvent: IPartialEvent<IContent> | undefined;
|
||||
|
||||
if (content['msgtype'] === MsgType.Text) {
|
||||
newEvent = MessageEvent.from(content['body'], content['formatted_body']).serialize();
|
||||
@ -5262,11 +5284,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
room.addEventsToTimeline(timelineEvents, true, room.getLiveTimeline());
|
||||
this.processThreadEvents(room, threadedEvents, true);
|
||||
|
||||
room.oldState.paginationToken = res.end;
|
||||
room.oldState.paginationToken = res.end ?? null;
|
||||
if (res.chunk.length === 0) {
|
||||
room.oldState.paginationToken = null;
|
||||
}
|
||||
this.store.storeEvents(room, matrixEvents, res.end, true);
|
||||
this.store.storeEvents(room, matrixEvents, res.end ?? null, true);
|
||||
delete this.ongoingScrollbacks[room.roomId];
|
||||
resolve(room);
|
||||
}).catch((err) => {
|
||||
@ -6261,7 +6283,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @param {string} roomId the id of the room.
|
||||
* @return {object} the rule or undefined.
|
||||
*/
|
||||
public getRoomPushRule(scope: string, roomId: string): IPushRule | undefined {
|
||||
public getRoomPushRule(scope: "global" | "device", roomId: string): IPushRule | undefined {
|
||||
// There can be only room-kind push rule per room
|
||||
// and its id is the room id.
|
||||
if (this.pushRules) {
|
||||
@ -6282,7 +6304,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* @return {Promise} Resolves: result object
|
||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||
*/
|
||||
public setRoomMutePushRule(scope: string, roomId: string, mute: boolean): Promise<void> | undefined {
|
||||
public setRoomMutePushRule(scope: "global" | "device", roomId: string, mute: boolean): Promise<void> | undefined {
|
||||
let promise: Promise<unknown> | undefined;
|
||||
let hasDontNotifyRule = false;
|
||||
|
||||
@ -6818,7 +6840,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
.filter(([key, value]) => {
|
||||
return primTypes.includes(typeof value);
|
||||
})
|
||||
.reduce((obj, [key, value]) => {
|
||||
.reduce<Record<string, any>>((obj, [key, value]) => {
|
||||
obj[key] = value;
|
||||
return obj;
|
||||
}, {});
|
||||
@ -7841,7 +7863,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
$roomId: roomId,
|
||||
});
|
||||
|
||||
const content = {
|
||||
const content: IContent = {
|
||||
[ReceiptType.FullyRead]: rmEventId,
|
||||
[ReceiptType.Read]: rrEventId,
|
||||
};
|
||||
@ -8540,7 +8562,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
* an error response ({@link module:http-api.MatrixError}).
|
||||
*/
|
||||
public downloadKeysForUsers(userIds: string[], opts: { token?: string } = {}): Promise<IDownloadKeyResult> {
|
||||
const content: any = {
|
||||
const content: IQueryKeysRequest = {
|
||||
device_keys: {},
|
||||
};
|
||||
if ('token' in opts) {
|
||||
@ -8582,7 +8604,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
queries[userId] = query;
|
||||
query[deviceId] = keyAlgorithm;
|
||||
}
|
||||
const content: any = { one_time_keys: queries };
|
||||
const content: IClaimKeysRequest = { one_time_keys: queries };
|
||||
if (timeout) {
|
||||
content.timeout = timeout;
|
||||
}
|
||||
@ -8874,7 +8896,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
const med = p[1].toLowerCase();
|
||||
const unhashed = `${addr} ${med}`;
|
||||
// Map the unhashed values to a known (case-sensitive) address. We use
|
||||
// the case sensitive version because the caller might be expecting that.
|
||||
// the case-sensitive version because the caller might be expecting that.
|
||||
localMapping[unhashed] = p[0];
|
||||
return unhashed;
|
||||
});
|
||||
@ -8883,12 +8905,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
throw new Error("Unsupported identity server: unknown hash algorithm");
|
||||
}
|
||||
|
||||
const response = await this.http.idServerRequest(
|
||||
Method.Post, "/lookup",
|
||||
params, IdentityPrefix.V2, identityAccessToken,
|
||||
);
|
||||
const response = await this.http.idServerRequest<{
|
||||
mappings: { [address: string]: string };
|
||||
}>(Method.Post, "/lookup", params, IdentityPrefix.V2, identityAccessToken);
|
||||
|
||||
if (!response || !response['mappings']) return []; // no results
|
||||
if (!response?.['mappings']) return []; // no results
|
||||
|
||||
const foundAddresses: { address: string, mxid: string }[] = [];
|
||||
for (const hashed of Object.keys(response['mappings'])) {
|
||||
@ -9028,7 +9049,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
messages: contentMap,
|
||||
};
|
||||
|
||||
const targets = Object.keys(contentMap).reduce((obj, key) => {
|
||||
const targets = Object.keys(contentMap).reduce<Record<string, string[]>>((obj, key) => {
|
||||
obj[key] = Object.keys(contentMap[key]);
|
||||
return obj;
|
||||
}, {});
|
||||
|
@ -84,6 +84,7 @@ export class CrossSigningInfo {
|
||||
const res = new CrossSigningInfo(userId);
|
||||
for (const prop in obj) {
|
||||
if (obj.hasOwnProperty(prop)) {
|
||||
// @ts-ignore - ts doesn't like this and nor should we
|
||||
res[prop] = obj[prop];
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ import { IAccountDataClient } from "./SecretStorage";
|
||||
|
||||
interface ICrossSigningKeys {
|
||||
authUpload: IBootstrapCrossSigningOpts["authUploadDeviceSigningKeys"];
|
||||
keys: Record<string, ICrossSigningKey>;
|
||||
keys: Record<"master" | "self_signing" | "user_signing", ICrossSigningKey>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -209,7 +209,7 @@ export class EncryptionSetupOperation {
|
||||
if (this.crossSigningKeys) {
|
||||
const keys: Partial<CrossSigningKeys> = {};
|
||||
for (const [name, key] of Object.entries(this.crossSigningKeys.keys)) {
|
||||
keys[name + "_key"] = key;
|
||||
keys[((name as keyof ICrossSigningKeys["keys"]) + "_key" as keyof CrossSigningKeys)] = key;
|
||||
}
|
||||
|
||||
// We must only call `uploadDeviceSigningKeys` from inside this auth
|
||||
|
@ -23,11 +23,19 @@ import { CryptoStore, IProblem, ISessionInfo, IWithheld } from "./store/base";
|
||||
import { IOlmDevice, IOutboundGroupSessionKey } from "./algorithms/megolm";
|
||||
import { IMegolmSessionData } from "./index";
|
||||
import { OlmGroupSessionExtraData } from "../@types/crypto";
|
||||
import { IMessage } from "./algorithms/olm";
|
||||
|
||||
// The maximum size of an event is 65K, and we base64 the content, so this is a
|
||||
// reasonable approximation to the biggest plaintext we can encrypt.
|
||||
const MAX_PLAINTEXT_LENGTH = 65536 * 3 / 4;
|
||||
|
||||
export class PayloadTooLargeError extends Error {
|
||||
public readonly data = {
|
||||
errcode: "M_TOO_LARGE",
|
||||
error: "Payload too large for encrypted message",
|
||||
};
|
||||
}
|
||||
|
||||
function checkPayloadLength(payloadString: string): void {
|
||||
if (payloadString === undefined) {
|
||||
throw new Error("payloadString undefined");
|
||||
@ -40,15 +48,8 @@ function checkPayloadLength(payloadString: string): void {
|
||||
// Note that even if we manage to do the encryption, the message send may fail,
|
||||
// because by the time we've wrapped the ciphertext in the event object, it may
|
||||
// exceed 65K. But at least we won't just fail with "abort()" in that case.
|
||||
const err = new Error("Message too long (" + payloadString.length + " bytes). " +
|
||||
"The maximum for an encrypted message is " +
|
||||
MAX_PLAINTEXT_LENGTH + " bytes.");
|
||||
// TODO: [TypeScript] We should have our own error types
|
||||
err["data"] = {
|
||||
errcode: "M_TOO_LARGE",
|
||||
error: "Payload too large for encrypted message",
|
||||
};
|
||||
throw err;
|
||||
throw new PayloadTooLargeError(`Message too long (${payloadString.length} bytes). ` +
|
||||
`The maximum for an encrypted message is ${MAX_PLAINTEXT_LENGTH} bytes.`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,6 +127,8 @@ interface IInboundGroupSessionKey {
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
type OneTimeKeys = { curve25519: { [keyId: string]: string } };
|
||||
|
||||
/**
|
||||
* Manages the olm cryptography functions. Each OlmDevice has a single
|
||||
* OlmAccount and a number of OlmSessions.
|
||||
@ -452,7 +455,7 @@ export class OlmDevice {
|
||||
* @return {Promise<string>} base64-encoded signature
|
||||
*/
|
||||
public async sign(message: string): Promise<string> {
|
||||
let result;
|
||||
let result: string;
|
||||
await this.cryptoStore.doTxn(
|
||||
'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||
(txn) => {
|
||||
@ -460,7 +463,7 @@ export class OlmDevice {
|
||||
result = account.sign(message);
|
||||
});
|
||||
});
|
||||
return result;
|
||||
return result!;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -470,8 +473,8 @@ export class OlmDevice {
|
||||
* <tt>curve25519</tt>, which is itself an object mapping key id to Curve25519
|
||||
* key.
|
||||
*/
|
||||
public async getOneTimeKeys(): Promise<{ curve25519: { [keyId: string]: string } }> {
|
||||
let result;
|
||||
public async getOneTimeKeys(): Promise<OneTimeKeys> {
|
||||
let result: OneTimeKeys;
|
||||
await this.cryptoStore.doTxn(
|
||||
'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||
(txn) => {
|
||||
@ -481,7 +484,7 @@ export class OlmDevice {
|
||||
},
|
||||
);
|
||||
|
||||
return result;
|
||||
return result!;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -821,10 +824,10 @@ export class OlmDevice {
|
||||
theirDeviceIdentityKey: string,
|
||||
sessionId: string,
|
||||
payloadString: string,
|
||||
): Promise<string> {
|
||||
): Promise<IMessage> {
|
||||
checkPayloadLength(payloadString);
|
||||
|
||||
let res;
|
||||
let res: IMessage;
|
||||
await this.cryptoStore.doTxn(
|
||||
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
|
||||
(txn) => {
|
||||
@ -840,7 +843,7 @@ export class OlmDevice {
|
||||
},
|
||||
logger.withPrefix("[encryptMessage]"),
|
||||
);
|
||||
return res;
|
||||
return res!;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -860,7 +863,7 @@ export class OlmDevice {
|
||||
messageType: number,
|
||||
ciphertext: string,
|
||||
): Promise<string> {
|
||||
let payloadString;
|
||||
let payloadString: string;
|
||||
await this.cryptoStore.doTxn(
|
||||
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
|
||||
(txn) => {
|
||||
@ -877,7 +880,7 @@ export class OlmDevice {
|
||||
},
|
||||
logger.withPrefix("[decryptMessage]"),
|
||||
);
|
||||
return payloadString;
|
||||
return payloadString!;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -902,7 +905,7 @@ export class OlmDevice {
|
||||
return false;
|
||||
}
|
||||
|
||||
let matches;
|
||||
let matches: boolean;
|
||||
await this.cryptoStore.doTxn(
|
||||
'readonly', [IndexedDBCryptoStore.STORE_SESSIONS],
|
||||
(txn) => {
|
||||
@ -912,7 +915,7 @@ export class OlmDevice {
|
||||
},
|
||||
logger.withPrefix("[matchesSession]"),
|
||||
);
|
||||
return matches;
|
||||
return matches!;
|
||||
}
|
||||
|
||||
public async recordSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void> {
|
||||
@ -1293,7 +1296,7 @@ export class OlmDevice {
|
||||
result = null;
|
||||
return;
|
||||
}
|
||||
let res;
|
||||
let res: ReturnType<InboundGroupSession["decrypt"]>;
|
||||
try {
|
||||
res = session.decrypt(body);
|
||||
} catch (e) {
|
||||
@ -1313,8 +1316,8 @@ export class OlmDevice {
|
||||
|
||||
let plaintext: string = res.plaintext;
|
||||
if (plaintext === undefined) {
|
||||
// Compatibility for older olm versions.
|
||||
plaintext = res;
|
||||
// @ts-ignore - Compatibility for older olm versions.
|
||||
plaintext = res as string;
|
||||
} else {
|
||||
// Check if we have seen this message index before to detect replay attacks.
|
||||
// If the event ID and timestamp are specified, and the match the event ID
|
||||
@ -1555,7 +1558,7 @@ export class OlmDevice {
|
||||
}
|
||||
}
|
||||
|
||||
export const WITHHELD_MESSAGES = {
|
||||
export const WITHHELD_MESSAGES: Record<string, string> = {
|
||||
"m.unverified": "The sender has disabled encrypting to unverified devices.",
|
||||
"m.blacklisted": "The sender has blocked you.",
|
||||
"m.unauthorised": "You are not authorised to read the message.",
|
||||
|
@ -18,10 +18,9 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { logger } from '../logger';
|
||||
import * as olmlib from './olmlib';
|
||||
import { encodeBase64 } from './olmlib';
|
||||
import { randomString } from '../randomstring';
|
||||
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from './aes';
|
||||
import { ICryptoCallbacks } from ".";
|
||||
import { ICryptoCallbacks, IEncryptedContent } from ".";
|
||||
import { IContent, MatrixEvent } from "../models/event";
|
||||
import { ClientEvent, ClientEventHandlerMap, MatrixClient } from "../client";
|
||||
import { IAddSecretStorageKeyOpts, ISecretStorageKeyInfo } from './api';
|
||||
@ -61,8 +60,7 @@ interface IDecryptors {
|
||||
|
||||
interface ISecretInfo {
|
||||
encrypted: {
|
||||
// eslint-disable-next-line camelcase
|
||||
key_id: IEncryptedPayload;
|
||||
[keyId: string]: IEncryptedPayload;
|
||||
};
|
||||
}
|
||||
|
||||
@ -318,23 +316,11 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
|
||||
`the keys it is encrypted with are for a supported algorithm`);
|
||||
}
|
||||
|
||||
let keyId: string;
|
||||
let decryption;
|
||||
try {
|
||||
// fetch private key from app
|
||||
[keyId, decryption] = await this.getSecretStorageKey(keys, name);
|
||||
|
||||
const [keyId, decryption] = await this.getSecretStorageKey(keys, name);
|
||||
const encInfo = secretInfo.encrypted[keyId];
|
||||
|
||||
// We don't actually need the decryption object if it's a passthrough
|
||||
// since we just want to return the key itself. It must be base64
|
||||
// encoded, since this is how a key would normally be stored.
|
||||
if (encInfo.passthrough) return encodeBase64(decryption.get_private_key());
|
||||
|
||||
return decryption.decrypt(encInfo);
|
||||
} finally {
|
||||
if (decryption && decryption.free) decryption.free();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -351,7 +337,7 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
|
||||
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer<ISecretInfo>(name);
|
||||
if (!secretInfo?.encrypted) return null;
|
||||
|
||||
const ret = {};
|
||||
const ret: Record<string, ISecretStorageKeyInfo> = {};
|
||||
|
||||
// filter secret encryption keys with supported algorithm
|
||||
for (const keyId of Object.keys(secretInfo.encrypted)) {
|
||||
@ -391,7 +377,7 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
|
||||
requesting_device_id: this.baseApis.deviceId,
|
||||
request_id: requestId,
|
||||
};
|
||||
const toDevice = {};
|
||||
const toDevice: Record<string, typeof cancelData> = {};
|
||||
for (const device of devices) {
|
||||
toDevice[device] = cancelData;
|
||||
}
|
||||
@ -412,7 +398,7 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
|
||||
request_id: requestId,
|
||||
[ToDeviceMessageId]: uuidv4(),
|
||||
};
|
||||
const toDevice = {};
|
||||
const toDevice: Record<string, typeof requestData> = {};
|
||||
for (const device of devices) {
|
||||
toDevice[device] = requestData;
|
||||
}
|
||||
@ -490,9 +476,9 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
|
||||
secret: secret,
|
||||
},
|
||||
};
|
||||
const encryptedContent = {
|
||||
const encryptedContent: IEncryptedContent = {
|
||||
algorithm: olmlib.OLM_ALGORITHM,
|
||||
sender_key: this.baseApis.crypto!.olmDevice.deviceCurve25519Key,
|
||||
sender_key: this.baseApis.crypto!.olmDevice.deviceCurve25519Key!,
|
||||
ciphertext: {},
|
||||
[ToDeviceMessageId]: uuidv4(),
|
||||
};
|
||||
|
@ -23,8 +23,14 @@ limitations under the License.
|
||||
import { MatrixClient } from "../../client";
|
||||
import { Room } from "../../models/room";
|
||||
import { OlmDevice } from "../OlmDevice";
|
||||
import { MatrixEvent, RoomMember } from "../../matrix";
|
||||
import { Crypto, IEventDecryptionResult, IMegolmSessionData, IncomingRoomKeyRequest } from "..";
|
||||
import { IContent, MatrixEvent, RoomMember } from "../../matrix";
|
||||
import {
|
||||
Crypto,
|
||||
IEncryptedContent,
|
||||
IEventDecryptionResult,
|
||||
IMegolmSessionData,
|
||||
IncomingRoomKeyRequest,
|
||||
} from "..";
|
||||
import { DeviceInfo } from "../deviceinfo";
|
||||
import { IRoomEncryption } from "../RoomList";
|
||||
|
||||
@ -108,7 +114,7 @@ export abstract class EncryptionAlgorithm {
|
||||
*
|
||||
* @return {Promise} Promise which resolves to the new event body
|
||||
*/
|
||||
public abstract encryptMessage(room: Room, eventType: string, content: object): Promise<object>;
|
||||
public abstract encryptMessage(room: Room, eventType: string, content: IContent): Promise<IEncryptedContent>;
|
||||
|
||||
/**
|
||||
* Called when the membership of a member of the room changes.
|
||||
|
@ -38,9 +38,15 @@ import { Room } from '../../models/room';
|
||||
import { DeviceInfo } from "../deviceinfo";
|
||||
import { IOlmSessionResult } from "../olmlib";
|
||||
import { DeviceInfoMap } from "../DeviceList";
|
||||
import { MatrixEvent } from "../../models/event";
|
||||
import { IContent, MatrixEvent } from "../../models/event";
|
||||
import { EventType, MsgType, ToDeviceMessageId } from '../../@types/event';
|
||||
import { IEncryptedContent, IEventDecryptionResult, IMegolmSessionData, IncomingRoomKeyRequest } from "../index";
|
||||
import {
|
||||
IMegolmEncryptedContent,
|
||||
IEventDecryptionResult,
|
||||
IMegolmSessionData,
|
||||
IncomingRoomKeyRequest,
|
||||
IEncryptedContent,
|
||||
} from "../index";
|
||||
import { RoomKeyRequestState } from '../OutgoingRoomKeyRequestManager';
|
||||
import { OlmGroupSessionExtraData } from "../../@types/crypto";
|
||||
import { MatrixError } from "../../http-api";
|
||||
@ -228,7 +234,7 @@ class OutboundSessionInfo {
|
||||
* @param {object} params parameters, as per
|
||||
* {@link module:crypto/algorithms/EncryptionAlgorithm}
|
||||
*/
|
||||
class MegolmEncryption extends EncryptionAlgorithm {
|
||||
export class MegolmEncryption extends EncryptionAlgorithm {
|
||||
// the most recent attempt to set up a session. This is used to serialise
|
||||
// the session setups, so that we have a race-free view of which session we
|
||||
// are using, and which devices we have shared the keys with. It resolves
|
||||
@ -761,9 +767,9 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
},
|
||||
};
|
||||
|
||||
const encryptedContent = {
|
||||
const encryptedContent: IEncryptedContent = {
|
||||
algorithm: olmlib.OLM_ALGORITHM,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key!,
|
||||
ciphertext: {},
|
||||
[ToDeviceMessageId]: uuidv4(),
|
||||
};
|
||||
@ -1010,7 +1016,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
*
|
||||
* @return {Promise} Promise which resolves to the new event body
|
||||
*/
|
||||
public async encryptMessage(room: Room, eventType: string, content: object): Promise<object> {
|
||||
public async encryptMessage(room: Room, eventType: string, content: IContent): Promise<IMegolmEncryptedContent> {
|
||||
logger.log(`Starting to encrypt event for ${this.roomId}`);
|
||||
|
||||
if (this.encryptionPreparation != null) {
|
||||
@ -1045,12 +1051,10 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
content: content,
|
||||
};
|
||||
|
||||
const ciphertext = this.olmDevice.encryptGroupMessage(
|
||||
session.sessionId, JSON.stringify(payloadJson),
|
||||
);
|
||||
const encryptedContent = {
|
||||
const ciphertext = this.olmDevice.encryptGroupMessage(session.sessionId, JSON.stringify(payloadJson));
|
||||
const encryptedContent: IEncryptedContent = {
|
||||
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key!,
|
||||
ciphertext: ciphertext,
|
||||
session_id: session.sessionId,
|
||||
// Include our device ID so that recipients can send us a
|
||||
@ -1064,7 +1068,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
return encryptedContent;
|
||||
}
|
||||
|
||||
private isVerificationEvent(eventType: string, content: object): boolean {
|
||||
private isVerificationEvent(eventType: string, content: IContent): boolean {
|
||||
switch (eventType) {
|
||||
case EventType.KeyVerificationCancel:
|
||||
case EventType.KeyVerificationDone:
|
||||
@ -1227,7 +1231,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
||||
* @param {object} params parameters, as per
|
||||
* {@link module:crypto/algorithms/DecryptionAlgorithm}
|
||||
*/
|
||||
class MegolmDecryption extends DecryptionAlgorithm {
|
||||
export class MegolmDecryption extends DecryptionAlgorithm {
|
||||
// events which we couldn't decrypt due to unknown sessions /
|
||||
// indexes, or which we could only decrypt with untrusted keys:
|
||||
// map from senderKey|sessionId to Set of MatrixEvents
|
||||
@ -1670,9 +1674,9 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
await olmlib.ensureOlmSessionsForDevices(
|
||||
this.olmDevice, this.baseApis, { [sender]: [device] }, false,
|
||||
);
|
||||
const encryptedContent = {
|
||||
const encryptedContent: IEncryptedContent = {
|
||||
algorithm: olmlib.OLM_ALGORITHM,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key!,
|
||||
ciphertext: {},
|
||||
[ToDeviceMessageId]: uuidv4(),
|
||||
};
|
||||
@ -1752,9 +1756,9 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
||||
body.room_id, body.sender_key, body.session_id,
|
||||
);
|
||||
}).then((payload) => {
|
||||
const encryptedContent = {
|
||||
const encryptedContent: IEncryptedContent = {
|
||||
algorithm: olmlib.OLM_ALGORITHM,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key!,
|
||||
ciphertext: {},
|
||||
[ToDeviceMessageId]: uuidv4(),
|
||||
};
|
||||
|
@ -30,13 +30,13 @@ import {
|
||||
registerAlgorithm,
|
||||
} from "./base";
|
||||
import { Room } from '../../models/room';
|
||||
import { MatrixEvent } from "../../models/event";
|
||||
import { IEventDecryptionResult } from "../index";
|
||||
import { IContent, MatrixEvent } from "../../models/event";
|
||||
import { IEncryptedContent, IEventDecryptionResult, IOlmEncryptedContent } from "../index";
|
||||
import { IInboundSession } from "../OlmDevice";
|
||||
|
||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||
|
||||
interface IMessage {
|
||||
export interface IMessage {
|
||||
type: number;
|
||||
body: string;
|
||||
}
|
||||
@ -91,7 +91,7 @@ class OlmEncryption extends EncryptionAlgorithm {
|
||||
*
|
||||
* @return {Promise} Promise which resolves to the new event body
|
||||
*/
|
||||
public async encryptMessage(room: Room, eventType: string, content: object): Promise<object> {
|
||||
public async encryptMessage(room: Room, eventType: string, content: IContent): Promise<IOlmEncryptedContent> {
|
||||
// pick the list of recipients based on the membership list.
|
||||
//
|
||||
// TODO: there is a race condition here! What if a new user turns up
|
||||
@ -111,9 +111,9 @@ class OlmEncryption extends EncryptionAlgorithm {
|
||||
content: content,
|
||||
};
|
||||
|
||||
const encryptedContent = {
|
||||
const encryptedContent: IEncryptedContent = {
|
||||
algorithm: olmlib.OLM_ALGORITHM,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key!,
|
||||
ciphertext: {},
|
||||
};
|
||||
|
||||
|
@ -93,7 +93,7 @@ interface BackupAlgorithmClass {
|
||||
|
||||
interface BackupAlgorithm {
|
||||
untrusted: boolean;
|
||||
encryptSession(data: Record<string, any>): Promise<any>;
|
||||
encryptSession(data: Record<string, any>): Promise<Curve25519SessionData | IEncryptedPayload>;
|
||||
decryptSessions(ciphertexts: Record<string, IKeyBackupSession>): Promise<IMegolmSessionData[]>;
|
||||
authData: AuthData;
|
||||
keyMatches(key: ArrayLike<number>): Promise<boolean>;
|
||||
@ -663,7 +663,7 @@ export class Curve25519 implements BackupAlgorithm {
|
||||
|
||||
public get untrusted(): boolean { return true; }
|
||||
|
||||
public async encryptSession(data: Record<string, any>): Promise<any> {
|
||||
public async encryptSession(data: Record<string, any>): Promise<Curve25519SessionData> {
|
||||
const plainText: Record<string, any> = Object.assign({}, data);
|
||||
delete plainText.session_id;
|
||||
delete plainText.room_id;
|
||||
@ -788,7 +788,7 @@ export class Aes256 implements BackupAlgorithm {
|
||||
|
||||
public get untrusted(): boolean { return false; }
|
||||
|
||||
public encryptSession(data: Record<string, any>): Promise<any> {
|
||||
public encryptSession(data: Record<string, any>): Promise<IEncryptedPayload> {
|
||||
const plainText: Record<string, any> = Object.assign({}, data);
|
||||
delete plainText.session_id;
|
||||
delete plainText.room_id;
|
||||
|
@ -258,7 +258,7 @@ export class DehydrationManager {
|
||||
}
|
||||
|
||||
logger.log("Preparing fallback keys");
|
||||
const fallbackKeys = {};
|
||||
const fallbackKeys: Record<string, IOneTimeKey> = {};
|
||||
for (const [keyId, key] of Object.entries(fallbacks.curve25519)) {
|
||||
const k: IOneTimeKey = { key, fallback: true };
|
||||
const signature = account.sign(anotherjson.stringify(k));
|
||||
|
@ -72,7 +72,8 @@ export class DeviceInfo {
|
||||
const res = new DeviceInfo(deviceId);
|
||||
for (const prop in obj) {
|
||||
if (obj.hasOwnProperty(prop)) {
|
||||
res[prop] = obj[prop];
|
||||
// @ts-ignore - this is messy and typescript doesn't like it
|
||||
res[prop as keyof IDevice] = obj[prop as keyof IDevice];
|
||||
}
|
||||
}
|
||||
return res;
|
||||
|
@ -88,6 +88,9 @@ import { CryptoStore } from "./store/base";
|
||||
import { IVerificationChannel } from "./verification/request/Channel";
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter";
|
||||
import { IContent } from "../models/event";
|
||||
import { ISyncResponse } from "../sync-accumulator";
|
||||
import { ISignatures } from "../@types/signed";
|
||||
import { IMessage } from "./algorithms/olm";
|
||||
|
||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||
|
||||
@ -100,7 +103,7 @@ const defaultVerificationMethods = {
|
||||
// to start.
|
||||
[SHOW_QR_CODE_METHOD]: IllegalMethod,
|
||||
[SCAN_QR_CODE_METHOD]: IllegalMethod,
|
||||
};
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* verification method names
|
||||
@ -109,7 +112,7 @@ const defaultVerificationMethods = {
|
||||
export const verificationMethods = {
|
||||
RECIPROCATE_QR_CODE: ReciprocateQRCode.NAME,
|
||||
SAS: SASVerification.NAME,
|
||||
};
|
||||
} as const;
|
||||
|
||||
export type VerificationMethod = keyof typeof verificationMethods | string;
|
||||
|
||||
@ -200,18 +203,13 @@ interface IUserOlmSession {
|
||||
}[];
|
||||
}
|
||||
|
||||
interface ISyncDeviceLists {
|
||||
changed: string[];
|
||||
left: string[];
|
||||
}
|
||||
|
||||
export interface IRoomKeyRequestRecipient {
|
||||
userId: string;
|
||||
deviceId: string;
|
||||
}
|
||||
|
||||
interface ISignableObject {
|
||||
signatures?: object;
|
||||
signatures?: ISignatures;
|
||||
unsigned?: object;
|
||||
}
|
||||
|
||||
@ -231,14 +229,25 @@ export interface IRequestsMap {
|
||||
}
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
export interface IEncryptedContent {
|
||||
algorithm: string;
|
||||
export interface IOlmEncryptedContent {
|
||||
algorithm: typeof olmlib.OLM_ALGORITHM;
|
||||
sender_key: string;
|
||||
ciphertext: Record<string, string>;
|
||||
[ToDeviceMessageId]: string;
|
||||
ciphertext: Record<string, IMessage>;
|
||||
[ToDeviceMessageId]?: string;
|
||||
}
|
||||
|
||||
export interface IMegolmEncryptedContent {
|
||||
algorithm: typeof olmlib.MEGOLM_ALGORITHM;
|
||||
sender_key: string;
|
||||
session_id: string;
|
||||
device_id: string;
|
||||
ciphertext: string;
|
||||
[ToDeviceMessageId]?: string;
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
export type IEncryptedContent = IOlmEncryptedContent | IMegolmEncryptedContent;
|
||||
|
||||
export enum CryptoEvent {
|
||||
DeviceVerificationChanged = "deviceVerificationChanged",
|
||||
UserTrustStatusChanged = "userTrustStatusChanged",
|
||||
@ -382,7 +391,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
private readonly clientStore: IStore,
|
||||
public readonly cryptoStore: CryptoStore,
|
||||
private readonly roomList: RoomList,
|
||||
verificationMethods: Array<keyof typeof defaultVerificationMethods | typeof VerificationBase>,
|
||||
verificationMethods: Array<VerificationMethod | (typeof VerificationBase & { NAME: string })>,
|
||||
) {
|
||||
super();
|
||||
this.reEmitter = new TypedReEmitter(this);
|
||||
@ -2947,7 +2956,10 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
* @param {Object} syncDeviceLists device_lists field from /sync, or response from
|
||||
* /keys/changes
|
||||
*/
|
||||
public async handleDeviceListChanges(syncData: ISyncStateData, syncDeviceLists: ISyncDeviceLists): Promise<void> {
|
||||
public async handleDeviceListChanges(
|
||||
syncData: ISyncStateData,
|
||||
syncDeviceLists: Required<ISyncResponse>["device_lists"],
|
||||
): Promise<void> {
|
||||
// Initial syncs don't have device change lists. We'll either get the complete list
|
||||
// of changes for the interval or will have invalidated everything in willProcessSync
|
||||
if (!syncData.oldSyncToken) return;
|
||||
@ -3087,15 +3099,14 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
* @param {Object} deviceLists device_lists field from /sync, or response from
|
||||
* /keys/changes
|
||||
*/
|
||||
private async evalDeviceListChanges(deviceLists: ISyncDeviceLists): Promise<void> {
|
||||
if (deviceLists.changed && Array.isArray(deviceLists.changed)) {
|
||||
private async evalDeviceListChanges(deviceLists: Required<ISyncResponse>["device_lists"]): Promise<void> {
|
||||
if (Array.isArray(deviceLists?.changed)) {
|
||||
deviceLists.changed.forEach((u) => {
|
||||
this.deviceList.invalidateUserDeviceList(u);
|
||||
});
|
||||
}
|
||||
|
||||
if (deviceLists.left && Array.isArray(deviceLists.left) &&
|
||||
deviceLists.left.length) {
|
||||
if (Array.isArray(deviceLists?.left) && deviceLists.left.length) {
|
||||
// Check we really don't share any rooms with these users
|
||||
// any more: the server isn't required to give us the
|
||||
// exact correct set.
|
||||
@ -3515,9 +3526,9 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
// same order we sent them, the other end will get this first, set up the new session,
|
||||
// then get the keyshare request and send the key over this new session (because it
|
||||
// is the session it has most recently received a message on).
|
||||
const encryptedContent = {
|
||||
const encryptedContent: IEncryptedContent = {
|
||||
algorithm: olmlib.OLM_ALGORITHM,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key,
|
||||
sender_key: this.olmDevice.deviceCurve25519Key!,
|
||||
ciphertext: {},
|
||||
[ToDeviceMessageId]: uuidv4(),
|
||||
};
|
||||
@ -3842,7 +3853,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
*
|
||||
* @param {Object} obj Object to which we will add a 'signatures' property
|
||||
*/
|
||||
public async signObject(obj: object & ISignableObject): Promise<void> {
|
||||
public async signObject<T extends ISignableObject & object>(obj: T): Promise<void> {
|
||||
const sigs = obj.signatures || {};
|
||||
const unsigned = obj.unsigned;
|
||||
|
||||
|
@ -31,6 +31,7 @@ import { IClaimOTKsResult, MatrixClient } from "../client";
|
||||
import { ISignatures } from "../@types/signed";
|
||||
import { MatrixEvent } from "../models/event";
|
||||
import { EventType } from "../@types/event";
|
||||
import { IMessage } from "./algorithms/olm";
|
||||
|
||||
enum Algorithm {
|
||||
Olm = "m.olm.v1.curve25519-aes-sha2",
|
||||
@ -75,7 +76,7 @@ export interface IOlmSessionResult {
|
||||
* has been encrypted into `resultsObject`
|
||||
*/
|
||||
export async function encryptMessageForDevice(
|
||||
resultsObject: Record<string, string>,
|
||||
resultsObject: Record<string, IMessage>,
|
||||
ourUserId: string,
|
||||
ourDeviceId: string | undefined,
|
||||
olmDevice: OlmDevice,
|
||||
@ -124,6 +125,7 @@ export async function encryptMessageForDevice(
|
||||
recipient_keys: {
|
||||
"ed25519": recipientDevice.getFingerprint(),
|
||||
},
|
||||
...payloadFields,
|
||||
};
|
||||
|
||||
// TODO: technically, a bunch of that stuff only needs to be included for
|
||||
@ -131,11 +133,7 @@ export async function encryptMessageForDevice(
|
||||
// involved in the session. If we're looking to reduce data transfer in the
|
||||
// future, we could elide them for subsequent messages.
|
||||
|
||||
Object.assign(payload, payloadFields);
|
||||
|
||||
resultsObject[deviceKey] = await olmDevice.encryptMessage(
|
||||
deviceKey, sessionId, JSON.stringify(payload),
|
||||
);
|
||||
resultsObject[deviceKey] = await olmDevice.encryptMessage(deviceKey, sessionId, JSON.stringify(payload));
|
||||
}
|
||||
|
||||
interface IExistingOlmSession {
|
||||
@ -495,7 +493,7 @@ export async function verifySignature(
|
||||
* @param {string} pubKey The public key (ignored if key is a seed)
|
||||
* @returns {string} the signature for the object
|
||||
*/
|
||||
export function pkSign(obj: IObject, key: PkSigning, userId: string, pubKey: string): string {
|
||||
export function pkSign(obj: object & IObject, key: Uint8Array | PkSigning, userId: string, pubKey: string): string {
|
||||
let createdKey = false;
|
||||
if (key instanceof Uint8Array) {
|
||||
const keyObj = new global.Olm.PkSigning();
|
||||
|
@ -69,7 +69,7 @@ export interface CryptoStore {
|
||||
deleteOutgoingRoomKeyRequest(requestId: string, expectedState: number): Promise<OutgoingRoomKeyRequest | null>;
|
||||
|
||||
// Olm Account
|
||||
getAccount(txn: unknown, func: (accountPickle: string | null) => void);
|
||||
getAccount(txn: unknown, func: (accountPickle: string | null) => void): void;
|
||||
storeAccount(txn: unknown, accountPickle: string): void;
|
||||
getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey> | null) => void): void;
|
||||
getSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(
|
||||
|
@ -338,20 +338,20 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
}
|
||||
|
||||
public unmarkSessionsNeedingBackup(sessions: ISession[]): Promise<void> {
|
||||
const sessionsNeedingBackup
|
||||
= getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
|
||||
const sessionsNeedingBackup = getJsonItem<{
|
||||
[senderKeySessionId: string]: string;
|
||||
}>(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
|
||||
for (const session of sessions) {
|
||||
delete sessionsNeedingBackup[session.senderKey + '/' + session.sessionId];
|
||||
}
|
||||
setJsonItem(
|
||||
this.store, KEY_SESSIONS_NEEDING_BACKUP, sessionsNeedingBackup,
|
||||
);
|
||||
setJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP, sessionsNeedingBackup);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public markSessionsNeedingBackup(sessions: ISession[]): Promise<void> {
|
||||
const sessionsNeedingBackup
|
||||
= getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
|
||||
const sessionsNeedingBackup = getJsonItem<{
|
||||
[senderKeySessionId: string]: boolean;
|
||||
}>(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
|
||||
for (const session of sessions) {
|
||||
sessionsNeedingBackup[session.senderKey + '/' + session.sessionId] = true;
|
||||
}
|
||||
|
@ -283,21 +283,21 @@ export class QRCodeData {
|
||||
private static generateBuffer(qrData: IQrData): Buffer {
|
||||
let buf = Buffer.alloc(0); // we'll concat our way through life
|
||||
|
||||
const appendByte = (b): void => {
|
||||
const appendByte = (b: number): void => {
|
||||
const tmpBuf = Buffer.from([b]);
|
||||
buf = Buffer.concat([buf, tmpBuf]);
|
||||
};
|
||||
const appendInt = (i): void => {
|
||||
const appendInt = (i: number): void => {
|
||||
const tmpBuf = Buffer.alloc(2);
|
||||
tmpBuf.writeInt16BE(i, 0);
|
||||
buf = Buffer.concat([buf, tmpBuf]);
|
||||
};
|
||||
const appendStr = (s, enc, withLengthPrefix = true): void => {
|
||||
const appendStr = (s: string, enc: BufferEncoding, withLengthPrefix = true): void => {
|
||||
const tmpBuf = Buffer.from(s, enc);
|
||||
if (withLengthPrefix) appendInt(tmpBuf.byteLength);
|
||||
buf = Buffer.concat([buf, tmpBuf]);
|
||||
};
|
||||
const appendEncBase64 = (b64): void => {
|
||||
const appendEncBase64 = (b64: string): void => {
|
||||
const b = decodeBase64(b64);
|
||||
const tmpBuf = Buffer.from(b);
|
||||
buf = Buffer.concat([buf, tmpBuf]);
|
||||
@ -307,7 +307,7 @@ export class QRCodeData {
|
||||
appendStr(qrData.prefix, "ascii", false);
|
||||
appendByte(qrData.version);
|
||||
appendByte(qrData.mode);
|
||||
appendStr(qrData.transactionId, "utf-8");
|
||||
appendStr(qrData.transactionId!, "utf-8");
|
||||
appendEncBase64(qrData.firstKeyB64);
|
||||
appendEncBase64(qrData.secondKeyB64);
|
||||
appendEncBase64(qrData.secretB64);
|
||||
|
@ -140,7 +140,7 @@ function generateEmojiSas(sasBytes: number[]): EmojiMapping[] {
|
||||
const sasGenerators = {
|
||||
decimal: generateDecimalSas,
|
||||
emoji: generateEmojiSas,
|
||||
};
|
||||
} as const;
|
||||
|
||||
export interface IGeneratedSas {
|
||||
decimal?: [number, number, number];
|
||||
@ -154,11 +154,12 @@ export interface ISasEvent {
|
||||
mismatch(): void;
|
||||
}
|
||||
|
||||
function generateSas(sasBytes: number[], methods: string[]): IGeneratedSas {
|
||||
function generateSas(sasBytes: Uint8Array, methods: string[]): IGeneratedSas {
|
||||
const sas: IGeneratedSas = {};
|
||||
for (const method of methods) {
|
||||
if (method in sasGenerators) {
|
||||
sas[method] = sasGenerators[method](sasBytes);
|
||||
// @ts-ignore - ts doesn't like us mixing types like this
|
||||
sas[method] = sasGenerators[method](Array.from(sasBytes));
|
||||
}
|
||||
}
|
||||
return sas;
|
||||
@ -168,15 +169,14 @@ const macMethods = {
|
||||
"hkdf-hmac-sha256": "calculate_mac",
|
||||
"org.matrix.msc3783.hkdf-hmac-sha256": "calculate_mac_fixed_base64",
|
||||
"hmac-sha256": "calculate_mac_long_kdf",
|
||||
};
|
||||
} as const;
|
||||
|
||||
type Method = keyof typeof macMethods;
|
||||
type MacMethod = keyof typeof macMethods;
|
||||
|
||||
function calculateMAC(olmSAS: OlmSAS, method: Method) {
|
||||
return function(...args): string {
|
||||
const macFunction = olmSAS[macMethods[method]];
|
||||
const mac: string = macFunction.apply(olmSAS, args);
|
||||
logger.log("SAS calculateMAC:", method, args, mac);
|
||||
function calculateMAC(olmSAS: OlmSAS, method: MacMethod) {
|
||||
return function(input: string, info: string): string {
|
||||
const mac = olmSAS[macMethods[method]](input, info);
|
||||
logger.log("SAS calculateMAC:", method, [input, info], mac);
|
||||
return mac;
|
||||
};
|
||||
}
|
||||
@ -202,15 +202,17 @@ const calculateKeyAgreement = {
|
||||
+ sas.channel.transactionId;
|
||||
return olmSAS.generate_bytes(sasInfo, bytes);
|
||||
},
|
||||
};
|
||||
} as const;
|
||||
|
||||
type KeyAgreement = keyof typeof calculateKeyAgreement;
|
||||
|
||||
/* lists of algorithms/methods that are supported. The key agreement, hashes,
|
||||
* and MAC lists should be sorted in order of preference (most preferred
|
||||
* first).
|
||||
*/
|
||||
const KEY_AGREEMENT_LIST = ["curve25519-hkdf-sha256", "curve25519"];
|
||||
const KEY_AGREEMENT_LIST: KeyAgreement[] = ["curve25519-hkdf-sha256", "curve25519"];
|
||||
const HASHES_LIST = ["sha256"];
|
||||
const MAC_LIST: Method[] = ["org.matrix.msc3783.hkdf-hmac-sha256", "hkdf-hmac-sha256", "hmac-sha256"];
|
||||
const MAC_LIST: MacMethod[] = ["org.matrix.msc3783.hkdf-hmac-sha256", "hkdf-hmac-sha256", "hmac-sha256"];
|
||||
const SAS_LIST = Object.keys(sasGenerators);
|
||||
|
||||
const KEY_AGREEMENT_SET = new Set(KEY_AGREEMENT_LIST);
|
||||
@ -299,10 +301,10 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
|
||||
}
|
||||
|
||||
private async verifyAndCheckMAC(
|
||||
keyAgreement: string,
|
||||
keyAgreement: KeyAgreement,
|
||||
sasMethods: string[],
|
||||
olmSAS: OlmSAS,
|
||||
macMethod: Method,
|
||||
macMethod: MacMethod,
|
||||
): Promise<void> {
|
||||
const sasBytes = calculateKeyAgreement[keyAgreement](this, olmSAS, 6);
|
||||
const verifySAS = new Promise<void>((resolve, reject) => {
|
||||
@ -354,7 +356,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
|
||||
throw new SwitchStartEventError(this.startEvent);
|
||||
}
|
||||
|
||||
let e;
|
||||
let e: MatrixEvent;
|
||||
try {
|
||||
e = await this.waitForEvent(EventType.KeyVerificationAccept);
|
||||
} finally {
|
||||
@ -445,8 +447,8 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
|
||||
}
|
||||
}
|
||||
|
||||
private sendMAC(olmSAS: OlmSAS, method: Method): Promise<void> {
|
||||
const mac = {};
|
||||
private sendMAC(olmSAS: OlmSAS, method: MacMethod): Promise<void> {
|
||||
const mac: Record<string, string> = {};
|
||||
const keyList: string[] = [];
|
||||
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
|
||||
+ this.baseApis.getUserId() + this.baseApis.deviceId
|
||||
@ -455,7 +457,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
|
||||
|
||||
const deviceKeyId = `ed25519:${this.baseApis.deviceId}`;
|
||||
mac[deviceKeyId] = calculateMAC(olmSAS, method)(
|
||||
this.baseApis.getDeviceEd25519Key(),
|
||||
this.baseApis.getDeviceEd25519Key()!,
|
||||
baseInfo + deviceKeyId,
|
||||
);
|
||||
keyList.push(deviceKeyId);
|
||||
@ -477,7 +479,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
|
||||
return this.send(EventType.KeyVerificationMac, { mac, keys });
|
||||
}
|
||||
|
||||
private async checkMAC(olmSAS: OlmSAS, content: IContent, method: Method): Promise<void> {
|
||||
private async checkMAC(olmSAS: OlmSAS, content: IContent, method: MacMethod): Promise<void> {
|
||||
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
|
||||
+ this.userId + this.deviceId
|
||||
+ this.baseApis.getUserId() + this.baseApis.deviceId
|
||||
|
@ -148,17 +148,17 @@ export class FilterComponent {
|
||||
"types": function(v: string): boolean {
|
||||
return matchesWildcard(eventType, v);
|
||||
},
|
||||
};
|
||||
} as const;
|
||||
|
||||
for (const name in literalKeys) {
|
||||
const matchFunc = literalKeys[name];
|
||||
const matchFunc = literalKeys[<keyof typeof literalKeys>name];
|
||||
const notName = "not_" + name;
|
||||
const disallowedValues: string[] = this.filterJson[notName];
|
||||
const disallowedValues = this.filterJson[<`not_${keyof typeof literalKeys}`>notName];
|
||||
if (disallowedValues?.some(matchFunc)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const allowedValues: string[] = this.filterJson[name];
|
||||
const allowedValues = this.filterJson[name as keyof typeof literalKeys];
|
||||
if (allowedValues && !allowedValues.some(matchFunc)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ import { MatrixEvent } from "./models/event";
|
||||
* @param {string} keyNesting
|
||||
* @param {*} val
|
||||
*/
|
||||
function setProp(obj: object, keyNesting: string, val: any): void {
|
||||
const nestedKeys = keyNesting.split(".");
|
||||
function setProp(obj: Record<string, any>, keyNesting: string, val: any): void {
|
||||
const nestedKeys = keyNesting.split(".") as [keyof typeof obj];
|
||||
let currentObj = obj;
|
||||
for (let i = 0; i < (nestedKeys.length - 1); i++) {
|
||||
if (!currentObj[nestedKeys[i]]) {
|
||||
@ -70,8 +70,7 @@ interface IStateFilter extends IRoomEventFilter {}
|
||||
|
||||
interface IRoomFilter {
|
||||
not_rooms?: string[];
|
||||
rooms?: string[];
|
||||
ephemeral?: IRoomEventFilter;
|
||||
rooms?: string[];ephemeral?: IRoomEventFilter;
|
||||
include_leave?: boolean;
|
||||
state?: IStateFilter;
|
||||
timeline?: IRoomEventFilter;
|
||||
|
@ -318,19 +318,19 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
||||
// 'membership' at the event level (rather than the content level) is a legacy
|
||||
// field that Element never otherwise looks at, but it will still take up a lot
|
||||
// of space if we don't intern it.
|
||||
["state_key", "type", "sender", "room_id", "membership"].forEach((prop) => {
|
||||
(["state_key", "type", "sender", "room_id", "membership"] as const).forEach((prop) => {
|
||||
if (typeof event[prop] !== "string") return;
|
||||
event[prop] = internaliseString(event[prop]);
|
||||
event[prop] = internaliseString(event[prop]!);
|
||||
});
|
||||
|
||||
["membership", "avatar_url", "displayname"].forEach((prop) => {
|
||||
(["membership", "avatar_url", "displayname"] as const).forEach((prop) => {
|
||||
if (typeof event.content?.[prop] !== "string") return;
|
||||
event.content[prop] = internaliseString(event.content[prop]);
|
||||
event.content[prop] = internaliseString(event.content[prop]!);
|
||||
});
|
||||
|
||||
["rel_type"].forEach((prop) => {
|
||||
(["rel_type"] as const).forEach((prop) => {
|
||||
if (typeof event.content?.["m.relates_to"]?.[prop] !== "string") return;
|
||||
event.content["m.relates_to"][prop] = internaliseString(event.content["m.relates_to"][prop]);
|
||||
event.content["m.relates_to"][prop] = internaliseString(event.content["m.relates_to"][prop]!);
|
||||
});
|
||||
|
||||
this.txnId = event.txn_id;
|
||||
@ -1120,7 +1120,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
||||
|
||||
for (const key in this.event) {
|
||||
if (this.event.hasOwnProperty(key) && !REDACT_KEEP_KEYS.has(key)) {
|
||||
delete this.event[key];
|
||||
delete this.event[key as keyof IEvent];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1129,7 +1129,9 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
||||
this.clearEvent = undefined;
|
||||
}
|
||||
|
||||
const keeps = REDACT_KEEP_CONTENT_MAP[this.getType()] || {};
|
||||
const keeps = this.getType() in REDACT_KEEP_CONTENT_MAP
|
||||
? REDACT_KEEP_CONTENT_MAP[this.getType() as keyof typeof REDACT_KEEP_CONTENT_MAP]
|
||||
: {};
|
||||
const content = this.getContent();
|
||||
for (const key in content) {
|
||||
if (content.hasOwnProperty(key) && !keeps[key]) {
|
||||
@ -1212,7 +1214,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
||||
*
|
||||
* @returns {object} The redaction event JSON, or an empty object
|
||||
*/
|
||||
public getRedactionEvent(): object | null {
|
||||
public getRedactionEvent(): IEvent | {} | null {
|
||||
if (!this.isRedacted()) return null;
|
||||
|
||||
if (this.clearEvent?.unsigned) {
|
||||
@ -1512,7 +1514,8 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
||||
const ev = new MatrixEvent(JSON.parse(JSON.stringify(this.event)));
|
||||
for (const [p, v] of Object.entries(this)) {
|
||||
if (p !== "event") { // exclude the thing we just cloned
|
||||
ev[p] = v;
|
||||
// @ts-ignore - XXX: this is just nasty
|
||||
ev[p as keyof MatrixEvent] = v;
|
||||
}
|
||||
}
|
||||
return ev;
|
||||
@ -1612,7 +1615,7 @@ const REDACT_KEEP_KEYS = new Set([
|
||||
]);
|
||||
|
||||
// a map from state event type to the .content keys we keep when an event is redacted
|
||||
const REDACT_KEEP_CONTENT_MAP = {
|
||||
const REDACT_KEEP_CONTENT_MAP: Record<string, Record<string, 1>> = {
|
||||
[EventType.RoomMember]: { 'membership': 1 },
|
||||
[EventType.RoomCreate]: { 'creator': 1 },
|
||||
[EventType.RoomJoinRules]: { 'join_rule': 1 },
|
||||
@ -1621,7 +1624,7 @@ const REDACT_KEEP_CONTENT_MAP = {
|
||||
'kick': 1, 'redact': 1, 'state_default': 1,
|
||||
'users': 1, 'users_default': 1,
|
||||
},
|
||||
};
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Fires when an event is decrypted
|
||||
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||
import { UnstableValue } from "matrix-events-sdk";
|
||||
|
||||
import { MatrixClient } from "../client";
|
||||
import { MatrixEvent } from "./event";
|
||||
import { IContent, MatrixEvent } from "./event";
|
||||
import { EventTimeline } from "./event-timeline";
|
||||
import { Preset } from "../@types/partials";
|
||||
import { globToRegexp } from "../utils";
|
||||
@ -262,7 +262,7 @@ export class IgnoredInvites {
|
||||
*/
|
||||
public async getOrCreateSourceRooms(): Promise<Room[]> {
|
||||
const ignoreInvitesPolicies = this.getIgnoreInvitesPolicies();
|
||||
let sources = ignoreInvitesPolicies.sources;
|
||||
let sources: string[] = ignoreInvitesPolicies.sources;
|
||||
|
||||
// Validate `sources`. If it is invalid, trash out the current `sources`
|
||||
// and create a new list of sources from `target`.
|
||||
@ -272,11 +272,11 @@ export class IgnoredInvites {
|
||||
hasChanges = true;
|
||||
sources = [];
|
||||
}
|
||||
let sourceRooms: Room[] = sources
|
||||
let sourceRooms = sources
|
||||
// `sources` could contain non-string / invalid room ids
|
||||
.filter(roomId => typeof roomId === "string")
|
||||
.map(roomId => this.client.getRoom(roomId))
|
||||
.filter(room => !!room);
|
||||
.filter(room => !!room) as Room[];
|
||||
if (sourceRooms.length != sources.length) {
|
||||
hasChanges = true;
|
||||
}
|
||||
@ -327,7 +327,7 @@ export class IgnoredInvites {
|
||||
*/
|
||||
private getPoliciesAndIgnoreInvitesPolicies():
|
||||
{policies: {[key: string]: any}, ignoreInvitesPolicies: {[key: string]: any}} {
|
||||
let policies = {};
|
||||
let policies: IContent = {};
|
||||
for (const key of [POLICIES_ACCOUNT_EVENT_TYPE.name, POLICIES_ACCOUNT_EVENT_TYPE.altName]) {
|
||||
if (!key) {
|
||||
continue;
|
||||
|
@ -22,7 +22,7 @@ import { RoomMember } from "./room-member";
|
||||
import { logger } from '../logger';
|
||||
import * as utils from "../utils";
|
||||
import { EventType, UNSTABLE_MSC2716_MARKER } from "../@types/event";
|
||||
import { MatrixEvent, MatrixEventEvent } from "./event";
|
||||
import { IEvent, MatrixEvent, MatrixEventEvent } from "./event";
|
||||
import { MatrixClient } from "../client";
|
||||
import { GuestAccess, HistoryVisibility, IJoinRuleEventContent, JoinRule } from "../@types/partials";
|
||||
import { TypedEventEmitter } from "./typed-event-emitter";
|
||||
@ -53,6 +53,20 @@ enum OobStatus {
|
||||
Finished,
|
||||
}
|
||||
|
||||
export interface IPowerLevelsContent {
|
||||
users?: Record<string, number>;
|
||||
events?: Record<string, number>;
|
||||
// eslint-disable-next-line camelcase
|
||||
users_default?: number;
|
||||
// eslint-disable-next-line camelcase
|
||||
events_default?: number;
|
||||
// eslint-disable-next-line camelcase
|
||||
state_default?: number;
|
||||
ban?: number;
|
||||
kick?: number;
|
||||
redact?: number;
|
||||
}
|
||||
|
||||
export enum RoomStateEvent {
|
||||
Events = "RoomState.events",
|
||||
Members = "RoomState.members",
|
||||
@ -499,7 +513,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
||||
const beacon = this.beacons.get(beaconIdentifier)!;
|
||||
|
||||
if (event.isRedacted()) {
|
||||
if (beacon.beaconInfoId === event.getRedactionEvent()?.['redacts']) {
|
||||
if (beacon.beaconInfoId === (<IEvent>event.getRedactionEvent())?.redacts) {
|
||||
beacon.destroy();
|
||||
this.beacons.delete(beaconIdentifier);
|
||||
}
|
||||
@ -724,17 +738,17 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
||||
* @param {number} powerLevel The power level of the member
|
||||
* @return {boolean} true if the given power level is sufficient
|
||||
*/
|
||||
public hasSufficientPowerLevelFor(action: string, powerLevel: number): boolean {
|
||||
public hasSufficientPowerLevelFor(action: "ban" | "kick" | "redact", powerLevel: number): boolean {
|
||||
const powerLevelsEvent = this.getStateEvents(EventType.RoomPowerLevels, "");
|
||||
|
||||
let powerLevels = {};
|
||||
let powerLevels: IPowerLevelsContent = {};
|
||||
if (powerLevelsEvent) {
|
||||
powerLevels = powerLevelsEvent.getContent();
|
||||
}
|
||||
|
||||
let requiredLevel = 50;
|
||||
if (utils.isNumber(powerLevels[action])) {
|
||||
requiredLevel = powerLevels[action];
|
||||
requiredLevel = powerLevels[action]!;
|
||||
}
|
||||
|
||||
return powerLevel >= requiredLevel;
|
||||
@ -807,8 +821,8 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
||||
private maySendEventOfType(eventType: EventType | string, userId: string, state: boolean): boolean {
|
||||
const powerLevelsEvent = this.getStateEvents(EventType.RoomPowerLevels, '');
|
||||
|
||||
let powerLevels;
|
||||
let eventsLevels = {};
|
||||
let powerLevels: IPowerLevelsContent;
|
||||
let eventsLevels: Record<EventType | string, number> = {};
|
||||
|
||||
let stateDefault = 0;
|
||||
let eventsDefault = 0;
|
||||
@ -818,20 +832,20 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
||||
eventsLevels = powerLevels.events || {};
|
||||
|
||||
if (Number.isSafeInteger(powerLevels.state_default)) {
|
||||
stateDefault = powerLevels.state_default;
|
||||
stateDefault = powerLevels.state_default!;
|
||||
} else {
|
||||
stateDefault = 50;
|
||||
}
|
||||
|
||||
const userPowerLevel = powerLevels.users && powerLevels.users[userId];
|
||||
if (Number.isSafeInteger(userPowerLevel)) {
|
||||
powerLevel = userPowerLevel;
|
||||
powerLevel = userPowerLevel!;
|
||||
} else if (Number.isSafeInteger(powerLevels.users_default)) {
|
||||
powerLevel = powerLevels.users_default;
|
||||
powerLevel = powerLevels.users_default!;
|
||||
}
|
||||
|
||||
if (Number.isSafeInteger(powerLevels.events_default)) {
|
||||
eventsDefault = powerLevels.events_default;
|
||||
eventsDefault = powerLevels.events_default!;
|
||||
}
|
||||
}
|
||||
|
||||
@ -877,7 +891,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
||||
*/
|
||||
public getJoinRule(): JoinRule {
|
||||
const joinRuleEvent = this.getStateEvents(EventType.RoomJoinRules, "");
|
||||
const joinRuleContent = joinRuleEvent?.getContent<IJoinRuleEventContent>() ?? {};
|
||||
const joinRuleContent: Partial<IJoinRuleEventContent> = joinRuleEvent?.getContent() ?? {};
|
||||
return joinRuleContent["join_rule"] || JoinRule.Invite;
|
||||
}
|
||||
|
||||
|
@ -20,8 +20,8 @@ limitations under the License.
|
||||
|
||||
export interface IRoomSummary {
|
||||
"m.heroes": string[];
|
||||
"m.joined_member_count": number;
|
||||
"m.invited_member_count": number;
|
||||
"m.joined_member_count"?: number;
|
||||
"m.invited_member_count"?: number;
|
||||
}
|
||||
|
||||
interface IInfo {
|
||||
|
@ -1312,10 +1312,10 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
const joinedCount = summary["m.joined_member_count"];
|
||||
const invitedCount = summary["m.invited_member_count"];
|
||||
if (Number.isInteger(joinedCount)) {
|
||||
this.currentState.setJoinedMemberCount(joinedCount);
|
||||
this.currentState.setJoinedMemberCount(joinedCount!);
|
||||
}
|
||||
if (Number.isInteger(invitedCount)) {
|
||||
this.currentState.setInvitedMemberCount(invitedCount);
|
||||
this.currentState.setInvitedMemberCount(invitedCount!);
|
||||
}
|
||||
if (Array.isArray(heroes)) {
|
||||
// be cautious about trusting server values,
|
||||
@ -1826,7 +1826,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
||||
timelineSet.getFilter(),
|
||||
);
|
||||
|
||||
timelineSet.getLiveTimeline().setPaginationToken(end, Direction.Backward);
|
||||
timelineSet.getLiveTimeline().setPaginationToken(end ?? null, Direction.Backward);
|
||||
|
||||
if (!events.length) return;
|
||||
|
||||
|
@ -518,13 +518,13 @@ export class PushProcessor {
|
||||
* @return {object} The push rule, or null if no such rule was found
|
||||
*/
|
||||
public getPushRuleById(ruleId: string): IPushRule | null {
|
||||
for (const scope of ['global']) {
|
||||
for (const scope of ['global'] as const) {
|
||||
if (this.client.pushRules?.[scope] === undefined) continue;
|
||||
|
||||
for (const kind of RULEKINDS_IN_ORDER) {
|
||||
if (this.client.pushRules[scope][kind] === undefined) continue;
|
||||
|
||||
for (const rule of this.client.pushRules[scope][kind]) {
|
||||
for (const rule of this.client.pushRules[scope][kind]!) {
|
||||
if (rule.rule_id === ruleId) return rule;
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
|
||||
* @see module:scheduler~queueAlgorithm
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
public static QUEUE_MESSAGES(event: MatrixEvent): "message" | null {
|
||||
public static QUEUE_MESSAGES(event: MatrixEvent): string | null {
|
||||
// enqueue messages or events that associate with another event (redactions and relations)
|
||||
if (event.getType() === EventType.RoomMessage || event.hasAssociation()) {
|
||||
// put these events in the 'message' queue.
|
||||
|
@ -22,7 +22,7 @@ import { ClientEvent, IStoredClientOpts, MatrixClient, PendingEventOrdering } fr
|
||||
import { ISyncStateData, SyncState, _createAndReEmitRoom } from "./sync";
|
||||
import { MatrixEvent } from "./models/event";
|
||||
import { Crypto } from "./crypto";
|
||||
import { IMinimalEvent, IRoomEvent, IStateEvent, IStrippedState } from "./sync-accumulator";
|
||||
import { IMinimalEvent, IRoomEvent, IStateEvent, IStrippedState, ISyncResponse } from "./sync-accumulator";
|
||||
import { MatrixError } from "./http-api";
|
||||
import {
|
||||
Extension,
|
||||
@ -44,7 +44,18 @@ import { RoomMemberEvent } from "./models/room-member";
|
||||
// keepAlive is successful but the server /sync fails.
|
||||
const FAILED_SYNC_ERROR_THRESHOLD = 3;
|
||||
|
||||
class ExtensionE2EE implements Extension {
|
||||
type ExtensionE2EERequest = {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
type ExtensionE2EEResponse = Pick<ISyncResponse,
|
||||
"device_lists" |
|
||||
"device_one_time_keys_count" |
|
||||
"device_unused_fallback_key_types" |
|
||||
"org.matrix.msc2732.device_unused_fallback_key_types"
|
||||
>;
|
||||
|
||||
class ExtensionE2EE implements Extension<ExtensionE2EERequest, ExtensionE2EEResponse> {
|
||||
public constructor(private readonly crypto: Crypto) {}
|
||||
|
||||
public name(): string {
|
||||
@ -55,7 +66,7 @@ class ExtensionE2EE implements Extension {
|
||||
return ExtensionState.PreProcess;
|
||||
}
|
||||
|
||||
public onRequest(isInitial: boolean): object | undefined {
|
||||
public onRequest(isInitial: boolean): ExtensionE2EERequest | undefined {
|
||||
if (!isInitial) {
|
||||
return undefined;
|
||||
}
|
||||
@ -64,7 +75,7 @@ class ExtensionE2EE implements Extension {
|
||||
};
|
||||
}
|
||||
|
||||
public async onResponse(data: object): Promise<void> {
|
||||
public async onResponse(data: ExtensionE2EEResponse): Promise<void> {
|
||||
// Handle device list updates
|
||||
if (data["device_lists"]) {
|
||||
await this.crypto.handleDeviceListChanges({
|
||||
@ -92,7 +103,18 @@ class ExtensionE2EE implements Extension {
|
||||
}
|
||||
}
|
||||
|
||||
class ExtensionToDevice implements Extension {
|
||||
type ExtensionToDeviceRequest = {
|
||||
since?: string;
|
||||
limit?: number;
|
||||
enabled?: boolean;
|
||||
};
|
||||
|
||||
type ExtensionToDeviceResponse = {
|
||||
events: Required<ISyncResponse>["to_device"]["events"];
|
||||
next_batch: string | null;
|
||||
};
|
||||
|
||||
class ExtensionToDevice implements Extension<ExtensionToDeviceRequest, ExtensionToDeviceResponse> {
|
||||
private nextBatch: string | null = null;
|
||||
|
||||
public constructor(private readonly client: MatrixClient) {}
|
||||
@ -105,8 +127,8 @@ class ExtensionToDevice implements Extension {
|
||||
return ExtensionState.PreProcess;
|
||||
}
|
||||
|
||||
public onRequest(isInitial: boolean): object {
|
||||
const extReq = {
|
||||
public onRequest(isInitial: boolean): ExtensionToDeviceRequest {
|
||||
const extReq: ExtensionToDeviceRequest = {
|
||||
since: this.nextBatch !== null ? this.nextBatch : undefined,
|
||||
};
|
||||
if (isInitial) {
|
||||
@ -116,11 +138,10 @@ class ExtensionToDevice implements Extension {
|
||||
return extReq;
|
||||
}
|
||||
|
||||
public async onResponse(data: object): Promise<void> {
|
||||
public async onResponse(data: ExtensionToDeviceResponse): Promise<void> {
|
||||
const cancelledKeyVerificationTxns: string[] = [];
|
||||
data["events"] = data["events"] || [];
|
||||
data["events"]
|
||||
.map(this.client.getEventMapper())
|
||||
data.events
|
||||
?.map(this.client.getEventMapper())
|
||||
.map((toDeviceEvent) => { // map is a cheap inline forEach
|
||||
// We want to flag m.key.verification.start events as cancelled
|
||||
// if there's an accompanying m.key.verification.cancel event, so
|
||||
@ -165,11 +186,20 @@ class ExtensionToDevice implements Extension {
|
||||
},
|
||||
);
|
||||
|
||||
this.nextBatch = data["next_batch"];
|
||||
this.nextBatch = data.next_batch;
|
||||
}
|
||||
}
|
||||
|
||||
class ExtensionAccountData implements Extension {
|
||||
type ExtensionAccountDataRequest = {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
type ExtensionAccountDataResponse = {
|
||||
global: IMinimalEvent[];
|
||||
rooms: Record<string, IMinimalEvent[]>;
|
||||
};
|
||||
|
||||
class ExtensionAccountData implements Extension<ExtensionAccountDataRequest, ExtensionAccountDataResponse> {
|
||||
public constructor(private readonly client: MatrixClient) {}
|
||||
|
||||
public name(): string {
|
||||
@ -180,7 +210,7 @@ class ExtensionAccountData implements Extension {
|
||||
return ExtensionState.PostProcess;
|
||||
}
|
||||
|
||||
public onRequest(isInitial: boolean): object | undefined {
|
||||
public onRequest(isInitial: boolean): ExtensionAccountDataRequest | undefined {
|
||||
if (!isInitial) {
|
||||
return undefined;
|
||||
}
|
||||
@ -189,7 +219,7 @@ class ExtensionAccountData implements Extension {
|
||||
};
|
||||
}
|
||||
|
||||
public onResponse(data: {global: object[], rooms: Record<string, object[]>}): void {
|
||||
public onResponse(data: ExtensionAccountDataResponse): void {
|
||||
if (data.global && data.global.length > 0) {
|
||||
this.processGlobalAccountData(data.global);
|
||||
}
|
||||
@ -208,9 +238,9 @@ class ExtensionAccountData implements Extension {
|
||||
}
|
||||
}
|
||||
|
||||
private processGlobalAccountData(globalAccountData: object[]): void {
|
||||
private processGlobalAccountData(globalAccountData: IMinimalEvent[]): void {
|
||||
const events = mapEvents(this.client, undefined, globalAccountData);
|
||||
const prevEventsMap = events.reduce((m, c) => {
|
||||
const prevEventsMap = events.reduce<Record<string, MatrixEvent | undefined>>((m, c) => {
|
||||
m[c.getType()] = this.client.store.getAccountData(c.getType());
|
||||
return m;
|
||||
}, {});
|
||||
@ -233,7 +263,15 @@ class ExtensionAccountData implements Extension {
|
||||
}
|
||||
}
|
||||
|
||||
class ExtensionTyping implements Extension {
|
||||
type ExtensionTypingRequest = {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
type ExtensionTypingResponse = {
|
||||
rooms: Record<string, IMinimalEvent>;
|
||||
};
|
||||
|
||||
class ExtensionTyping implements Extension<ExtensionTypingRequest, ExtensionTypingResponse> {
|
||||
public constructor(private readonly client: MatrixClient) {}
|
||||
|
||||
public name(): string {
|
||||
@ -244,7 +282,7 @@ class ExtensionTyping implements Extension {
|
||||
return ExtensionState.PostProcess;
|
||||
}
|
||||
|
||||
public onRequest(isInitial: boolean): object | undefined {
|
||||
public onRequest(isInitial: boolean): ExtensionTypingRequest | undefined {
|
||||
if (!isInitial) {
|
||||
return undefined; // don't send a JSON object for subsequent requests, we don't need to.
|
||||
}
|
||||
@ -253,20 +291,26 @@ class ExtensionTyping implements Extension {
|
||||
};
|
||||
}
|
||||
|
||||
public onResponse(data: {rooms: Record<string, IMinimalEvent>}): void {
|
||||
if (!data || !data.rooms) {
|
||||
public onResponse(data: ExtensionTypingResponse): void {
|
||||
if (!data?.rooms) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const roomId in data.rooms) {
|
||||
processEphemeralEvents(
|
||||
this.client, roomId, [data.rooms[roomId]],
|
||||
);
|
||||
processEphemeralEvents(this.client, roomId, [data.rooms[roomId]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExtensionReceipts implements Extension {
|
||||
type ExtensionReceiptsRequest = {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
type ExtensionReceiptsResponse = {
|
||||
rooms: Record<string, IMinimalEvent>;
|
||||
};
|
||||
|
||||
class ExtensionReceipts implements Extension<ExtensionReceiptsRequest, ExtensionReceiptsResponse> {
|
||||
public constructor(private readonly client: MatrixClient) {}
|
||||
|
||||
public name(): string {
|
||||
@ -277,7 +321,7 @@ class ExtensionReceipts implements Extension {
|
||||
return ExtensionState.PostProcess;
|
||||
}
|
||||
|
||||
public onRequest(isInitial: boolean): object | undefined {
|
||||
public onRequest(isInitial: boolean): ExtensionReceiptsRequest | undefined {
|
||||
if (isInitial) {
|
||||
return {
|
||||
enabled: true,
|
||||
@ -286,8 +330,8 @@ class ExtensionReceipts implements Extension {
|
||||
return undefined; // don't send a JSON object for subsequent requests, we don't need to.
|
||||
}
|
||||
|
||||
public onResponse(data: {rooms: Record<string, IMinimalEvent>}): void {
|
||||
if (!data || !data.rooms) {
|
||||
public onResponse(data: ExtensionReceiptsResponse): void {
|
||||
if (!data?.rooms) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -336,7 +380,7 @@ export class SlidingSyncSdk {
|
||||
|
||||
this.slidingSync.on(SlidingSyncEvent.Lifecycle, this.onLifecycle.bind(this));
|
||||
this.slidingSync.on(SlidingSyncEvent.RoomData, this.onRoomData.bind(this));
|
||||
const extensions: Extension[] = [
|
||||
const extensions: Extension<any, any>[] = [
|
||||
new ExtensionToDevice(this.client),
|
||||
new ExtensionAccountData(this.client),
|
||||
new ExtensionTyping(this.client),
|
||||
@ -533,7 +577,7 @@ export class SlidingSyncSdk {
|
||||
// room::decryptCriticalEvent is in charge of decrypting all the events
|
||||
// required for a client to function properly
|
||||
let timelineEvents = mapEvents(this.client, room.roomId, roomData.timeline, false);
|
||||
const ephemeralEvents = []; // TODO this.mapSyncEventsFormat(joinObj.ephemeral);
|
||||
const ephemeralEvents: MatrixEvent[] = []; // TODO this.mapSyncEventsFormat(joinObj.ephemeral);
|
||||
|
||||
// TODO: handle threaded / beacon events
|
||||
|
||||
@ -967,12 +1011,14 @@ function ensureNameEvent(client: MatrixClient, roomId: string, roomData: MSC3575
|
||||
return roomData;
|
||||
}
|
||||
|
||||
type TaggedEvent = (IStrippedState | IRoomEvent | IStateEvent | IMinimalEvent) & { room_id?: string };
|
||||
|
||||
// Helper functions which set up JS SDK structs are below and are identical to the sync v2 counterparts,
|
||||
// just outside the class.
|
||||
function mapEvents(client: MatrixClient, roomId: string | undefined, events: object[], decrypt = true): MatrixEvent[] {
|
||||
const mapper = client.getEventMapper({ decrypt });
|
||||
return (events as Array<IStrippedState | IRoomEvent | IStateEvent | IMinimalEvent>).map(function(e) {
|
||||
e["room_id"] = roomId;
|
||||
return (events as TaggedEvent[]).map(function(e) {
|
||||
e.room_id = roomId;
|
||||
return mapper(e);
|
||||
});
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ export interface MSC3575SlidingSyncResponse {
|
||||
txn_id?: string;
|
||||
lists: ListResponse[];
|
||||
rooms: Record<string, MSC3575RoomData>;
|
||||
extensions: object;
|
||||
extensions: Record<string, object>;
|
||||
}
|
||||
|
||||
export enum SlidingSyncState {
|
||||
@ -265,7 +265,7 @@ export enum ExtensionState {
|
||||
/**
|
||||
* An interface that must be satisfied to register extensions
|
||||
*/
|
||||
export interface Extension {
|
||||
export interface Extension<Req extends {}, Res extends {}> {
|
||||
/**
|
||||
* The extension name to go under 'extensions' in the request body.
|
||||
* @returns The JSON key.
|
||||
@ -277,12 +277,12 @@ export interface Extension {
|
||||
* @param isInitial True when this is part of the initial request (send sticky params)
|
||||
* @returns The request JSON to send.
|
||||
*/
|
||||
onRequest(isInitial: boolean): object | undefined;
|
||||
onRequest(isInitial: boolean): Req | undefined;
|
||||
/**
|
||||
* A function which is called when there is response JSON under this extension.
|
||||
* @param data The response JSON under the extension name.
|
||||
*/
|
||||
onResponse(data: object);
|
||||
onResponse(data: Res): void;
|
||||
/**
|
||||
* Controls when onResponse should be called.
|
||||
* @returns The state when it should be called.
|
||||
@ -353,7 +353,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
// a defer to resolve/reject depending on whether they were successfully sent or not.
|
||||
private txnIdDefers: (IDeferred<string> & { txnId: string})[] = [];
|
||||
// map of extension name to req/resp handler
|
||||
private extensions: Record<string, Extension> = {};
|
||||
private extensions: Record<string, Extension<any, any>> = {};
|
||||
|
||||
private desiredRoomSubscriptions = new Set<string>(); // the *desired* room subscriptions
|
||||
private confirmedRoomSubscriptions = new Set<string>();
|
||||
@ -519,22 +519,22 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
* Register an extension to send with the /sync request.
|
||||
* @param ext The extension to register.
|
||||
*/
|
||||
public registerExtension(ext: Extension): void {
|
||||
public registerExtension(ext: Extension<any, any>): void {
|
||||
if (this.extensions[ext.name()]) {
|
||||
throw new Error(`registerExtension: ${ext.name()} already exists as an extension`);
|
||||
}
|
||||
this.extensions[ext.name()] = ext;
|
||||
}
|
||||
|
||||
private getExtensionRequest(isInitial: boolean): object {
|
||||
const ext = {};
|
||||
private getExtensionRequest(isInitial: boolean): Record<string, object | undefined> {
|
||||
const ext: Record<string, object | undefined> = {};
|
||||
Object.keys(this.extensions).forEach((extName) => {
|
||||
ext[extName] = this.extensions[extName].onRequest(isInitial);
|
||||
});
|
||||
return ext;
|
||||
}
|
||||
|
||||
private onPreExtensionsResponse(ext: object): void {
|
||||
private onPreExtensionsResponse(ext: Record<string, object>): void {
|
||||
Object.keys(ext).forEach((extName) => {
|
||||
if (this.extensions[extName].when() == ExtensionState.PreProcess) {
|
||||
this.extensions[extName].onResponse(ext[extName]);
|
||||
@ -542,7 +542,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
||||
});
|
||||
}
|
||||
|
||||
private onPostExtensionsResponse(ext: object): void {
|
||||
private onPostExtensionsResponse(ext: Record<string, object>): void {
|
||||
Object.keys(ext).forEach((extName) => {
|
||||
if (this.extensions[extName].when() == ExtensionState.PostProcess) {
|
||||
this.extensions[extName].onResponse(ext[extName]);
|
||||
|
@ -123,7 +123,7 @@ export interface IStore {
|
||||
* @param {string} token The token associated with these events.
|
||||
* @param {boolean} toStart True if these are paginated results.
|
||||
*/
|
||||
storeEvents(room: Room, events: MatrixEvent[], token: string, toStart: boolean): void;
|
||||
storeEvents(room: Room, events: MatrixEvent[], token: string | null, toStart: boolean): void;
|
||||
|
||||
/**
|
||||
* Store a filter.
|
||||
|
@ -315,10 +315,10 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
||||
|
||||
const minStateKeyProm = reqAsCursorPromise(
|
||||
roomIndex.openKeyCursor(roomRange, "next"),
|
||||
).then((cursor) => cursor && cursor.primaryKey[1]);
|
||||
).then((cursor) => (<IDBValidKey[]>cursor?.primaryKey)[1]);
|
||||
const maxStateKeyProm = reqAsCursorPromise(
|
||||
roomIndex.openKeyCursor(roomRange, "prev"),
|
||||
).then((cursor) => cursor && cursor.primaryKey[1]);
|
||||
).then((cursor) => (<IDBValidKey[]>cursor?.primaryKey)[1]);
|
||||
const [minStateKey, maxStateKey] = await Promise.all(
|
||||
[minStateKeyProm, maxStateKeyProm]);
|
||||
|
||||
|
@ -217,7 +217,7 @@ export class MemoryStore implements IStore {
|
||||
* @param {string} token The token associated with these events.
|
||||
* @param {boolean} toStart True if these are paginated results.
|
||||
*/
|
||||
public storeEvents(room: Room, events: MatrixEvent[], token: string, toStart: boolean): void {
|
||||
public storeEvents(room: Room, events: MatrixEvent[], token: string | null, toStart: boolean): void {
|
||||
// no-op because they've already been added to the room instance.
|
||||
}
|
||||
|
||||
|
@ -139,7 +139,7 @@ export class StubStore implements IStore {
|
||||
* @param {string} token The token associated with these events.
|
||||
* @param {boolean} toStart True if these are paginated results.
|
||||
*/
|
||||
public storeEvents(room: Room, events: MatrixEvent[], token: string, toStart: boolean): void {}
|
||||
public storeEvents(room: Room, events: MatrixEvent[], token: string | null, toStart: boolean): void {}
|
||||
|
||||
/**
|
||||
* Store a filter.
|
||||
|
@ -116,7 +116,7 @@ interface IAccountData {
|
||||
events: IMinimalEvent[];
|
||||
}
|
||||
|
||||
interface IToDeviceEvent {
|
||||
export interface IToDeviceEvent {
|
||||
content: IContent;
|
||||
sender: string;
|
||||
type: string;
|
||||
@ -127,8 +127,8 @@ interface IToDevice {
|
||||
}
|
||||
|
||||
interface IDeviceLists {
|
||||
changed: string[];
|
||||
left: string[];
|
||||
changed?: string[];
|
||||
left?: string[];
|
||||
}
|
||||
|
||||
export interface ISyncResponse {
|
||||
@ -139,6 +139,9 @@ export interface ISyncResponse {
|
||||
to_device?: IToDevice;
|
||||
device_lists?: IDeviceLists;
|
||||
device_one_time_keys_count?: Record<string, number>;
|
||||
|
||||
device_unused_fallback_key_types?: string[];
|
||||
"org.matrix.msc2732.device_unused_fallback_key_types"?: string[];
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
@ -182,6 +185,12 @@ export interface ISyncData {
|
||||
roomsData: IRooms;
|
||||
}
|
||||
|
||||
type TaggedEvent = IRoomEvent & { _localTs?: number };
|
||||
|
||||
function isTaggedEvent(event: IRoomEvent): event is TaggedEvent {
|
||||
return "_localTs" in event && event["_localTs"] !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The purpose of this class is to accumulate /sync responses such that a
|
||||
* complete "initial" JSON response can be returned which accurately represents
|
||||
@ -473,18 +482,15 @@ export class SyncAccumulator {
|
||||
// - existing state which didn't come down /sync.
|
||||
// - State events under the 'state' key.
|
||||
// - State events in the 'timeline'.
|
||||
if (data.state && data.state.events) {
|
||||
data.state.events.forEach((e) => {
|
||||
data.state?.events?.forEach((e) => {
|
||||
setState(currentData._currentState, e);
|
||||
});
|
||||
}
|
||||
if (data.timeline && data.timeline.events) {
|
||||
data.timeline.events.forEach((e, index) => {
|
||||
data.timeline?.events?.forEach((e, index) => {
|
||||
// this nops if 'e' isn't a state event
|
||||
setState(currentData._currentState, e);
|
||||
// append the event to the timeline. The back-pagination token
|
||||
// corresponds to the first event in the timeline
|
||||
let transformedEvent: IRoomEvent & { _localTs?: number };
|
||||
let transformedEvent: TaggedEvent;
|
||||
if (!fromDatabase) {
|
||||
transformedEvent = Object.assign({}, e);
|
||||
if (transformedEvent.unsigned !== undefined) {
|
||||
@ -501,7 +507,6 @@ export class SyncAccumulator {
|
||||
token: index === 0 ? (data.timeline.prev_batch ?? null) : null,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// attempt to prune the timeline by jumping between events which have
|
||||
// pagination tokens.
|
||||
@ -581,7 +586,7 @@ export class SyncAccumulator {
|
||||
room_id: roomId,
|
||||
content: {
|
||||
// $event_id: { "m.read": { $user_id: $json } }
|
||||
},
|
||||
} as IContent,
|
||||
};
|
||||
|
||||
for (const [userId, receiptData] of Object.entries(roomData._readReceipts)) {
|
||||
@ -626,8 +631,8 @@ export class SyncAccumulator {
|
||||
}
|
||||
|
||||
let transformedEvent: (IRoomEvent | IStateEvent) & { _localTs?: number };
|
||||
if (!forDatabase && msgData.event["_localTs"]) {
|
||||
// This means we have to copy each event so we can fix it up to
|
||||
if (!forDatabase && isTaggedEvent(msgData.event)) {
|
||||
// This means we have to copy each event, so we can fix it up to
|
||||
// set a correct 'age' parameter whilst keeping the local timestamp
|
||||
// on our stored event. If this turns out to be a bottleneck, it could
|
||||
// be optimised either by doing this in the main process after the data
|
||||
@ -641,7 +646,7 @@ export class SyncAccumulator {
|
||||
}
|
||||
delete transformedEvent._localTs;
|
||||
transformedEvent.unsigned = transformedEvent.unsigned || {};
|
||||
transformedEvent.unsigned.age = Date.now() - msgData.event["_localTs"];
|
||||
transformedEvent.unsigned.age = Date.now() - msgData.event._localTs!;
|
||||
} else {
|
||||
transformedEvent = msgData.event;
|
||||
}
|
||||
|
16
src/sync.ts
16
src/sync.ts
@ -106,7 +106,7 @@ function getFilterName(userId: string, suffix?: string): string {
|
||||
return `FILTER_SYNC_${userId}` + (suffix ? "_" + suffix : "");
|
||||
}
|
||||
|
||||
function debuglog(...params): void {
|
||||
function debuglog(...params: any[]): void {
|
||||
if (!DEBUG) return;
|
||||
logger.log(...params);
|
||||
}
|
||||
@ -1093,7 +1093,7 @@ export class SyncApi {
|
||||
// handle non-room account_data
|
||||
if (Array.isArray(data.account_data?.events)) {
|
||||
const events = data.account_data.events.map(client.getEventMapper());
|
||||
const prevEventsMap = events.reduce((m, c) => {
|
||||
const prevEventsMap = events.reduce<Record<string, MatrixEvent | undefined>>((m, c) => {
|
||||
m[c.getType()!] = client.store.getAccountData(c.getType());
|
||||
return m;
|
||||
}, {});
|
||||
@ -1473,12 +1473,13 @@ export class SyncApi {
|
||||
this.opts.crypto.updateOneTimeKeyCount(currentCount);
|
||||
}
|
||||
if (this.opts.crypto &&
|
||||
(data["device_unused_fallback_key_types"] ||
|
||||
data["org.matrix.msc2732.device_unused_fallback_key_types"])) {
|
||||
(data.device_unused_fallback_key_types ||
|
||||
data["org.matrix.msc2732.device_unused_fallback_key_types"])
|
||||
) {
|
||||
// The presence of device_unused_fallback_key_types indicates that the
|
||||
// server supports fallback keys. If there's no unused
|
||||
// signed_curve25519 fallback key we need a new one.
|
||||
const unusedFallbackKeys = data["device_unused_fallback_key_types"] ||
|
||||
const unusedFallbackKeys = data.device_unused_fallback_key_types ||
|
||||
data["org.matrix.msc2732.device_unused_fallback_key_types"];
|
||||
this.opts.crypto.setNeedsNewFallback(
|
||||
Array.isArray(unusedFallbackKeys) &&
|
||||
@ -1607,9 +1608,10 @@ export class SyncApi {
|
||||
return [];
|
||||
}
|
||||
const mapper = this.client.getEventMapper({ decrypt });
|
||||
return (obj.events as Array<IStrippedState | IRoomEvent | IStateEvent | IMinimalEvent>).map(function(e) {
|
||||
type TaggedEvent = (IStrippedState | IRoomEvent | IStateEvent | IMinimalEvent) & { room_id?: string };
|
||||
return (obj.events as TaggedEvent[]).map(function(e) {
|
||||
if (room) {
|
||||
e["room_id"] = room.roomId;
|
||||
e.room_id = room.roomId;
|
||||
}
|
||||
return mapper(e);
|
||||
});
|
||||
|
16
src/utils.ts
16
src/utils.ts
@ -316,7 +316,7 @@ export function deepSortedObjectEntries(obj: any): [string, any][] {
|
||||
* @param {*} value the value to test
|
||||
* @return {boolean} whether or not value is a finite number without type-coercion
|
||||
*/
|
||||
export function isNumber(value: any): boolean {
|
||||
export function isNumber(value: any): value is number {
|
||||
return typeof value === 'number' && isFinite(value);
|
||||
}
|
||||
|
||||
@ -428,8 +428,8 @@ export interface IDeferred<T> {
|
||||
|
||||
// Returns a Deferred
|
||||
export function defer<T = void>(): IDeferred<T> {
|
||||
let resolve;
|
||||
let reject;
|
||||
let resolve!: IDeferred<T>["resolve"];
|
||||
let reject!: IDeferred<T>["reject"];
|
||||
|
||||
const promise = new Promise<T>((_resolve, _reject) => {
|
||||
resolve = _resolve;
|
||||
@ -665,18 +665,22 @@ export function compare(a: string, b: string): number {
|
||||
* @param {Object} source
|
||||
* @returns the target object
|
||||
*/
|
||||
export function recursivelyAssign(target: Object, source: Object, ignoreNullish = false): any {
|
||||
export function recursivelyAssign<T1 extends T2, T2 extends Record<string, any>>(
|
||||
target: T1,
|
||||
source: T2,
|
||||
ignoreNullish = false,
|
||||
): T1 & T2 {
|
||||
for (const [sourceKey, sourceValue] of Object.entries(source)) {
|
||||
if (target[sourceKey] instanceof Object && sourceValue) {
|
||||
recursivelyAssign(target[sourceKey], sourceValue);
|
||||
continue;
|
||||
}
|
||||
if ((sourceValue !== null && sourceValue !== undefined) || !ignoreNullish) {
|
||||
target[sourceKey] = sourceValue;
|
||||
target[sourceKey as keyof T1] = sourceValue;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return target;
|
||||
return target as T1 & T2;
|
||||
}
|
||||
|
||||
function getContentTimestampWithFallback(event: MatrixEvent): number {
|
||||
|
@ -27,7 +27,7 @@ import { parse as parseSdp, write as writeSdp } from "sdp-transform";
|
||||
|
||||
import { logger } from '../logger';
|
||||
import * as utils from '../utils';
|
||||
import { MatrixEvent } from '../models/event';
|
||||
import { IContent, MatrixEvent } from '../models/event';
|
||||
import { EventType, ToDeviceMessageId } from '../@types/event';
|
||||
import { RoomMember } from '../models/room-member';
|
||||
import { randomString } from '../randomstring';
|
||||
@ -1101,7 +1101,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.terminate(CallParty.Local, reason, !suppressEvent);
|
||||
// We don't want to send hangup here if we didn't even get to sending an invite
|
||||
if ([CallState.Fledgling, CallState.WaitLocalMedia].includes(this.state)) return;
|
||||
const content = {};
|
||||
const content: IContent = {};
|
||||
// Don't send UserHangup reason to older clients
|
||||
if ((this.opponentVersion && this.opponentVersion !== 0) || reason !== CallErrorCode.UserHangup) {
|
||||
content["reason"] = reason;
|
||||
|
@ -5,7 +5,6 @@
|
||||
"esModuleInterop": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": false,
|
||||
"noUnusedLocals": true,
|
||||
"noEmit": true,
|
||||
"declaration": true,
|
||||
|
@ -1423,9 +1423,9 @@
|
||||
dependencies:
|
||||
lodash "^4.17.21"
|
||||
|
||||
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.13.tgz":
|
||||
version "3.2.13"
|
||||
resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.13.tgz#0109fde93bcc61def851f79826c9384c073b5175"
|
||||
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz":
|
||||
version "3.2.14"
|
||||
resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz#acd96c00a881d0f462e1f97a56c73742c8dbc984"
|
||||
|
||||
"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3":
|
||||
version "2.1.8-no-fsevents.3"
|
||||
|
Reference in New Issue
Block a user