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
37
.github/workflows/static_analysis.yml
vendored
37
.github/workflows/static_analysis.yml
vendored
@ -64,7 +64,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate Docs
|
- name: Generate Docs
|
||||||
run: "yarn run gendoc"
|
run: "yarn run gendoc"
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
@ -72,38 +72,3 @@ jobs:
|
|||||||
path: _docs
|
path: _docs
|
||||||
# We'll only use this in a workflow_run, then we're done with it
|
# We'll only use this in a workflow_run, then we're done with it
|
||||||
retention-days: 1
|
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: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:types": "tsc -p tsconfig-build.json --emitDeclarationOnly",
|
||||||
"build:compile": "babel -d lib --verbose --extensions \".ts,.js\" src",
|
"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",
|
"build:minify-browser": "terser dist/browser-matrix.js --compress --mangle --source-map --output dist/browser-matrix.min.js",
|
||||||
"gendoc": "typedoc",
|
"gendoc": "typedoc",
|
||||||
"lint": "yarn lint:types && yarn lint:js",
|
"lint": "yarn lint:types && yarn lint:js",
|
||||||
@ -33,9 +33,9 @@
|
|||||||
"matrix-org"
|
"matrix-org"
|
||||||
],
|
],
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
"browser": "./lib/browser-index.js",
|
"browser": "./lib/browser-index.ts",
|
||||||
"matrix_src_main": "./src/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_main": "./lib/index.js",
|
||||||
"matrix_lib_typings": "./lib/index.d.ts",
|
"matrix_lib_typings": "./lib/index.d.ts",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
@ -80,7 +80,7 @@
|
|||||||
"@babel/preset-typescript": "^7.12.7",
|
"@babel/preset-typescript": "^7.12.7",
|
||||||
"@babel/register": "^7.12.10",
|
"@babel/register": "^7.12.10",
|
||||||
"@casualbot/jest-sonar-reporter": "^2.2.5",
|
"@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/bs58": "^4.0.1",
|
||||||
"@types/content-type": "^1.1.5",
|
"@types/content-type": "^1.1.5",
|
||||||
"@types/domexception": "^4.0.0",
|
"@types/domexception": "^4.0.0",
|
||||||
|
@ -183,7 +183,7 @@ export class TestClient {
|
|||||||
this.httpBackend.when('POST', '/keys/query').respond<IDownloadKeyResult>(
|
this.httpBackend.when('POST', '/keys/query').respond<IDownloadKeyResult>(
|
||||||
200, (_path, content) => {
|
200, (_path, content) => {
|
||||||
Object.keys(response.device_keys).forEach((userId) => {
|
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;
|
return response;
|
||||||
});
|
});
|
||||||
|
@ -15,19 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import "../../dist/browser-matrix"; // uses browser-matrix instead of the src
|
import "../../dist/browser-matrix"; // uses browser-matrix instead of the src
|
||||||
import type { MatrixClient, ClientEvent } from "../../src";
|
import type { default as BrowserMatrix } from "../../src/browser-index";
|
||||||
|
|
||||||
declare global {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
||||||
namespace NodeJS {
|
|
||||||
interface Global {
|
|
||||||
matrixcs: {
|
|
||||||
MatrixClient: typeof MatrixClient;
|
|
||||||
ClientEvent: typeof ClientEvent;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// stub for browser-matrix browserify tests
|
// stub for browser-matrix browserify tests
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -43,4 +31,4 @@ afterAll(() => {
|
|||||||
global.matrixcs = {
|
global.matrixcs = {
|
||||||
...global.matrixcs,
|
...global.matrixcs,
|
||||||
timeoutSignal: () => new AbortController().signal,
|
timeoutSignal: () => new AbortController().signal,
|
||||||
};
|
} as typeof BrowserMatrix;
|
||||||
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import HttpBackend from "matrix-mock-request";
|
import HttpBackend from "matrix-mock-request";
|
||||||
|
|
||||||
import "./setupTests";// uses browser-matrix instead of the src
|
import "./setupTests"; // uses browser-matrix instead of the src
|
||||||
import type { MatrixClient } from "../../src";
|
import type { MatrixClient } from "../../src";
|
||||||
|
|
||||||
const USER_ID = "@user:test.server";
|
const USER_ID = "@user:test.server";
|
||||||
@ -65,15 +65,16 @@ describe("Browserify Test", function() {
|
|||||||
const syncData = {
|
const syncData = {
|
||||||
next_batch: "batch1",
|
next_batch: "batch1",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[ROOM_ID]: {
|
||||||
};
|
timeline: {
|
||||||
syncData.rooms.join[ROOM_ID] = {
|
events: [
|
||||||
timeline: {
|
event,
|
||||||
events: [
|
],
|
||||||
event,
|
limited: false,
|
||||||
],
|
},
|
||||||
limited: false,
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ const ROOM_ID = "!room:id";
|
|||||||
*
|
*
|
||||||
* @return {object} sync response
|
* @return {object} sync response
|
||||||
*/
|
*/
|
||||||
function getSyncResponse(roomMembers) {
|
function getSyncResponse(roomMembers: string[]) {
|
||||||
const stateEvents = [
|
const stateEvents = [
|
||||||
testUtils.mkEvent({
|
testUtils.mkEvent({
|
||||||
type: 'm.room.encryption',
|
type: 'm.room.encryption',
|
||||||
@ -43,12 +43,10 @@ function getSyncResponse(roomMembers) {
|
|||||||
|
|
||||||
Array.prototype.push.apply(
|
Array.prototype.push.apply(
|
||||||
stateEvents,
|
stateEvents,
|
||||||
roomMembers.map(
|
roomMembers.map((m) => testUtils.mkMembership({
|
||||||
(m) => testUtils.mkMembership({
|
mship: 'join',
|
||||||
mship: 'join',
|
sender: m,
|
||||||
sender: m,
|
})),
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const syncResponse = {
|
const syncResponse = {
|
||||||
@ -73,8 +71,8 @@ describe("DeviceList management:", function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sessionStoreBackend;
|
let aliceTestClient: TestClient;
|
||||||
let aliceTestClient;
|
let sessionStoreBackend: Storage;
|
||||||
|
|
||||||
async function createTestClient() {
|
async function createTestClient() {
|
||||||
const testClient = new TestClient(
|
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() {
|
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() {
|
return aliceTestClient.start().then(function() {
|
||||||
const syncResponse = getSyncResponse(['@bob:xyz']);
|
const syncResponse = getSyncResponse(['@bob:xyz']);
|
||||||
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
|
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", () => {
|
it.skip("We should not get confused by out-of-order device query responses", () => {
|
||||||
// https://github.com/vector-im/element-web/issues/3126
|
// 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(() => {
|
return aliceTestClient.start().then(() => {
|
||||||
aliceTestClient.httpBackend.when('GET', '/sync').respond(
|
aliceTestClient.httpBackend.when('GET', '/sync').respond(
|
||||||
200, getSyncResponse(['@bob:xyz', '@chris:abc']));
|
200, getSyncResponse(['@bob:xyz', '@chris:abc']));
|
||||||
@ -164,11 +168,12 @@ describe("DeviceList management:", function() {
|
|||||||
aliceTestClient.httpBackend.flush('/keys/query', 1).then(
|
aliceTestClient.httpBackend.flush('/keys/query', 1).then(
|
||||||
() => aliceTestClient.httpBackend.flush('/send/', 1),
|
() => aliceTestClient.httpBackend.flush('/send/', 1),
|
||||||
),
|
),
|
||||||
aliceTestClient.client.crypto.deviceList.saveIfDirty(),
|
aliceTestClient.client.crypto!.deviceList.saveIfDirty(),
|
||||||
]);
|
]);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
aliceTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
// @ts-ignore accessing a protected field
|
||||||
expect(data.syncToken).toEqual(1);
|
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
|
||||||
|
expect(data!.syncToken).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// invalidate bob's and chris's device lists in separate syncs
|
// 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);
|
return aliceTestClient.httpBackend.flush('/keys/query', 1);
|
||||||
}).then((flushed) => {
|
}).then((flushed) => {
|
||||||
expect(flushed).toEqual(0);
|
expect(flushed).toEqual(0);
|
||||||
return aliceTestClient.client.crypto.deviceList.saveIfDirty();
|
return aliceTestClient.client.crypto!.deviceList.saveIfDirty();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
aliceTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
// @ts-ignore accessing a protected field
|
||||||
const bobStat = data.trackingStatus['@bob:xyz'];
|
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
|
||||||
|
const bobStat = data!.trackingStatus['@bob:xyz'];
|
||||||
if (bobStat != 1 && bobStat != 2) {
|
if (bobStat != 1 && bobStat != 2) {
|
||||||
throw new Error('Unexpected status for bob: wanted 1 or 2, got ' +
|
throw new Error('Unexpected status for bob: wanted 1 or 2, got ' +
|
||||||
bobStat);
|
bobStat);
|
||||||
}
|
}
|
||||||
const chrisStat = data.trackingStatus['@chris:abc'];
|
const chrisStat = data!.trackingStatus['@chris:abc'];
|
||||||
if (chrisStat != 1 && chrisStat != 2) {
|
if (chrisStat != 1 && chrisStat != 2) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Unexpected status for chris: wanted 1 or 2, got ' + chrisStat,
|
'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
|
// wait for the client to stop processing the response
|
||||||
return aliceTestClient.client.downloadKeys(['@bob:xyz']);
|
return aliceTestClient.client.downloadKeys(['@bob:xyz']);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return aliceTestClient.client.crypto.deviceList.saveIfDirty();
|
return aliceTestClient.client.crypto!.deviceList.saveIfDirty();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
aliceTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
// @ts-ignore accessing a protected field
|
||||||
const bobStat = data.trackingStatus['@bob:xyz'];
|
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
|
||||||
|
const bobStat = data!.trackingStatus['@bob:xyz'];
|
||||||
expect(bobStat).toEqual(3);
|
expect(bobStat).toEqual(3);
|
||||||
const chrisStat = data.trackingStatus['@chris:abc'];
|
const chrisStat = data!.trackingStatus['@chris:abc'];
|
||||||
if (chrisStat != 1 && chrisStat != 2) {
|
if (chrisStat != 1 && chrisStat != 2) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Unexpected status for chris: wanted 1 or 2, got ' + bobStat,
|
'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
|
// wait for the client to stop processing the response
|
||||||
return aliceTestClient.client.downloadKeys(['@chris:abc']);
|
return aliceTestClient.client.downloadKeys(['@chris:abc']);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return aliceTestClient.client.crypto.deviceList.saveIfDirty();
|
return aliceTestClient.client.crypto!.deviceList.saveIfDirty();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
aliceTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
// @ts-ignore accessing a protected field
|
||||||
const bobStat = data.trackingStatus['@bob:xyz'];
|
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
|
||||||
const chrisStat = data.trackingStatus['@bob:xyz'];
|
const bobStat = data!.trackingStatus['@bob:xyz'];
|
||||||
|
const chrisStat = data!.trackingStatus['@bob:xyz'];
|
||||||
|
|
||||||
expect(bobStat).toEqual(3);
|
expect(bobStat).toEqual(3);
|
||||||
expect(chrisStat).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.httpBackend.flush('/keys/query', 1);
|
||||||
await aliceTestClient.client.crypto.deviceList.saveIfDirty();
|
await aliceTestClient.client.crypto!.deviceList.saveIfDirty();
|
||||||
|
|
||||||
aliceTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
// @ts-ignore accessing a protected field
|
||||||
const bobStat = data.trackingStatus['@bob:xyz'];
|
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
|
||||||
|
const bobStat = data!.trackingStatus['@bob:xyz'];
|
||||||
|
|
||||||
// Alice should be tracking bob's device list
|
// Alice should be tracking bob's device list
|
||||||
expect(bobStat).toBeGreaterThan(
|
expect(bobStat).toBeGreaterThan(
|
||||||
@ -322,10 +331,11 @@ describe("DeviceList management:", function() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await aliceTestClient.flushSync();
|
await aliceTestClient.flushSync();
|
||||||
await aliceTestClient.client.crypto.deviceList.saveIfDirty();
|
await aliceTestClient.client.crypto!.deviceList.saveIfDirty();
|
||||||
|
|
||||||
aliceTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
// @ts-ignore accessing a protected field
|
||||||
const bobStat = data.trackingStatus['@bob:xyz'];
|
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
|
||||||
|
const bobStat = data!.trackingStatus['@bob:xyz'];
|
||||||
|
|
||||||
// Alice should have marked bob's device list as untracked
|
// Alice should have marked bob's device list as untracked
|
||||||
expect(bobStat).toEqual(
|
expect(bobStat).toEqual(
|
||||||
@ -359,15 +369,14 @@ describe("DeviceList management:", function() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await aliceTestClient.flushSync();
|
await aliceTestClient.flushSync();
|
||||||
await aliceTestClient.client.crypto.deviceList.saveIfDirty();
|
await aliceTestClient.client.crypto!.deviceList.saveIfDirty();
|
||||||
|
|
||||||
aliceTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
// @ts-ignore accessing a protected field
|
||||||
const bobStat = data.trackingStatus['@bob:xyz'];
|
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
|
||||||
|
const bobStat = data!.trackingStatus['@bob:xyz'];
|
||||||
|
|
||||||
// Alice should have marked bob's device list as untracked
|
// Alice should have marked bob's device list as untracked
|
||||||
expect(bobStat).toEqual(
|
expect(bobStat).toEqual(0);
|
||||||
0,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -388,9 +397,7 @@ describe("DeviceList management:", function() {
|
|||||||
const bobStat = data!.trackingStatus['@bob:xyz'];
|
const bobStat = data!.trackingStatus['@bob:xyz'];
|
||||||
|
|
||||||
// Alice should have marked bob's device list as untracked
|
// Alice should have marked bob's device list as untracked
|
||||||
expect(bobStat).toEqual(
|
expect(bobStat).toEqual(0);
|
||||||
0,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
anotherTestClient.stop();
|
anotherTestClient.stop();
|
||||||
|
@ -28,12 +28,14 @@ limitations under the License.
|
|||||||
// load olm before the sdk if possible
|
// load olm before the sdk if possible
|
||||||
import '../olm-loader';
|
import '../olm-loader';
|
||||||
|
|
||||||
|
import type { Session } from "@matrix-org/olm";
|
||||||
import { logger } from '../../src/logger';
|
import { logger } from '../../src/logger';
|
||||||
import * as testUtils from "../test-utils/test-utils";
|
import * as testUtils from "../test-utils/test-utils";
|
||||||
import { TestClient } from "../TestClient";
|
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 { ClientEvent, IContent, ISendEventResponse, MatrixClient, MatrixEvent } from "../../src/matrix";
|
||||||
import { DeviceInfo } from '../../src/crypto/deviceinfo';
|
import { DeviceInfo } from '../../src/crypto/deviceinfo';
|
||||||
|
import { IDeviceKeys, IOneTimeKey } from "../../src/crypto/dehydration";
|
||||||
|
|
||||||
let aliTestClient: TestClient;
|
let aliTestClient: TestClient;
|
||||||
const roomId = "!room:localhost";
|
const roomId = "!room:localhost";
|
||||||
@ -47,11 +49,7 @@ const bobAccessToken = "fewgfkuesa";
|
|||||||
let aliMessages: IContent[];
|
let aliMessages: IContent[];
|
||||||
let bobMessages: IContent[];
|
let bobMessages: IContent[];
|
||||||
|
|
||||||
// IMessage isn't exported by src/crypto/algorithms/olm.ts
|
type OlmPayload = ReturnType<Session["encrypt"]>;
|
||||||
interface OlmPayload {
|
|
||||||
type: number;
|
|
||||||
body: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function bobUploadsDeviceKeys(): Promise<void> {
|
async function bobUploadsDeviceKeys(): Promise<void> {
|
||||||
bobTestClient.expectDeviceKeyUpload();
|
bobTestClient.expectDeviceKeyUpload();
|
||||||
@ -71,12 +69,12 @@ function expectQueryKeys(querier: TestClient, uploader: TestClient): Promise<num
|
|||||||
// can't query keys before bob has uploaded them
|
// can't query keys before bob has uploaded them
|
||||||
expect(uploader.deviceKeys).toBeTruthy();
|
expect(uploader.deviceKeys).toBeTruthy();
|
||||||
|
|
||||||
const uploaderKeys = {};
|
const uploaderKeys: Record<string, IDeviceKeys> = {};
|
||||||
uploaderKeys[uploader.deviceId!] = uploader.deviceKeys;
|
uploaderKeys[uploader.deviceId!] = uploader.deviceKeys!;
|
||||||
querier.httpBackend.when("POST", "/keys/query")
|
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([]);
|
expect(content.device_keys![uploader.userId!]).toEqual([]);
|
||||||
const result = {};
|
const result: Record<string, Record<string, IDeviceKeys>> = {};
|
||||||
result[uploader.userId!] = uploaderKeys;
|
result[uploader.userId!] = uploaderKeys;
|
||||||
return { device_keys: result };
|
return { device_keys: result };
|
||||||
});
|
});
|
||||||
@ -94,7 +92,7 @@ async function expectAliClaimKeys(): Promise<void> {
|
|||||||
const keys = await bobTestClient.awaitOneTimeKeyUpload();
|
const keys = await bobTestClient.awaitOneTimeKeyUpload();
|
||||||
aliTestClient.httpBackend.when(
|
aliTestClient.httpBackend.when(
|
||||||
"POST", "/keys/claim",
|
"POST", "/keys/claim",
|
||||||
).respond(200, function(_path, content: IUploadKeysRequest) {
|
).respond(200, function(_path, content: IClaimKeysRequest) {
|
||||||
const claimType = content.one_time_keys![bobUserId][bobDeviceId];
|
const claimType = content.one_time_keys![bobUserId][bobDeviceId];
|
||||||
expect(claimType).toEqual("signed_curve25519");
|
expect(claimType).toEqual("signed_curve25519");
|
||||||
let keyId = '';
|
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] = {};
|
||||||
result[bobUserId][bobDeviceId] = {};
|
result[bobUserId][bobDeviceId] = {};
|
||||||
result[bobUserId][bobDeviceId][keyId] = keys[keyId];
|
result[bobUserId][bobDeviceId][keyId] = keys[keyId];
|
||||||
@ -276,22 +274,21 @@ async function recvMessage(
|
|||||||
next_batch: "x",
|
next_batch: "x",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {
|
join: {
|
||||||
|
[roomId]: {
|
||||||
|
timeline: {
|
||||||
|
events: [
|
||||||
|
testUtils.mkEvent({
|
||||||
|
type: "m.room.encrypted",
|
||||||
|
room: roomId,
|
||||||
|
content: message,
|
||||||
|
sender: sender,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
syncData.rooms.join[roomId] = {
|
|
||||||
timeline: {
|
|
||||||
events: [
|
|
||||||
testUtils.mkEvent({
|
|
||||||
type: "m.room.encrypted",
|
|
||||||
room: roomId,
|
|
||||||
content: message,
|
|
||||||
sender: sender,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
httpBackend.when("GET", "/sync").respond(200, syncData);
|
httpBackend.when("GET", "/sync").respond(200, syncData);
|
||||||
|
|
||||||
const eventPromise = new Promise<MatrixEvent>((resolve) => {
|
const eventPromise = new Promise<MatrixEvent>((resolve) => {
|
||||||
@ -335,24 +332,25 @@ function firstSync(testClient: TestClient): Promise<void> {
|
|||||||
const syncData = {
|
const syncData = {
|
||||||
next_batch: "x",
|
next_batch: "x",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: { },
|
join: {
|
||||||
},
|
[roomId]: {
|
||||||
};
|
state: {
|
||||||
syncData.rooms.join[roomId] = {
|
events: [
|
||||||
state: {
|
testUtils.mkMembership({
|
||||||
events: [
|
mship: "join",
|
||||||
testUtils.mkMembership({
|
user: aliUserId,
|
||||||
mship: "join",
|
}),
|
||||||
user: aliUserId,
|
testUtils.mkMembership({
|
||||||
}),
|
mship: "join",
|
||||||
testUtils.mkMembership({
|
user: bobUserId,
|
||||||
mship: "join",
|
}),
|
||||||
user: bobUserId,
|
],
|
||||||
}),
|
},
|
||||||
],
|
timeline: {
|
||||||
},
|
events: [],
|
||||||
timeline: {
|
},
|
||||||
events: [],
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -424,7 +422,7 @@ describe("MatrixClient crypto", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const bobKeys = {};
|
const bobKeys: Record<string, typeof bobDeviceKeys> = {};
|
||||||
bobKeys[bobDeviceId] = bobDeviceKeys;
|
bobKeys[bobDeviceId] = bobDeviceKeys;
|
||||||
aliTestClient.httpBackend.when(
|
aliTestClient.httpBackend.when(
|
||||||
"POST", "/keys/query",
|
"POST", "/keys/query",
|
||||||
@ -460,7 +458,7 @@ describe("MatrixClient crypto", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const bobKeys = {};
|
const bobKeys: Record<string, typeof bobDeviceKeys> = {};
|
||||||
bobKeys[bobDeviceId] = bobDeviceKeys;
|
bobKeys[bobDeviceId] = bobDeviceKeys;
|
||||||
aliTestClient.httpBackend.when(
|
aliTestClient.httpBackend.when(
|
||||||
"POST", "/keys/query",
|
"POST", "/keys/query",
|
||||||
@ -515,22 +513,21 @@ describe("MatrixClient crypto", () => {
|
|||||||
next_batch: "x",
|
next_batch: "x",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {
|
join: {
|
||||||
|
[roomId]: {
|
||||||
|
timeline: {
|
||||||
|
events: [
|
||||||
|
testUtils.mkEvent({
|
||||||
|
type: "m.room.encrypted",
|
||||||
|
room: roomId,
|
||||||
|
content: message,
|
||||||
|
sender: "@bogus:sender",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
syncData.rooms.join[roomId] = {
|
|
||||||
timeline: {
|
|
||||||
events: [
|
|
||||||
testUtils.mkEvent({
|
|
||||||
type: "m.room.encrypted",
|
|
||||||
room: roomId,
|
|
||||||
content: message,
|
|
||||||
sender: "@bogus:sender",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
bobTestClient.httpBackend.when("GET", "/sync").respond(200, syncData);
|
bobTestClient.httpBackend.when("GET", "/sync").respond(200, syncData);
|
||||||
|
|
||||||
const eventPromise = new Promise<MatrixEvent>((resolve) => {
|
const eventPromise = new Promise<MatrixEvent>((resolve) => {
|
||||||
@ -607,20 +604,21 @@ describe("MatrixClient crypto", () => {
|
|||||||
const syncData = {
|
const syncData = {
|
||||||
next_batch: '2',
|
next_batch: '2',
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[roomId]: {
|
||||||
};
|
state: {
|
||||||
syncData.rooms.join[roomId] = {
|
events: [
|
||||||
state: {
|
testUtils.mkEvent({
|
||||||
events: [
|
type: 'm.room.encryption',
|
||||||
testUtils.mkEvent({
|
skey: '',
|
||||||
type: 'm.room.encryption',
|
content: {
|
||||||
skey: '',
|
algorithm: 'm.olm.v1.curve25519-aes-sha2',
|
||||||
content: {
|
},
|
||||||
algorithm: 'm.olm.v1.curve25519-aes-sha2',
|
}),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
],
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -536,20 +536,20 @@ describe("MatrixClient event timelines", function() {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
let tl0;
|
let tl0: EventTimeline;
|
||||||
let tl3;
|
let tl3: EventTimeline;
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
client.getEventTimeline(timelineSet, EVENTS[0].event_id!,
|
client.getEventTimeline(timelineSet, EVENTS[0].event_id!,
|
||||||
).then(function(tl) {
|
).then(function(tl) {
|
||||||
expect(tl!.getEvents().length).toEqual(1);
|
expect(tl!.getEvents().length).toEqual(1);
|
||||||
tl0 = tl;
|
tl0 = tl!;
|
||||||
return client.getEventTimeline(timelineSet, EVENTS[2].event_id!);
|
return client.getEventTimeline(timelineSet, EVENTS[2].event_id!);
|
||||||
}).then(function(tl) {
|
}).then(function(tl) {
|
||||||
expect(tl!.getEvents().length).toEqual(1);
|
expect(tl!.getEvents().length).toEqual(1);
|
||||||
return client.getEventTimeline(timelineSet, EVENTS[3].event_id!);
|
return client.getEventTimeline(timelineSet, EVENTS[3].event_id!);
|
||||||
}).then(function(tl) {
|
}).then(function(tl) {
|
||||||
expect(tl!.getEvents().length).toEqual(1);
|
expect(tl!.getEvents().length).toEqual(1);
|
||||||
tl3 = tl;
|
tl3 = tl!;
|
||||||
return client.getEventTimeline(timelineSet, EVENTS[1].event_id!);
|
return client.getEventTimeline(timelineSet, EVENTS[1].event_id!);
|
||||||
}).then(function(tl) {
|
}).then(function(tl) {
|
||||||
// we expect it to get merged in with event 2
|
// 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([
|
return Promise.all([
|
||||||
client.getEventTimeline(timelineSet, EVENTS[0].event_id!,
|
client.getEventTimeline(timelineSet, EVENTS[0].event_id!,
|
||||||
).then(function(tl0) {
|
).then(function(tl0) {
|
||||||
tl = tl0;
|
tl = tl0!;
|
||||||
return client.paginateEventTimeline(tl, { backwards: true });
|
return client.paginateEventTimeline(tl, { backwards: true });
|
||||||
}).then(function(success) {
|
}).then(function(success) {
|
||||||
expect(success).toBeTruthy();
|
expect(success).toBeTruthy();
|
||||||
@ -1043,11 +1043,11 @@ describe("MatrixClient event timelines", function() {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
let tl;
|
let tl: EventTimeline;
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
client.getEventTimeline(timelineSet, EVENTS[0].event_id!,
|
client.getEventTimeline(timelineSet, EVENTS[0].event_id!,
|
||||||
).then(function(tl0) {
|
).then(function(tl0) {
|
||||||
tl = tl0;
|
tl = tl0!;
|
||||||
return client.paginateEventTimeline(
|
return client.paginateEventTimeline(
|
||||||
tl, { backwards: false, limit: 20 });
|
tl, { backwards: false, limit: 20 });
|
||||||
}).then(function(success) {
|
}).then(function(success) {
|
||||||
@ -1569,16 +1569,17 @@ describe("MatrixClient event timelines", function() {
|
|||||||
const syncData = {
|
const syncData = {
|
||||||
next_batch: "batch1",
|
next_batch: "batch1",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[roomId]: {
|
||||||
};
|
timeline: {
|
||||||
syncData.rooms.join[roomId] = {
|
events: [
|
||||||
timeline: {
|
event,
|
||||||
events: [
|
redaction,
|
||||||
event,
|
],
|
||||||
redaction,
|
limited: false,
|
||||||
],
|
},
|
||||||
limited: false,
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
httpBackend.when("GET", "/sync").respond(200, syncData);
|
httpBackend.when("GET", "/sync").respond(200, syncData);
|
||||||
@ -1595,18 +1596,19 @@ describe("MatrixClient event timelines", function() {
|
|||||||
const sync2 = {
|
const sync2 = {
|
||||||
next_batch: "batch2",
|
next_batch: "batch2",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[roomId]: {
|
||||||
};
|
timeline: {
|
||||||
sync2.rooms.join[roomId] = {
|
events: [
|
||||||
timeline: {
|
utils.mkMessage({
|
||||||
events: [
|
user: otherUserId, msg: "world",
|
||||||
utils.mkMessage({
|
}),
|
||||||
user: otherUserId, msg: "world",
|
],
|
||||||
}),
|
limited: true,
|
||||||
],
|
prev_batch: "newerTok",
|
||||||
limited: true,
|
},
|
||||||
prev_batch: "newerTok",
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
httpBackend.when("GET", "/sync").respond(200, sync2);
|
httpBackend.when("GET", "/sync").respond(200, sync2);
|
||||||
|
@ -618,13 +618,13 @@ describe("MatrixClient", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("partitionThreadedEvents", function() {
|
describe("partitionThreadedEvents", function() {
|
||||||
let room;
|
let room: Room;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
room = new Room("!STrMRsukXHtqQdSeHa:matrix.org", client!, userId);
|
room = new Room("!STrMRsukXHtqQdSeHa:matrix.org", client!, userId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns empty arrays when given an empty arrays", function() {
|
it("returns empty arrays when given an empty arrays", function() {
|
||||||
const events = [];
|
const events: MatrixEvent[] = [];
|
||||||
const [timeline, threaded] = room.partitionThreadedEvents(events);
|
const [timeline, threaded] = room.partitionThreadedEvents(events);
|
||||||
expect(timeline).toEqual([]);
|
expect(timeline).toEqual([]);
|
||||||
expect(threaded).toEqual([]);
|
expect(threaded).toEqual([]);
|
||||||
@ -1645,7 +1645,7 @@ const buildEventCreate = () => new MatrixEvent({
|
|||||||
"user_id": "@andybalaam-test1:matrix.org",
|
"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) {
|
for (const k in expected) {
|
||||||
if (expected.hasOwnProperty(k)) {
|
if (expected.hasOwnProperty(k)) {
|
||||||
expect(obj[k]).toEqual(expected[k]);
|
expect(obj[k]).toEqual(expected[k]);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import HttpBackend from "matrix-mock-request";
|
import HttpBackend from "matrix-mock-request";
|
||||||
|
|
||||||
import * as utils from "../test-utils/test-utils";
|
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 { MatrixScheduler } from "../../src/scheduler";
|
||||||
import { MemoryStore } from "../../src/store/memory";
|
import { MemoryStore } from "../../src/store/memory";
|
||||||
import { MatrixError } from "../../src/http-api";
|
import { MatrixError } from "../../src/http-api";
|
||||||
@ -65,7 +65,7 @@ describe("MatrixClient opts", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("without opts.store", function() {
|
describe("without opts.store", function() {
|
||||||
let client;
|
let client: MatrixClient;
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
client = new MatrixClient({
|
client = new MatrixClient({
|
||||||
fetchFn: httpBackend.fetchFn as typeof global.fetch,
|
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.message", "m.room.name", "m.room.member", "m.room.member",
|
||||||
"m.room.create",
|
"m.room.create",
|
||||||
];
|
];
|
||||||
client.on("event", function(event) {
|
client.on(ClientEvent.Event, function(event) {
|
||||||
expect(expectedEventTypes.indexOf(event.getType())).not.toEqual(
|
expect(expectedEventTypes.indexOf(event.getType())).not.toEqual(
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
@ -125,7 +125,7 @@ describe("MatrixClient opts", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("without opts.scheduler", function() {
|
describe("without opts.scheduler", function() {
|
||||||
let client;
|
let client: MatrixClient;
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
client = new MatrixClient({
|
client = new MatrixClient({
|
||||||
fetchFn: httpBackend.fetchFn as typeof global.fetch,
|
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 * as utils from "../test-utils/test-utils";
|
||||||
import { EventStatus } from "../../src/models/event";
|
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";
|
import { TestClient } from "../TestClient";
|
||||||
|
|
||||||
describe("MatrixClient room timelines", function() {
|
describe("MatrixClient room timelines", function() {
|
||||||
@ -39,7 +48,7 @@ describe("MatrixClient room timelines", function() {
|
|||||||
name: "Old room name",
|
name: "Old room name",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let NEXT_SYNC_DATA;
|
let NEXT_SYNC_DATA: Partial<ISyncResponse>;
|
||||||
const SYNC_DATA = {
|
const SYNC_DATA = {
|
||||||
next_batch: "s_5_3",
|
next_batch: "s_5_3",
|
||||||
rooms: {
|
rooms: {
|
||||||
@ -88,7 +97,7 @@ describe("MatrixClient room timelines", function() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
leave: {},
|
leave: {},
|
||||||
},
|
} as unknown as ISyncResponse["rooms"],
|
||||||
};
|
};
|
||||||
events.forEach(function(e) {
|
events.forEach(function(e) {
|
||||||
if (e.room_id !== roomId) {
|
if (e.room_id !== roomId) {
|
||||||
@ -96,11 +105,11 @@ describe("MatrixClient room timelines", function() {
|
|||||||
}
|
}
|
||||||
if (e.state_key) {
|
if (e.state_key) {
|
||||||
// push the current
|
// 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) {
|
} 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 {
|
} 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() {
|
describe("paginated events", function() {
|
||||||
let sbEvents;
|
let sbEvents: Partial<IEvent>[];
|
||||||
const sbEndTok = "pagin_end";
|
const sbEndTok = "pagin_end";
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
@ -559,7 +568,7 @@ describe("MatrixClient room timelines", function() {
|
|||||||
utils.mkMessage({ user: userId, room: roomId }),
|
utils.mkMessage({ user: userId, room: roomId }),
|
||||||
];
|
];
|
||||||
setNextSyncData(eventData);
|
setNextSyncData(eventData);
|
||||||
NEXT_SYNC_DATA.rooms.join[roomId].timeline.limited = true;
|
NEXT_SYNC_DATA.rooms!.join[roomId].timeline.limited = true;
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
httpBackend!.flush("/versions", 1),
|
httpBackend!.flush("/versions", 1),
|
||||||
@ -593,7 +602,7 @@ describe("MatrixClient room timelines", function() {
|
|||||||
utils.mkMessage({ user: userId, room: roomId }),
|
utils.mkMessage({ user: userId, room: roomId }),
|
||||||
];
|
];
|
||||||
setNextSyncData(eventData);
|
setNextSyncData(eventData);
|
||||||
NEXT_SYNC_DATA.rooms.join[roomId].timeline.limited = true;
|
NEXT_SYNC_DATA.rooms!.join[roomId].timeline.limited = true;
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
httpBackend!.flush("/sync", 1),
|
httpBackend!.flush("/sync", 1),
|
||||||
@ -638,7 +647,7 @@ describe("MatrixClient room timelines", function() {
|
|||||||
end: "end_token",
|
end: "end_token",
|
||||||
};
|
};
|
||||||
|
|
||||||
let room;
|
let room: Room;
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
setNextSyncData(initialSyncEventData);
|
setNextSyncData(initialSyncEventData);
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ import {
|
|||||||
IJoinedRoom,
|
IJoinedRoom,
|
||||||
IStateEvent,
|
IStateEvent,
|
||||||
IMinimalEvent,
|
IMinimalEvent,
|
||||||
NotificationCountType,
|
NotificationCountType, IEphemeral,
|
||||||
} from "../../src";
|
} from "../../src";
|
||||||
import { UNREAD_THREAD_NOTIFICATIONS } from '../../src/@types/sync';
|
import { UNREAD_THREAD_NOTIFICATIONS } from '../../src/@types/sync';
|
||||||
import * as utils from "../test-utils/test-utils";
|
import * as utils from "../test-utils/test-utils";
|
||||||
@ -524,105 +524,101 @@ describe("MatrixClient syncing", () => {
|
|||||||
const syncData = {
|
const syncData = {
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {
|
join: {
|
||||||
|
[roomOne]: {
|
||||||
|
timeline: {
|
||||||
|
events: [
|
||||||
|
utils.mkMessage({
|
||||||
|
room: roomOne, user: otherUserId, msg: "hello",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
events: [
|
||||||
|
utils.mkEvent({
|
||||||
|
type: "m.room.name", room: roomOne, user: otherUserId,
|
||||||
|
content: {
|
||||||
|
name: "Old room name",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
utils.mkMembership({
|
||||||
|
room: roomOne, mship: "join", user: otherUserId,
|
||||||
|
}),
|
||||||
|
utils.mkMembership({
|
||||||
|
room: roomOne, mship: "join", user: selfUserId,
|
||||||
|
}),
|
||||||
|
utils.mkEvent({
|
||||||
|
type: "m.room.create", room: roomOne, user: selfUserId,
|
||||||
|
content: {
|
||||||
|
creator: selfUserId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[roomTwo]: {
|
||||||
|
timeline: {
|
||||||
|
events: [
|
||||||
|
utils.mkMessage({
|
||||||
|
room: roomTwo, user: otherUserId, msg: "hiii",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
events: [
|
||||||
|
utils.mkMembership({
|
||||||
|
room: roomTwo, mship: "join", user: otherUserId,
|
||||||
|
name: otherDisplayName,
|
||||||
|
}),
|
||||||
|
utils.mkMembership({
|
||||||
|
room: roomTwo, mship: "join", user: selfUserId,
|
||||||
|
}),
|
||||||
|
utils.mkEvent({
|
||||||
|
type: "m.room.create", room: roomTwo, user: selfUserId,
|
||||||
|
content: {
|
||||||
|
creator: selfUserId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
syncData.rooms.join[roomOne] = {
|
|
||||||
timeline: {
|
|
||||||
events: [
|
|
||||||
utils.mkMessage({
|
|
||||||
room: roomOne, user: otherUserId, msg: "hello",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
state: {
|
|
||||||
events: [
|
|
||||||
utils.mkEvent({
|
|
||||||
type: "m.room.name", room: roomOne, user: otherUserId,
|
|
||||||
content: {
|
|
||||||
name: "Old room name",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
utils.mkMembership({
|
|
||||||
room: roomOne, mship: "join", user: otherUserId,
|
|
||||||
}),
|
|
||||||
utils.mkMembership({
|
|
||||||
room: roomOne, mship: "join", user: selfUserId,
|
|
||||||
}),
|
|
||||||
utils.mkEvent({
|
|
||||||
type: "m.room.create", room: roomOne, user: selfUserId,
|
|
||||||
content: {
|
|
||||||
creator: selfUserId,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
syncData.rooms.join[roomTwo] = {
|
|
||||||
timeline: {
|
|
||||||
events: [
|
|
||||||
utils.mkMessage({
|
|
||||||
room: roomTwo, user: otherUserId, msg: "hiii",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
state: {
|
|
||||||
events: [
|
|
||||||
utils.mkMembership({
|
|
||||||
room: roomTwo, mship: "join", user: otherUserId,
|
|
||||||
name: otherDisplayName,
|
|
||||||
}),
|
|
||||||
utils.mkMembership({
|
|
||||||
room: roomTwo, mship: "join", user: selfUserId,
|
|
||||||
}),
|
|
||||||
utils.mkEvent({
|
|
||||||
type: "m.room.create", room: roomTwo, user: selfUserId,
|
|
||||||
content: {
|
|
||||||
creator: selfUserId,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const nextSyncData = {
|
const nextSyncData = {
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {
|
join: {
|
||||||
|
[roomOne]: {
|
||||||
|
state: {
|
||||||
|
events: [
|
||||||
|
utils.mkEvent({
|
||||||
|
type: "m.room.name", room: roomOne, user: selfUserId,
|
||||||
|
content: { name: "A new room name" },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[roomTwo]: {
|
||||||
|
timeline: {
|
||||||
|
events: [
|
||||||
|
utils.mkMessage({
|
||||||
|
room: roomTwo, user: otherUserId, msg: msgText,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
ephemeral: {
|
||||||
|
events: [
|
||||||
|
utils.mkEvent({
|
||||||
|
type: "m.typing", room: roomTwo,
|
||||||
|
content: { user_ids: [otherUserId] },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
nextSyncData.rooms.join[roomOne] = {
|
|
||||||
state: {
|
|
||||||
events: [
|
|
||||||
utils.mkEvent({
|
|
||||||
type: "m.room.name", room: roomOne, user: selfUserId,
|
|
||||||
content: { name: "A new room name" },
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
nextSyncData.rooms.join[roomTwo] = {
|
|
||||||
timeline: {
|
|
||||||
events: [
|
|
||||||
utils.mkMessage({
|
|
||||||
room: roomTwo, user: otherUserId, msg: msgText,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
ephemeral: {
|
|
||||||
events: [
|
|
||||||
utils.mkEvent({
|
|
||||||
type: "m.typing", room: roomTwo,
|
|
||||||
content: { user_ids: [otherUserId] },
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
it("should continually recalculate the right room name.", () => {
|
it("should continually recalculate the right room name.", () => {
|
||||||
httpBackend!.when("GET", "/sync").respond(200, syncData);
|
httpBackend!.when("GET", "/sync").respond(200, syncData);
|
||||||
httpBackend!.when("GET", "/sync").respond(200, nextSyncData);
|
httpBackend!.when("GET", "/sync").respond(200, nextSyncData);
|
||||||
@ -635,9 +631,7 @@ describe("MatrixClient syncing", () => {
|
|||||||
]).then(() => {
|
]).then(() => {
|
||||||
const room = client!.getRoom(roomOne)!;
|
const room = client!.getRoom(roomOne)!;
|
||||||
// should have clobbered the name to the one from /events
|
// should have clobbered the name to the one from /events
|
||||||
expect(room.name).toEqual(
|
expect(room.name).toEqual(nextSyncData.rooms.join[roomOne].state.events[0].content?.name);
|
||||||
nextSyncData.rooms.join[roomOne].state.events[0].content.name,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -742,46 +736,48 @@ describe("MatrixClient syncing", () => {
|
|||||||
const normalFirstSync = {
|
const normalFirstSync = {
|
||||||
next_batch: "batch_token",
|
next_batch: "batch_token",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[roomOne]: {
|
||||||
};
|
timeline: {
|
||||||
normalFirstSync.rooms.join[roomOne] = {
|
events: [normalMessageEvent],
|
||||||
timeline: {
|
prev_batch: "pagTok",
|
||||||
events: [normalMessageEvent],
|
},
|
||||||
prev_batch: "pagTok",
|
state: {
|
||||||
},
|
events: [roomCreateEvent],
|
||||||
state: {
|
},
|
||||||
events: [roomCreateEvent],
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const nextSyncData = {
|
const nextSyncData = {
|
||||||
next_batch: "batch_token",
|
next_batch: "batch_token",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[roomOne]: {
|
||||||
};
|
timeline: {
|
||||||
nextSyncData.rooms.join[roomOne] = {
|
events: [
|
||||||
timeline: {
|
// In subsequent syncs, a marker event in timeline
|
||||||
events: [
|
// range should normally trigger
|
||||||
// In subsequent syncs, a marker event in timeline
|
// `timelineNeedsRefresh=true` but this marker isn't
|
||||||
// range should normally trigger
|
// being sent by the room creator so it has no
|
||||||
// `timelineNeedsRefresh=true` but this marker isn't
|
// special meaning in existing room versions.
|
||||||
// being sent by the room creator so it has no
|
utils.mkEvent({
|
||||||
// special meaning in existing room versions.
|
type: UNSTABLE_MSC2716_MARKER.name,
|
||||||
utils.mkEvent({
|
room: roomOne,
|
||||||
type: UNSTABLE_MSC2716_MARKER.name,
|
// The important part we're testing is here!
|
||||||
room: roomOne,
|
// `userC` is not the room creator.
|
||||||
// The important part we're testing is here!
|
user: userC,
|
||||||
// `userC` is not the room creator.
|
skey: "",
|
||||||
user: userC,
|
content: {
|
||||||
skey: "",
|
"m.insertion_id": "$abc",
|
||||||
content: {
|
},
|
||||||
"m.insertion_id": "$abc",
|
}),
|
||||||
|
],
|
||||||
|
prev_batch: "pagTok",
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
],
|
},
|
||||||
prev_batch: "pagTok",
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -831,16 +827,17 @@ describe("MatrixClient syncing", () => {
|
|||||||
const normalFirstSync = {
|
const normalFirstSync = {
|
||||||
next_batch: "batch_token",
|
next_batch: "batch_token",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[roomOne]: {
|
||||||
};
|
timeline: {
|
||||||
normalFirstSync.rooms.join[roomOne] = {
|
events: [normalMessageEvent],
|
||||||
timeline: {
|
prev_batch: "pagTok",
|
||||||
events: [normalMessageEvent],
|
},
|
||||||
prev_batch: "pagTok",
|
state: {
|
||||||
},
|
events: [roomCreateEvent],
|
||||||
state: {
|
},
|
||||||
events: [roomCreateEvent],
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -849,16 +846,17 @@ describe("MatrixClient syncing", () => {
|
|||||||
const syncData = {
|
const syncData = {
|
||||||
next_batch: "batch_token",
|
next_batch: "batch_token",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[roomOne]: {
|
||||||
};
|
timeline: {
|
||||||
syncData.rooms.join[roomOne] = {
|
events: [normalMessageEvent],
|
||||||
timeline: {
|
prev_batch: "pagTok",
|
||||||
events: [normalMessageEvent],
|
},
|
||||||
prev_batch: "pagTok",
|
state: {
|
||||||
},
|
events: [roomCreateEvent],
|
||||||
state: {
|
},
|
||||||
events: [roomCreateEvent],
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -879,16 +877,17 @@ describe("MatrixClient syncing", () => {
|
|||||||
const syncData = {
|
const syncData = {
|
||||||
next_batch: "batch_token",
|
next_batch: "batch_token",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[roomOne]: {
|
||||||
};
|
timeline: {
|
||||||
syncData.rooms.join[roomOne] = {
|
events: [markerEventFromRoomCreator],
|
||||||
timeline: {
|
prev_batch: "pagTok",
|
||||||
events: [markerEventFromRoomCreator],
|
},
|
||||||
prev_batch: "pagTok",
|
state: {
|
||||||
},
|
events: [roomCreateEvent],
|
||||||
state: {
|
},
|
||||||
events: [roomCreateEvent],
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -909,19 +908,20 @@ describe("MatrixClient syncing", () => {
|
|||||||
const syncData = {
|
const syncData = {
|
||||||
next_batch: "batch_token",
|
next_batch: "batch_token",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[roomOne]: {
|
||||||
};
|
timeline: {
|
||||||
syncData.rooms.join[roomOne] = {
|
events: [normalMessageEvent],
|
||||||
timeline: {
|
prev_batch: "pagTok",
|
||||||
events: [normalMessageEvent],
|
},
|
||||||
prev_batch: "pagTok",
|
state: {
|
||||||
},
|
events: [
|
||||||
state: {
|
roomCreateEvent,
|
||||||
events: [
|
markerEventFromRoomCreator,
|
||||||
roomCreateEvent,
|
],
|
||||||
markerEventFromRoomCreator,
|
},
|
||||||
],
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -942,17 +942,18 @@ describe("MatrixClient syncing", () => {
|
|||||||
const nextSyncData = {
|
const nextSyncData = {
|
||||||
next_batch: "batch_token",
|
next_batch: "batch_token",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[roomOne]: {
|
||||||
};
|
timeline: {
|
||||||
nextSyncData.rooms.join[roomOne] = {
|
events: [
|
||||||
timeline: {
|
// In subsequent syncs, a marker event in timeline
|
||||||
events: [
|
// range should trigger `timelineNeedsRefresh=true`
|
||||||
// In subsequent syncs, a marker event in timeline
|
markerEventFromRoomCreator,
|
||||||
// range should trigger `timelineNeedsRefresh=true`
|
],
|
||||||
markerEventFromRoomCreator,
|
prev_batch: "pagTok",
|
||||||
],
|
},
|
||||||
prev_batch: "pagTok",
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -993,24 +994,25 @@ describe("MatrixClient syncing", () => {
|
|||||||
const nextSyncData = {
|
const nextSyncData = {
|
||||||
next_batch: "batch_token",
|
next_batch: "batch_token",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[roomOne]: {
|
||||||
};
|
timeline: {
|
||||||
nextSyncData.rooms.join[roomOne] = {
|
events: [
|
||||||
timeline: {
|
utils.mkMessage({
|
||||||
events: [
|
room: roomOne, user: otherUserId, msg: "hello again",
|
||||||
utils.mkMessage({
|
}),
|
||||||
room: roomOne, user: otherUserId, msg: "hello again",
|
],
|
||||||
}),
|
prev_batch: "pagTok",
|
||||||
],
|
},
|
||||||
prev_batch: "pagTok",
|
state: {
|
||||||
},
|
events: [
|
||||||
state: {
|
// In subsequent syncs, a marker event in state
|
||||||
events: [
|
// should trigger `timelineNeedsRefresh=true`
|
||||||
// In subsequent syncs, a marker event in state
|
markerEventFromRoomCreator,
|
||||||
// should trigger `timelineNeedsRefresh=true`
|
],
|
||||||
markerEventFromRoomCreator,
|
},
|
||||||
],
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1095,19 +1097,20 @@ describe("MatrixClient syncing", () => {
|
|||||||
const limitedSyncData = {
|
const limitedSyncData = {
|
||||||
next_batch: "batch_token",
|
next_batch: "batch_token",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[roomOne]: {
|
||||||
};
|
timeline: {
|
||||||
limitedSyncData.rooms.join[roomOne] = {
|
events: [
|
||||||
timeline: {
|
utils.mkMessage({
|
||||||
events: [
|
room: roomOne, user: otherUserId, msg: "world",
|
||||||
utils.mkMessage({
|
}),
|
||||||
room: roomOne, user: otherUserId, msg: "world",
|
],
|
||||||
}),
|
// The important part, make the sync `limited`
|
||||||
],
|
limited: true,
|
||||||
// The important part, make the sync `limited`
|
prev_batch: "newerTok",
|
||||||
limited: true,
|
},
|
||||||
prev_batch: "newerTok",
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
httpBackend!.when("GET", "/sync").respond(200, limitedSyncData);
|
httpBackend!.when("GET", "/sync").respond(200, limitedSyncData);
|
||||||
@ -1167,7 +1170,7 @@ describe("MatrixClient syncing", () => {
|
|||||||
|
|
||||||
const eventsInRoom = syncData.rooms.join[roomOne].timeline.events;
|
const eventsInRoom = syncData.rooms.join[roomOne].timeline.events;
|
||||||
const contextUrl = `/rooms/${encodeURIComponent(roomOne)}/context/` +
|
const contextUrl = `/rooms/${encodeURIComponent(roomOne)}/context/` +
|
||||||
`${encodeURIComponent(eventsInRoom[0].event_id)}`;
|
`${encodeURIComponent(eventsInRoom[0].event_id!)}`;
|
||||||
httpBackend!.when("GET", contextUrl)
|
httpBackend!.when("GET", contextUrl)
|
||||||
.respond(200, () => {
|
.respond(200, () => {
|
||||||
return {
|
return {
|
||||||
@ -1202,17 +1205,18 @@ describe("MatrixClient syncing", () => {
|
|||||||
const syncData = {
|
const syncData = {
|
||||||
next_batch: "batch_token",
|
next_batch: "batch_token",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[roomOne]: {
|
||||||
};
|
timeline: {
|
||||||
syncData.rooms.join[roomOne] = {
|
events: [
|
||||||
timeline: {
|
utils.mkMessage({
|
||||||
events: [
|
room: roomOne, user: otherUserId, msg: "hello",
|
||||||
utils.mkMessage({
|
}),
|
||||||
room: roomOne, user: otherUserId, msg: "hello",
|
],
|
||||||
}),
|
prev_batch: "pagTok",
|
||||||
],
|
},
|
||||||
prev_batch: "pagTok",
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1229,17 +1233,18 @@ describe("MatrixClient syncing", () => {
|
|||||||
const syncData = {
|
const syncData = {
|
||||||
next_batch: "batch_token",
|
next_batch: "batch_token",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[roomTwo]: {
|
||||||
};
|
timeline: {
|
||||||
syncData.rooms.join[roomTwo] = {
|
events: [
|
||||||
timeline: {
|
utils.mkMessage({
|
||||||
events: [
|
room: roomTwo, user: otherUserId, msg: "roomtwo",
|
||||||
utils.mkMessage({
|
}),
|
||||||
room: roomTwo, user: otherUserId, msg: "roomtwo",
|
],
|
||||||
}),
|
prev_batch: "roomtwotok",
|
||||||
],
|
},
|
||||||
prev_batch: "roomtwotok",
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1261,18 +1266,19 @@ describe("MatrixClient syncing", () => {
|
|||||||
const syncData = {
|
const syncData = {
|
||||||
next_batch: "batch_token",
|
next_batch: "batch_token",
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[roomOne]: {
|
||||||
};
|
timeline: {
|
||||||
syncData.rooms.join[roomOne] = {
|
events: [
|
||||||
timeline: {
|
utils.mkMessage({
|
||||||
events: [
|
room: roomOne, user: otherUserId, msg: "world",
|
||||||
utils.mkMessage({
|
}),
|
||||||
room: roomOne, user: otherUserId, msg: "world",
|
],
|
||||||
}),
|
limited: true,
|
||||||
],
|
prev_batch: "newerTok",
|
||||||
limited: true,
|
},
|
||||||
prev_batch: "newerTok",
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
httpBackend!.when("GET", "/sync").respond(200, syncData);
|
httpBackend!.when("GET", "/sync").respond(200, syncData);
|
||||||
@ -1304,44 +1310,46 @@ describe("MatrixClient syncing", () => {
|
|||||||
const syncData = {
|
const syncData = {
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {
|
join: {
|
||||||
|
[roomOne]: {
|
||||||
|
ephemeral: {
|
||||||
|
events: [],
|
||||||
|
} as IEphemeral,
|
||||||
|
timeline: {
|
||||||
|
events: [
|
||||||
|
utils.mkMessage({
|
||||||
|
room: roomOne, user: otherUserId, msg: "hello",
|
||||||
|
}),
|
||||||
|
utils.mkMessage({
|
||||||
|
room: roomOne, user: otherUserId, msg: "world",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
events: [
|
||||||
|
utils.mkEvent({
|
||||||
|
type: "m.room.name", room: roomOne, user: otherUserId,
|
||||||
|
content: {
|
||||||
|
name: "Old room name",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
utils.mkMembership({
|
||||||
|
room: roomOne, mship: "join", user: otherUserId,
|
||||||
|
}),
|
||||||
|
utils.mkMembership({
|
||||||
|
room: roomOne, mship: "join", user: selfUserId,
|
||||||
|
}),
|
||||||
|
utils.mkEvent({
|
||||||
|
type: "m.room.create", room: roomOne, user: selfUserId,
|
||||||
|
content: {
|
||||||
|
creator: selfUserId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
} as Partial<IJoinedRoom>,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
syncData.rooms.join[roomOne] = {
|
|
||||||
timeline: {
|
|
||||||
events: [
|
|
||||||
utils.mkMessage({
|
|
||||||
room: roomOne, user: otherUserId, msg: "hello",
|
|
||||||
}),
|
|
||||||
utils.mkMessage({
|
|
||||||
room: roomOne, user: otherUserId, msg: "world",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
state: {
|
|
||||||
events: [
|
|
||||||
utils.mkEvent({
|
|
||||||
type: "m.room.name", room: roomOne, user: otherUserId,
|
|
||||||
content: {
|
|
||||||
name: "Old room name",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
utils.mkMembership({
|
|
||||||
room: roomOne, mship: "join", user: otherUserId,
|
|
||||||
}),
|
|
||||||
utils.mkMembership({
|
|
||||||
room: roomOne, mship: "join", user: selfUserId,
|
|
||||||
}),
|
|
||||||
utils.mkEvent({
|
|
||||||
type: "m.room.create", room: roomOne, user: selfUserId,
|
|
||||||
content: {
|
|
||||||
creator: selfUserId,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
syncData.rooms.join[roomOne].ephemeral = {
|
syncData.rooms.join[roomOne].ephemeral = {
|
||||||
@ -1351,16 +1359,15 @@ describe("MatrixClient syncing", () => {
|
|||||||
|
|
||||||
it("should sync receipts from /sync.", () => {
|
it("should sync receipts from /sync.", () => {
|
||||||
const ackEvent = syncData.rooms.join[roomOne].timeline.events[0];
|
const ackEvent = syncData.rooms.join[roomOne].timeline.events[0];
|
||||||
const receipt = {};
|
const receipt: Record<string, any> = {};
|
||||||
receipt[ackEvent.event_id] = {
|
receipt[ackEvent.event_id!] = {
|
||||||
"m.read": {},
|
"m.read": {},
|
||||||
};
|
};
|
||||||
receipt[ackEvent.event_id]["m.read"][userC] = {
|
receipt[ackEvent.event_id!]["m.read"][userC] = {
|
||||||
ts: 176592842636,
|
ts: 176592842636,
|
||||||
};
|
};
|
||||||
syncData.rooms.join[roomOne].ephemeral.events = [{
|
syncData.rooms.join[roomOne].ephemeral.events = [{
|
||||||
content: receipt,
|
content: receipt,
|
||||||
room_id: roomOne,
|
|
||||||
type: "m.receipt",
|
type: "m.receipt",
|
||||||
}];
|
}];
|
||||||
httpBackend!.when("GET", "/sync").respond(200, syncData);
|
httpBackend!.when("GET", "/sync").respond(200, syncData);
|
||||||
@ -1425,7 +1432,7 @@ describe("MatrixClient syncing", () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
} as unknown as ISyncResponse;
|
||||||
it("should sync unread notifications.", () => {
|
it("should sync unread notifications.", () => {
|
||||||
syncData.rooms.join[roomOne][UNREAD_THREAD_NOTIFICATIONS.name] = {
|
syncData.rooms.join[roomOne][UNREAD_THREAD_NOTIFICATIONS.name] = {
|
||||||
[THREAD_ID]: {
|
[THREAD_ID]: {
|
||||||
@ -1509,18 +1516,18 @@ describe("MatrixClient syncing", () => {
|
|||||||
const syncData = {
|
const syncData = {
|
||||||
next_batch: "batch_token",
|
next_batch: "batch_token",
|
||||||
rooms: {
|
rooms: {
|
||||||
leave: {},
|
leave: {
|
||||||
},
|
[roomTwo]: {
|
||||||
};
|
timeline: {
|
||||||
|
events: [
|
||||||
syncData.rooms.leave[roomTwo] = {
|
utils.mkMessage({
|
||||||
timeline: {
|
room: roomTwo, user: otherUserId, msg: "hello",
|
||||||
events: [
|
}),
|
||||||
utils.mkMessage({
|
],
|
||||||
room: roomTwo, user: otherUserId, msg: "hello",
|
prev_batch: "pagTok",
|
||||||
}),
|
},
|
||||||
],
|
},
|
||||||
prev_batch: "pagTok",
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -126,12 +126,13 @@ describe("megolm key backups", function() {
|
|||||||
const syncResponse = {
|
const syncResponse = {
|
||||||
next_batch: 1,
|
next_batch: 1,
|
||||||
rooms: {
|
rooms: {
|
||||||
join: {},
|
join: {
|
||||||
},
|
[ROOM_ID]: {
|
||||||
};
|
timeline: {
|
||||||
syncResponse.rooms.join[ROOM_ID] = {
|
events: [ENCRYPTED_EVENT],
|
||||||
timeline: {
|
},
|
||||||
events: [ENCRYPTED_EVENT],
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -119,13 +119,13 @@ describe("SlidingSyncSdk", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// find an extension on a SlidingSyncSdk instance
|
// find an extension on a SlidingSyncSdk instance
|
||||||
const findExtension = (name: string): Extension => {
|
const findExtension = (name: string): Extension<any, any> => {
|
||||||
expect(mockSlidingSync!.registerExtension).toHaveBeenCalled();
|
expect(mockSlidingSync!.registerExtension).toHaveBeenCalled();
|
||||||
const mockFn = mockSlidingSync!.registerExtension as jest.Mock;
|
const mockFn = mockSlidingSync!.registerExtension as jest.Mock;
|
||||||
// find the extension
|
// find the extension
|
||||||
for (let i = 0; i < mockFn.mock.calls.length; i++) {
|
for (let i = 0; i < mockFn.mock.calls.length; i++) {
|
||||||
const calledExtension = mockFn.mock.calls[i][0] as Extension;
|
const calledExtension = mockFn.mock.calls[i][0] as Extension<any, any>;
|
||||||
if (calledExtension && calledExtension.name() === name) {
|
if (calledExtension?.name() === name) {
|
||||||
return calledExtension;
|
return calledExtension;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -581,7 +581,7 @@ describe("SlidingSyncSdk", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("ExtensionE2EE", () => {
|
describe("ExtensionE2EE", () => {
|
||||||
let ext: Extension;
|
let ext: Extension<any, any>;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await setupClient({
|
await setupClient({
|
||||||
@ -647,7 +647,7 @@ describe("SlidingSyncSdk", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("ExtensionAccountData", () => {
|
describe("ExtensionAccountData", () => {
|
||||||
let ext: Extension;
|
let ext: Extension<any, any>;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await setupClient();
|
await setupClient();
|
||||||
@ -773,7 +773,7 @@ describe("SlidingSyncSdk", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("ExtensionToDevice", () => {
|
describe("ExtensionToDevice", () => {
|
||||||
let ext: Extension;
|
let ext: Extension<any, any>;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await setupClient();
|
await setupClient();
|
||||||
@ -871,7 +871,7 @@ describe("SlidingSyncSdk", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("ExtensionTyping", () => {
|
describe("ExtensionTyping", () => {
|
||||||
let ext: Extension;
|
let ext: Extension<any, any>;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await setupClient();
|
await setupClient();
|
||||||
@ -970,7 +970,7 @@ describe("SlidingSyncSdk", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("ExtensionReceipts", () => {
|
describe("ExtensionReceipts", () => {
|
||||||
let ext: Extension;
|
let ext: Extension<any, any>;
|
||||||
|
|
||||||
const generateReceiptResponse = (
|
const generateReceiptResponse = (
|
||||||
userId: string, roomId: string, eventId: string, recType: string, ts: number,
|
userId: string, roomId: string, eventId: string, recType: string, ts: number,
|
||||||
|
@ -18,7 +18,15 @@ limitations under the License.
|
|||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import MockHttpBackend from "matrix-mock-request";
|
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 { TestClient } from "../TestClient";
|
||||||
import { logger } from "../../src/logger";
|
import { logger } from "../../src/logger";
|
||||||
import { MatrixClient } from "../../src";
|
import { MatrixClient } from "../../src";
|
||||||
@ -94,7 +102,7 @@ describe("SlidingSync", () => {
|
|||||||
is_dm: true,
|
is_dm: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const ext = {
|
const ext: Extension<any, any> = {
|
||||||
name: () => "custom_extension",
|
name: () => "custom_extension",
|
||||||
onRequest: (initial) => { return { initial: initial }; },
|
onRequest: (initial) => { return { initial: initial }; },
|
||||||
onResponse: (res) => { return {}; },
|
onResponse: (res) => { return {}; },
|
||||||
@ -107,7 +115,7 @@ describe("SlidingSync", () => {
|
|||||||
slidingSync.start();
|
slidingSync.start();
|
||||||
|
|
||||||
// expect everything to be sent
|
// expect everything to be sent
|
||||||
let txnId;
|
let txnId: string | undefined;
|
||||||
httpBackend!.when("POST", syncUrl).check(function(req) {
|
httpBackend!.when("POST", syncUrl).check(function(req) {
|
||||||
const body = req.data;
|
const body = req.data;
|
||||||
logger.debug("got ", body);
|
logger.debug("got ", body);
|
||||||
@ -390,8 +398,8 @@ describe("SlidingSync", () => {
|
|||||||
}],
|
}],
|
||||||
rooms: rooms,
|
rooms: rooms,
|
||||||
});
|
});
|
||||||
const listenerData = {};
|
const listenerData: Record<string, MSC3575RoomData> = {};
|
||||||
const dataListener = (roomId, roomData) => {
|
const dataListener: SlidingSyncEventHandlerMap[SlidingSyncEvent.RoomData] = (roomId, roomData) => {
|
||||||
expect(listenerData[roomId]).toBeFalsy();
|
expect(listenerData[roomId]).toBeFalsy();
|
||||||
listenerData[roomId] = roomData;
|
listenerData[roomId] = roomData;
|
||||||
};
|
};
|
||||||
@ -912,7 +920,7 @@ describe("SlidingSync", () => {
|
|||||||
slidingSync = new SlidingSync(proxyBaseUrl, [], roomSubInfo, client!, 1);
|
slidingSync = new SlidingSync(proxyBaseUrl, [], roomSubInfo, client!, 1);
|
||||||
// modification before SlidingSync.start()
|
// modification before SlidingSync.start()
|
||||||
const subscribePromise = slidingSync.modifyRoomSubscriptions(new Set([roomId]));
|
const subscribePromise = slidingSync.modifyRoomSubscriptions(new Set([roomId]));
|
||||||
let txnId;
|
let txnId: string | undefined;
|
||||||
httpBackend!.when("POST", syncUrl).check(function(req) {
|
httpBackend!.when("POST", syncUrl).check(function(req) {
|
||||||
const body = req.data;
|
const body = req.data;
|
||||||
logger.debug("got ", body);
|
logger.debug("got ", body);
|
||||||
@ -944,7 +952,7 @@ describe("SlidingSync", () => {
|
|||||||
ranges: [[0, 20]],
|
ranges: [[0, 20]],
|
||||||
};
|
};
|
||||||
const promise = slidingSync.setList(0, newList);
|
const promise = slidingSync.setList(0, newList);
|
||||||
let txnId;
|
let txnId: string | undefined;
|
||||||
httpBackend!.when("POST", syncUrl).check(function(req) {
|
httpBackend!.when("POST", syncUrl).check(function(req) {
|
||||||
const body = req.data;
|
const body = req.data;
|
||||||
logger.debug("got ", body);
|
logger.debug("got ", body);
|
||||||
@ -966,7 +974,7 @@ describe("SlidingSync", () => {
|
|||||||
});
|
});
|
||||||
it("should resolve setListRanges during a connection", async () => {
|
it("should resolve setListRanges during a connection", async () => {
|
||||||
const promise = slidingSync.setListRanges(0, [[20, 40]]);
|
const promise = slidingSync.setListRanges(0, [[20, 40]]);
|
||||||
let txnId;
|
let txnId: string | undefined;
|
||||||
httpBackend!.when("POST", syncUrl).check(function(req) {
|
httpBackend!.when("POST", syncUrl).check(function(req) {
|
||||||
const body = req.data;
|
const body = req.data;
|
||||||
logger.debug("got ", body);
|
logger.debug("got ", body);
|
||||||
@ -992,7 +1000,7 @@ describe("SlidingSync", () => {
|
|||||||
const promise = slidingSync.modifyRoomSubscriptionInfo({
|
const promise = slidingSync.modifyRoomSubscriptionInfo({
|
||||||
timeline_limit: 99,
|
timeline_limit: 99,
|
||||||
});
|
});
|
||||||
let txnId;
|
let txnId: string | undefined;
|
||||||
httpBackend!.when("POST", syncUrl).check(function(req) {
|
httpBackend!.when("POST", syncUrl).check(function(req) {
|
||||||
const body = req.data;
|
const body = req.data;
|
||||||
logger.debug("got ", body);
|
logger.debug("got ", body);
|
||||||
@ -1016,7 +1024,7 @@ describe("SlidingSync", () => {
|
|||||||
it("should reject earlier pending promises if a later transaction is acknowledged", async () => {
|
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.
|
// i.e if we have [A,B,C] and see txn_id=C then A,B should be rejected.
|
||||||
const gotTxnIds: any[] = [];
|
const gotTxnIds: any[] = [];
|
||||||
const pushTxn = function(req) {
|
const pushTxn = function(req: MockHttpBackend["requests"][0]) {
|
||||||
gotTxnIds.push(req.data.txn_id);
|
gotTxnIds.push(req.data.txn_id);
|
||||||
};
|
};
|
||||||
const failPromise = slidingSync.setListRanges(0, [[20, 40]]);
|
const failPromise = slidingSync.setListRanges(0, [[20, 40]]);
|
||||||
@ -1032,7 +1040,7 @@ describe("SlidingSync", () => {
|
|||||||
expect(failPromise2).rejects.toEqual(gotTxnIds[1]);
|
expect(failPromise2).rejects.toEqual(gotTxnIds[1]);
|
||||||
|
|
||||||
const okPromise = slidingSync.setListRanges(0, [[0, 20]]);
|
const okPromise = slidingSync.setListRanges(0, [[0, 20]]);
|
||||||
let txnId;
|
let txnId: string | undefined;
|
||||||
httpBackend!.when("POST", syncUrl).check((req) => {
|
httpBackend!.when("POST", syncUrl).check((req) => {
|
||||||
txnId = req.data.txn_id;
|
txnId = req.data.txn_id;
|
||||||
}).respond(200, () => {
|
}).respond(200, () => {
|
||||||
@ -1050,7 +1058,7 @@ describe("SlidingSync", () => {
|
|||||||
it("should not reject later pending promises if an earlier transaction is acknowledged", async () => {
|
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.
|
// 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 gotTxnIds: any[] = [];
|
||||||
const pushTxn = function(req) {
|
const pushTxn = function(req: MockHttpBackend["requests"][0]) {
|
||||||
gotTxnIds.push(req.data?.txn_id);
|
gotTxnIds.push(req.data?.txn_id);
|
||||||
};
|
};
|
||||||
const A = slidingSync.setListRanges(0, [[20, 40]]);
|
const A = slidingSync.setListRanges(0, [[20, 40]]);
|
||||||
@ -1087,7 +1095,7 @@ describe("SlidingSync", () => {
|
|||||||
promise.finally(() => {
|
promise.finally(() => {
|
||||||
pending = false;
|
pending = false;
|
||||||
});
|
});
|
||||||
let txnId;
|
let txnId: string | undefined;
|
||||||
httpBackend!.when("POST", syncUrl).check(function(req) {
|
httpBackend!.when("POST", syncUrl).check(function(req) {
|
||||||
const body = req.data;
|
const body = req.data;
|
||||||
logger.debug("got ", body);
|
logger.debug("got ", body);
|
||||||
@ -1275,21 +1283,21 @@ describe("SlidingSync", () => {
|
|||||||
|
|
||||||
// Pre-extensions get called BEFORE processing the sync response
|
// Pre-extensions get called BEFORE processing the sync response
|
||||||
const preExtName = "foobar";
|
const preExtName = "foobar";
|
||||||
let onPreExtensionRequest;
|
let onPreExtensionRequest: Extension<any, any>["onRequest"];
|
||||||
let onPreExtensionResponse;
|
let onPreExtensionResponse: Extension<any, any>["onResponse"];
|
||||||
|
|
||||||
// Post-extensions get called AFTER processing the sync response
|
// Post-extensions get called AFTER processing the sync response
|
||||||
const postExtName = "foobar2";
|
const postExtName = "foobar2";
|
||||||
let onPostExtensionRequest;
|
let onPostExtensionRequest: Extension<any, any>["onRequest"];
|
||||||
let onPostExtensionResponse;
|
let onPostExtensionResponse: Extension<any, any>["onResponse"];
|
||||||
|
|
||||||
const extPre = {
|
const extPre: Extension<any, any> = {
|
||||||
name: () => preExtName,
|
name: () => preExtName,
|
||||||
onRequest: (initial) => { return onPreExtensionRequest(initial); },
|
onRequest: (initial) => { return onPreExtensionRequest(initial); },
|
||||||
onResponse: (res) => { return onPreExtensionResponse(res); },
|
onResponse: (res) => { return onPreExtensionResponse(res); },
|
||||||
when: () => ExtensionState.PreProcess,
|
when: () => ExtensionState.PreProcess,
|
||||||
};
|
};
|
||||||
const extPost = {
|
const extPost: Extension<any, any> = {
|
||||||
name: () => postExtName,
|
name: () => postExtName,
|
||||||
onRequest: (initial) => { return onPostExtensionRequest(initial); },
|
onRequest: (initial) => { return onPostExtensionRequest(initial); },
|
||||||
onResponse: (res) => { return onPostExtensionResponse(res); },
|
onResponse: (res) => { return onPostExtensionResponse(res); },
|
||||||
@ -1421,7 +1429,7 @@ describe("SlidingSync", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function timeout(delayMs: number, reason: string): { promise: Promise<never>, cancel: () => void } {
|
function timeout(delayMs: number, reason: string): { promise: Promise<never>, cancel: () => void } {
|
||||||
let timeoutId;
|
let timeoutId: NodeJS.Timeout;
|
||||||
return {
|
return {
|
||||||
promise: new Promise((resolve, reject) => {
|
promise: new Promise((resolve, reject) => {
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
@ -1454,7 +1462,7 @@ function listenUntil<T>(
|
|||||||
const trace = new Error().stack?.split(`\n`)[2];
|
const trace = new Error().stack?.split(`\n`)[2];
|
||||||
const t = timeout(timeoutMs, "timed out waiting for event " + eventName + " " + trace);
|
const t = timeout(timeoutMs, "timed out waiting for event " + eventName + " " + trace);
|
||||||
return Promise.race([new Promise<T>((resolve, reject) => {
|
return Promise.race([new Promise<T>((resolve, reject) => {
|
||||||
const wrapper = (...args) => {
|
const wrapper = (...args: any[]) => {
|
||||||
try {
|
try {
|
||||||
const data = callback(...args);
|
const data = callback(...args);
|
||||||
if (data) {
|
if (data) {
|
||||||
|
@ -332,7 +332,7 @@ export function mkReplyMessage(
|
|||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
export class MockStorageApi {
|
export class MockStorageApi implements Storage {
|
||||||
private data: Record<string, any> = {};
|
private data: Record<string, any> = {};
|
||||||
|
|
||||||
public get length() {
|
public get length() {
|
||||||
@ -354,6 +354,10 @@ export class MockStorageApi {
|
|||||||
public removeItem(k: string): void {
|
public removeItem(k: string): void {
|
||||||
delete this.data[k];
|
delete this.data[k];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public clear(): void {
|
||||||
|
this.data = {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3,7 +3,7 @@ import '../olm-loader';
|
|||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
import type { PkDecryption, PkSigning } from "@matrix-org/olm";
|
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 { Crypto } from "../../src/crypto";
|
||||||
import { MemoryCryptoStore } from "../../src/crypto/store/memory-crypto-store";
|
import { MemoryCryptoStore } from "../../src/crypto/store/memory-crypto-store";
|
||||||
import { MockStorageApi } from "../MockStorageApi";
|
import { MockStorageApi } from "../MockStorageApi";
|
||||||
@ -23,16 +23,16 @@ import { IRoomEncryption, RoomList } from "../../src/crypto/RoomList";
|
|||||||
|
|
||||||
const Olm = global.Olm;
|
const Olm = global.Olm;
|
||||||
|
|
||||||
function awaitEvent(emitter, event) {
|
function awaitEvent(emitter: EventEmitter, event: string): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve) => {
|
||||||
emitter.once(event, (result) => {
|
emitter.once(event, (result) => {
|
||||||
resolve(result);
|
resolve(result);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function keyshareEventForEvent(client, event, index): Promise<MatrixEvent> {
|
async function keyshareEventForEvent(client: MatrixClient, event: MatrixEvent, index?: number): Promise<MatrixEvent> {
|
||||||
const roomId = event.getRoomId();
|
const roomId = event.getRoomId()!;
|
||||||
const eventContent = event.getWireContent();
|
const eventContent = event.getWireContent();
|
||||||
const key = await client.crypto!.olmDevice.getInboundGroupSessionKey(
|
const key = await client.crypto!.olmDevice.getInboundGroupSessionKey(
|
||||||
roomId,
|
roomId,
|
||||||
@ -42,16 +42,16 @@ async function keyshareEventForEvent(client, event, index): Promise<MatrixEvent>
|
|||||||
);
|
);
|
||||||
const ksEvent = new MatrixEvent({
|
const ksEvent = new MatrixEvent({
|
||||||
type: "m.forwarded_room_key",
|
type: "m.forwarded_room_key",
|
||||||
sender: client.getUserId(),
|
sender: client.getUserId()!,
|
||||||
content: {
|
content: {
|
||||||
"algorithm": olmlib.MEGOLM_ALGORITHM,
|
"algorithm": olmlib.MEGOLM_ALGORITHM,
|
||||||
"room_id": roomId,
|
"room_id": roomId,
|
||||||
"sender_key": eventContent.sender_key,
|
"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_id": eventContent.session_id,
|
||||||
"session_key": key.key,
|
"session_key": key?.key,
|
||||||
"chain_index": key.chain_index,
|
"chain_index": key?.chain_index,
|
||||||
"forwarding_curve25519_key_chain": key.forwarding_curve_key_chain,
|
"forwarding_curve25519_key_chain": key?.forwarding_curve25519_key_chain,
|
||||||
"org.matrix.msc3061.shared_history": true,
|
"org.matrix.msc3061.shared_history": true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -172,7 +172,8 @@ describe("Crypto", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Session management', function() {
|
describe('Session management', function() {
|
||||||
const otkResponse = {
|
const otkResponse: IClaimOTKsResult = {
|
||||||
|
failures: {},
|
||||||
one_time_keys: {
|
one_time_keys: {
|
||||||
'@alice:home.server': {
|
'@alice:home.server': {
|
||||||
aliceDevice: {
|
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() {
|
beforeEach(async function() {
|
||||||
const mockStorage = new MockStorageApi() as unknown as Storage;
|
const mockStorage = new MockStorageApi() as unknown as Storage;
|
||||||
@ -219,8 +221,8 @@ describe("Crypto", function() {
|
|||||||
sendToDevice: jest.fn(),
|
sendToDevice: jest.fn(),
|
||||||
getKeyBackupVersion: jest.fn(),
|
getKeyBackupVersion: jest.fn(),
|
||||||
isGuest: jest.fn(),
|
isGuest: jest.fn(),
|
||||||
};
|
} as unknown as MatrixClient;
|
||||||
mockRoomList = {};
|
mockRoomList = {} as unknown as RoomList;
|
||||||
|
|
||||||
fakeEmitter = new EventEmitter();
|
fakeEmitter = new EventEmitter();
|
||||||
|
|
||||||
@ -233,7 +235,7 @@ describe("Crypto", function() {
|
|||||||
mockRoomList,
|
mockRoomList,
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
crypto.registerEventHandlers(fakeEmitter);
|
crypto.registerEventHandlers(fakeEmitter as any);
|
||||||
await crypto.init();
|
await crypto.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -245,7 +247,7 @@ describe("Crypto", function() {
|
|||||||
const prom = new Promise<void>((resolve) => {
|
const prom = new Promise<void>((resolve) => {
|
||||||
mockBaseApis.claimOneTimeKeys = function() {
|
mockBaseApis.claimOneTimeKeys = function() {
|
||||||
resolve();
|
resolve();
|
||||||
return otkResponse;
|
return Promise.resolve(otkResponse);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -989,7 +991,7 @@ describe("Crypto", function() {
|
|||||||
ensureOlmSessionsForDevices.mockResolvedValue({});
|
ensureOlmSessionsForDevices.mockResolvedValue({});
|
||||||
encryptMessageForDevice = jest.spyOn(olmlib, "encryptMessageForDevice");
|
encryptMessageForDevice = jest.spyOn(olmlib, "encryptMessageForDevice");
|
||||||
encryptMessageForDevice.mockImplementation(async (...[result,,,,,, payload]) => {
|
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");
|
client = new TestClient("@alice:example.org", "aliceweb");
|
||||||
@ -998,7 +1000,7 @@ describe("Crypto", function() {
|
|||||||
encryptedPayload = {
|
encryptedPayload = {
|
||||||
algorithm: "m.olm.v1.curve25519-aes-sha2",
|
algorithm: "m.olm.v1.curve25519-aes-sha2",
|
||||||
sender_key: client.client.crypto!.olmDevice.deviceCurve25519Key,
|
sender_key: client.client.crypto!.olmDevice.deviceCurve25519Key,
|
||||||
ciphertext: { plaintext: JSON.stringify(payload) },
|
ciphertext: { plaintext: { type: 0, body: JSON.stringify(payload) } },
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1046,7 +1048,7 @@ describe("Crypto", function() {
|
|||||||
encryptMessageForDevice.mockImplementation(async (...[result,,,, userId, device, payload]) => {
|
encryptMessageForDevice.mockImplementation(async (...[result,,,, userId, device, payload]) => {
|
||||||
// Refuse to encrypt to Carol's desktop device
|
// Refuse to encrypt to Carol's desktop device
|
||||||
if (userId === "@carol:example.org" && device.deviceId === "caroldesktop") return;
|
if (userId === "@carol:example.org" && device.deviceId === "caroldesktop") return;
|
||||||
result.plaintext = JSON.stringify(payload);
|
result.plaintext = { type: 0, body: JSON.stringify(payload) };
|
||||||
});
|
});
|
||||||
|
|
||||||
client.httpBackend
|
client.httpBackend
|
||||||
|
@ -232,7 +232,7 @@ describe.each([
|
|||||||
return store;
|
return store;
|
||||||
}],
|
}],
|
||||||
])("CrossSigning > createCryptoStoreCacheCallbacks [%s]", function(name, dbFactory) {
|
])("CrossSigning > createCryptoStoreCacheCallbacks [%s]", function(name, dbFactory) {
|
||||||
let store;
|
let store: IndexedDBCryptoStore;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
store = dbFactory();
|
store = dbFactory();
|
||||||
|
@ -22,6 +22,7 @@ import { MemoryCryptoStore } from "../../../src/crypto/store/memory-crypto-store
|
|||||||
import { DeviceList } from "../../../src/crypto/DeviceList";
|
import { DeviceList } from "../../../src/crypto/DeviceList";
|
||||||
import { IDownloadKeyResult, MatrixClient } from "../../../src";
|
import { IDownloadKeyResult, MatrixClient } from "../../../src";
|
||||||
import { OlmDevice } from "../../../src/crypto/OlmDevice";
|
import { OlmDevice } from "../../../src/crypto/OlmDevice";
|
||||||
|
import { CryptoStore } from "../../../src/crypto/store/base";
|
||||||
|
|
||||||
const signedDeviceList: IDownloadKeyResult = {
|
const signedDeviceList: IDownloadKeyResult = {
|
||||||
"failures": {},
|
"failures": {},
|
||||||
@ -88,8 +89,8 @@ const signedDeviceList2: IDownloadKeyResult = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('DeviceList', function() {
|
describe('DeviceList', function() {
|
||||||
let downloadSpy;
|
let downloadSpy: jest.Mock;
|
||||||
let cryptoStore;
|
let cryptoStore: CryptoStore;
|
||||||
let deviceLists: DeviceList[] = [];
|
let deviceLists: DeviceList[] = [];
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
@ -112,7 +113,7 @@ describe('DeviceList', function() {
|
|||||||
deviceId: 'HGKAWHRVJQ',
|
deviceId: 'HGKAWHRVJQ',
|
||||||
} as unknown as MatrixClient;
|
} as unknown as MatrixClient;
|
||||||
const mockOlm = {
|
const mockOlm = {
|
||||||
verifySignature: function(key, message, signature) {},
|
verifySignature: function(key: string, message: string, signature: string) {},
|
||||||
} as unknown as OlmDevice;
|
} as unknown as OlmDevice;
|
||||||
const dl = new DeviceList(baseApis, cryptoStore, mockOlm, keyDownloadChunkSize);
|
const dl = new DeviceList(baseApis, cryptoStore, mockOlm, keyDownloadChunkSize);
|
||||||
deviceLists.push(dl);
|
deviceLists.push(dl);
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
import { mocked, MockedObject } from 'jest-mock';
|
import { mocked, MockedObject } from 'jest-mock';
|
||||||
|
|
||||||
import '../../../olm-loader';
|
import '../../../olm-loader';
|
||||||
|
import type { OutboundGroupSession } from "@matrix-org/olm";
|
||||||
import * as algorithms from "../../../../src/crypto/algorithms";
|
import * as algorithms from "../../../../src/crypto/algorithms";
|
||||||
import { MemoryCryptoStore } from "../../../../src/crypto/store/memory-crypto-store";
|
import { MemoryCryptoStore } from "../../../../src/crypto/store/memory-crypto-store";
|
||||||
import * as testUtils from "../../../test-utils/test-utils";
|
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 { ClientEvent, MatrixClient, RoomMember } from '../../../../src';
|
||||||
import { DeviceInfo, IDevice } from '../../../../src/crypto/deviceinfo';
|
import { DeviceInfo, IDevice } from '../../../../src/crypto/deviceinfo';
|
||||||
import { DeviceTrustLevel } from '../../../../src/crypto/CrossSigning';
|
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 MegolmDecryption = algorithms.DECRYPTION_CLASSES.get('m.megolm.v1.aes-sha2')!;
|
||||||
const MegolmEncryption = algorithms.ENCRYPTION_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() {
|
describe('receives some keys:', function() {
|
||||||
let groupSession;
|
let groupSession: OutboundGroupSession;
|
||||||
beforeEach(async function() {
|
beforeEach(async function() {
|
||||||
groupSession = new global.Olm.OutboundGroupSession();
|
groupSession = new global.Olm.OutboundGroupSession();
|
||||||
groupSession.create();
|
groupSession.create();
|
||||||
@ -298,10 +300,10 @@ describe("MegolmDecryption", function() {
|
|||||||
describe("session reuse and key reshares", () => {
|
describe("session reuse and key reshares", () => {
|
||||||
const rotationPeriodMs = 999 * 24 * 60 * 60 * 1000; // 999 days, so we don't have to deal with it
|
const rotationPeriodMs = 999 * 24 * 60 * 60 * 1000; // 999 days, so we don't have to deal with it
|
||||||
|
|
||||||
let megolmEncryption;
|
let megolmEncryption: MegolmEncryptionClass;
|
||||||
let aliceDeviceInfo;
|
let aliceDeviceInfo: DeviceInfo;
|
||||||
let mockRoom;
|
let mockRoom: Room;
|
||||||
let olmDevice;
|
let olmDevice: OlmDevice;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// @ts-ignore assigning to readonly prop
|
// @ts-ignore assigning to readonly prop
|
||||||
@ -342,7 +344,7 @@ describe("MegolmDecryption", function() {
|
|||||||
'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE',
|
'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE',
|
||||||
),
|
),
|
||||||
getFingerprint: jest.fn().mockReturnValue(''),
|
getFingerprint: jest.fn().mockReturnValue(''),
|
||||||
};
|
} as unknown as DeviceInfo;
|
||||||
|
|
||||||
mockCrypto.downloadKeys.mockReturnValue(Promise.resolve({
|
mockCrypto.downloadKeys.mockReturnValue(Promise.resolve({
|
||||||
'@alice:home.server': {
|
'@alice:home.server': {
|
||||||
@ -365,7 +367,7 @@ describe("MegolmDecryption", function() {
|
|||||||
algorithm: 'm.megolm.v1.aes-sha2',
|
algorithm: 'm.megolm.v1.aes-sha2',
|
||||||
rotation_period_ms: rotationPeriodMs,
|
rotation_period_ms: rotationPeriodMs,
|
||||||
},
|
},
|
||||||
});
|
}) as MegolmEncryptionClass;
|
||||||
|
|
||||||
// Splice the real method onto the mock object as megolm uses this method
|
// Splice the real method onto the mock object as megolm uses this method
|
||||||
// on the crypto class in order to encrypt / start sessions
|
// on the crypto class in order to encrypt / start sessions
|
||||||
@ -381,7 +383,7 @@ describe("MegolmDecryption", function() {
|
|||||||
[{ userId: "@alice:home.server" }],
|
[{ userId: "@alice:home.server" }],
|
||||||
),
|
),
|
||||||
getBlacklistUnverifiedDevices: jest.fn().mockReturnValue(false),
|
getBlacklistUnverifiedDevices: jest.fn().mockReturnValue(false),
|
||||||
};
|
} as unknown as Room;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should use larger otkTimeout when preparing to encrypt room", async () => {
|
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 () => {
|
it("should generate a new session if this one needs rotation", async () => {
|
||||||
|
// @ts-ignore - private method access
|
||||||
const session = await megolmEncryption.prepareNewSession(false);
|
const session = await megolmEncryption.prepareNewSession(false);
|
||||||
session.creationTime -= rotationPeriodMs + 10000; // a smidge over the rotation time
|
session.creationTime -= rotationPeriodMs + 10000; // a smidge over the rotation time
|
||||||
// Inject expired session which needs rotation
|
// Inject expired session which needs rotation
|
||||||
|
// @ts-ignore - private field access
|
||||||
megolmEncryption.setupPromise = Promise.resolve(session);
|
megolmEncryption.setupPromise = Promise.resolve(session);
|
||||||
|
|
||||||
|
// @ts-ignore - private method access
|
||||||
const prepareNewSessionSpy = jest.spyOn(megolmEncryption, "prepareNewSession");
|
const prepareNewSessionSpy = jest.spyOn(megolmEncryption, "prepareNewSession");
|
||||||
await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
|
await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
|
||||||
body: "Some text",
|
body: "Some text",
|
||||||
@ -446,8 +451,8 @@ describe("MegolmDecryption", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
mockBaseApis.sendToDevice.mockClear();
|
mockBaseApis.sendToDevice.mockClear();
|
||||||
await megolmEncryption.reshareKeyWithDevice(
|
await megolmEncryption.reshareKeyWithDevice!(
|
||||||
olmDevice.deviceCurve25519Key,
|
olmDevice.deviceCurve25519Key!,
|
||||||
ct1.session_id,
|
ct1.session_id,
|
||||||
'@alice:home.server',
|
'@alice:home.server',
|
||||||
aliceDeviceInfo,
|
aliceDeviceInfo,
|
||||||
@ -466,8 +471,8 @@ describe("MegolmDecryption", function() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
mockBaseApis.queueToDevice.mockClear();
|
mockBaseApis.queueToDevice.mockClear();
|
||||||
await megolmEncryption.reshareKeyWithDevice(
|
await megolmEncryption.reshareKeyWithDevice!(
|
||||||
olmDevice.deviceCurve25519Key,
|
olmDevice.deviceCurve25519Key!,
|
||||||
ct1.session_id,
|
ct1.session_id,
|
||||||
'@alice:home.server',
|
'@alice:home.server',
|
||||||
aliceDeviceInfo,
|
aliceDeviceInfo,
|
||||||
|
@ -31,17 +31,21 @@ function makeOlmDevice() {
|
|||||||
return olmDevice;
|
return olmDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setupSession(initiator, opponent) {
|
async function setupSession(initiator: OlmDevice, opponent: OlmDevice) {
|
||||||
await opponent.generateOneTimeKeys(1);
|
await opponent.generateOneTimeKeys(1);
|
||||||
const keys = await opponent.getOneTimeKeys();
|
const keys = await opponent.getOneTimeKeys();
|
||||||
const firstKey = Object.values(keys['curve25519'])[0];
|
const firstKey = Object.values(keys['curve25519'])[0];
|
||||||
|
|
||||||
const sid = await initiator.createOutboundSession(
|
const sid = await initiator.createOutboundSession(opponent.deviceCurve25519Key!, firstKey);
|
||||||
opponent.deviceCurve25519Key, firstKey,
|
|
||||||
);
|
|
||||||
return sid;
|
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() {
|
describe("OlmDevice", function() {
|
||||||
if (!global.Olm) {
|
if (!global.Olm) {
|
||||||
logger.warn('Not running megolm unit tests: libolm not present');
|
logger.warn('Not running megolm unit tests: libolm not present');
|
||||||
@ -159,11 +163,6 @@ describe("OlmDevice", function() {
|
|||||||
}, "ABCDEFG"),
|
}, "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
|
// start two tasks that try to ensure that there's an olm session
|
||||||
const promises = Promise.all([
|
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(
|
const task1 = alwaysSucceed(olmlib.ensureOlmSessionsForDevices(
|
||||||
aliceOlmDevice, baseApis, devicesByUserAB,
|
aliceOlmDevice, baseApis, devicesByUserAB,
|
||||||
));
|
));
|
||||||
|
@ -15,8 +15,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MockedObject } from "jest-mock";
|
|
||||||
|
|
||||||
import '../../olm-loader';
|
import '../../olm-loader';
|
||||||
import { logger } from "../../../src/logger";
|
import { logger } from "../../../src/logger";
|
||||||
import * as olmlib from "../../../src/crypto/olmlib";
|
import * as olmlib from "../../../src/crypto/olmlib";
|
||||||
@ -30,7 +28,10 @@ import { Crypto } from "../../../src/crypto";
|
|||||||
import { resetCrossSigningKeys } from "./crypto-utils";
|
import { resetCrossSigningKeys } from "./crypto-utils";
|
||||||
import { BackupManager } from "../../../src/crypto/backup";
|
import { BackupManager } from "../../../src/crypto/backup";
|
||||||
import { StubStore } from "../../../src/store/stub";
|
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;
|
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",
|
algorithm: "org.matrix.msc3270.v1.aes-hmac-sha2",
|
||||||
version: '1',
|
version: '1',
|
||||||
auth_data: {
|
auth_data: {} as IKeyBackupInfo["auth_data"],
|
||||||
// FIXME: add iv and mac
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const keys = {};
|
const keys: Record<string, Uint8Array> = {};
|
||||||
|
|
||||||
function getCrossSigningKey(type) {
|
function getCrossSigningKey(type: string) {
|
||||||
return keys[type];
|
return Promise.resolve(keys[type]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveCrossSigningKeys(k) {
|
function saveCrossSigningKeys(k: Record<string, Uint8Array>) {
|
||||||
Object.assign(keys, k);
|
Object.assign(keys, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeTestClient(cryptoStore) {
|
function makeTestScheduler(): MatrixScheduler {
|
||||||
const scheduler = [
|
return ([
|
||||||
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
|
"getQueueForEvent",
|
||||||
|
"queueEvent",
|
||||||
|
"removeEventFromQueue",
|
||||||
"setProcessFunction",
|
"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();
|
const store = new StubStore();
|
||||||
|
|
||||||
return new MatrixClient({
|
return new MatrixClient({
|
||||||
@ -151,36 +159,33 @@ describe("MegolmBackup", function() {
|
|||||||
return Olm.init();
|
return Olm.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
let olmDevice;
|
let olmDevice: OlmDevice;
|
||||||
let mockOlmLib;
|
let mockOlmLib: typeof olmlib;
|
||||||
let mockCrypto;
|
let mockCrypto: Crypto;
|
||||||
let cryptoStore;
|
let cryptoStore: CryptoStore;
|
||||||
let megolmDecryption;
|
let megolmDecryption: MegolmDecryptionClass;
|
||||||
beforeEach(async function() {
|
beforeEach(async function() {
|
||||||
mockCrypto = testUtils.mock(Crypto, 'Crypto');
|
mockCrypto = testUtils.mock(Crypto, 'Crypto');
|
||||||
|
// @ts-ignore making mock
|
||||||
mockCrypto.backupManager = testUtils.mock(BackupManager, "BackupManager");
|
mockCrypto.backupManager = testUtils.mock(BackupManager, "BackupManager");
|
||||||
mockCrypto.backupKey = new Olm.PkEncryption();
|
mockCrypto.backupManager.backupInfo = CURVE25519_BACKUP_INFO;
|
||||||
mockCrypto.backupKey.set_recipient_key(
|
|
||||||
"hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
|
|
||||||
);
|
|
||||||
mockCrypto.backupInfo = CURVE25519_BACKUP_INFO;
|
|
||||||
|
|
||||||
cryptoStore = new MemoryCryptoStore();
|
cryptoStore = new MemoryCryptoStore();
|
||||||
|
|
||||||
olmDevice = new OlmDevice(cryptoStore);
|
olmDevice = new OlmDevice(cryptoStore);
|
||||||
|
|
||||||
// we stub out the olm encryption bits
|
// we stub out the olm encryption bits
|
||||||
mockOlmLib = {};
|
mockOlmLib = {} as unknown as typeof olmlib;
|
||||||
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
|
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
|
||||||
mockOlmLib.encryptMessageForDevice =
|
mockOlmLib.encryptMessageForDevice =
|
||||||
jest.fn().mockResolvedValue(undefined);
|
jest.fn().mockResolvedValue(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("backup", function() {
|
describe("backup", function() {
|
||||||
let mockBaseApis;
|
let mockBaseApis: MatrixClient;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
mockBaseApis = {};
|
mockBaseApis = {} as unknown as MatrixClient;
|
||||||
|
|
||||||
megolmDecryption = new MegolmDecryption({
|
megolmDecryption = new MegolmDecryption({
|
||||||
userId: '@user:id',
|
userId: '@user:id',
|
||||||
@ -188,8 +193,9 @@ describe("MegolmBackup", function() {
|
|||||||
olmDevice: olmDevice,
|
olmDevice: olmDevice,
|
||||||
baseApis: mockBaseApis,
|
baseApis: mockBaseApis,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
});
|
}) as MegolmDecryptionClass;
|
||||||
|
|
||||||
|
// @ts-ignore private field access
|
||||||
megolmDecryption.olmlib = mockOlmLib;
|
megolmDecryption.olmlib = mockOlmLib;
|
||||||
|
|
||||||
// clobber the setTimeout function to run 100x faster.
|
// clobber the setTimeout function to run 100x faster.
|
||||||
@ -239,6 +245,7 @@ describe("MegolmBackup", function() {
|
|||||||
};
|
};
|
||||||
mockCrypto.cancelRoomKeyRequest = function() {};
|
mockCrypto.cancelRoomKeyRequest = function() {};
|
||||||
|
|
||||||
|
// @ts-ignore readonly field write
|
||||||
mockCrypto.backupManager = {
|
mockCrypto.backupManager = {
|
||||||
backupGroupSession: jest.fn(),
|
backupGroupSession: jest.fn(),
|
||||||
};
|
};
|
||||||
@ -264,21 +271,22 @@ describe("MegolmBackup", function() {
|
|||||||
olmDevice: olmDevice,
|
olmDevice: olmDevice,
|
||||||
baseApis: client,
|
baseApis: client,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
});
|
}) as MegolmDecryptionClass;
|
||||||
|
|
||||||
|
// @ts-ignore private field access
|
||||||
megolmDecryption.olmlib = mockOlmLib;
|
megolmDecryption.olmlib = mockOlmLib;
|
||||||
|
|
||||||
return client.initCrypto()
|
return client.initCrypto()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return cryptoStore.doTxn(
|
return cryptoStore.doTxn(
|
||||||
"readwrite",
|
"readwrite",
|
||||||
[cryptoStore.STORE_SESSION],
|
[IndexedDBCryptoStore.STORE_SESSIONS],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
cryptoStore.addEndToEndInboundGroupSession(
|
cryptoStore.addEndToEndInboundGroupSession(
|
||||||
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
||||||
groupSession.session_id(),
|
groupSession.session_id(),
|
||||||
{
|
{
|
||||||
forwardingCurve25519KeyChain: undefined,
|
forwardingCurve25519KeyChain: undefined!,
|
||||||
keysClaimed: {
|
keysClaimed: {
|
||||||
ed25519: "SENDER_ED25519",
|
ed25519: "SENDER_ED25519",
|
||||||
},
|
},
|
||||||
@ -298,25 +306,25 @@ describe("MegolmBackup", function() {
|
|||||||
});
|
});
|
||||||
let numCalls = 0;
|
let numCalls = 0;
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
client.http.authedRequest = function<T>(
|
client.http.authedRequest = function(
|
||||||
method, path, queryParams, data, opts,
|
method, path, queryParams, data, opts,
|
||||||
): Promise<T> {
|
): any {
|
||||||
++numCalls;
|
++numCalls;
|
||||||
expect(numCalls).toBeLessThanOrEqual(1);
|
expect(numCalls).toBeLessThanOrEqual(1);
|
||||||
if (numCalls >= 2) {
|
if (numCalls >= 2) {
|
||||||
// exit out of retry loop if there's something wrong
|
// exit out of retry loop if there's something wrong
|
||||||
reject(new Error("authedRequest called too many timmes"));
|
reject(new Error("authedRequest called too many timmes"));
|
||||||
return Promise.resolve({} as T);
|
return Promise.resolve({});
|
||||||
}
|
}
|
||||||
expect(method).toBe("PUT");
|
expect(method).toBe("PUT");
|
||||||
expect(path).toBe("/room_keys/keys");
|
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).toBeDefined();
|
||||||
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toHaveProperty(
|
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toHaveProperty(
|
||||||
groupSession.session_id(),
|
groupSession.session_id(),
|
||||||
);
|
);
|
||||||
resolve();
|
resolve();
|
||||||
return Promise.resolve({} as T);
|
return Promise.resolve({});
|
||||||
};
|
};
|
||||||
client.crypto!.backupManager.backupGroupSession(
|
client.crypto!.backupManager.backupGroupSession(
|
||||||
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
||||||
@ -343,8 +351,9 @@ describe("MegolmBackup", function() {
|
|||||||
olmDevice: olmDevice,
|
olmDevice: olmDevice,
|
||||||
baseApis: client,
|
baseApis: client,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
});
|
}) as MegolmDecryptionClass;
|
||||||
|
|
||||||
|
// @ts-ignore private field access
|
||||||
megolmDecryption.olmlib = mockOlmLib;
|
megolmDecryption.olmlib = mockOlmLib;
|
||||||
|
|
||||||
return client.initCrypto()
|
return client.initCrypto()
|
||||||
@ -354,13 +363,13 @@ describe("MegolmBackup", function() {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return cryptoStore.doTxn(
|
return cryptoStore.doTxn(
|
||||||
"readwrite",
|
"readwrite",
|
||||||
[cryptoStore.STORE_SESSION],
|
[IndexedDBCryptoStore.STORE_SESSIONS],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
cryptoStore.addEndToEndInboundGroupSession(
|
cryptoStore.addEndToEndInboundGroupSession(
|
||||||
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
||||||
groupSession.session_id(),
|
groupSession.session_id(),
|
||||||
{
|
{
|
||||||
forwardingCurve25519KeyChain: undefined,
|
forwardingCurve25519KeyChain: undefined!,
|
||||||
keysClaimed: {
|
keysClaimed: {
|
||||||
ed25519: "SENDER_ED25519",
|
ed25519: "SENDER_ED25519",
|
||||||
},
|
},
|
||||||
@ -381,25 +390,25 @@ describe("MegolmBackup", function() {
|
|||||||
});
|
});
|
||||||
let numCalls = 0;
|
let numCalls = 0;
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
client.http.authedRequest = function<T>(
|
client.http.authedRequest = function(
|
||||||
method, path, queryParams, data, opts,
|
method, path, queryParams, data, opts,
|
||||||
): Promise<T> {
|
): any {
|
||||||
++numCalls;
|
++numCalls;
|
||||||
expect(numCalls).toBeLessThanOrEqual(1);
|
expect(numCalls).toBeLessThanOrEqual(1);
|
||||||
if (numCalls >= 2) {
|
if (numCalls >= 2) {
|
||||||
// exit out of retry loop if there's something wrong
|
// exit out of retry loop if there's something wrong
|
||||||
reject(new Error("authedRequest called too many timmes"));
|
reject(new Error("authedRequest called too many timmes"));
|
||||||
return Promise.resolve({} as T);
|
return Promise.resolve({});
|
||||||
}
|
}
|
||||||
expect(method).toBe("PUT");
|
expect(method).toBe("PUT");
|
||||||
expect(path).toBe("/room_keys/keys");
|
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).toBeDefined();
|
||||||
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toHaveProperty(
|
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toHaveProperty(
|
||||||
groupSession.session_id(),
|
groupSession.session_id(),
|
||||||
);
|
);
|
||||||
resolve();
|
resolve();
|
||||||
return Promise.resolve({} as T);
|
return Promise.resolve({});
|
||||||
};
|
};
|
||||||
client.crypto!.backupManager.backupGroupSession(
|
client.crypto!.backupManager.backupGroupSession(
|
||||||
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
||||||
@ -426,8 +435,9 @@ describe("MegolmBackup", function() {
|
|||||||
olmDevice: olmDevice,
|
olmDevice: olmDevice,
|
||||||
baseApis: client,
|
baseApis: client,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
});
|
}) as MegolmDecryptionClass;
|
||||||
|
|
||||||
|
// @ts-ignore private field access
|
||||||
megolmDecryption.olmlib = mockOlmLib;
|
megolmDecryption.olmlib = mockOlmLib;
|
||||||
|
|
||||||
await client.initCrypto();
|
await client.initCrypto();
|
||||||
@ -437,10 +447,10 @@ describe("MegolmBackup", function() {
|
|||||||
let numCalls = 0;
|
let numCalls = 0;
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
new Promise<void>((resolve, reject) => {
|
new Promise<void>((resolve, reject) => {
|
||||||
let backupInfo;
|
let backupInfo: Record<string, any> | BodyInit | undefined;
|
||||||
client.http.authedRequest = function(
|
client.http.authedRequest = function(
|
||||||
method, path, queryParams, data, opts,
|
method, path, queryParams, data, opts,
|
||||||
) {
|
): any {
|
||||||
++numCalls;
|
++numCalls;
|
||||||
expect(numCalls).toBeLessThanOrEqual(2);
|
expect(numCalls).toBeLessThanOrEqual(2);
|
||||||
if (numCalls === 1) {
|
if (numCalls === 1) {
|
||||||
@ -486,10 +496,7 @@ describe("MegolmBackup", function() {
|
|||||||
const ibGroupSession = new Olm.InboundGroupSession();
|
const ibGroupSession = new Olm.InboundGroupSession();
|
||||||
ibGroupSession.create(groupSession.session_key());
|
ibGroupSession.create(groupSession.session_key());
|
||||||
|
|
||||||
const scheduler = [
|
const scheduler = makeTestScheduler();
|
||||||
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
|
|
||||||
"setProcessFunction",
|
|
||||||
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {}) as MockedObject<MatrixScheduler>;
|
|
||||||
const store = new StubStore();
|
const store = new StubStore();
|
||||||
const client = new MatrixClient({
|
const client = new MatrixClient({
|
||||||
baseUrl: "https://my.home.server",
|
baseUrl: "https://my.home.server",
|
||||||
@ -509,20 +516,21 @@ describe("MegolmBackup", function() {
|
|||||||
olmDevice: olmDevice,
|
olmDevice: olmDevice,
|
||||||
baseApis: client,
|
baseApis: client,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
});
|
}) as MegolmDecryptionClass;
|
||||||
|
|
||||||
|
// @ts-ignore private field access
|
||||||
megolmDecryption.olmlib = mockOlmLib;
|
megolmDecryption.olmlib = mockOlmLib;
|
||||||
|
|
||||||
await client.initCrypto();
|
await client.initCrypto();
|
||||||
await cryptoStore.doTxn(
|
await cryptoStore.doTxn(
|
||||||
"readwrite",
|
"readwrite",
|
||||||
[cryptoStore.STORE_SESSION],
|
[IndexedDBCryptoStore.STORE_SESSIONS],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
cryptoStore.addEndToEndInboundGroupSession(
|
cryptoStore.addEndToEndInboundGroupSession(
|
||||||
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
||||||
groupSession.session_id(),
|
groupSession.session_id(),
|
||||||
{
|
{
|
||||||
forwardingCurve25519KeyChain: undefined,
|
forwardingCurve25519KeyChain: undefined!,
|
||||||
keysClaimed: {
|
keysClaimed: {
|
||||||
ed25519: "SENDER_ED25519",
|
ed25519: "SENDER_ED25519",
|
||||||
},
|
},
|
||||||
@ -542,26 +550,26 @@ describe("MegolmBackup", function() {
|
|||||||
let numCalls = 0;
|
let numCalls = 0;
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
client.http.authedRequest = function<T>(
|
client.http.authedRequest = function(
|
||||||
method, path, queryParams, data, opts,
|
method, path, queryParams, data, opts,
|
||||||
): Promise<T> {
|
): any {
|
||||||
++numCalls;
|
++numCalls;
|
||||||
expect(numCalls).toBeLessThanOrEqual(2);
|
expect(numCalls).toBeLessThanOrEqual(2);
|
||||||
if (numCalls >= 3) {
|
if (numCalls >= 3) {
|
||||||
// exit out of retry loop if there's something wrong
|
// exit out of retry loop if there's something wrong
|
||||||
reject(new Error("authedRequest called too many timmes"));
|
reject(new Error("authedRequest called too many timmes"));
|
||||||
return Promise.resolve({} as T);
|
return Promise.resolve({});
|
||||||
}
|
}
|
||||||
expect(method).toBe("PUT");
|
expect(method).toBe("PUT");
|
||||||
expect(path).toBe("/room_keys/keys");
|
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).toBeDefined();
|
||||||
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toHaveProperty(
|
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toHaveProperty(
|
||||||
groupSession.session_id(),
|
groupSession.session_id(),
|
||||||
);
|
);
|
||||||
if (numCalls > 1) {
|
if (numCalls > 1) {
|
||||||
resolve();
|
resolve();
|
||||||
return Promise.resolve({} as T);
|
return Promise.resolve({});
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
new Error("this is an expected failure"),
|
new Error("this is an expected failure"),
|
||||||
@ -579,7 +587,7 @@ describe("MegolmBackup", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("restore", function() {
|
describe("restore", function() {
|
||||||
let client;
|
let client: MatrixClient;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
client = makeTestClient(cryptoStore);
|
client = makeTestClient(cryptoStore);
|
||||||
@ -590,8 +598,9 @@ describe("MegolmBackup", function() {
|
|||||||
olmDevice: olmDevice,
|
olmDevice: olmDevice,
|
||||||
baseApis: client,
|
baseApis: client,
|
||||||
roomId: ROOM_ID,
|
roomId: ROOM_ID,
|
||||||
});
|
}) as MegolmDecryptionClass;
|
||||||
|
|
||||||
|
// @ts-ignore private field access
|
||||||
megolmDecryption.olmlib = mockOlmLib;
|
megolmDecryption.olmlib = mockOlmLib;
|
||||||
|
|
||||||
return client.initCrypto();
|
return client.initCrypto();
|
||||||
@ -603,7 +612,7 @@ describe("MegolmBackup", function() {
|
|||||||
|
|
||||||
it('can restore from backup (Curve25519 version)', function() {
|
it('can restore from backup (Curve25519 version)', function() {
|
||||||
client.http.authedRequest = function() {
|
client.http.authedRequest = function() {
|
||||||
return Promise.resolve(CURVE25519_KEY_BACKUP_DATA);
|
return Promise.resolve<any>(CURVE25519_KEY_BACKUP_DATA);
|
||||||
};
|
};
|
||||||
return client.restoreKeyBackupWithRecoveryKey(
|
return client.restoreKeyBackupWithRecoveryKey(
|
||||||
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
|
"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() {
|
it('can restore from backup (AES-256 version)', function() {
|
||||||
client.http.authedRequest = function() {
|
client.http.authedRequest = function() {
|
||||||
return Promise.resolve(AES256_KEY_BACKUP_DATA);
|
return Promise.resolve<any>(AES256_KEY_BACKUP_DATA);
|
||||||
};
|
};
|
||||||
return client.restoreKeyBackupWithRecoveryKey(
|
return client.restoreKeyBackupWithRecoveryKey(
|
||||||
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
|
"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() {
|
it('can restore backup by room (Curve25519 version)', function() {
|
||||||
client.http.authedRequest = function() {
|
client.http.authedRequest = function() {
|
||||||
return Promise.resolve({
|
return Promise.resolve<any>({
|
||||||
rooms: {
|
rooms: {
|
||||||
[ROOM_ID]: {
|
[ROOM_ID]: {
|
||||||
sessions: {
|
sessions: {
|
||||||
@ -649,7 +658,7 @@ describe("MegolmBackup", function() {
|
|||||||
};
|
};
|
||||||
return client.restoreKeyBackupWithRecoveryKey(
|
return client.restoreKeyBackupWithRecoveryKey(
|
||||||
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
|
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
|
||||||
null, null, CURVE25519_BACKUP_INFO,
|
null!, null!, CURVE25519_BACKUP_INFO,
|
||||||
).then(() => {
|
).then(() => {
|
||||||
return megolmDecryption.decryptEvent(ENCRYPTED_EVENT);
|
return megolmDecryption.decryptEvent(ENCRYPTED_EVENT);
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
@ -659,18 +668,18 @@ describe("MegolmBackup", function() {
|
|||||||
|
|
||||||
it('has working cache functions', async function() {
|
it('has working cache functions', async function() {
|
||||||
const key = Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 8]);
|
const key = Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||||
await client.crypto.storeSessionBackupPrivateKey(key);
|
await client.crypto!.storeSessionBackupPrivateKey(key);
|
||||||
const result = await client.crypto.getSessionBackupPrivateKey();
|
const result = await client.crypto!.getSessionBackupPrivateKey();
|
||||||
expect(new Uint8Array(result)).toEqual(key);
|
expect(new Uint8Array(result!)).toEqual(key);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('caches session backup keys as it encounters them', async function() {
|
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();
|
expect(cachedNull).toBeNull();
|
||||||
client.http.authedRequest = function() {
|
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(
|
client.restoreKeyBackupWithRecoveryKey(
|
||||||
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
|
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
|
||||||
ROOM_ID,
|
ROOM_ID,
|
||||||
@ -679,7 +688,7 @@ describe("MegolmBackup", function() {
|
|||||||
{ cacheCompleteCallback: resolve },
|
{ cacheCompleteCallback: resolve },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
const cachedKey = await client.crypto.getSessionBackupPrivateKey();
|
const cachedKey = await client.crypto!.getSessionBackupPrivateKey();
|
||||||
expect(cachedKey).not.toBeNull();
|
expect(cachedKey).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -688,7 +697,7 @@ describe("MegolmBackup", function() {
|
|||||||
algorithm: "this.algorithm.does.not.exist",
|
algorithm: "this.algorithm.does.not.exist",
|
||||||
});
|
});
|
||||||
client.http.authedRequest = function() {
|
client.http.authedRequest = function() {
|
||||||
return Promise.resolve(CURVE25519_KEY_BACKUP_DATA);
|
return Promise.resolve<any>(CURVE25519_KEY_BACKUP_DATA);
|
||||||
};
|
};
|
||||||
|
|
||||||
await expect(client.restoreKeyBackupWithRecoveryKey(
|
await expect(client.restoreKeyBackupWithRecoveryKey(
|
||||||
@ -702,10 +711,7 @@ describe("MegolmBackup", function() {
|
|||||||
|
|
||||||
describe("flagAllGroupSessionsForBackup", () => {
|
describe("flagAllGroupSessionsForBackup", () => {
|
||||||
it("should return number of sesions needing backup", async () => {
|
it("should return number of sesions needing backup", async () => {
|
||||||
const scheduler = [
|
const scheduler = makeTestScheduler();
|
||||||
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
|
|
||||||
"setProcessFunction",
|
|
||||||
].reduce((r, k) => {r[k] = jest.fn(); return r;}, {}) as MockedObject<MatrixScheduler>;
|
|
||||||
const store = new StubStore();
|
const store = new StubStore();
|
||||||
const client = new MatrixClient({
|
const client = new MatrixClient({
|
||||||
baseUrl: "https://my.home.server",
|
baseUrl: "https://my.home.server",
|
||||||
|
@ -18,23 +18,24 @@ limitations under the License.
|
|||||||
import '../../olm-loader';
|
import '../../olm-loader';
|
||||||
import anotherjson from 'another-json';
|
import anotherjson from 'another-json';
|
||||||
import { PkSigning } from '@matrix-org/olm';
|
import { PkSigning } from '@matrix-org/olm';
|
||||||
|
import HttpBackend from "matrix-mock-request";
|
||||||
|
|
||||||
import * as olmlib from "../../../src/crypto/olmlib";
|
import * as olmlib from "../../../src/crypto/olmlib";
|
||||||
import { MatrixError } from '../../../src/http-api';
|
import { MatrixError } from '../../../src/http-api';
|
||||||
import { logger } from '../../../src/logger';
|
import { logger } from '../../../src/logger';
|
||||||
import { ICrossSigningKey, ICreateClientOpts, ISignedKey } from '../../../src/client';
|
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 { IDevice } from '../../../src/crypto/deviceinfo';
|
||||||
import { TestClient } from '../../TestClient';
|
import { TestClient } from '../../TestClient';
|
||||||
import { resetCrossSigningKeys } from "./crypto-utils";
|
import { resetCrossSigningKeys } from "./crypto-utils";
|
||||||
|
|
||||||
const PUSH_RULES_RESPONSE = {
|
const PUSH_RULES_RESPONSE: Response = {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
path: "/pushrules/",
|
path: "/pushrules/",
|
||||||
data: {},
|
data: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterResponse = function(userId) {
|
const filterResponse = function(userId: string): Response {
|
||||||
const filterPath = "/user/" + encodeURIComponent(userId) + "/filter";
|
const filterPath = "/user/" + encodeURIComponent(userId) + "/filter";
|
||||||
return {
|
return {
|
||||||
method: "POST",
|
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 => {
|
responses.forEach(response => {
|
||||||
httpBackend
|
httpBackend
|
||||||
.when(response.method, response.path)
|
.when(response.method, response.path)
|
||||||
@ -54,13 +61,13 @@ function setHttpResponses(httpBackend, responses) {
|
|||||||
async function makeTestClient(
|
async function makeTestClient(
|
||||||
userInfo: { userId: string, deviceId: string},
|
userInfo: { userId: string, deviceId: string},
|
||||||
options: Partial<ICreateClientOpts> = {},
|
options: Partial<ICreateClientOpts> = {},
|
||||||
keys = {},
|
keys: Record<string, Uint8Array> = {},
|
||||||
) {
|
) {
|
||||||
function getCrossSigningKey(type) {
|
function getCrossSigningKey(type: string) {
|
||||||
return keys[type];
|
return keys[type] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveCrossSigningKeys(k) {
|
function saveCrossSigningKeys(k: Record<string, Uint8Array>) {
|
||||||
Object.assign(keys, k);
|
Object.assign(keys, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +149,9 @@ describe("Cross Signing", function() {
|
|||||||
alice.uploadKeySignatures = async () => ({ failures: {} });
|
alice.uploadKeySignatures = async () => ({ failures: {} });
|
||||||
alice.setAccountData = async () => ({});
|
alice.setAccountData = async () => ({});
|
||||||
alice.getAccountDataFromServer = async <T extends {[k: string]: any}>(): Promise<T | null> => ({} as T);
|
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
|
// Try bootstrap, expecting `authUploadDeviceSigningKeys` to pass
|
||||||
// through failure, stopping before actually applying changes.
|
// 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
|
// feed sync result that includes master key, ssk, device key
|
||||||
const responses = [
|
const responses: Response[] = [
|
||||||
PUSH_RULES_RESPONSE,
|
PUSH_RULES_RESPONSE,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -464,7 +473,7 @@ describe("Cross Signing", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.skip("should trust signatures received from other devices", async 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(
|
const { client: alice, httpBackend } = await makeTestClient(
|
||||||
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
||||||
undefined,
|
undefined,
|
||||||
@ -494,8 +503,7 @@ describe("Cross Signing", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// @ts-ignore private property
|
// @ts-ignore private property
|
||||||
const deviceInfo = alice.crypto!.deviceList.devices["@alice:example.com"]
|
const deviceInfo = alice.crypto!.deviceList.devices["@alice:example.com"].Osborne2;
|
||||||
.Osborne2;
|
|
||||||
const aliceDevice = {
|
const aliceDevice = {
|
||||||
user_id: "@alice:example.com",
|
user_id: "@alice:example.com",
|
||||||
device_id: "Osborne2",
|
device_id: "Osborne2",
|
||||||
@ -549,7 +557,7 @@ describe("Cross Signing", function() {
|
|||||||
// - ssk
|
// - ssk
|
||||||
// - master key signed by her usk (pretend that it was signed by another
|
// - master key signed by her usk (pretend that it was signed by another
|
||||||
// of Alice's devices)
|
// of Alice's devices)
|
||||||
const responses = [
|
const responses: Response[] = [
|
||||||
PUSH_RULES_RESPONSE,
|
PUSH_RULES_RESPONSE,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -853,7 +861,7 @@ describe("Cross Signing", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should offer to upgrade device verifications to cross-signing", async function() {
|
it("should offer to upgrade device verifications to cross-signing", async function() {
|
||||||
let upgradeResolveFunc;
|
let upgradeResolveFunc: Function;
|
||||||
|
|
||||||
const { client: alice } = await makeTestClient(
|
const { client: alice } = await makeTestClient(
|
||||||
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import { IRecoveryKey } from '../../../src/crypto/api';
|
import { IRecoveryKey } from '../../../src/crypto/api';
|
||||||
import { CrossSigningLevel } from '../../../src/crypto/CrossSigning';
|
import { CrossSigningLevel } from '../../../src/crypto/CrossSigning';
|
||||||
import { IndexedDBCryptoStore } from '../../../src/crypto/store/indexeddb-crypto-store';
|
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,
|
// needs to be phased out and replaced with bootstrapSecretStorage,
|
||||||
// but that is doing too much extra stuff for it to be an easy transition.
|
// but that is doing too much extra stuff for it to be an easy transition.
|
||||||
export async function resetCrossSigningKeys(
|
export async function resetCrossSigningKeys(
|
||||||
client,
|
client: MatrixClient,
|
||||||
{ level }: { level?: CrossSigningLevel} = {},
|
{ level }: { level?: CrossSigningLevel} = {},
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const crypto = client.crypto;
|
const crypto = client.crypto!;
|
||||||
|
|
||||||
const oldKeys = Object.assign({}, crypto.crossSigningInfo.keys);
|
const oldKeys = Object.assign({}, crypto.crossSigningInfo.keys);
|
||||||
try {
|
try {
|
||||||
@ -28,7 +30,8 @@ export async function resetCrossSigningKeys(
|
|||||||
crypto.crossSigningInfo.keys = oldKeys;
|
crypto.crossSigningInfo.keys = oldKeys;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
crypto.emit("crossSigning.keysChanged", {});
|
crypto.emit(CryptoEvent.KeysChanged, {});
|
||||||
|
// @ts-ignore
|
||||||
await crypto.afterCrossSigningLocalKeyChange();
|
await crypto.afterCrossSigningLocalKeyChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import '../../olm-loader';
|
import '../../olm-loader';
|
||||||
import * as olmlib from "../../../src/crypto/olmlib";
|
import * as olmlib from "../../../src/crypto/olmlib";
|
||||||
|
import { IObject } from "../../../src/crypto/olmlib";
|
||||||
import { SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/crypto/SecretStorage";
|
import { SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/crypto/SecretStorage";
|
||||||
import { MatrixEvent } from "../../../src/models/event";
|
import { MatrixEvent } from "../../../src/models/event";
|
||||||
import { TestClient } from '../../TestClient';
|
import { TestClient } from '../../TestClient';
|
||||||
@ -23,9 +24,11 @@ import { makeTestClients } from './verification/util';
|
|||||||
import { encryptAES } from "../../../src/crypto/aes";
|
import { encryptAES } from "../../../src/crypto/aes";
|
||||||
import { createSecretStorageKey, resetCrossSigningKeys } from "./crypto-utils";
|
import { createSecretStorageKey, resetCrossSigningKeys } from "./crypto-utils";
|
||||||
import { logger } from '../../../src/logger';
|
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 { ISecretStorageKeyInfo } from '../../../src/crypto/api';
|
||||||
import { DeviceInfo } from '../../../src/crypto/deviceinfo';
|
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> = {}) {
|
async function makeTestClient(userInfo: { userId: string, deviceId: string}, options: Partial<ICreateClientOpts> = {}) {
|
||||||
const client = (new TestClient(
|
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
|
// Wrapper around pkSign to return a signed object. pkSign returns the
|
||||||
// signature, rather than the signed object.
|
// 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, '');
|
olmlib.pkSign(obj, key, userId, '');
|
||||||
return obj;
|
return obj as T & {
|
||||||
|
signatures: ISignatures;
|
||||||
|
unsigned?: object;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("Secrets", function() {
|
describe("Secrets", function() {
|
||||||
@ -169,12 +178,12 @@ describe("Secrets", function() {
|
|||||||
return [newKeyId, key];
|
return [newKeyId, key];
|
||||||
});
|
});
|
||||||
|
|
||||||
let keys = {};
|
let keys: Record<string, Uint8Array> = {};
|
||||||
const alice = await makeTestClient(
|
const alice = await makeTestClient(
|
||||||
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
{ userId: "@alice:example.com", deviceId: "Osborne2" },
|
||||||
{
|
{
|
||||||
cryptoCallbacks: {
|
cryptoCallbacks: {
|
||||||
getCrossSigningKey: t => keys[t],
|
getCrossSigningKey: t => Promise.resolve(keys[t]),
|
||||||
saveCrossSigningKeys: k => keys = k,
|
saveCrossSigningKeys: k => keys = k,
|
||||||
getSecretStorageKey: getKey,
|
getSecretStorageKey: getKey,
|
||||||
},
|
},
|
||||||
@ -227,7 +236,7 @@ describe("Secrets", function() {
|
|||||||
cryptoCallbacks: {
|
cryptoCallbacks: {
|
||||||
onSecretRequested: (userId, deviceId, requestId, secretName, deviceTrust) => {
|
onSecretRequested: (userId, deviceId, requestId, secretName, deviceTrust) => {
|
||||||
expect(secretName).toBe("foo");
|
expect(secretName).toBe("foo");
|
||||||
return "bar";
|
return Promise.resolve("bar");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -354,7 +363,7 @@ describe("Secrets", function() {
|
|||||||
const storagePublicKey = decryption.generate_key();
|
const storagePublicKey = decryption.generate_key();
|
||||||
const storagePrivateKey = decryption.get_private_key();
|
const storagePrivateKey = decryption.get_private_key();
|
||||||
|
|
||||||
const bob = await makeTestClient(
|
const bob: MatrixClient = await makeTestClient(
|
||||||
{
|
{
|
||||||
userId: "@bob:example.com",
|
userId: "@bob:example.com",
|
||||||
deviceId: "bob1",
|
deviceId: "bob1",
|
||||||
@ -364,15 +373,15 @@ describe("Secrets", function() {
|
|||||||
getSecretStorageKey: async request => {
|
getSecretStorageKey: async request => {
|
||||||
const defaultKeyId = await bob.getDefaultSecretStorageKeyId();
|
const defaultKeyId = await bob.getDefaultSecretStorageKeyId();
|
||||||
expect(Object.keys(request.keys)).toEqual([defaultKeyId]);
|
expect(Object.keys(request.keys)).toEqual([defaultKeyId]);
|
||||||
return [defaultKeyId, storagePrivateKey];
|
return [defaultKeyId!, storagePrivateKey];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
bob.uploadDeviceSigningKeys = async () => {};
|
bob.uploadDeviceSigningKeys = async () => ({});
|
||||||
bob.uploadKeySignatures = async () => {};
|
bob.uploadKeySignatures = async () => ({ failures: {} });
|
||||||
bob.setAccountData = async function(eventType, contents, callback) {
|
bob.setAccountData = async function(eventType, contents) {
|
||||||
const event = new MatrixEvent({
|
const event = new MatrixEvent({
|
||||||
type: eventType,
|
type: eventType,
|
||||||
content: contents,
|
content: contents,
|
||||||
@ -380,16 +389,19 @@ describe("Secrets", function() {
|
|||||||
this.store.storeAccountDataEvents([
|
this.store.storeAccountDataEvents([
|
||||||
event,
|
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 crossSigning = bob.crypto!.crossSigningInfo;
|
||||||
const secretStorage = bob.crypto.secretStorage;
|
const secretStorage = bob.crypto!.secretStorage;
|
||||||
|
|
||||||
// Set up cross-signing keys from scratch with specific storage key
|
// Set up cross-signing keys from scratch with specific storage key
|
||||||
await bob.bootstrapCrossSigning({
|
await bob.bootstrapCrossSigning({
|
||||||
authUploadDeviceSigningKeys: async func => await func({}),
|
authUploadDeviceSigningKeys: async func => {
|
||||||
|
await func({});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
await bob.bootstrapSecretStorage({
|
await bob.bootstrapSecretStorage({
|
||||||
createSecretStorageKey: async () => ({
|
createSecretStorageKey: async () => ({
|
||||||
@ -400,13 +412,15 @@ describe("Secrets", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Clear local cross-signing keys and read from secret storage
|
// Clear local cross-signing keys and read from secret storage
|
||||||
bob.crypto.deviceList.storeCrossSigningForUser(
|
bob.crypto!.deviceList.storeCrossSigningForUser(
|
||||||
"@bob:example.com",
|
"@bob:example.com",
|
||||||
crossSigning.toStorage(),
|
crossSigning.toStorage(),
|
||||||
);
|
);
|
||||||
crossSigning.keys = {};
|
crossSigning.keys = {};
|
||||||
await bob.bootstrapCrossSigning({
|
await bob.bootstrapCrossSigning({
|
||||||
authUploadDeviceSigningKeys: async func => await func({}),
|
authUploadDeviceSigningKeys: async func => {
|
||||||
|
await func({});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(crossSigning.getId()).toBeTruthy();
|
expect(crossSigning.getId()).toBeTruthy();
|
||||||
@ -422,7 +436,7 @@ describe("Secrets", function() {
|
|||||||
user_signing: USK,
|
user_signing: USK,
|
||||||
self_signing: SSK,
|
self_signing: SSK,
|
||||||
};
|
};
|
||||||
const secretStorageKeys = {
|
const secretStorageKeys: Record<string, Uint8Array> = {
|
||||||
key_id: SSSSKey,
|
key_id: SSSSKey,
|
||||||
};
|
};
|
||||||
const alice = await makeTestClient(
|
const alice = await makeTestClient(
|
||||||
@ -498,14 +512,14 @@ describe("Secrets", function() {
|
|||||||
[`ed25519:${XSPubKey}`]: XSPubKey,
|
[`ed25519:${XSPubKey}`]: XSPubKey,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
self_signing: sign({
|
self_signing: sign<ICrossSigningKey>({
|
||||||
user_id: "@alice:example.com",
|
user_id: "@alice:example.com",
|
||||||
usage: ["self_signing"],
|
usage: ["self_signing"],
|
||||||
keys: {
|
keys: {
|
||||||
[`ed25519:${SSPubKey}`]: SSPubKey,
|
[`ed25519:${SSPubKey}`]: SSPubKey,
|
||||||
},
|
},
|
||||||
}, XSK, "@alice:example.com"),
|
}, XSK, "@alice:example.com"),
|
||||||
user_signing: sign({
|
user_signing: sign<ICrossSigningKey>({
|
||||||
user_id: "@alice:example.com",
|
user_id: "@alice:example.com",
|
||||||
usage: ["user_signing"],
|
usage: ["user_signing"],
|
||||||
keys: {
|
keys: {
|
||||||
@ -557,7 +571,7 @@ describe("Secrets", function() {
|
|||||||
user_signing: USK,
|
user_signing: USK,
|
||||||
self_signing: SSK,
|
self_signing: SSK,
|
||||||
};
|
};
|
||||||
const secretStorageKeys = {
|
const secretStorageKeys: Record<string, Uint8Array> = {
|
||||||
key_id: SSSSKey,
|
key_id: SSSSKey,
|
||||||
};
|
};
|
||||||
const alice = await makeTestClient(
|
const alice = await makeTestClient(
|
||||||
@ -642,14 +656,14 @@ describe("Secrets", function() {
|
|||||||
[`ed25519:${XSPubKey}`]: XSPubKey,
|
[`ed25519:${XSPubKey}`]: XSPubKey,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
self_signing: sign({
|
self_signing: sign<ICrossSigningKey>({
|
||||||
user_id: "@alice:example.com",
|
user_id: "@alice:example.com",
|
||||||
usage: ["self_signing"],
|
usage: ["self_signing"],
|
||||||
keys: {
|
keys: {
|
||||||
[`ed25519:${SSPubKey}`]: SSPubKey,
|
[`ed25519:${SSPubKey}`]: SSPubKey,
|
||||||
},
|
},
|
||||||
}, XSK, "@alice:example.com"),
|
}, XSK, "@alice:example.com"),
|
||||||
user_signing: sign({
|
user_signing: sign<ICrossSigningKey>({
|
||||||
user_id: "@alice:example.com",
|
user_id: "@alice:example.com",
|
||||||
usage: ["user_signing"],
|
usage: ["user_signing"],
|
||||||
keys: {
|
keys: {
|
||||||
|
@ -18,12 +18,12 @@ import "../../../olm-loader";
|
|||||||
import { makeTestClients } from './util';
|
import { makeTestClients } from './util';
|
||||||
import { MatrixEvent } from "../../../../src/models/event";
|
import { MatrixEvent } from "../../../../src/models/event";
|
||||||
import { ISasEvent, SAS, SasEvent } from "../../../../src/crypto/verification/SAS";
|
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 { CryptoEvent, verificationMethods } from "../../../../src/crypto";
|
||||||
import * as olmlib from "../../../../src/crypto/olmlib";
|
import * as olmlib from "../../../../src/crypto/olmlib";
|
||||||
import { logger } from "../../../../src/logger";
|
import { logger } from "../../../../src/logger";
|
||||||
import { resetCrossSigningKeys } from "../crypto-utils";
|
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 { IVerificationChannel } from "../../../../src/crypto/verification/request/Channel";
|
||||||
import { MatrixClient } from "../../../../src";
|
import { MatrixClient } from "../../../../src";
|
||||||
import { VerificationRequest } from "../../../../src/crypto/verification/request/VerificationRequest";
|
import { VerificationRequest } from "../../../../src/crypto/verification/request/VerificationRequest";
|
||||||
@ -31,8 +31,8 @@ import { TestClient } from "../../../TestClient";
|
|||||||
|
|
||||||
const Olm = global.Olm;
|
const Olm = global.Olm;
|
||||||
|
|
||||||
let ALICE_DEVICES;
|
let ALICE_DEVICES: Record<string, IDevice>;
|
||||||
let BOB_DEVICES;
|
let BOB_DEVICES: Record<string, IDevice>;
|
||||||
|
|
||||||
describe("SAS verification", function() {
|
describe("SAS verification", function() {
|
||||||
if (!global.Olm) {
|
if (!global.Olm) {
|
||||||
@ -75,7 +75,7 @@ describe("SAS verification", function() {
|
|||||||
let bob: TestClient;
|
let bob: TestClient;
|
||||||
let aliceSasEvent: ISasEvent | null;
|
let aliceSasEvent: ISasEvent | null;
|
||||||
let bobSasEvent: ISasEvent | null;
|
let bobSasEvent: ISasEvent | null;
|
||||||
let aliceVerifier: Verification<any, any>;
|
let aliceVerifier: SAS;
|
||||||
let bobPromise: Promise<VerificationBase<any, any>>;
|
let bobPromise: Promise<VerificationBase<any, any>>;
|
||||||
let clearTestClientTimeouts: () => void;
|
let clearTestClientTimeouts: () => void;
|
||||||
|
|
||||||
@ -95,25 +95,25 @@ describe("SAS verification", function() {
|
|||||||
|
|
||||||
ALICE_DEVICES = {
|
ALICE_DEVICES = {
|
||||||
Osborne2: {
|
Osborne2: {
|
||||||
user_id: "@alice:example.com",
|
|
||||||
device_id: "Osborne2",
|
|
||||||
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
|
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
|
||||||
keys: {
|
keys: {
|
||||||
"ed25519:Osborne2": aliceDevice.deviceEd25519Key,
|
"ed25519:Osborne2": aliceDevice.deviceEd25519Key!,
|
||||||
"curve25519:Osborne2": aliceDevice.deviceCurve25519Key,
|
"curve25519:Osborne2": aliceDevice.deviceCurve25519Key!,
|
||||||
},
|
},
|
||||||
|
verified: DeviceInfo.DeviceVerification.UNVERIFIED,
|
||||||
|
known: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
BOB_DEVICES = {
|
BOB_DEVICES = {
|
||||||
Dynabook: {
|
Dynabook: {
|
||||||
user_id: "@bob:example.com",
|
|
||||||
device_id: "Dynabook",
|
|
||||||
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
|
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
|
||||||
keys: {
|
keys: {
|
||||||
"ed25519:Dynabook": bobDevice.deviceEd25519Key,
|
"ed25519:Dynabook": bobDevice.deviceEd25519Key!,
|
||||||
"curve25519:Dynabook": bobDevice.deviceCurve25519Key,
|
"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) => {
|
bobPromise = new Promise<VerificationBase<any, any>>((resolve, reject) => {
|
||||||
bob.client.on(CryptoEvent.VerificationRequest, request => {
|
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) {
|
if (!e.sas.emoji || !e.sas.decimal) {
|
||||||
e.cancel();
|
e.cancel();
|
||||||
} else if (!aliceSasEvent) {
|
} else if (!aliceSasEvent) {
|
||||||
@ -158,7 +158,7 @@ describe("SAS verification", function() {
|
|||||||
|
|
||||||
aliceVerifier = alice.client.beginKeyVerification(
|
aliceVerifier = alice.client.beginKeyVerification(
|
||||||
verificationMethods.SAS, bob.client.getUserId()!, bob.deviceId!,
|
verificationMethods.SAS, bob.client.getUserId()!, bob.deviceId!,
|
||||||
);
|
) as SAS;
|
||||||
aliceVerifier.on(SasEvent.ShowSas, (e) => {
|
aliceVerifier.on(SasEvent.ShowSas, (e) => {
|
||||||
if (!e.sas.emoji || !e.sas.decimal) {
|
if (!e.sas.emoji || !e.sas.decimal) {
|
||||||
e.cancel();
|
e.cancel();
|
||||||
@ -413,7 +413,7 @@ describe("SAS verification", function() {
|
|||||||
|
|
||||||
const bobPromise = new Promise<VerificationBase<any, any>>((resolve, reject) => {
|
const bobPromise = new Promise<VerificationBase<any, any>>((resolve, reject) => {
|
||||||
bob.client.on(CryptoEvent.VerificationRequest, request => {
|
bob.client.on(CryptoEvent.VerificationRequest, request => {
|
||||||
request.verifier!.on("show_sas", (e) => {
|
(<SAS>request.verifier!).on(SasEvent.ShowSas, (e) => {
|
||||||
e.mismatch();
|
e.mismatch();
|
||||||
});
|
});
|
||||||
resolve(request.verifier!);
|
resolve(request.verifier!);
|
||||||
@ -443,13 +443,13 @@ describe("SAS verification", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("verification in DM", function() {
|
describe("verification in DM", function() {
|
||||||
let alice;
|
let alice: TestClient;
|
||||||
let bob;
|
let bob: TestClient;
|
||||||
let aliceSasEvent;
|
let aliceSasEvent: ISasEvent | null;
|
||||||
let bobSasEvent;
|
let bobSasEvent: ISasEvent | null;
|
||||||
let aliceVerifier;
|
let aliceVerifier: SAS;
|
||||||
let bobPromise;
|
let bobPromise: Promise<void>;
|
||||||
let clearTestClientTimeouts;
|
let clearTestClientTimeouts: Function;
|
||||||
|
|
||||||
beforeEach(async function() {
|
beforeEach(async function() {
|
||||||
[[alice, bob], clearTestClientTimeouts] = await makeTestClients(
|
[[alice, bob], clearTestClientTimeouts] = await makeTestClients(
|
||||||
@ -477,7 +477,7 @@ describe("SAS verification", function() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
alice.client.downloadKeys = () => {
|
alice.client.downloadKeys = () => {
|
||||||
return Promise.resolve();
|
return Promise.resolve({});
|
||||||
};
|
};
|
||||||
|
|
||||||
bob.client.crypto!.setDeviceVerification = jest.fn();
|
bob.client.crypto!.setDeviceVerification = jest.fn();
|
||||||
@ -495,16 +495,16 @@ describe("SAS verification", function() {
|
|||||||
return "bob+base64+ed25519+key";
|
return "bob+base64+ed25519+key";
|
||||||
};
|
};
|
||||||
bob.client.downloadKeys = () => {
|
bob.client.downloadKeys = () => {
|
||||||
return Promise.resolve();
|
return Promise.resolve({});
|
||||||
};
|
};
|
||||||
|
|
||||||
aliceSasEvent = null;
|
aliceSasEvent = null;
|
||||||
bobSasEvent = null;
|
bobSasEvent = null;
|
||||||
|
|
||||||
bobPromise = new Promise<void>((resolve, reject) => {
|
bobPromise = new Promise<void>((resolve, reject) => {
|
||||||
bob.client.on("crypto.verification.request", async (request) => {
|
bob.client.on(CryptoEvent.VerificationRequest, async (request) => {
|
||||||
const verifier = request.beginKeyVerification(SAS.NAME);
|
const verifier = request.beginKeyVerification(SAS.NAME) as SAS;
|
||||||
verifier.on("show_sas", (e) => {
|
verifier.on(SasEvent.ShowSas, (e) => {
|
||||||
if (!e.sas.emoji || !e.sas.decimal) {
|
if (!e.sas.emoji || !e.sas.decimal) {
|
||||||
e.cancel();
|
e.cancel();
|
||||||
} else if (!aliceSasEvent) {
|
} else if (!aliceSasEvent) {
|
||||||
@ -525,12 +525,10 @@ describe("SAS verification", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const aliceRequest = await alice.client.requestVerificationDM(
|
const aliceRequest = await alice.client.requestVerificationDM(bob.client.getUserId()!, "!room_id");
|
||||||
bob.client.getUserId(), "!room_id",
|
|
||||||
);
|
|
||||||
await aliceRequest.waitFor(r => r.started);
|
await aliceRequest.waitFor(r => r.started);
|
||||||
aliceVerifier = aliceRequest.verifier;
|
aliceVerifier = aliceRequest.verifier! as SAS;
|
||||||
aliceVerifier.on("show_sas", (e) => {
|
aliceVerifier.on(SasEvent.ShowSas, (e) => {
|
||||||
if (!e.sas.emoji || !e.sas.decimal) {
|
if (!e.sas.emoji || !e.sas.decimal) {
|
||||||
e.cancel();
|
e.cancel();
|
||||||
} else if (!bobSasEvent) {
|
} else if (!bobSasEvent) {
|
||||||
|
@ -125,7 +125,7 @@ describe("self-verifications", () => {
|
|||||||
expect(restoreKeyBackupWithCache).toHaveBeenCalled();
|
expect(restoreKeyBackupWithCache).toHaveBeenCalled();
|
||||||
|
|
||||||
expect(result).toBeInstanceOf(Array);
|
expect(result).toBeInstanceOf(Array);
|
||||||
expect(result[0][0]).toBe(testKeyPub);
|
expect(result![0][0]).toBe(testKeyPub);
|
||||||
expect(result[1][0]).toBe(testKeyPub);
|
expect(result![1][0]).toBe(testKeyPub);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -16,13 +16,21 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { TestClient } from '../../../TestClient';
|
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 { IRoomTimelineData } from "../../../../src/models/event-timeline-set";
|
||||||
import { Room, RoomEvent } from "../../../../src/models/room";
|
import { Room, RoomEvent } from "../../../../src/models/room";
|
||||||
import { logger } from '../../../../src/logger';
|
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 clients: TestClient[] = [];
|
||||||
const timeouts: ReturnType<typeof setTimeout>[] = [];
|
const timeouts: ReturnType<typeof setTimeout>[] = [];
|
||||||
const clientMap: Record<string, Record<string, MatrixClient>> = {};
|
const clientMap: Record<string, Record<string, MatrixClient>> = {};
|
||||||
@ -51,7 +59,7 @@ export async function makeTestClients(userInfos, options): Promise<[TestClient[]
|
|||||||
}
|
}
|
||||||
return {};
|
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
|
// make up a unique ID as the event ID
|
||||||
const eventId = "$" + matrixClient.makeTxnId();
|
const eventId = "$" + matrixClient.makeTxnId();
|
||||||
const rawEvent = {
|
const rawEvent = {
|
||||||
@ -88,11 +96,12 @@ export async function makeTestClients(userInfos, options): Promise<[TestClient[]
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (const userInfo of userInfos) {
|
for (const userInfo of userInfos) {
|
||||||
let keys = {};
|
let keys: Record<string, Uint8Array> = {};
|
||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
if (!options.cryptoCallbacks) options.cryptoCallbacks = {};
|
if (!options.cryptoCallbacks) options.cryptoCallbacks = {};
|
||||||
if (!options.cryptoCallbacks.saveCrossSigningKeys) {
|
if (!options.cryptoCallbacks.saveCrossSigningKeys) {
|
||||||
options.cryptoCallbacks.saveCrossSigningKeys = k => { keys = k; };
|
options.cryptoCallbacks.saveCrossSigningKeys = k => { keys = k; };
|
||||||
|
// @ts-ignore tsc getting confused by overloads
|
||||||
options.cryptoCallbacks.getCrossSigningKey = typ => keys[typ];
|
options.cryptoCallbacks.getCrossSigningKey = typ => keys[typ];
|
||||||
}
|
}
|
||||||
const testClient = new TestClient(
|
const testClient = new TestClient(
|
||||||
@ -104,6 +113,7 @@ export async function makeTestClients(userInfos, options): Promise<[TestClient[]
|
|||||||
}
|
}
|
||||||
clientMap[userInfo.userId][userInfo.deviceId] = testClient.client;
|
clientMap[userInfo.userId][userInfo.deviceId] = testClient.client;
|
||||||
testClient.client.sendToDevice = makeSendToDevice(testClient.client);
|
testClient.client.sendToDevice = makeSendToDevice(testClient.client);
|
||||||
|
// @ts-ignore tsc getting confused by overloads
|
||||||
testClient.client.sendEvent = makeSendEvent(testClient.client);
|
testClient.client.sendEvent = makeSendEvent(testClient.client);
|
||||||
clients.push(testClient);
|
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 { InRoomChannel } from "../../../../src/crypto/verification/request/InRoomChannel";
|
||||||
import { ToDeviceChannel } from
|
import { ToDeviceChannel } from
|
||||||
"../../../../src/crypto/verification/request/ToDeviceChannel";
|
"../../../../src/crypto/verification/request/ToDeviceChannel";
|
||||||
import { MatrixEvent } from "../../../../src/models/event";
|
import { IContent, MatrixEvent } from "../../../../src/models/event";
|
||||||
import { MatrixClient } from "../../../../src/client";
|
import { MatrixClient } from "../../../../src/client";
|
||||||
import { IVerificationChannel } from "../../../../src/crypto/verification/request/Channel";
|
import { IVerificationChannel } from "../../../../src/crypto/verification/request/Channel";
|
||||||
import { VerificationBase } from "../../../../src/crypto/verification/Base";
|
import { VerificationBase } from "../../../../src/crypto/verification/Base";
|
||||||
@ -30,12 +30,12 @@ type MockClient = MatrixClient & {
|
|||||||
function makeMockClient(userId: string, deviceId: string): MockClient {
|
function makeMockClient(userId: string, deviceId: string): MockClient {
|
||||||
let counter = 1;
|
let counter = 1;
|
||||||
let events: MatrixEvent[] = [];
|
let events: MatrixEvent[] = [];
|
||||||
const deviceEvents = {};
|
const deviceEvents: Record<string, Record<string, MatrixEvent[]>> = {};
|
||||||
return {
|
return {
|
||||||
getUserId() { return userId; },
|
getUserId() { return userId; },
|
||||||
getDeviceId() { return deviceId; },
|
getDeviceId() { return deviceId; },
|
||||||
|
|
||||||
sendEvent(roomId, type, content) {
|
sendEvent(roomId: string, type: string, content: IContent) {
|
||||||
counter = counter + 1;
|
counter = counter + 1;
|
||||||
const eventId = `$${userId}-${deviceId}-${counter}`;
|
const eventId = `$${userId}-${deviceId}-${counter}`;
|
||||||
events.push(new MatrixEvent({
|
events.push(new MatrixEvent({
|
||||||
@ -49,7 +49,7 @@ function makeMockClient(userId: string, deviceId: string): MockClient {
|
|||||||
return Promise.resolve({ event_id: eventId });
|
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)) {
|
for (const userId of Object.keys(msgMap)) {
|
||||||
const deviceMap = msgMap[userId];
|
const deviceMap = msgMap[userId];
|
||||||
for (const deviceId of Object.keys(deviceMap)) {
|
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) {
|
if (event.getType() === DONE_TYPE && !this._startEvent) {
|
||||||
await this._channel.send(DONE_TYPE, {});
|
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, {
|
return new MatrixEvent(Object.assign({}, event.event, {
|
||||||
unsigned: {
|
unsigned: {
|
||||||
transaction_id: "abc",
|
transaction_id: "abc",
|
||||||
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
|
|
||||||
import { logger } from "../../src/logger";
|
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 { Filter } from "../../src/filter";
|
||||||
import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE } from "../../src/models/MSC3089TreeSpace";
|
import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE } from "../../src/models/MSC3089TreeSpace";
|
||||||
import {
|
import {
|
||||||
@ -36,7 +36,16 @@ import { ReceiptType } from "../../src/@types/read_receipts";
|
|||||||
import * as testUtils from "../test-utils/test-utils";
|
import * as testUtils from "../test-utils/test-utils";
|
||||||
import { makeBeaconInfoContent } from "../../src/content-helpers";
|
import { makeBeaconInfoContent } from "../../src/content-helpers";
|
||||||
import { M_BEACON_INFO } from "../../src/@types/beacon";
|
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 { supportsMatrixCall } from "../../src/webrtc/call";
|
||||||
import { makeBeaconEvent } from "../test-utils/beacon";
|
import { makeBeaconEvent } from "../test-utils/beacon";
|
||||||
import {
|
import {
|
||||||
@ -44,6 +53,9 @@ import {
|
|||||||
POLICIES_ACCOUNT_EVENT_TYPE,
|
POLICIES_ACCOUNT_EVENT_TYPE,
|
||||||
PolicyScope,
|
PolicyScope,
|
||||||
} from "../../src/models/invites-ignorer";
|
} from "../../src/models/invites-ignorer";
|
||||||
|
import { IOlmDevice } from "../../src/crypto/algorithms/megolm";
|
||||||
|
import { QueryDict } from "../../src/utils";
|
||||||
|
import { SyncState } from "../../src/sync";
|
||||||
|
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
|
||||||
@ -52,17 +64,36 @@ jest.mock("../../src/webrtc/call", () => ({
|
|||||||
supportsMatrixCall: jest.fn(() => false),
|
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() {
|
describe("MatrixClient", function() {
|
||||||
const userId = "@alice:bar";
|
const userId = "@alice:bar";
|
||||||
const identityServerUrl = "https://identity.server";
|
const identityServerUrl = "https://identity.server";
|
||||||
const identityServerDomain = "identity.server";
|
const identityServerDomain = "identity.server";
|
||||||
let client;
|
let client: MatrixClient;
|
||||||
let store;
|
let store: Store;
|
||||||
let scheduler;
|
let scheduler: MatrixScheduler;
|
||||||
|
|
||||||
const KEEP_ALIVE_PATH = "/_matrix/client/versions";
|
const KEEP_ALIVE_PATH = "/_matrix/client/versions";
|
||||||
|
|
||||||
const PUSH_RULES_RESPONSE = {
|
const PUSH_RULES_RESPONSE: HttpLookup = {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
path: "/pushrules/",
|
path: "/pushrules/",
|
||||||
data: {},
|
data: {},
|
||||||
@ -70,7 +101,7 @@ describe("MatrixClient", function() {
|
|||||||
|
|
||||||
const FILTER_PATH = "/user/" + encodeURIComponent(userId) + "/filter";
|
const FILTER_PATH = "/user/" + encodeURIComponent(userId) + "/filter";
|
||||||
|
|
||||||
const FILTER_RESPONSE = {
|
const FILTER_RESPONSE: HttpLookup = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
path: FILTER_PATH,
|
path: FILTER_PATH,
|
||||||
data: { filter_id: "f1lt3r" },
|
data: { filter_id: "f1lt3r" },
|
||||||
@ -82,29 +113,21 @@ describe("MatrixClient", function() {
|
|||||||
rooms: {},
|
rooms: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const SYNC_RESPONSE = {
|
const SYNC_RESPONSE: HttpLookup = {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
path: "/sync",
|
path: "/sync",
|
||||||
data: SYNC_DATA,
|
data: SYNC_DATA,
|
||||||
};
|
};
|
||||||
|
|
||||||
// items are popped off when processed and block if no items left.
|
// items are popped off when processed and block if no items left.
|
||||||
let httpLookups: {
|
let httpLookups: HttpLookup[] = [];
|
||||||
method: string;
|
|
||||||
path: string;
|
|
||||||
data?: object;
|
|
||||||
error?: object;
|
|
||||||
expectBody?: object;
|
|
||||||
expectQueryParams?: object;
|
|
||||||
thenCall?: Function;
|
|
||||||
}[] = [];
|
|
||||||
let acceptKeepalives: boolean;
|
let acceptKeepalives: boolean;
|
||||||
let pendingLookup: {
|
let pendingLookup: {
|
||||||
promise: Promise<any>;
|
promise: Promise<any>;
|
||||||
method: string;
|
method: string;
|
||||||
path: string;
|
path: string;
|
||||||
} | null = null;
|
} | 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) {
|
if (path === KEEP_ALIVE_PATH && acceptKeepalives) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
unstable_features: {
|
unstable_features: {
|
||||||
@ -145,7 +168,7 @@ describe("MatrixClient", function() {
|
|||||||
}
|
}
|
||||||
if (next.expectQueryParams) {
|
if (next.expectQueryParams) {
|
||||||
Object.keys(next.expectQueryParams).forEach(function(k) {
|
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,
|
userId: userId,
|
||||||
});
|
});
|
||||||
// FIXME: We shouldn't be yanking http like this.
|
// FIXME: We shouldn't be yanking http like this.
|
||||||
client.http = [
|
client.http = ([
|
||||||
"authedRequest", "getContentUri", "request", "uploadContent",
|
"authedRequest",
|
||||||
].reduce((r, k) => { r[k] = jest.fn(); return r; }, {});
|
"getContentUri",
|
||||||
client.http.authedRequest.mockImplementation(httpReq);
|
"request",
|
||||||
client.http.request.mockImplementation(httpReq);
|
"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() {
|
beforeEach(function() {
|
||||||
scheduler = [
|
scheduler = ([
|
||||||
"getQueueForEvent", "queueEvent", "removeEventFromQueue",
|
"getQueueForEvent",
|
||||||
|
"queueEvent",
|
||||||
|
"removeEventFromQueue",
|
||||||
"setProcessFunction",
|
"setProcessFunction",
|
||||||
].reduce((r, k) => { r[k] = jest.fn(); return r; }, {});
|
] as const).reduce((r, k) => {
|
||||||
store = [
|
r[k] = jest.fn();
|
||||||
|
return r;
|
||||||
|
}, {} as MatrixScheduler);
|
||||||
|
store = ([
|
||||||
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
|
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
|
||||||
"save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom", "storeUser",
|
"save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom", "storeUser",
|
||||||
"getFilterIdByName", "setFilterIdByName", "getFilter", "storeFilter",
|
"getFilterIdByName", "setFilterIdByName", "getFilter", "storeFilter",
|
||||||
"getSyncAccumulator", "startup", "deleteAllData",
|
"startup", "deleteAllData",
|
||||||
].reduce((r, k) => { r[k] = jest.fn(); return r; }, {});
|
] as const).reduce((r, k) => {
|
||||||
|
r[k] = jest.fn();
|
||||||
|
return r;
|
||||||
|
}, {} as Store);
|
||||||
store.getSavedSync = jest.fn().mockReturnValue(Promise.resolve(null));
|
store.getSavedSync = jest.fn().mockReturnValue(Promise.resolve(null));
|
||||||
store.getSavedSyncToken = jest.fn().mockReturnValue(Promise.resolve(null));
|
store.getSavedSyncToken = jest.fn().mockReturnValue(Promise.resolve(null));
|
||||||
store.setSyncData = 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
|
// means they may call /events and then fail an expect() which will fail
|
||||||
// a DIFFERENT test (pollution between tests!) - we return unresolved
|
// a DIFFERENT test (pollution between tests!) - we return unresolved
|
||||||
// promises to stop the client from continuing to run.
|
// promises to stop the client from continuing to run.
|
||||||
client.http.authedRequest.mockImplementation(function() {
|
mocked(client.http.authedRequest).mockImplementation(function() {
|
||||||
return new Promise(() => {});
|
return new Promise(() => {});
|
||||||
});
|
});
|
||||||
client.stopClient();
|
client.stopClient();
|
||||||
@ -289,7 +326,7 @@ describe("MatrixClient", function() {
|
|||||||
const txnId = client.makeTxnId();
|
const txnId = client.makeTxnId();
|
||||||
|
|
||||||
const room = new Room(roomId, client, userId);
|
const room = new Room(roomId, client, userId);
|
||||||
store.getRoom.mockReturnValue(room);
|
mocked(store.getRoom).mockReturnValue(room);
|
||||||
|
|
||||||
const rootEvent = new MatrixEvent({ event_id: threadId });
|
const rootEvent = new MatrixEvent({ event_id: threadId });
|
||||||
room.createThread(threadId, rootEvent, [rootEvent], false);
|
room.createThread(threadId, rootEvent, [rootEvent], false);
|
||||||
@ -329,7 +366,7 @@ describe("MatrixClient", function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const room = new Room(roomId, client, userId);
|
const room = new Room(roomId, client, userId);
|
||||||
store.getRoom.mockReturnValue(room);
|
mocked(store.getRoom).mockReturnValue(room);
|
||||||
|
|
||||||
const rootEvent = new MatrixEvent({ event_id: threadId });
|
const rootEvent = new MatrixEvent({ event_id: threadId });
|
||||||
room.createThread(threadId, rootEvent, [rootEvent], false);
|
room.createThread(threadId, rootEvent, [rootEvent], false);
|
||||||
@ -359,7 +396,7 @@ describe("MatrixClient", function() {
|
|||||||
const userId = "@test:example.org";
|
const userId = "@test:example.org";
|
||||||
const roomId = "!room:example.org";
|
const roomId = "!room:example.org";
|
||||||
const roomName = "Test Tree";
|
const roomName = "Test Tree";
|
||||||
const mockRoom = {};
|
const mockRoom = {} as unknown as Room;
|
||||||
const fn = jest.fn().mockImplementation((opts) => {
|
const fn = jest.fn().mockImplementation((opts) => {
|
||||||
expect(opts).toMatchObject({
|
expect(opts).toMatchObject({
|
||||||
name: roomName,
|
name: roomName,
|
||||||
@ -431,23 +468,23 @@ describe("MatrixClient", function() {
|
|||||||
throw new Error("Unexpected event type or state key");
|
throw new Error("Unexpected event type or state key");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
} as Room["currentState"],
|
||||||
};
|
} as unknown as Room;
|
||||||
client.getRoom = (getRoomId) => {
|
client.getRoom = (getRoomId) => {
|
||||||
expect(getRoomId).toEqual(roomId);
|
expect(getRoomId).toEqual(roomId);
|
||||||
return mockRoom;
|
return mockRoom;
|
||||||
};
|
};
|
||||||
const tree = client.unstableGetFileTreeSpace(roomId);
|
const tree = client.unstableGetFileTreeSpace(roomId);
|
||||||
expect(tree).toBeDefined();
|
expect(tree).toBeDefined();
|
||||||
expect(tree.roomId).toEqual(roomId);
|
expect(tree!.roomId).toEqual(roomId);
|
||||||
expect(tree.room).toBe(mockRoom);
|
expect(tree!.room).toBe(mockRoom);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not get (unstable) file trees if not joined", async () => {
|
it("should not get (unstable) file trees if not joined", async () => {
|
||||||
const roomId = "!room:example.org";
|
const roomId = "!room:example.org";
|
||||||
const mockRoom = {
|
const mockRoom = {
|
||||||
getMyMembership: () => "leave", // "not join"
|
getMyMembership: () => "leave", // "not join"
|
||||||
};
|
} as unknown as Room;
|
||||||
client.getRoom = (getRoomId) => {
|
client.getRoom = (getRoomId) => {
|
||||||
expect(getRoomId).toEqual(roomId);
|
expect(getRoomId).toEqual(roomId);
|
||||||
return mockRoom;
|
return mockRoom;
|
||||||
@ -491,8 +528,8 @@ describe("MatrixClient", function() {
|
|||||||
throw new Error("Unexpected event type or state key");
|
throw new Error("Unexpected event type or state key");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
} as Room["currentState"],
|
||||||
};
|
} as unknown as Room;
|
||||||
client.getRoom = (getRoomId) => {
|
client.getRoom = (getRoomId) => {
|
||||||
expect(getRoomId).toEqual(roomId);
|
expect(getRoomId).toEqual(roomId);
|
||||||
return mockRoom;
|
return mockRoom;
|
||||||
@ -525,8 +562,8 @@ describe("MatrixClient", function() {
|
|||||||
throw new Error("Unexpected event type or state key");
|
throw new Error("Unexpected event type or state key");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
} as Room["currentState"],
|
||||||
};
|
} as unknown as Room;
|
||||||
client.getRoom = (getRoomId) => {
|
client.getRoom = (getRoomId) => {
|
||||||
expect(getRoomId).toEqual(roomId);
|
expect(getRoomId).toEqual(roomId);
|
||||||
return mockRoom;
|
return mockRoom;
|
||||||
@ -541,15 +578,15 @@ describe("MatrixClient", function() {
|
|||||||
SYNC_RESPONSE,
|
SYNC_RESPONSE,
|
||||||
];
|
];
|
||||||
const filterId = "ehfewf";
|
const filterId = "ehfewf";
|
||||||
store.getFilterIdByName.mockReturnValue(filterId);
|
mocked(store.getFilterIdByName).mockReturnValue(filterId);
|
||||||
const filter = new Filter("0", filterId);
|
const filter = new Filter("0", filterId);
|
||||||
filter.setDefinition({ "room": { "timeline": { "limit": 8 } } });
|
filter.setDefinition({ "room": { "timeline": { "limit": 8 } } });
|
||||||
store.getFilter.mockReturnValue(filter);
|
mocked(store.getFilter).mockReturnValue(filter);
|
||||||
const syncPromise = new Promise<void>((resolve, reject) => {
|
const syncPromise = new Promise<void>((resolve, reject) => {
|
||||||
client.on("sync", function syncListener(state) {
|
client.on(ClientEvent.Sync, function syncListener(state) {
|
||||||
if (state === "SYNCING") {
|
if (state === "SYNCING") {
|
||||||
expect(httpLookups.length).toEqual(0);
|
expect(httpLookups.length).toEqual(0);
|
||||||
client.removeListener("sync", syncListener);
|
client.removeListener(ClientEvent.Sync, syncListener);
|
||||||
resolve();
|
resolve();
|
||||||
} else if (state === "ERROR") {
|
} else if (state === "ERROR") {
|
||||||
reject(new Error("sync 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() {
|
it("should return the same sync state as emitted sync events", async function() {
|
||||||
const syncingPromise = new Promise<void>((resolve) => {
|
const syncingPromise = new Promise<void>((resolve) => {
|
||||||
client.on("sync", function syncListener(state) {
|
client.on(ClientEvent.Sync, function syncListener(state) {
|
||||||
expect(state).toEqual(client.getSyncState());
|
expect(state).toEqual(client.getSyncState());
|
||||||
if (state === "SYNCING") {
|
if (state === "SYNCING") {
|
||||||
client.removeListener("sync", syncListener);
|
client.removeListener(ClientEvent.Sync, syncListener);
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -586,7 +623,7 @@ describe("MatrixClient", function() {
|
|||||||
it("should use an existing filter if id is present in localStorage", 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) {
|
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
|
// scope this on the user ID because people may login on many accounts
|
||||||
// and they all need to be stored!
|
// and they all need to be stored!
|
||||||
return "FILTER_SYNC_" + userId + (suffix ? "_" + suffix : "");
|
return "FILTER_SYNC_" + userId + (suffix ? "_" + suffix : "");
|
||||||
@ -605,14 +642,14 @@ describe("MatrixClient", function() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
httpLookups.push(FILTER_RESPONSE);
|
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);
|
client.store.setFilterIdByName(filterName, invalidFilterId);
|
||||||
const filter = new Filter(client.credentials.userId);
|
const filter = new Filter(client.credentials.userId);
|
||||||
|
|
||||||
client.getOrCreateFilter(filterName, filter).then(function(filterId) {
|
client.getOrCreateFilter(filterName, filter).then(function(filterId) {
|
||||||
expect(filterId).toEqual(FILTER_RESPONSE.data.filter_id);
|
expect(filterId).toEqual(FILTER_RESPONSE.data?.filter_id);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -634,13 +671,13 @@ describe("MatrixClient", function() {
|
|||||||
httpLookups.push(FILTER_RESPONSE);
|
httpLookups.push(FILTER_RESPONSE);
|
||||||
httpLookups.push(SYNC_RESPONSE);
|
httpLookups.push(SYNC_RESPONSE);
|
||||||
|
|
||||||
client.on("sync", function syncListener(state) {
|
client.on(ClientEvent.Sync, function syncListener(state) {
|
||||||
if (state === "ERROR" && httpLookups.length > 0) {
|
if (state === "ERROR" && httpLookups.length > 0) {
|
||||||
expect(httpLookups.length).toEqual(2);
|
expect(httpLookups.length).toEqual(2);
|
||||||
expect(client.retryImmediately()).toBe(true);
|
expect(client.retryImmediately()).toBe(true);
|
||||||
jest.advanceTimersByTime(1);
|
jest.advanceTimersByTime(1);
|
||||||
} else if (state === "PREPARED" && httpLookups.length === 0) {
|
} else if (state === "PREPARED" && httpLookups.length === 0) {
|
||||||
client.removeListener("sync", syncListener);
|
client.removeListener(ClientEvent.Sync, syncListener);
|
||||||
done();
|
done();
|
||||||
} else {
|
} else {
|
||||||
// unexpected state transition!
|
// unexpected state transition!
|
||||||
@ -658,7 +695,7 @@ describe("MatrixClient", function() {
|
|||||||
method: "GET", path: "/sync", data: SYNC_DATA,
|
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) {
|
if (state === "ERROR" && httpLookups.length > 0) {
|
||||||
expect(httpLookups.length).toEqual(1);
|
expect(httpLookups.length).toEqual(1);
|
||||||
expect(client.retryImmediately()).toBe(
|
expect(client.retryImmediately()).toBe(
|
||||||
@ -668,7 +705,7 @@ describe("MatrixClient", function() {
|
|||||||
} else if (state === "RECONNECTING" && httpLookups.length > 0) {
|
} else if (state === "RECONNECTING" && httpLookups.length > 0) {
|
||||||
jest.advanceTimersByTime(10000);
|
jest.advanceTimersByTime(10000);
|
||||||
} else if (state === "SYNCING" && httpLookups.length === 0) {
|
} else if (state === "SYNCING" && httpLookups.length === 0) {
|
||||||
client.removeListener("sync", syncListener);
|
client.removeListener(ClientEvent.Sync, syncListener);
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -684,13 +721,13 @@ describe("MatrixClient", function() {
|
|||||||
httpLookups.push(FILTER_RESPONSE);
|
httpLookups.push(FILTER_RESPONSE);
|
||||||
httpLookups.push(SYNC_RESPONSE);
|
httpLookups.push(SYNC_RESPONSE);
|
||||||
|
|
||||||
client.on("sync", function syncListener(state) {
|
client.on(ClientEvent.Sync, function syncListener(state) {
|
||||||
if (state === "ERROR" && httpLookups.length > 0) {
|
if (state === "ERROR" && httpLookups.length > 0) {
|
||||||
expect(httpLookups.length).toEqual(3);
|
expect(httpLookups.length).toEqual(3);
|
||||||
expect(client.retryImmediately()).toBe(true);
|
expect(client.retryImmediately()).toBe(true);
|
||||||
jest.advanceTimersByTime(1);
|
jest.advanceTimersByTime(1);
|
||||||
} else if (state === "PREPARED" && httpLookups.length === 0) {
|
} else if (state === "PREPARED" && httpLookups.length === 0) {
|
||||||
client.removeListener("sync", syncListener);
|
client.removeListener(ClientEvent.Sync, syncListener);
|
||||||
done();
|
done();
|
||||||
} else {
|
} else {
|
||||||
// unexpected state transition!
|
// unexpected state transition!
|
||||||
@ -702,8 +739,8 @@ describe("MatrixClient", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("emitted sync events", function() {
|
describe("emitted sync events", function() {
|
||||||
function syncChecker(expectedStates, done) {
|
function syncChecker(expectedStates: [string, string | null][], done: Function) {
|
||||||
return function syncListener(state, old) {
|
return function syncListener(state: SyncState, old: SyncState | null) {
|
||||||
const expected = expectedStates.shift();
|
const expected = expectedStates.shift();
|
||||||
logger.log(
|
logger.log(
|
||||||
"'sync' curr=%s old=%s EXPECT=%s", state, old, expected,
|
"'sync' curr=%s old=%s EXPECT=%s", state, old, expected,
|
||||||
@ -715,7 +752,7 @@ describe("MatrixClient", function() {
|
|||||||
expect(state).toEqual(expected[0]);
|
expect(state).toEqual(expected[0]);
|
||||||
expect(old).toEqual(expected[1]);
|
expect(old).toEqual(expected[1]);
|
||||||
if (expectedStates.length === 0) {
|
if (expectedStates.length === 0) {
|
||||||
client.removeListener("sync", syncListener);
|
client.removeListener(ClientEvent.Sync, syncListener);
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
// standard retry time is 5 to 10 seconds
|
// 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) {
|
it("should transition null -> PREPARED after the first /sync", function(done) {
|
||||||
const expectedStates: [string, string | null][] = [];
|
const expectedStates: [string, string | null][] = [];
|
||||||
expectedStates.push(["PREPARED", null]);
|
expectedStates.push(["PREPARED", null]);
|
||||||
client.on("sync", syncChecker(expectedStates, done));
|
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
|
||||||
client.startClient();
|
client.startClient();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -738,7 +775,7 @@ describe("MatrixClient", function() {
|
|||||||
method: "POST", path: FILTER_PATH, error: { errcode: "NOPE_NOPE_NOPE" },
|
method: "POST", path: FILTER_PATH, error: { errcode: "NOPE_NOPE_NOPE" },
|
||||||
});
|
});
|
||||||
expectedStates.push(["ERROR", null]);
|
expectedStates.push(["ERROR", null]);
|
||||||
client.on("sync", syncChecker(expectedStates, done));
|
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
|
||||||
client.startClient();
|
client.startClient();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -768,7 +805,7 @@ describe("MatrixClient", function() {
|
|||||||
expectedStates.push(["RECONNECTING", null]);
|
expectedStates.push(["RECONNECTING", null]);
|
||||||
expectedStates.push(["ERROR", "RECONNECTING"]);
|
expectedStates.push(["ERROR", "RECONNECTING"]);
|
||||||
expectedStates.push(["CATCHUP", "ERROR"]);
|
expectedStates.push(["CATCHUP", "ERROR"]);
|
||||||
client.on("sync", syncChecker(expectedStates, done));
|
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
|
||||||
client.startClient();
|
client.startClient();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -776,7 +813,7 @@ describe("MatrixClient", function() {
|
|||||||
const expectedStates: [string, string | null][] = [];
|
const expectedStates: [string, string | null][] = [];
|
||||||
expectedStates.push(["PREPARED", null]);
|
expectedStates.push(["PREPARED", null]);
|
||||||
expectedStates.push(["SYNCING", "PREPARED"]);
|
expectedStates.push(["SYNCING", "PREPARED"]);
|
||||||
client.on("sync", syncChecker(expectedStates, done));
|
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
|
||||||
client.startClient();
|
client.startClient();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -795,7 +832,7 @@ describe("MatrixClient", function() {
|
|||||||
expectedStates.push(["SYNCING", "PREPARED"]);
|
expectedStates.push(["SYNCING", "PREPARED"]);
|
||||||
expectedStates.push(["RECONNECTING", "SYNCING"]);
|
expectedStates.push(["RECONNECTING", "SYNCING"]);
|
||||||
expectedStates.push(["ERROR", "RECONNECTING"]);
|
expectedStates.push(["ERROR", "RECONNECTING"]);
|
||||||
client.on("sync", syncChecker(expectedStates, done));
|
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
|
||||||
client.startClient();
|
client.startClient();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -809,7 +846,7 @@ describe("MatrixClient", function() {
|
|||||||
expectedStates.push(["PREPARED", null]);
|
expectedStates.push(["PREPARED", null]);
|
||||||
expectedStates.push(["SYNCING", "PREPARED"]);
|
expectedStates.push(["SYNCING", "PREPARED"]);
|
||||||
expectedStates.push(["ERROR", "SYNCING"]);
|
expectedStates.push(["ERROR", "SYNCING"]);
|
||||||
client.on("sync", syncChecker(expectedStates, done));
|
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
|
||||||
client.startClient();
|
client.startClient();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -821,7 +858,7 @@ describe("MatrixClient", function() {
|
|||||||
expectedStates.push(["PREPARED", null]);
|
expectedStates.push(["PREPARED", null]);
|
||||||
expectedStates.push(["SYNCING", "PREPARED"]);
|
expectedStates.push(["SYNCING", "PREPARED"]);
|
||||||
expectedStates.push(["SYNCING", "SYNCING"]);
|
expectedStates.push(["SYNCING", "SYNCING"]);
|
||||||
client.on("sync", syncChecker(expectedStates, done));
|
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
|
||||||
client.startClient();
|
client.startClient();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -845,7 +882,7 @@ describe("MatrixClient", function() {
|
|||||||
expectedStates.push(["RECONNECTING", "SYNCING"]);
|
expectedStates.push(["RECONNECTING", "SYNCING"]);
|
||||||
expectedStates.push(["ERROR", "RECONNECTING"]);
|
expectedStates.push(["ERROR", "RECONNECTING"]);
|
||||||
expectedStates.push(["ERROR", "ERROR"]);
|
expectedStates.push(["ERROR", "ERROR"]);
|
||||||
client.on("sync", syncChecker(expectedStates, done));
|
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
|
||||||
client.startClient();
|
client.startClient();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -914,14 +951,14 @@ describe("MatrixClient", function() {
|
|||||||
throw new Error("Unexpected event type or state key");
|
throw new Error("Unexpected event type or state key");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
} as Room["currentState"],
|
||||||
getThread: jest.fn(),
|
getThread: jest.fn(),
|
||||||
addPendingEvent: jest.fn(),
|
addPendingEvent: jest.fn(),
|
||||||
updatePendingEvent: jest.fn(),
|
updatePendingEvent: jest.fn(),
|
||||||
reEmitter: {
|
reEmitter: {
|
||||||
reEmit: jest.fn(),
|
reEmit: jest.fn(),
|
||||||
},
|
},
|
||||||
};
|
} as unknown as Room;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
client.getRoom = (getRoomId) => {
|
client.getRoom = (getRoomId) => {
|
||||||
@ -987,7 +1024,7 @@ describe("MatrixClient", function() {
|
|||||||
|
|
||||||
const mockRoom = {
|
const mockRoom = {
|
||||||
getMyMembership: () => "join",
|
getMyMembership: () => "join",
|
||||||
updatePendingEvent: (event, status) => event.setStatus(status),
|
updatePendingEvent: (event: MatrixEvent, status: EventStatus) => event.setStatus(status),
|
||||||
currentState: {
|
currentState: {
|
||||||
getStateEvents: (eventType, stateKey) => {
|
getStateEvents: (eventType, stateKey) => {
|
||||||
if (eventType === EventType.RoomCreate) {
|
if (eventType === EventType.RoomCreate) {
|
||||||
@ -1004,15 +1041,14 @@ describe("MatrixClient", function() {
|
|||||||
throw new Error("Unexpected event type or state key");
|
throw new Error("Unexpected event type or state key");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
} as Room["currentState"],
|
||||||
};
|
} as unknown as Room;
|
||||||
|
|
||||||
let event;
|
let event: MatrixEvent;
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
event = new MatrixEvent({
|
event = new MatrixEvent({
|
||||||
event_id: "~" + roomId + ":" + txnId,
|
event_id: "~" + roomId + ":" + txnId,
|
||||||
user_id: client.credentials.userId,
|
sender: client.credentials.userId!,
|
||||||
sender: client.credentials.userId,
|
|
||||||
room_id: roomId,
|
room_id: roomId,
|
||||||
origin_server_ts: new Date().getTime(),
|
origin_server_ts: new Date().getTime(),
|
||||||
});
|
});
|
||||||
@ -1023,25 +1059,26 @@ describe("MatrixClient", function() {
|
|||||||
return mockRoom;
|
return mockRoom;
|
||||||
};
|
};
|
||||||
client.crypto = { // mock crypto
|
client.crypto = { // mock crypto
|
||||||
encryptEvent: (event, room) => new Promise(() => {}),
|
encryptEvent: () => new Promise(() => {}),
|
||||||
stop: jest.fn(),
|
stop: jest.fn(),
|
||||||
};
|
} as unknown as Crypto;
|
||||||
});
|
});
|
||||||
|
|
||||||
function assertCancelled() {
|
function assertCancelled() {
|
||||||
expect(event.status).toBe(EventStatus.CANCELLED);
|
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);
|
expect(httpLookups.filter(h => h.path.includes("/send/")).length).toBe(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should cancel an event which is queued", () => {
|
it("should cancel an event which is queued", () => {
|
||||||
event.setStatus(EventStatus.QUEUED);
|
event.setStatus(EventStatus.QUEUED);
|
||||||
client.scheduler.queueEvent(event);
|
client.scheduler?.queueEvent(event);
|
||||||
client.cancelPendingEvent(event);
|
client.cancelPendingEvent(event);
|
||||||
assertCancelled();
|
assertCancelled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should cancel an event which is encrypting", async () => {
|
it("should cancel an event which is encrypting", async () => {
|
||||||
|
// @ts-ignore protected method access
|
||||||
client.encryptAndSendEvent(null, event);
|
client.encryptAndSendEvent(null, event);
|
||||||
await testUtils.emitPromise(event, "Event.status");
|
await testUtils.emitPromise(event, "Event.status");
|
||||||
client.cancelPendingEvent(event);
|
client.cancelPendingEvent(event);
|
||||||
@ -1103,7 +1140,7 @@ describe("MatrixClient", function() {
|
|||||||
const room = {
|
const room = {
|
||||||
hasPendingEvent: jest.fn().mockReturnValue(false),
|
hasPendingEvent: jest.fn().mockReturnValue(false),
|
||||||
addLocalEchoReceipt: jest.fn(),
|
addLocalEchoReceipt: jest.fn(),
|
||||||
};
|
} as unknown as Room;
|
||||||
const rrEvent = new MatrixEvent({ event_id: "read_event_id" });
|
const rrEvent = new MatrixEvent({ event_id: "read_event_id" });
|
||||||
const rpEvent = new MatrixEvent({ event_id: "read_private_event_id" });
|
const rpEvent = new MatrixEvent({ event_id: "read_private_event_id" });
|
||||||
client.getRoom = () => room;
|
client.getRoom = () => room;
|
||||||
@ -1142,7 +1179,7 @@ describe("MatrixClient", function() {
|
|||||||
const content = makeBeaconInfoContent(100, true);
|
const content = makeBeaconInfoContent(100, true);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
client.http.authedRequest.mockClear().mockResolvedValue({});
|
mocked(client.http.authedRequest).mockClear().mockResolvedValue({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("creates new beacon info", async () => {
|
it("creates new beacon info", async () => {
|
||||||
@ -1150,7 +1187,7 @@ describe("MatrixClient", function() {
|
|||||||
|
|
||||||
// event type combined
|
// event type combined
|
||||||
const expectedEventType = M_BEACON_INFO.name;
|
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(method).toBe('PUT');
|
||||||
expect(path).toEqual(
|
expect(path).toEqual(
|
||||||
`/rooms/${encodeURIComponent(roomId)}/state/` +
|
`/rooms/${encodeURIComponent(roomId)}/state/` +
|
||||||
@ -1164,7 +1201,7 @@ describe("MatrixClient", function() {
|
|||||||
await client.unstable_setLiveBeacon(roomId, content);
|
await client.unstable_setLiveBeacon(roomId, content);
|
||||||
|
|
||||||
// event type combined
|
// 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(
|
expect(path).toEqual(
|
||||||
`/rooms/${encodeURIComponent(roomId)}/state/` +
|
`/rooms/${encodeURIComponent(roomId)}/state/` +
|
||||||
`${encodeURIComponent(M_BEACON_INFO.name)}/${encodeURIComponent(userId)}`,
|
`${encodeURIComponent(M_BEACON_INFO.name)}/${encodeURIComponent(userId)}`,
|
||||||
@ -1242,7 +1279,7 @@ describe("MatrixClient", function() {
|
|||||||
const newPassword = 'newpassword';
|
const newPassword = 'newpassword';
|
||||||
|
|
||||||
const passwordTest = (expectedRequestContent: any) => {
|
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(method).toBe('POST');
|
||||||
expect(path).toEqual('/account/password');
|
expect(path).toEqual('/account/password');
|
||||||
expect(queryParams).toBeFalsy();
|
expect(queryParams).toBeFalsy();
|
||||||
@ -1250,7 +1287,7 @@ describe("MatrixClient", function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
client.http.authedRequest.mockClear().mockResolvedValue({});
|
mocked(client.http.authedRequest).mockClear().mockResolvedValue({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("no logout_devices specified", async () => {
|
it("no logout_devices specified", async () => {
|
||||||
@ -1289,13 +1326,13 @@ describe("MatrixClient", function() {
|
|||||||
const response = {
|
const response = {
|
||||||
aliases: ["#woop:example.org", "#another:example.org"],
|
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 roomId = "!whatever:example.org";
|
||||||
const result = await client.getLocalAliases(roomId);
|
const result = await client.getLocalAliases(roomId);
|
||||||
|
|
||||||
// Current version of the endpoint we support is v3
|
// 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(data).toBeFalsy();
|
||||||
expect(method).toBe('GET');
|
expect(method).toBe('GET');
|
||||||
expect(path).toEqual(`/rooms/${encodeURIComponent(roomId)}/aliases`);
|
expect(path).toEqual(`/rooms/${encodeURIComponent(roomId)}/aliases`);
|
||||||
@ -1352,11 +1389,11 @@ describe("MatrixClient", function() {
|
|||||||
],
|
],
|
||||||
username: "1443779631:@user:example.com",
|
username: "1443779631:@user:example.com",
|
||||||
password: "JlKfBy1QwLrO20385QyAtEyIv0=",
|
password: "JlKfBy1QwLrO20385QyAtEyIv0=",
|
||||||
};
|
} as unknown as ITurnServerResponse;
|
||||||
jest.spyOn(client, "turnServer").mockResolvedValue(turnServer);
|
jest.spyOn(client, "turnServer").mockResolvedValue(turnServer);
|
||||||
|
|
||||||
const events: any[][] = [];
|
const events: any[][] = [];
|
||||||
const onTurnServers = (...args) => events.push(args);
|
const onTurnServers = (...args: any[]) => events.push(args);
|
||||||
client.on(ClientEvent.TurnServers, onTurnServers);
|
client.on(ClientEvent.TurnServers, onTurnServers);
|
||||||
expect(await client.checkTurnServers()).toBe(true);
|
expect(await client.checkTurnServers()).toBe(true);
|
||||||
client.off(ClientEvent.TurnServers, onTurnServers);
|
client.off(ClientEvent.TurnServers, onTurnServers);
|
||||||
@ -1372,7 +1409,7 @@ describe("MatrixClient", function() {
|
|||||||
jest.spyOn(client, "turnServer").mockRejectedValue(error);
|
jest.spyOn(client, "turnServer").mockRejectedValue(error);
|
||||||
|
|
||||||
const events: any[][] = [];
|
const events: any[][] = [];
|
||||||
const onTurnServersError = (...args) => events.push(args);
|
const onTurnServersError = (...args: any[]) => events.push(args);
|
||||||
client.on(ClientEvent.TurnServersError, onTurnServersError);
|
client.on(ClientEvent.TurnServersError, onTurnServersError);
|
||||||
expect(await client.checkTurnServers()).toBe(false);
|
expect(await client.checkTurnServers()).toBe(false);
|
||||||
client.off(ClientEvent.TurnServersError, onTurnServersError);
|
client.off(ClientEvent.TurnServersError, onTurnServersError);
|
||||||
@ -1384,7 +1421,7 @@ describe("MatrixClient", function() {
|
|||||||
jest.spyOn(client, "turnServer").mockRejectedValue(error);
|
jest.spyOn(client, "turnServer").mockRejectedValue(error);
|
||||||
|
|
||||||
const events: any[][] = [];
|
const events: any[][] = [];
|
||||||
const onTurnServersError = (...args) => events.push(args);
|
const onTurnServersError = (...args: any[]) => events.push(args);
|
||||||
client.on(ClientEvent.TurnServersError, onTurnServersError);
|
client.on(ClientEvent.TurnServersError, onTurnServersError);
|
||||||
expect(await client.checkTurnServers()).toBe(false);
|
expect(await client.checkTurnServers()).toBe(false);
|
||||||
client.off(ClientEvent.TurnServersError, onTurnServersError);
|
client.off(ClientEvent.TurnServersError, onTurnServersError);
|
||||||
@ -1400,7 +1437,7 @@ describe("MatrixClient", function() {
|
|||||||
|
|
||||||
it("is an alias for the crypto method", async () => {
|
it("is an alias for the crypto method", async () => {
|
||||||
client.crypto = testUtils.mock(Crypto, "Crypto");
|
client.crypto = testUtils.mock(Crypto, "Crypto");
|
||||||
const deviceInfos = [];
|
const deviceInfos: IOlmDevice[] = [];
|
||||||
const payload = {};
|
const payload = {};
|
||||||
await client.encryptAndSendToDevices(deviceInfos, payload);
|
await client.encryptAndSendToDevices(deviceInfos, payload);
|
||||||
expect(client.crypto.encryptAndSendToDevices).toHaveBeenLastCalledWith(deviceInfos, payload);
|
expect(client.crypto.encryptAndSendToDevices).toHaveBeenLastCalledWith(deviceInfos, payload);
|
||||||
@ -1413,7 +1450,7 @@ describe("MatrixClient", function() {
|
|||||||
const dataStore = new Map();
|
const dataStore = new Map();
|
||||||
client.setAccountData = function(eventType, content) {
|
client.setAccountData = function(eventType, content) {
|
||||||
dataStore.set(eventType, content);
|
dataStore.set(eventType, content);
|
||||||
return Promise.resolve();
|
return Promise.resolve({});
|
||||||
};
|
};
|
||||||
client.getAccountData = function(eventType) {
|
client.getAccountData = function(eventType) {
|
||||||
const data = dataStore.get(eventType);
|
const data = dataStore.get(eventType);
|
||||||
@ -1424,9 +1461,9 @@ describe("MatrixClient", function() {
|
|||||||
|
|
||||||
// Mockup `createRoom`/`getRoom`/`joinRoom`, including state.
|
// Mockup `createRoom`/`getRoom`/`joinRoom`, including state.
|
||||||
const rooms = new Map();
|
const rooms = new Map();
|
||||||
client.createRoom = function(options = {}) {
|
client.createRoom = function(options: Options = {}) {
|
||||||
const roomId = options["_roomId"] || `!room-${rooms.size}:example.org`;
|
const roomId = options["_roomId"] || `!room-${rooms.size}:example.org`;
|
||||||
const state = new Map();
|
const state = new Map<string, any>();
|
||||||
const room = {
|
const room = {
|
||||||
roomId,
|
roomId,
|
||||||
_options: options,
|
_options: options,
|
||||||
@ -1444,24 +1481,24 @@ describe("MatrixClient", function() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
} as EventTimeline;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
} as unknown as WrappedRoom;
|
||||||
rooms.set(roomId, room);
|
rooms.set(roomId, room);
|
||||||
return Promise.resolve({ room_id: roomId });
|
return Promise.resolve({ room_id: roomId });
|
||||||
};
|
};
|
||||||
client.getRoom = function(roomId) {
|
client.getRoom = function(roomId) {
|
||||||
return rooms.get(roomId);
|
return rooms.get(roomId);
|
||||||
};
|
};
|
||||||
client.joinRoom = function(roomId) {
|
client.joinRoom = async function(roomId) {
|
||||||
return this.getRoom(roomId) || this.createRoom({ _roomId: roomId });
|
return this.getRoom(roomId)! || this.createRoom({ _roomId: roomId } as ICreateRoomOpts);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mockup state events
|
// Mockup state events
|
||||||
client.sendStateEvent = function(roomId, type, content) {
|
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;
|
const state: Map<string, any> = room._state;
|
||||||
let store = state.get(type);
|
let store = state.get(type);
|
||||||
if (!store) {
|
if (!store) {
|
||||||
@ -1480,14 +1517,15 @@ describe("MatrixClient", function() {
|
|||||||
return content;
|
return content;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return { event_id: eventId };
|
return Promise.resolve({ event_id: eventId });
|
||||||
};
|
};
|
||||||
client.redactEvent = function(roomId, 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;
|
const state: Map<string, any> = room._state;
|
||||||
for (const store of state.values()) {
|
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",
|
roomId: "!snafu:somewhere.org",
|
||||||
});
|
});
|
||||||
expect(ruleMatch).toBeTruthy();
|
expect(ruleMatch).toBeTruthy();
|
||||||
expect(ruleMatch.getContent()).toMatchObject({
|
expect(ruleMatch!.getContent()).toMatchObject({
|
||||||
recommendation: "m.ban",
|
recommendation: "m.ban",
|
||||||
reason: "just a test",
|
reason: "just a test",
|
||||||
});
|
});
|
||||||
@ -1552,7 +1590,7 @@ describe("MatrixClient", function() {
|
|||||||
roomId: "!snafu:somewhere.org",
|
roomId: "!snafu:somewhere.org",
|
||||||
});
|
});
|
||||||
expect(ruleSenderMatch).toBeTruthy();
|
expect(ruleSenderMatch).toBeTruthy();
|
||||||
expect(ruleSenderMatch.getContent()).toMatchObject({
|
expect(ruleSenderMatch!.getContent()).toMatchObject({
|
||||||
recommendation: "m.ban",
|
recommendation: "m.ban",
|
||||||
reason: REASON,
|
reason: REASON,
|
||||||
});
|
});
|
||||||
@ -1562,7 +1600,7 @@ describe("MatrixClient", function() {
|
|||||||
roomId: "!snafu:example.org",
|
roomId: "!snafu:example.org",
|
||||||
});
|
});
|
||||||
expect(ruleRoomMatch).toBeTruthy();
|
expect(ruleRoomMatch).toBeTruthy();
|
||||||
expect(ruleRoomMatch.getContent()).toMatchObject({
|
expect(ruleRoomMatch!.getContent()).toMatchObject({
|
||||||
recommendation: "m.ban",
|
recommendation: "m.ban",
|
||||||
reason: REASON,
|
reason: REASON,
|
||||||
});
|
});
|
||||||
@ -1587,7 +1625,7 @@ describe("MatrixClient", function() {
|
|||||||
roomId: BAD_ROOM_ID,
|
roomId: BAD_ROOM_ID,
|
||||||
});
|
});
|
||||||
expect(ruleSenderMatch).toBeTruthy();
|
expect(ruleSenderMatch).toBeTruthy();
|
||||||
expect(ruleSenderMatch.getContent()).toMatchObject({
|
expect(ruleSenderMatch!.getContent()).toMatchObject({
|
||||||
recommendation: "m.ban",
|
recommendation: "m.ban",
|
||||||
reason: REASON,
|
reason: REASON,
|
||||||
});
|
});
|
||||||
@ -1621,7 +1659,7 @@ describe("MatrixClient", function() {
|
|||||||
roomId: "!snafu:somewhere.org",
|
roomId: "!snafu:somewhere.org",
|
||||||
});
|
});
|
||||||
expect(ruleMatch).toBeTruthy();
|
expect(ruleMatch).toBeTruthy();
|
||||||
expect(ruleMatch.getContent()).toMatchObject({
|
expect(ruleMatch!.getContent()).toMatchObject({
|
||||||
recommendation: "m.ban",
|
recommendation: "m.ban",
|
||||||
reason: "just a test",
|
reason: "just a test",
|
||||||
});
|
});
|
||||||
@ -1649,13 +1687,13 @@ describe("MatrixClient", function() {
|
|||||||
roomId: "!snafu:somewhere.org",
|
roomId: "!snafu:somewhere.org",
|
||||||
});
|
});
|
||||||
expect(ruleMatch).toBeTruthy();
|
expect(ruleMatch).toBeTruthy();
|
||||||
expect(ruleMatch.getContent()).toMatchObject({
|
expect(ruleMatch!.getContent()).toMatchObject({
|
||||||
recommendation: "m.ban",
|
recommendation: "m.ban",
|
||||||
reason: "just a test",
|
reason: "just a test",
|
||||||
});
|
});
|
||||||
|
|
||||||
// After removing the invite, we shouldn't reject it anymore.
|
// 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({
|
const ruleMatch2 = await client.ignoredInvites.getRuleForInvite({
|
||||||
sender: "@foobar:example.org",
|
sender: "@foobar:example.org",
|
||||||
roomId: "!snafu:somewhere.org",
|
roomId: "!snafu:somewhere.org",
|
||||||
@ -1669,10 +1707,10 @@ describe("MatrixClient", function() {
|
|||||||
// Make sure that everything is initialized.
|
// Make sure that everything is initialized.
|
||||||
await client.ignoredInvites.getOrCreateSourceRooms();
|
await client.ignoredInvites.getOrCreateSourceRooms();
|
||||||
await client.joinRoom(NEW_SOURCE_ROOM_ID);
|
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.
|
// 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();
|
expect(policies).toBeTruthy();
|
||||||
const ignoreInvites = policies[IGNORE_INVITES_ACCOUNT_EVENT_KEY.name];
|
const ignoreInvites = policies[IGNORE_INVITES_ACCOUNT_EVENT_KEY.name];
|
||||||
expect(ignoreInvites).toBeTruthy();
|
expect(ignoreInvites).toBeTruthy();
|
||||||
@ -1686,7 +1724,7 @@ describe("MatrixClient", function() {
|
|||||||
expect(added2).toBe(false);
|
expect(added2).toBe(false);
|
||||||
|
|
||||||
// Fetch the list of sources and check that we have added the new room.
|
// 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();
|
expect(policies2).toBeTruthy();
|
||||||
const ignoreInvites2 = policies2[IGNORE_INVITES_ACCOUNT_EVENT_KEY.name];
|
const ignoreInvites2 = policies2[IGNORE_INVITES_ACCOUNT_EVENT_KEY.name];
|
||||||
expect(ignoreInvites2).toBeTruthy();
|
expect(ignoreInvites2).toBeTruthy();
|
||||||
@ -1698,7 +1736,7 @@ describe("MatrixClient", function() {
|
|||||||
|
|
||||||
// Check where it shows up.
|
// Check where it shows up.
|
||||||
const targetRoomId = ignoreInvites2.target;
|
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(targetRoom._state.get(PolicyScope.User)[eventId]).toBeTruthy();
|
||||||
expect(newSourceRoom._state.get(PolicyScope.User)?.[eventId]).toBeFalsy();
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MatrixClient } from "../../../src";
|
import { IContent, MatrixClient } from "../../../src";
|
||||||
import { Room } from "../../../src/models/room";
|
import { Room } from "../../../src/models/room";
|
||||||
import { MatrixEvent } from "../../../src/models/event";
|
import { MatrixEvent } from "../../../src/models/event";
|
||||||
import { EventType, MsgType, UNSTABLE_MSC3089_BRANCH, UNSTABLE_MSC3089_LEAF } from "../../../src/@types/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 roomId = "!tree:localhost";
|
||||||
const targetUser = "@target:example.org";
|
const targetUser = "@target:example.org";
|
||||||
|
|
||||||
let powerLevels;
|
let powerLevels: MatrixEvent;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// TODO: Use utility functions to create test rooms and clients
|
// TODO: Use utility functions to create test rooms and clients
|
||||||
@ -480,7 +480,7 @@ describe("MSC3089TreeSpace", () => {
|
|||||||
const staticDomain = "static.example.org";
|
const staticDomain = "static.example.org";
|
||||||
|
|
||||||
function addSubspace(roomId: string, createTs?: number, order?: string) {
|
function addSubspace(roomId: string, createTs?: number, order?: string) {
|
||||||
const content = {
|
const content: IContent = {
|
||||||
via: [staticDomain],
|
via: [staticDomain],
|
||||||
};
|
};
|
||||||
if (order) content['order'] = order;
|
if (order) content['order'] = order;
|
||||||
|
@ -121,7 +121,7 @@ describe('MatrixEvent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe(".attemptDecryption", () => {
|
describe(".attemptDecryption", () => {
|
||||||
let encryptedEvent;
|
let encryptedEvent: MatrixEvent;
|
||||||
const eventId = 'test_encrypted_event';
|
const eventId = 'test_encrypted_event';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -155,7 +155,7 @@ describe('MatrixEvent', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
};
|
} as unknown as Crypto;
|
||||||
|
|
||||||
await encryptedEvent.attemptDecryption(crypto);
|
await encryptedEvent.attemptDecryption(crypto);
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ let threadEvent: MatrixEvent;
|
|||||||
const ROOM_ID = "!roomId:example.org";
|
const ROOM_ID = "!roomId:example.org";
|
||||||
let THREAD_ID: string;
|
let THREAD_ID: string;
|
||||||
|
|
||||||
function mkPushAction(notify, highlight): IActionsObject {
|
function mkPushAction(notify: boolean, highlight: boolean): IActionsObject {
|
||||||
return {
|
return {
|
||||||
notify,
|
notify,
|
||||||
tweaks: {
|
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);
|
client.canSupport.set(Feature.ThreadUnreadNotifications, serverSideSupport);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ let wallTime = 1234567890;
|
|||||||
jest.useFakeTimers().setSystemTime(wallTime);
|
jest.useFakeTimers().setSystemTime(wallTime);
|
||||||
|
|
||||||
describe("realtime-callbacks", function() {
|
describe("realtime-callbacks", function() {
|
||||||
function tick(millis) {
|
function tick(millis: number): void {
|
||||||
wallTime += millis;
|
wallTime += millis;
|
||||||
jest.advanceTimersByTime(millis);
|
jest.advanceTimersByTime(millis);
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ describe("Rendezvous", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let httpBackend: MockHttpBackend;
|
let httpBackend: MockHttpBackend;
|
||||||
let fetchFn: typeof global.fetchFn;
|
let fetchFn: typeof global.fetch;
|
||||||
let transports: DummyTransport<any, MSC3903ECDHPayload>[];
|
let transports: DummyTransport<any, MSC3903ECDHPayload>[];
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
@ -373,37 +373,36 @@ describe("RoomMember", function() {
|
|||||||
expect(member.events.member).toEqual(joinEvent);
|
expect(member.events.member).toEqual(joinEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should set 'name' based on user_id, displayname and room state",
|
it("should set 'name' based on user_id, displayname and room state", function() {
|
||||||
function() {
|
const roomState = {
|
||||||
const roomState = {
|
getStateEvents: function(type: string) {
|
||||||
getStateEvents: function(type) {
|
if (type !== "m.room.member") {
|
||||||
if (type !== "m.room.member") {
|
return [];
|
||||||
return [];
|
}
|
||||||
}
|
return [
|
||||||
return [
|
utils.mkMembership({
|
||||||
utils.mkMembership({
|
event: true, mship: "join", room: roomId,
|
||||||
event: true, mship: "join", room: roomId,
|
user: userB,
|
||||||
user: userB,
|
}),
|
||||||
}),
|
utils.mkMembership({
|
||||||
utils.mkMembership({
|
event: true, mship: "join", room: roomId,
|
||||||
event: true, mship: "join", room: roomId,
|
user: userC, name: "Alice",
|
||||||
user: userC, name: "Alice",
|
}),
|
||||||
}),
|
joinEvent,
|
||||||
joinEvent,
|
];
|
||||||
];
|
},
|
||||||
},
|
getUserIdsWithDisplayName: function(displayName: string) {
|
||||||
getUserIdsWithDisplayName: function(displayName) {
|
return [userA, userC];
|
||||||
return [userA, userC];
|
},
|
||||||
},
|
} as unknown as RoomState;
|
||||||
} as unknown as RoomState;
|
expect(member.name).toEqual(userA); // default = user_id
|
||||||
expect(member.name).toEqual(userA); // default = user_id
|
member.setMembershipEvent(joinEvent);
|
||||||
member.setMembershipEvent(joinEvent);
|
expect(member.name).toEqual("Alice"); // prefer displayname
|
||||||
expect(member.name).toEqual("Alice"); // prefer displayname
|
member.setMembershipEvent(joinEvent, roomState);
|
||||||
member.setMembershipEvent(joinEvent, roomState);
|
expect(member.name).not.toEqual("Alice"); // it should disambig.
|
||||||
expect(member.name).not.toEqual("Alice"); // it should disambig.
|
// user_id should be there somewhere
|
||||||
// user_id should be there somewhere
|
expect(member.name.indexOf(userA)).not.toEqual(-1);
|
||||||
expect(member.name.indexOf(userA)).not.toEqual(-1);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it("should emit 'RoomMember.membership' if the membership changes", function() {
|
it("should emit 'RoomMember.membership' if the membership changes", function() {
|
||||||
let emitCount = 0;
|
let emitCount = 0;
|
||||||
@ -455,7 +454,7 @@ describe("RoomMember", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const roomState = {
|
const roomState = {
|
||||||
getStateEvents: function(type) {
|
getStateEvents: function(type: string) {
|
||||||
if (type !== "m.room.member") {
|
if (type !== "m.room.member") {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -467,7 +466,7 @@ describe("RoomMember", function() {
|
|||||||
joinEvent,
|
joinEvent,
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
getUserIdsWithDisplayName: function(displayName) {
|
getUserIdsWithDisplayName: function(displayName: string) {
|
||||||
return [userA, userC];
|
return [userA, userC];
|
||||||
},
|
},
|
||||||
} as unknown as RoomState;
|
} as unknown as RoomState;
|
||||||
|
@ -19,27 +19,32 @@ limitations under the License.
|
|||||||
* @module client
|
* @module client
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { mocked } from "jest-mock";
|
||||||
|
|
||||||
import * as utils from "../test-utils/test-utils";
|
import * as utils from "../test-utils/test-utils";
|
||||||
|
import { emitPromise } from "../test-utils/test-utils";
|
||||||
import {
|
import {
|
||||||
|
Direction,
|
||||||
DuplicateStrategy,
|
DuplicateStrategy,
|
||||||
EventStatus,
|
EventStatus,
|
||||||
EventTimelineSet,
|
EventTimelineSet,
|
||||||
EventType, IStateEventWithRoomId,
|
EventType, IContent,
|
||||||
|
IStateEventWithRoomId,
|
||||||
JoinRule,
|
JoinRule,
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
MatrixEventEvent,
|
MatrixEventEvent,
|
||||||
PendingEventOrdering,
|
PendingEventOrdering,
|
||||||
RelationType,
|
RelationType,
|
||||||
RoomEvent,
|
RoomEvent,
|
||||||
|
RoomMember,
|
||||||
} from "../../src";
|
} from "../../src";
|
||||||
import { EventTimeline } from "../../src/models/event-timeline";
|
import { EventTimeline } from "../../src/models/event-timeline";
|
||||||
import { NotificationCountType, Room } from "../../src/models/room";
|
import { NotificationCountType, Room } from "../../src/models/room";
|
||||||
import { RoomState } from "../../src/models/room-state";
|
import { RoomState } from "../../src/models/room-state";
|
||||||
import { UNSTABLE_ELEMENT_FUNCTIONAL_USERS } from "../../src/@types/event";
|
import { UNSTABLE_ELEMENT_FUNCTIONAL_USERS } from "../../src/@types/event";
|
||||||
import { TestClient } from "../TestClient";
|
import { TestClient } from "../TestClient";
|
||||||
import { emitPromise } from "../test-utils/test-utils";
|
|
||||||
import { ReceiptType, WrappedReceipt } from "../../src/@types/read_receipts";
|
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";
|
import { Crypto } from "../../src/crypto";
|
||||||
|
|
||||||
describe("Room", function() {
|
describe("Room", function() {
|
||||||
@ -48,7 +53,7 @@ describe("Room", function() {
|
|||||||
const userB = "@bertha:bar";
|
const userB = "@bertha:bar";
|
||||||
const userC = "@clarissa:bar";
|
const userC = "@clarissa:bar";
|
||||||
const userD = "@dorothy:bar";
|
const userD = "@dorothy:bar";
|
||||||
let room;
|
let room: Room;
|
||||||
|
|
||||||
const mkMessage = () => utils.mkMessage({
|
const mkMessage = () => utils.mkMessage({
|
||||||
event: true,
|
event: true,
|
||||||
@ -131,13 +136,16 @@ describe("Room", function() {
|
|||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
room = new Room(roomId, new TestClient(userA, "device").client, userA);
|
room = new Room(roomId, new TestClient(userA, "device").client, userA);
|
||||||
// mock RoomStates
|
// mock RoomStates
|
||||||
|
// @ts-ignore
|
||||||
room.oldState = room.getLiveTimeline().startState = utils.mock(RoomState, "oldState");
|
room.oldState = room.getLiveTimeline().startState = utils.mock(RoomState, "oldState");
|
||||||
|
// @ts-ignore
|
||||||
room.currentState = room.getLiveTimeline().endState = utils.mock(RoomState, "currentState");
|
room.currentState = room.getLiveTimeline().endState = utils.mock(RoomState, "currentState");
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getCreator', () => {
|
describe('getCreator', () => {
|
||||||
it("should return the creator from m.room.create", function() {
|
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 === "") {
|
if (type === EventType.RoomCreate && key === "") {
|
||||||
return utils.mkEvent({
|
return utils.mkEvent({
|
||||||
event: true,
|
event: true,
|
||||||
@ -160,7 +168,8 @@ describe("Room", function() {
|
|||||||
const hsUrl = "https://my.home.server";
|
const hsUrl = "https://my.home.server";
|
||||||
|
|
||||||
it("should return the URL from m.room.avatar preferentially", function() {
|
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 === "") {
|
if (type === EventType.RoomAvatar && key === "") {
|
||||||
return utils.mkEvent({
|
return utils.mkEvent({
|
||||||
event: true,
|
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
|
// we don't care about how the mxc->http conversion is done, other
|
||||||
// than it contains the mxc body.
|
// 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",
|
it("should return nothing if there is no m.room.avatar and allowDefault=false",
|
||||||
@ -189,12 +198,12 @@ describe("Room", function() {
|
|||||||
|
|
||||||
describe("getMember", function() {
|
describe("getMember", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
room.currentState.getMember.mockImplementation(function(userId) {
|
mocked(room.currentState.getMember).mockImplementation(function(userId) {
|
||||||
return {
|
return {
|
||||||
"@alice:bar": {
|
"@alice:bar": {
|
||||||
userId: userA,
|
userId: userA,
|
||||||
roomId: roomId,
|
roomId: roomId,
|
||||||
},
|
} as unknown as RoomMember,
|
||||||
}[userId] || null;
|
}[userId] || null;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -222,11 +231,13 @@ describe("Room", function() {
|
|||||||
it("Make sure legacy overload passing options directly as parameters still works", () => {
|
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.Replace, false)).not.toThrow();
|
||||||
expect(() => room.addLiveEvents(events, DuplicateStrategy.Ignore, true)).not.toThrow();
|
expect(() => room.addLiveEvents(events, DuplicateStrategy.Ignore, true)).not.toThrow();
|
||||||
|
// @ts-ignore
|
||||||
expect(() => room.addLiveEvents(events, "shouldfailbecauseinvalidduplicatestrategy", false)).toThrow();
|
expect(() => room.addLiveEvents(events, "shouldfailbecauseinvalidduplicatestrategy", false)).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should throw if duplicateStrategy isn't 'replace' or 'ignore'", function() {
|
it("should throw if duplicateStrategy isn't 'replace' or 'ignore'", function() {
|
||||||
expect(function() {
|
expect(function() {
|
||||||
|
// @ts-ignore
|
||||||
room.addLiveEvents(events, {
|
room.addLiveEvents(events, {
|
||||||
duplicateStrategy: "foo",
|
duplicateStrategy: "foo",
|
||||||
});
|
});
|
||||||
@ -255,6 +266,7 @@ describe("Room", function() {
|
|||||||
dupe.event.event_id = events[0].getId();
|
dupe.event.event_id = events[0].getId();
|
||||||
room.addLiveEvents(events);
|
room.addLiveEvents(events);
|
||||||
expect(room.timeline[0]).toEqual(events[0]);
|
expect(room.timeline[0]).toEqual(events[0]);
|
||||||
|
// @ts-ignore
|
||||||
room.addLiveEvents([dupe], {
|
room.addLiveEvents([dupe], {
|
||||||
duplicateStrategy: "ignore",
|
duplicateStrategy: "ignore",
|
||||||
});
|
});
|
||||||
@ -263,7 +275,7 @@ describe("Room", function() {
|
|||||||
|
|
||||||
it("should emit 'Room.timeline' events", function() {
|
it("should emit 'Room.timeline' events", function() {
|
||||||
let callCount = 0;
|
let callCount = 0;
|
||||||
room.on("Room.timeline", function(event, emitRoom, toStart) {
|
room.on(RoomEvent.Timeline, function(event, emitRoom, toStart) {
|
||||||
callCount += 1;
|
callCount += 1;
|
||||||
expect(room.timeline.length).toEqual(callCount);
|
expect(room.timeline.length).toEqual(callCount);
|
||||||
expect(event).toEqual(events[callCount - 1]);
|
expect(event).toEqual(events[callCount - 1]);
|
||||||
@ -306,8 +318,8 @@ describe("Room", function() {
|
|||||||
userId: userA,
|
userId: userA,
|
||||||
membership: "join",
|
membership: "join",
|
||||||
name: "Alice",
|
name: "Alice",
|
||||||
};
|
} as unknown as RoomMember;
|
||||||
room.currentState.getSentinelMember.mockImplementation(function(uid) {
|
mocked(room.currentState.getSentinelMember).mockImplementation(function(uid) {
|
||||||
if (uid === userA) {
|
if (uid === userA) {
|
||||||
return sentinel;
|
return sentinel;
|
||||||
}
|
}
|
||||||
@ -331,27 +343,25 @@ describe("Room", function() {
|
|||||||
const remoteEventId = remoteEvent.getId();
|
const remoteEventId = remoteEvent.getId();
|
||||||
|
|
||||||
let callCount = 0;
|
let callCount = 0;
|
||||||
room.on("Room.localEchoUpdated",
|
room.on(RoomEvent.LocalEchoUpdated, (event, emitRoom, oldEventId, oldStatus) => {
|
||||||
function(event, emitRoom, oldEventId, oldStatus) {
|
switch (callCount) {
|
||||||
switch (callCount) {
|
case 0:
|
||||||
case 0:
|
expect(event.getId()).toEqual(localEventId);
|
||||||
expect(event.getId()).toEqual(localEventId);
|
expect(event.status).toEqual(EventStatus.SENDING);
|
||||||
expect(event.status).toEqual(EventStatus.SENDING);
|
expect(emitRoom).toEqual(room);
|
||||||
expect(emitRoom).toEqual(room);
|
expect(oldEventId).toBeUndefined();
|
||||||
expect(oldEventId).toBeUndefined();
|
expect(oldStatus).toBeUndefined();
|
||||||
expect(oldStatus).toBeUndefined();
|
break;
|
||||||
break;
|
case 1:
|
||||||
case 1:
|
expect(event.getId()).toEqual(remoteEventId);
|
||||||
expect(event.getId()).toEqual(remoteEventId);
|
expect(event.status).toBeNull();
|
||||||
expect(event.status).toBeNull();
|
expect(emitRoom).toEqual(room);
|
||||||
expect(emitRoom).toEqual(room);
|
expect(oldEventId).toEqual(localEventId);
|
||||||
expect(oldEventId).toEqual(localEventId);
|
expect(oldStatus).toBe(EventStatus.SENDING);
|
||||||
expect(oldStatus).toBe(EventStatus.SENDING);
|
break;
|
||||||
break;
|
}
|
||||||
}
|
callCount += 1;
|
||||||
callCount += 1;
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// first add the local echo
|
// first add the local echo
|
||||||
room.addPendingEvent(localEvent, "TXN_ID");
|
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() {
|
it("should be able to update local echo without a txn ID (/send then /sync)", function() {
|
||||||
const eventJson = utils.mkMessage({
|
const eventJson = utils.mkMessage({
|
||||||
room: roomId, user: userA, event: false,
|
room: roomId, user: userA, event: false,
|
||||||
}) as object;
|
});
|
||||||
delete eventJson["txn_id"];
|
delete eventJson["txn_id"];
|
||||||
delete eventJson["event_id"];
|
delete eventJson["event_id"];
|
||||||
const localEvent = new MatrixEvent(Object.assign({ event_id: "$temp" }, eventJson));
|
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() {
|
it("should be able to update local echo without a txn ID (/sync then /send)", function() {
|
||||||
const eventJson = utils.mkMessage({
|
const eventJson = utils.mkMessage({
|
||||||
room: roomId, user: userA, event: false,
|
room: roomId, user: userA, event: false,
|
||||||
}) as object;
|
});
|
||||||
delete eventJson["txn_id"];
|
delete eventJson["txn_id"];
|
||||||
delete eventJson["event_id"];
|
delete eventJson["event_id"];
|
||||||
const txnId = "My_txn_id";
|
const txnId = "My_txn_id";
|
||||||
@ -483,7 +493,7 @@ describe("Room", function() {
|
|||||||
it("should emit 'Room.timeline' events when added to the start",
|
it("should emit 'Room.timeline' events when added to the start",
|
||||||
function() {
|
function() {
|
||||||
let callCount = 0;
|
let callCount = 0;
|
||||||
room.on("Room.timeline", function(event, emitRoom, toStart) {
|
room.on(RoomEvent.Timeline, function(event, emitRoom, toStart) {
|
||||||
callCount += 1;
|
callCount += 1;
|
||||||
expect(room.timeline.length).toEqual(callCount);
|
expect(room.timeline.length).toEqual(callCount);
|
||||||
expect(event).toEqual(events[callCount - 1]);
|
expect(event).toEqual(events[callCount - 1]);
|
||||||
@ -501,19 +511,19 @@ describe("Room", function() {
|
|||||||
userId: userA,
|
userId: userA,
|
||||||
membership: "join",
|
membership: "join",
|
||||||
name: "Alice",
|
name: "Alice",
|
||||||
};
|
} as unknown as RoomMember;
|
||||||
const oldSentinel = {
|
const oldSentinel = {
|
||||||
userId: userA,
|
userId: userA,
|
||||||
membership: "join",
|
membership: "join",
|
||||||
name: "Old Alice",
|
name: "Old Alice",
|
||||||
};
|
} as unknown as RoomMember;
|
||||||
room.currentState.getSentinelMember.mockImplementation(function(uid) {
|
mocked(room.currentState.getSentinelMember).mockImplementation(function(uid) {
|
||||||
if (uid === userA) {
|
if (uid === userA) {
|
||||||
return sentinel;
|
return sentinel;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
room.oldState.getSentinelMember.mockImplementation(function(uid) {
|
mocked(room.oldState.getSentinelMember).mockImplementation(function(uid) {
|
||||||
if (uid === userA) {
|
if (uid === userA) {
|
||||||
return oldSentinel;
|
return oldSentinel;
|
||||||
}
|
}
|
||||||
@ -539,19 +549,19 @@ describe("Room", function() {
|
|||||||
userId: userA,
|
userId: userA,
|
||||||
membership: "join",
|
membership: "join",
|
||||||
name: "Alice",
|
name: "Alice",
|
||||||
};
|
} as unknown as RoomMember;
|
||||||
const oldSentinel = {
|
const oldSentinel = {
|
||||||
userId: userA,
|
userId: userA,
|
||||||
membership: "join",
|
membership: "join",
|
||||||
name: "Old Alice",
|
name: "Old Alice",
|
||||||
};
|
} as unknown as RoomMember;
|
||||||
room.currentState.getSentinelMember.mockImplementation(function(uid) {
|
mocked(room.currentState.getSentinelMember).mockImplementation(function(uid) {
|
||||||
if (uid === userA) {
|
if (uid === userA) {
|
||||||
return sentinel;
|
return sentinel;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
room.oldState.getSentinelMember.mockImplementation(function(uid) {
|
mocked(room.oldState.getSentinelMember).mockImplementation(function(uid) {
|
||||||
if (uid === userA) {
|
if (uid === userA) {
|
||||||
return oldSentinel;
|
return oldSentinel;
|
||||||
}
|
}
|
||||||
@ -599,7 +609,7 @@ describe("Room", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const resetTimelineTests = function(timelineSupport) {
|
const resetTimelineTests = function(timelineSupport: boolean) {
|
||||||
let events: MatrixEvent[];
|
let events: MatrixEvent[];
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
@ -630,8 +640,8 @@ describe("Room", function() {
|
|||||||
const oldState = room.getLiveTimeline().getState(EventTimeline.BACKWARDS);
|
const oldState = room.getLiveTimeline().getState(EventTimeline.BACKWARDS);
|
||||||
const newState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
const newState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
||||||
expect(room.getLiveTimeline().getEvents().length).toEqual(1);
|
expect(room.getLiveTimeline().getEvents().length).toEqual(1);
|
||||||
expect(oldState.getStateEvents(EventType.RoomName, "")).toEqual(events[1]);
|
expect(oldState?.getStateEvents(EventType.RoomName, "")).toEqual(events[1]);
|
||||||
expect(newState.getStateEvents(EventType.RoomName, "")).toEqual(events[2]);
|
expect(newState?.getStateEvents(EventType.RoomName, "")).toEqual(events[2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should reset the legacy timeline fields", function() {
|
it("should reset the legacy timeline fields", function() {
|
||||||
@ -669,17 +679,14 @@ describe("Room", function() {
|
|||||||
expect(currentStateUpdateEmitCount).toEqual(timelineSupport ? 1 : 0);
|
expect(currentStateUpdateEmitCount).toEqual(timelineSupport ? 1 : 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should emit Room.timelineReset event and set the correct " +
|
it("should emit Room.timelineReset event and set the correct pagination token", function() {
|
||||||
"pagination token", function() {
|
|
||||||
let callCount = 0;
|
let callCount = 0;
|
||||||
room.on("Room.timelineReset", function(emitRoom) {
|
room.on(RoomEvent.TimelineReset, function(emitRoom) {
|
||||||
callCount += 1;
|
callCount += 1;
|
||||||
expect(emitRoom).toEqual(room);
|
expect(emitRoom).toEqual(room);
|
||||||
|
|
||||||
// make sure that the pagination token has been set before the
|
// make sure that the pagination token has been set before the event is emitted.
|
||||||
// event is emitted.
|
const tok = emitRoom?.getLiveTimeline().getPaginationToken(EventTimeline.BACKWARDS);
|
||||||
const tok = emitRoom.getLiveTimeline()
|
|
||||||
.getPaginationToken(EventTimeline.BACKWARDS);
|
|
||||||
|
|
||||||
expect(tok).toEqual("pagToken");
|
expect(tok).toEqual("pagToken");
|
||||||
});
|
});
|
||||||
@ -693,7 +700,7 @@ describe("Room", function() {
|
|||||||
const firstLiveTimeline = room.getLiveTimeline();
|
const firstLiveTimeline = room.getLiveTimeline();
|
||||||
room.resetLiveTimeline('sometoken', 'someothertoken');
|
room.resetLiveTimeline('sometoken', 'someothertoken');
|
||||||
|
|
||||||
const tl = room.getTimelineForEvent(events[0].getId());
|
const tl = room.getTimelineForEvent(events[0].getId()!);
|
||||||
expect(tl).toBe(timelineSupport ? firstLiveTimeline : null);
|
expect(tl).toBe(timelineSupport ? firstLiveTimeline : null);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -721,30 +728,25 @@ describe("Room", function() {
|
|||||||
it("should handle events in the same timeline", function() {
|
it("should handle events in the same timeline", function() {
|
||||||
room.addLiveEvents(events);
|
room.addLiveEvents(events);
|
||||||
|
|
||||||
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!,
|
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!, events[1].getId()!))
|
||||||
events[1].getId()))
|
|
||||||
.toBeLessThan(0);
|
.toBeLessThan(0);
|
||||||
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[2].getId()!,
|
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[2].getId()!, events[1].getId()!))
|
||||||
events[1].getId()))
|
|
||||||
.toBeGreaterThan(0);
|
.toBeGreaterThan(0);
|
||||||
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!,
|
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!, events[1].getId()!))
|
||||||
events[1].getId()))
|
|
||||||
.toEqual(0);
|
.toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle events in adjacent timelines", function() {
|
it("should handle events in adjacent timelines", function() {
|
||||||
const oldTimeline = room.addTimeline();
|
const oldTimeline = room.addTimeline();
|
||||||
oldTimeline.setNeighbouringTimeline(room.getLiveTimeline(), 'f');
|
oldTimeline.setNeighbouringTimeline(room.getLiveTimeline(), Direction.Forward);
|
||||||
room.getLiveTimeline().setNeighbouringTimeline(oldTimeline, 'b');
|
room.getLiveTimeline().setNeighbouringTimeline(oldTimeline, Direction.Backward);
|
||||||
|
|
||||||
room.addEventsToTimeline([events[0]], false, oldTimeline);
|
room.addEventsToTimeline([events[0]], false, oldTimeline);
|
||||||
room.addLiveEvents([events[1]]);
|
room.addLiveEvents([events[1]]);
|
||||||
|
|
||||||
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!,
|
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!, events[1].getId()!))
|
||||||
events[1].getId()))
|
|
||||||
.toBeLessThan(0);
|
.toBeLessThan(0);
|
||||||
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!,
|
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!, events[0].getId()!))
|
||||||
events[0].getId()))
|
|
||||||
.toBeGreaterThan(0);
|
.toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -754,11 +756,9 @@ describe("Room", function() {
|
|||||||
room.addEventsToTimeline([events[0]], false, oldTimeline);
|
room.addEventsToTimeline([events[0]], false, oldTimeline);
|
||||||
room.addLiveEvents([events[1]]);
|
room.addLiveEvents([events[1]]);
|
||||||
|
|
||||||
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!,
|
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!, events[1].getId()!))
|
||||||
events[1].getId()))
|
|
||||||
.toBe(null);
|
.toBe(null);
|
||||||
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!,
|
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!, events[0].getId()!))
|
||||||
events[0].getId()))
|
|
||||||
.toBe(null);
|
.toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -769,21 +769,21 @@ describe("Room", function() {
|
|||||||
.compareEventOrdering(events[0].getId()!, "xxx"))
|
.compareEventOrdering(events[0].getId()!, "xxx"))
|
||||||
.toBe(null);
|
.toBe(null);
|
||||||
expect(room.getUnfilteredTimelineSet()
|
expect(room.getUnfilteredTimelineSet()
|
||||||
.compareEventOrdering("xxx", events[0].getId()))
|
.compareEventOrdering("xxx", events[0].getId()!))
|
||||||
.toBe(null);
|
.toBe(null);
|
||||||
expect(room.getUnfilteredTimelineSet()
|
expect(room.getUnfilteredTimelineSet()
|
||||||
.compareEventOrdering(events[0].getId()!, events[0].getId()))
|
.compareEventOrdering(events[0].getId()!, events[0].getId()!))
|
||||||
.toBe(0);
|
.toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getJoinedMembers", function() {
|
describe("getJoinedMembers", function() {
|
||||||
it("should return members whose membership is 'join'", function() {
|
it("should return members whose membership is 'join'", function() {
|
||||||
room.currentState.getMembers.mockImplementation(function() {
|
mocked(room.currentState.getMembers).mockImplementation(function() {
|
||||||
return [
|
return [
|
||||||
{ userId: "@alice:bar", membership: "join" },
|
{ userId: "@alice:bar", membership: "join" } as unknown as RoomMember,
|
||||||
{ userId: "@bob:bar", membership: "invite" },
|
{ userId: "@bob:bar", membership: "invite" } as unknown as RoomMember,
|
||||||
{ userId: "@cleo:bar", membership: "leave" },
|
{ userId: "@cleo:bar", membership: "leave" } as unknown as RoomMember,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
const res = room.getJoinedMembers();
|
const res = room.getJoinedMembers();
|
||||||
@ -792,9 +792,9 @@ describe("Room", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should return an empty list if no membership is 'join'", 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 [
|
return [
|
||||||
{ userId: "@bob:bar", membership: "invite" },
|
{ userId: "@bob:bar", membership: "invite" } as unknown as RoomMember,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
const res = room.getJoinedMembers();
|
const res = room.getJoinedMembers();
|
||||||
@ -805,41 +805,41 @@ describe("Room", function() {
|
|||||||
describe("hasMembershipState", function() {
|
describe("hasMembershipState", function() {
|
||||||
it("should return true for a matching userId and membership",
|
it("should return true for a matching userId and membership",
|
||||||
function() {
|
function() {
|
||||||
room.currentState.getMember.mockImplementation(function(userId) {
|
mocked(room.currentState.getMember).mockImplementation(function(userId) {
|
||||||
return {
|
return {
|
||||||
"@alice:bar": { userId: "@alice:bar", membership: "join" },
|
"@alice:bar": { userId: "@alice:bar", membership: "join" },
|
||||||
"@bob:bar": { userId: "@bob:bar", membership: "invite" },
|
"@bob:bar": { userId: "@bob:bar", membership: "invite" },
|
||||||
}[userId];
|
}[userId] as unknown as RoomMember;
|
||||||
});
|
});
|
||||||
expect(room.hasMembershipState("@bob:bar", "invite")).toBe(true);
|
expect(room.hasMembershipState("@bob:bar", "invite")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return false if match membership but no match userId",
|
it("should return false if match membership but no match userId",
|
||||||
function() {
|
function() {
|
||||||
room.currentState.getMember.mockImplementation(function(userId) {
|
mocked(room.currentState.getMember).mockImplementation(function(userId) {
|
||||||
return {
|
return {
|
||||||
"@alice:bar": { userId: "@alice:bar", membership: "join" },
|
"@alice:bar": { userId: "@alice:bar", membership: "join" },
|
||||||
}[userId];
|
}[userId] as unknown as RoomMember;
|
||||||
});
|
});
|
||||||
expect(room.hasMembershipState("@bob:bar", "join")).toBe(false);
|
expect(room.hasMembershipState("@bob:bar", "join")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return false if match userId but no match membership",
|
it("should return false if match userId but no match membership",
|
||||||
function() {
|
function() {
|
||||||
room.currentState.getMember.mockImplementation(function(userId) {
|
mocked(room.currentState.getMember).mockImplementation(function(userId) {
|
||||||
return {
|
return {
|
||||||
"@alice:bar": { userId: "@alice:bar", membership: "join" },
|
"@alice:bar": { userId: "@alice:bar", membership: "join" },
|
||||||
}[userId];
|
}[userId] as unknown as RoomMember;
|
||||||
});
|
});
|
||||||
expect(room.hasMembershipState("@alice:bar", "ban")).toBe(false);
|
expect(room.hasMembershipState("@alice:bar", "ban")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return false if no match membership or userId",
|
it("should return false if no match membership or userId",
|
||||||
function() {
|
function() {
|
||||||
room.currentState.getMember.mockImplementation(function(userId) {
|
mocked(room.currentState.getMember).mockImplementation(function(userId) {
|
||||||
return {
|
return {
|
||||||
"@alice:bar": { userId: "@alice:bar", membership: "join" },
|
"@alice:bar": { userId: "@alice:bar", membership: "join" },
|
||||||
}[userId];
|
}[userId] as unknown as RoomMember;
|
||||||
});
|
});
|
||||||
expect(room.hasMembershipState("@bob:bar", "invite")).toBe(false);
|
expect(room.hasMembershipState("@bob:bar", "invite")).toBe(false);
|
||||||
});
|
});
|
||||||
@ -1193,8 +1193,8 @@ describe("Room", function() {
|
|||||||
event: true,
|
event: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
function mkReceipt(roomId: string, records) {
|
function mkReceipt(roomId: string, records: Array<ReturnType<typeof mkRecord>>) {
|
||||||
const content = {};
|
const content: IContent = {};
|
||||||
records.forEach(function(r) {
|
records.forEach(function(r) {
|
||||||
if (!content[r.eventId]) {
|
if (!content[r.eventId]) {
|
||||||
content[r.eventId] = {};
|
content[r.eventId] = {};
|
||||||
@ -1241,7 +1241,7 @@ describe("Room", function() {
|
|||||||
it("should emit an event when a receipt is added",
|
it("should emit an event when a receipt is added",
|
||||||
function() {
|
function() {
|
||||||
const listener = jest.fn();
|
const listener = jest.fn();
|
||||||
room.on("Room.receipt", listener);
|
room.on(RoomEvent.Receipt, listener);
|
||||||
|
|
||||||
const ts = 13787898424;
|
const ts = 13787898424;
|
||||||
|
|
||||||
@ -1448,7 +1448,7 @@ describe("Room", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("tags", function() {
|
describe("tags", function() {
|
||||||
function mkTags(roomId, tags) {
|
function mkTags(roomId: string, tags: object) {
|
||||||
const content = { "tags": tags };
|
const content = { "tags": tags };
|
||||||
return new MatrixEvent({
|
return new MatrixEvent({
|
||||||
content: content,
|
content: content,
|
||||||
@ -1470,7 +1470,7 @@ describe("Room", function() {
|
|||||||
"received on the event stream",
|
"received on the event stream",
|
||||||
function() {
|
function() {
|
||||||
const listener = jest.fn();
|
const listener = jest.fn();
|
||||||
room.on("Room.tags", listener);
|
room.on(RoomEvent.Tags, listener);
|
||||||
|
|
||||||
const tags = { "m.foo": { "order": 0.5 } };
|
const tags = { "m.foo": { "order": 0.5 } };
|
||||||
const event = mkTags(roomId, tags);
|
const event = mkTags(roomId, tags);
|
||||||
@ -1642,11 +1642,14 @@ describe("Room", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("loadMembersIfNeeded", function() {
|
describe("loadMembersIfNeeded", function() {
|
||||||
function createClientMock(serverResponse, storageResponse: MatrixEvent[] | Error | null = null) {
|
function createClientMock(
|
||||||
|
serverResponse: Error | MatrixEvent[],
|
||||||
|
storageResponse: MatrixEvent[] | Error | null = null,
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
getEventMapper: function() {
|
getEventMapper: function() {
|
||||||
// events should already be MatrixEvents
|
// events should already be MatrixEvents
|
||||||
return function(event) {return event;};
|
return function(event: MatrixEvent) {return event;};
|
||||||
},
|
},
|
||||||
isCryptoEnabled() {
|
isCryptoEnabled() {
|
||||||
return true;
|
return true;
|
||||||
@ -1671,7 +1674,7 @@ describe("Room", function() {
|
|||||||
return Promise.resolve(this.storageResponse);
|
return Promise.resolve(this.storageResponse);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setOutOfBandMembers: function(roomId, memberEvents) {
|
setOutOfBandMembers: function(roomId: string, memberEvents: IStateEventWithRoomId[]) {
|
||||||
this.storedMembers = memberEvents;
|
this.storedMembers = memberEvents;
|
||||||
return Promise.resolve();
|
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({
|
const rootEvent = new MatrixEvent({
|
||||||
event_id: "$666",
|
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", () => {
|
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);
|
Thread.setServerSideListSupport(FeatureSupport.Stable);
|
||||||
|
|
||||||
room.client.createThreadListMessagesRequest = () => Promise.resolve({
|
room.client.createThreadListMessagesRequest = () => Promise.resolve({
|
||||||
start: null,
|
|
||||||
end: null,
|
|
||||||
chunk: [],
|
chunk: [],
|
||||||
state: [],
|
state: [],
|
||||||
});
|
});
|
||||||
@ -2761,7 +2762,7 @@ describe("Room", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("thread notifications", () => {
|
describe("thread notifications", () => {
|
||||||
let room;
|
let room: Room;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const client = new TestClient(userA).client;
|
const client = new TestClient(userA).client;
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
// This file had a function whose name is all caps, which displeases eslint
|
// This file had a function whose name is all caps, which displeases eslint
|
||||||
/* eslint new-cap: "off" */
|
/* eslint new-cap: "off" */
|
||||||
|
|
||||||
import { defer } from '../../src/utils';
|
import { defer, IDeferred } from '../../src/utils';
|
||||||
import { MatrixError } from "../../src/http-api";
|
import { MatrixError } from "../../src/http-api";
|
||||||
import { MatrixScheduler } from "../../src/scheduler";
|
import { MatrixScheduler } from "../../src/scheduler";
|
||||||
import * as utils from "../test-utils/test-utils";
|
import * as utils from "../test-utils/test-utils";
|
||||||
|
import { MatrixEvent } from "../../src";
|
||||||
|
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
|
||||||
describe("MatrixScheduler", function() {
|
describe("MatrixScheduler", function() {
|
||||||
let scheduler;
|
let scheduler: MatrixScheduler<Record<string, boolean>>;
|
||||||
let retryFn;
|
let retryFn: Function | null;
|
||||||
let queueFn;
|
let queueFn: ((event: MatrixEvent) => string | null) | null;
|
||||||
let deferred;
|
let deferred: IDeferred<Record<string, boolean>>;
|
||||||
const roomId = "!foo:bar";
|
const roomId = "!foo:bar";
|
||||||
const eventA = utils.mkMessage({
|
const eventA = utils.mkMessage({
|
||||||
user: "@alice:bar", room: roomId, event: true,
|
user: "@alice:bar", room: roomId, event: true,
|
||||||
@ -65,8 +66,8 @@ describe("MatrixScheduler", function() {
|
|||||||
deferB.resolve({ b: true });
|
deferB.resolve({ b: true });
|
||||||
deferA.resolve({ a: true });
|
deferA.resolve({ a: true });
|
||||||
const [a, b] = await abPromise;
|
const [a, b] = await abPromise;
|
||||||
expect(a.a).toEqual(true);
|
expect(a!.a).toEqual(true);
|
||||||
expect(b.b).toEqual(true);
|
expect(b!.b).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should invoke the retryFn on failure and wait the amount of time specified",
|
it("should invoke the retryFn on failure and wait the amount of time specified",
|
||||||
@ -92,6 +93,7 @@ describe("MatrixScheduler", function() {
|
|||||||
return new Promise(() => {});
|
return new Promise(() => {});
|
||||||
}
|
}
|
||||||
expect(procCount).toBeLessThan(3);
|
expect(procCount).toBeLessThan(3);
|
||||||
|
return new Promise(() => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
scheduler.queueEvent(eventA);
|
scheduler.queueEvent(eventA);
|
||||||
@ -119,8 +121,8 @@ describe("MatrixScheduler", function() {
|
|||||||
return "yep";
|
return "yep";
|
||||||
};
|
};
|
||||||
|
|
||||||
const deferA = defer();
|
const deferA = defer<Record<string, boolean>>();
|
||||||
const deferB = defer();
|
const deferB = defer<Record<string, boolean>>();
|
||||||
let procCount = 0;
|
let procCount = 0;
|
||||||
scheduler.setProcessFunction(function(ev) {
|
scheduler.setProcessFunction(function(ev) {
|
||||||
procCount += 1;
|
procCount += 1;
|
||||||
@ -132,6 +134,7 @@ describe("MatrixScheduler", function() {
|
|||||||
return deferB.promise;
|
return deferB.promise;
|
||||||
}
|
}
|
||||||
expect(procCount).toBeLessThan(3);
|
expect(procCount).toBeLessThan(3);
|
||||||
|
return new Promise<Record<string, boolean>>(() => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
const globalA = scheduler.queueEvent(eventA);
|
const globalA = scheduler.queueEvent(eventA);
|
||||||
@ -159,7 +162,7 @@ describe("MatrixScheduler", function() {
|
|||||||
const eventC = utils.mkMessage({ user: "@a:bar", room: roomId, event: true });
|
const eventC = utils.mkMessage({ user: "@a:bar", room: roomId, event: true });
|
||||||
const eventD = utils.mkMessage({ user: "@b:bar", room: roomId, event: true });
|
const eventD = utils.mkMessage({ user: "@b:bar", room: roomId, event: true });
|
||||||
|
|
||||||
const buckets = {};
|
const buckets: Record<string, string> = {};
|
||||||
buckets[eventA.getId()!] = "queue_A";
|
buckets[eventA.getId()!] = "queue_A";
|
||||||
buckets[eventD.getId()!] = "queue_A";
|
buckets[eventD.getId()!] = "queue_A";
|
||||||
buckets[eventB.getId()!] = "queue_B";
|
buckets[eventB.getId()!] = "queue_B";
|
||||||
@ -169,13 +172,13 @@ describe("MatrixScheduler", function() {
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
queueFn = function(event) {
|
queueFn = function(event) {
|
||||||
return buckets[event.getId()];
|
return buckets[event.getId()!];
|
||||||
};
|
};
|
||||||
|
|
||||||
const expectOrder = [
|
const expectOrder = [
|
||||||
eventA.getId(), eventB.getId(), eventD.getId(),
|
eventA.getId(), eventB.getId(), eventD.getId(),
|
||||||
];
|
];
|
||||||
const deferA = defer<void>();
|
const deferA = defer<Record<string, boolean>>();
|
||||||
scheduler.setProcessFunction(function(event) {
|
scheduler.setProcessFunction(function(event) {
|
||||||
const id = expectOrder.shift();
|
const id = expectOrder.shift();
|
||||||
expect(id).toEqual(event.getId());
|
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.
|
// wait a bit then resolve A and we should get D (not C) next.
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
deferA.resolve();
|
deferA.resolve({});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
jest.advanceTimersByTime(1000);
|
jest.advanceTimersByTime(1000);
|
||||||
});
|
});
|
||||||
@ -210,7 +213,7 @@ describe("MatrixScheduler", function() {
|
|||||||
};
|
};
|
||||||
const prom = scheduler.queueEvent(eventA);
|
const prom = scheduler.queueEvent(eventA);
|
||||||
expect(prom).toBeTruthy();
|
expect(prom).toBeTruthy();
|
||||||
expect(prom.then).toBeTruthy();
|
expect(prom!.then).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -237,15 +240,15 @@ describe("MatrixScheduler", function() {
|
|||||||
scheduler.queueEvent(eventA);
|
scheduler.queueEvent(eventA);
|
||||||
scheduler.queueEvent(eventB);
|
scheduler.queueEvent(eventB);
|
||||||
const queue = scheduler.getQueueForEvent(eventA);
|
const queue = scheduler.getQueueForEvent(eventA);
|
||||||
expect(queue.length).toEqual(2);
|
expect(queue).toHaveLength(2);
|
||||||
expect(queue).toEqual([eventA, eventB]);
|
expect(queue).toEqual([eventA, eventB]);
|
||||||
// modify the queue
|
// modify the queue
|
||||||
const eventC = utils.mkMessage(
|
const eventC = utils.mkMessage(
|
||||||
{ user: "@a:bar", room: roomId, event: true },
|
{ user: "@a:bar", room: roomId, event: true },
|
||||||
);
|
);
|
||||||
queue.push(eventC);
|
queue!.push(eventC);
|
||||||
const queueAgain = scheduler.getQueueForEvent(eventA);
|
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" +
|
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(eventA);
|
||||||
scheduler.queueEvent(eventB);
|
scheduler.queueEvent(eventB);
|
||||||
const queue = scheduler.getQueueForEvent(eventA);
|
const queue = scheduler.getQueueForEvent(eventA)!;
|
||||||
queue[1].event.content.body = "foo";
|
queue[1].event.content!.body = "foo";
|
||||||
const queueAgain = scheduler.getQueueForEvent(eventA);
|
const queueAgain = scheduler.getQueueForEvent(eventA)!;
|
||||||
expect(queueAgain[1].event.content.body).toEqual("foo");
|
expect(queueAgain[1].event.content?.body).toEqual("foo");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -16,7 +16,8 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ReceiptType } from "../../src/@types/read_receipts";
|
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
|
// The event body & unsigned object get frozen to assert that they don't get altered
|
||||||
// by the impl
|
// by the impl
|
||||||
@ -55,10 +56,10 @@ const RES_WITH_AGE = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
} as unknown as ISyncResponse;
|
||||||
|
|
||||||
describe("SyncAccumulator", function() {
|
describe("SyncAccumulator", function() {
|
||||||
let sa;
|
let sa: SyncAccumulator;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
sa = new SyncAccumulator({
|
sa = new SyncAccumulator({
|
||||||
@ -98,7 +99,7 @@ describe("SyncAccumulator", function() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
} as unknown as ISyncResponse;
|
||||||
sa.accumulate(res);
|
sa.accumulate(res);
|
||||||
const output = sa.getJSON();
|
const output = sa.getJSON();
|
||||||
expect(output.nextBatch).toEqual(res.next_batch);
|
expect(output.nextBatch).toEqual(res.next_batch);
|
||||||
@ -223,7 +224,6 @@ describe("SyncAccumulator", function() {
|
|||||||
content: {
|
content: {
|
||||||
user_ids: ["@alice:localhost"],
|
user_ids: ["@alice:localhost"],
|
||||||
},
|
},
|
||||||
room_id: "!foo:bar",
|
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -281,12 +281,12 @@ describe("SyncAccumulator", function() {
|
|||||||
account_data: {
|
account_data: {
|
||||||
events: [acc1],
|
events: [acc1],
|
||||||
},
|
},
|
||||||
});
|
} as unknown as ISyncResponse);
|
||||||
sa.accumulate({
|
sa.accumulate({
|
||||||
account_data: {
|
account_data: {
|
||||||
events: [acc2],
|
events: [acc2],
|
||||||
},
|
},
|
||||||
});
|
} as unknown as ISyncResponse);
|
||||||
expect(
|
expect(
|
||||||
sa.getJSON().accountData.length,
|
sa.getJSON().accountData.length,
|
||||||
).toEqual(1);
|
).toEqual(1);
|
||||||
@ -422,7 +422,7 @@ describe("SyncAccumulator", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("summary field", function() {
|
describe("summary field", function() {
|
||||||
function createSyncResponseWithSummary(summary) {
|
function createSyncResponseWithSummary(summary: IRoomSummary): ISyncResponse {
|
||||||
return {
|
return {
|
||||||
next_batch: "abc",
|
next_batch: "abc",
|
||||||
rooms: {
|
rooms: {
|
||||||
@ -444,7 +444,7 @@ describe("SyncAccumulator", function() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
} as unknown as ISyncResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -487,8 +487,8 @@ describe("SyncAccumulator", function() {
|
|||||||
jest.spyOn(global.Date, 'now').mockReturnValue(startingTs + delta);
|
jest.spyOn(global.Date, 'now').mockReturnValue(startingTs + delta);
|
||||||
|
|
||||||
const output = sa.getJSON();
|
const output = sa.getJSON();
|
||||||
expect(output.roomsData.join["!foo:bar"].timeline.events[0].unsigned.age).toEqual(
|
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,
|
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(
|
expect(Object.keys(output.roomsData.join["!foo:bar"].timeline.events[0])).toEqual(
|
||||||
Object.keys(RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0]),
|
Object.keys(RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0]),
|
||||||
@ -507,12 +507,12 @@ describe("SyncAccumulator", function() {
|
|||||||
sa.accumulate(RES_WITH_AGE);
|
sa.accumulate(RES_WITH_AGE);
|
||||||
const output = sa.getJSON();
|
const output = sa.getJSON();
|
||||||
expect(output.roomsData.join["!foo:bar"]
|
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 || {};
|
joinObj = joinObj || {};
|
||||||
return {
|
return {
|
||||||
next_batch: "abc",
|
next_batch: "abc",
|
||||||
@ -521,11 +521,12 @@ function syncSkeleton(joinObj) {
|
|||||||
"!foo:bar": joinObj,
|
"!foo:bar": joinObj,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
} as unknown as ISyncResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
function msg(localpart, text) {
|
function msg(localpart: string, text: string) {
|
||||||
return {
|
return {
|
||||||
|
event_id: "$" + Math.random(),
|
||||||
content: {
|
content: {
|
||||||
body: text,
|
body: text,
|
||||||
},
|
},
|
||||||
@ -535,8 +536,9 @@ function msg(localpart, text) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function member(localpart, membership) {
|
function member(localpart: string, membership: string) {
|
||||||
return {
|
return {
|
||||||
|
event_id: "$" + Math.random(),
|
||||||
content: {
|
content: {
|
||||||
membership: membership,
|
membership: membership,
|
||||||
},
|
},
|
||||||
|
@ -455,7 +455,11 @@ describe("utils", function() {
|
|||||||
|
|
||||||
describe("recursivelyAssign", () => {
|
describe("recursivelyAssign", () => {
|
||||||
it("doesn't override with null/undefined", () => {
|
it("doesn't override with null/undefined", () => {
|
||||||
const result = utils.recursivelyAssign(
|
const result = utils.recursivelyAssign<{
|
||||||
|
string: string;
|
||||||
|
object: object;
|
||||||
|
float: number;
|
||||||
|
}, {}>(
|
||||||
{
|
{
|
||||||
string: "Hello world",
|
string: "Hello world",
|
||||||
object: {},
|
object: {},
|
||||||
@ -475,7 +479,11 @@ describe("utils", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("assigns recursively", () => {
|
it("assigns recursively", () => {
|
||||||
const result = utils.recursivelyAssign(
|
const result = utils.recursivelyAssign<{
|
||||||
|
number: number;
|
||||||
|
object: object;
|
||||||
|
thing: string | object;
|
||||||
|
}, {}>(
|
||||||
{
|
{
|
||||||
number: 42,
|
number: 42,
|
||||||
object: {
|
object: {
|
||||||
|
@ -933,7 +933,7 @@ describe('Call', function() {
|
|||||||
await fakeIncomingCall(client, call, "1");
|
await fakeIncomingCall(client, call, "1");
|
||||||
});
|
});
|
||||||
|
|
||||||
const untilEventSent = async (...args) => {
|
const untilEventSent = async (...args: any[]) => {
|
||||||
const maxTries = 20;
|
const maxTries = 20;
|
||||||
|
|
||||||
for (let tries = 0; tries < maxTries; ++tries) {
|
for (let tries = 0; tries < maxTries; ++tries) {
|
||||||
@ -971,7 +971,7 @@ describe('Call', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("ICE candidate sending", () => {
|
describe("ICE candidate sending", () => {
|
||||||
let mockPeerConn;
|
let mockPeerConn: MockRTCPeerConnection;
|
||||||
const fakeCandidateString = "here is a fake candidate!";
|
const fakeCandidateString = "here is a fake candidate!";
|
||||||
const fakeCandidateEvent = {
|
const fakeCandidateEvent = {
|
||||||
candidate: {
|
candidate: {
|
||||||
@ -1086,7 +1086,7 @@ describe('Call', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Screen sharing", () => {
|
describe("Screen sharing", () => {
|
||||||
const waitNegotiateFunc = resolve => {
|
const waitNegotiateFunc = (resolve: Function): void => {
|
||||||
mockSendEvent.mockImplementationOnce(() => {
|
mockSendEvent.mockImplementationOnce(() => {
|
||||||
// Note that the peer connection here is a dummy one and always returns
|
// 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
|
// 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 {
|
export interface ReceiptContent {
|
||||||
[eventId: string]: {
|
[eventId: string]: {
|
||||||
[key in ReceiptType]: {
|
[key in ReceiptType | string]: {
|
||||||
[userId: string]: Receipt;
|
[userId: string]: Receipt;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -49,7 +49,7 @@ interface WellKnownConfig extends Omit<IWellKnownConfig, "error"> {
|
|||||||
error?: IWellKnownConfig["error"] | null;
|
error?: IWellKnownConfig["error"] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ClientConfig {
|
interface ClientConfig extends Omit<IClientWellKnown, "m.homeserver" | "m.identity_server"> {
|
||||||
"m.homeserver": WellKnownConfig;
|
"m.homeserver": WellKnownConfig;
|
||||||
"m.identity_server": WellKnownConfig;
|
"m.identity_server": WellKnownConfig;
|
||||||
}
|
}
|
||||||
@ -131,7 +131,7 @@ export class AutoDiscovery {
|
|||||||
* configuration, which may include error states. Rejects on unexpected
|
* configuration, which may include error states. Rejects on unexpected
|
||||||
* failure, not when verification fails.
|
* 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.
|
// 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
|
// 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(
|
const hsVersions = await this.fetchWellKnownObject(
|
||||||
`${hsUrl}/_matrix/client/versions`,
|
`${hsUrl}/_matrix/client/versions`,
|
||||||
);
|
);
|
||||||
if (!hsVersions || !hsVersions.raw["versions"]) {
|
if (!hsVersions || !hsVersions.raw?.["versions"]) {
|
||||||
logger.error("Invalid /versions response");
|
logger.error("Invalid /versions response");
|
||||||
clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID_HOMESERVER;
|
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
|
// Step 5a: Make sure the URL is valid *looking*. We'll make sure it
|
||||||
// points to an identity server in Step 5b.
|
// points to an identity server in Step 5b.
|
||||||
isUrl = this.sanitizeWellKnownUrl(
|
isUrl = this.sanitizeWellKnownUrl(wellknown["m.identity_server"]["base_url"]);
|
||||||
wellknown["m.identity_server"]["base_url"],
|
|
||||||
);
|
|
||||||
if (!isUrl) {
|
if (!isUrl) {
|
||||||
logger.error("Invalid base_url for m.identity_server");
|
logger.error("Invalid base_url for m.identity_server");
|
||||||
failingClientConfig["m.identity_server"].error =
|
failingClientConfig["m.identity_server"].error =
|
||||||
@ -234,7 +232,7 @@ export class AutoDiscovery {
|
|||||||
const isResponse = await this.fetchWellKnownObject(
|
const isResponse = await this.fetchWellKnownObject(
|
||||||
`${isUrl}/_matrix/identity/api/v1`,
|
`${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");
|
logger.error("Invalid /api/v1 response");
|
||||||
failingClientConfig["m.identity_server"].error =
|
failingClientConfig["m.identity_server"].error =
|
||||||
AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER;
|
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
|
// Step 7: Copy any other keys directly into the clientConfig. This is for
|
||||||
// things like custom configuration of services.
|
// 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") {
|
if (k === "m.homeserver" || k === "m.identity_server") {
|
||||||
// Only copy selected parts of the config to avoid overwriting
|
// Only copy selected parts of the config to avoid overwriting
|
||||||
// properties computed by the validation logic above.
|
// properties computed by the validation logic above.
|
||||||
const notProps = ["error", "state", "base_url"];
|
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;
|
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 {
|
} else {
|
||||||
// Just copy the whole thing over otherwise
|
// Just copy the whole thing over otherwise
|
||||||
@ -347,7 +347,7 @@ export class AutoDiscovery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Validate and parse the config
|
// 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.
|
* @return {string|boolean} The sanitized URL or a falsey value if the URL is invalid.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private static sanitizeWellKnownUrl(url: string): string | false {
|
private static sanitizeWellKnownUrl(url?: string | null): string | false {
|
||||||
if (!url) return false;
|
if (!url) return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -16,27 +16,28 @@ limitations under the License.
|
|||||||
|
|
||||||
import * as matrixcs from "./matrix";
|
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) {
|
if (global.__js_sdk_entrypoint) {
|
||||||
throw new Error("Multiple matrix-js-sdk entrypoints detected!");
|
throw new Error("Multiple matrix-js-sdk entrypoints detected!");
|
||||||
}
|
}
|
||||||
global.__js_sdk_entrypoint = true;
|
global.__js_sdk_entrypoint = true;
|
||||||
|
|
||||||
// just *accessing* indexedDB throws an exception in firefox with
|
// just *accessing* indexedDB throws an exception in firefox with indexeddb disabled.
|
||||||
// indexeddb disabled.
|
let indexedDB: IDBFactory | undefined;
|
||||||
let indexedDB;
|
|
||||||
try {
|
try {
|
||||||
indexedDB = global.indexedDB;
|
indexedDB = global.indexedDB;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
// if our browser (appears to) support indexeddb, use an indexeddb crypto store.
|
// if our browser (appears to) support indexeddb, use an indexeddb crypto store.
|
||||||
if (indexedDB) {
|
if (indexedDB) {
|
||||||
matrixcs.setCryptoStoreFactory(
|
matrixcs.setCryptoStoreFactory(() => new matrixcs.IndexedDBCryptoStore(indexedDB!, "matrix-js-sdk:crypto"));
|
||||||
function() {
|
|
||||||
return new matrixcs.IndexedDBCryptoStore(
|
|
||||||
indexedDB, "matrix-js-sdk:crypto",
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We export 3 things to make browserify happy as well as downstream projects.
|
// We export 3 things to make browserify happy as well as downstream projects.
|
@ -530,7 +530,7 @@ export interface IPreviewUrlResponse {
|
|||||||
"matrix:image:size"?: number;
|
"matrix:image:size"?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ITurnServerResponse {
|
export interface ITurnServerResponse {
|
||||||
uris: string[];
|
uris: string[];
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
@ -561,7 +561,7 @@ export interface IClientWellKnown {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IWellKnownConfig {
|
export interface IWellKnownConfig {
|
||||||
raw?: any; // todo typings
|
raw?: IClientWellKnown;
|
||||||
action?: AutoDiscoveryAction;
|
action?: AutoDiscoveryAction;
|
||||||
reason?: string;
|
reason?: string;
|
||||||
error?: Error | string;
|
error?: Error | string;
|
||||||
@ -605,10 +605,10 @@ interface ITagMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IMessagesResponse {
|
interface IMessagesResponse {
|
||||||
start: string;
|
start?: string;
|
||||||
end: string;
|
end?: string;
|
||||||
chunk: IRoomEvent[];
|
chunk: IRoomEvent[];
|
||||||
state: IStateEvent[];
|
state?: IStateEvent[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IThreadedMessagesResponse {
|
interface IThreadedMessagesResponse {
|
||||||
@ -635,6 +635,17 @@ export interface IUploadKeysRequest {
|
|||||||
"org.matrix.msc2732.fallback_keys"?: Record<string, IOneTimeKey>;
|
"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 {
|
export interface IOpenIDToken {
|
||||||
access_token: string;
|
access_token: string;
|
||||||
token_type: "Bearer" | string;
|
token_type: "Bearer" | string;
|
||||||
@ -1744,13 +1755,15 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.http.authedRequest<{
|
type Response = {
|
||||||
capabilities?: ICapabilities;
|
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
|
// We swallow errors because we need a default object anyhow
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
|
return {};
|
||||||
}).then((r = {}) => {
|
}).then((r = {}) => {
|
||||||
const capabilities: ICapabilities = r["capabilities"] || {};
|
const capabilities = r["capabilities"] || {};
|
||||||
|
|
||||||
// If the capabilities missed the cache, cache it for a shorter amount
|
// If the capabilities missed the cache, cache it for a shorter amount
|
||||||
// of time to try and refresh them later.
|
// of time to try and refresh them later.
|
||||||
@ -3229,28 +3242,28 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
targetRoomId: undefined,
|
targetRoomId: undefined,
|
||||||
targetSessionId: undefined,
|
targetSessionId: undefined,
|
||||||
backupInfo: IKeyBackupInfo,
|
backupInfo: IKeyBackupInfo,
|
||||||
opts: IKeyBackupRestoreOpts,
|
opts?: IKeyBackupRestoreOpts,
|
||||||
): Promise<IKeyBackupRestoreResult>;
|
): Promise<IKeyBackupRestoreResult>;
|
||||||
public restoreKeyBackupWithRecoveryKey(
|
public restoreKeyBackupWithRecoveryKey(
|
||||||
recoveryKey: string,
|
recoveryKey: string,
|
||||||
targetRoomId: string,
|
targetRoomId: string,
|
||||||
targetSessionId: undefined,
|
targetSessionId: undefined,
|
||||||
backupInfo: IKeyBackupInfo,
|
backupInfo: IKeyBackupInfo,
|
||||||
opts: IKeyBackupRestoreOpts,
|
opts?: IKeyBackupRestoreOpts,
|
||||||
): Promise<IKeyBackupRestoreResult>;
|
): Promise<IKeyBackupRestoreResult>;
|
||||||
public restoreKeyBackupWithRecoveryKey(
|
public restoreKeyBackupWithRecoveryKey(
|
||||||
recoveryKey: string,
|
recoveryKey: string,
|
||||||
targetRoomId: string,
|
targetRoomId: string,
|
||||||
targetSessionId: string,
|
targetSessionId: string,
|
||||||
backupInfo: IKeyBackupInfo,
|
backupInfo: IKeyBackupInfo,
|
||||||
opts: IKeyBackupRestoreOpts,
|
opts?: IKeyBackupRestoreOpts,
|
||||||
): Promise<IKeyBackupRestoreResult>;
|
): Promise<IKeyBackupRestoreResult>;
|
||||||
public restoreKeyBackupWithRecoveryKey(
|
public restoreKeyBackupWithRecoveryKey(
|
||||||
recoveryKey: string,
|
recoveryKey: string,
|
||||||
targetRoomId: string | undefined,
|
targetRoomId: string | undefined,
|
||||||
targetSessionId: string | undefined,
|
targetSessionId: string | undefined,
|
||||||
backupInfo: IKeyBackupInfo,
|
backupInfo: IKeyBackupInfo,
|
||||||
opts: IKeyBackupRestoreOpts,
|
opts?: IKeyBackupRestoreOpts,
|
||||||
): Promise<IKeyBackupRestoreResult> {
|
): Promise<IKeyBackupRestoreResult> {
|
||||||
const privKey = decodeRecoveryKey(recoveryKey);
|
const privKey = decodeRecoveryKey(recoveryKey);
|
||||||
return this.restoreKeyBackup(privKey, targetRoomId!, targetSessionId!, backupInfo, opts);
|
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 deviceInfos = await this.crypto.downloadKeys(userIds);
|
||||||
const devicesByUser = {};
|
const devicesByUser: Record<string, DeviceInfo[]> = {};
|
||||||
for (const [userId, devices] of Object.entries(deviceInfos)) {
|
for (const [userId, devices] of Object.entries(deviceInfos)) {
|
||||||
devicesByUser[userId] = Object.values(devices);
|
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.
|
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||||
*/
|
*/
|
||||||
public setIgnoredUsers(userIds: string[]): Promise<{}> {
|
public setIgnoredUsers(userIds: string[]): Promise<{}> {
|
||||||
const content = { ignored_users: {} };
|
const content = { ignored_users: {} as Record<string, object> };
|
||||||
userIds.forEach((u) => {
|
userIds.forEach((u) => {
|
||||||
content.ignored_users[u] = {};
|
content.ignored_users[u] = {};
|
||||||
});
|
});
|
||||||
@ -3910,16 +3923,25 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
): Promise<ISendEventResponse>;
|
): Promise<ISendEventResponse>;
|
||||||
public sendEvent(
|
public sendEvent(
|
||||||
roomId: string,
|
roomId: string,
|
||||||
threadId: string | null,
|
threadIdOrEventType: string | null,
|
||||||
eventType: string | IContent,
|
eventTypeOrContent: string | IContent,
|
||||||
content?: IContent | string,
|
contentOrTxnId?: IContent | string,
|
||||||
txnId?: string,
|
txnIdOrVoid?: string,
|
||||||
): Promise<ISendEventResponse> {
|
): Promise<ISendEventResponse> {
|
||||||
if (!threadId?.startsWith(EVENT_ID_PREFIX) && threadId !== null) {
|
let threadId: string | null;
|
||||||
txnId = content as string;
|
let eventType: string;
|
||||||
content = eventType as IContent;
|
let content: IContent;
|
||||||
eventType = threadId;
|
let txnId: string | undefined;
|
||||||
|
if (!threadIdOrEventType?.startsWith(EVENT_ID_PREFIX) && threadIdOrEventType !== null) {
|
||||||
|
txnId = contentOrTxnId as string;
|
||||||
|
content = eventTypeOrContent as IContent;
|
||||||
|
eventType = threadIdOrEventType;
|
||||||
threadId = null;
|
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
|
// 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 eventType: string = EventType.RoomMessage;
|
||||||
let sendContent: IContent = content as IContent;
|
let sendContent: IContent = content as IContent;
|
||||||
const makeContentExtensible = (content: IContent = {}, recurse = true): IPartialEvent<object> | undefined => {
|
const makeContentExtensible = (content: IContent = {}, recurse = true): IPartialEvent<object> | undefined => {
|
||||||
let newEvent: IPartialEvent<object> | undefined;
|
let newEvent: IPartialEvent<IContent> | undefined;
|
||||||
|
|
||||||
if (content['msgtype'] === MsgType.Text) {
|
if (content['msgtype'] === MsgType.Text) {
|
||||||
newEvent = MessageEvent.from(content['body'], content['formatted_body']).serialize();
|
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());
|
room.addEventsToTimeline(timelineEvents, true, room.getLiveTimeline());
|
||||||
this.processThreadEvents(room, threadedEvents, true);
|
this.processThreadEvents(room, threadedEvents, true);
|
||||||
|
|
||||||
room.oldState.paginationToken = res.end;
|
room.oldState.paginationToken = res.end ?? null;
|
||||||
if (res.chunk.length === 0) {
|
if (res.chunk.length === 0) {
|
||||||
room.oldState.paginationToken = null;
|
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];
|
delete this.ongoingScrollbacks[room.roomId];
|
||||||
resolve(room);
|
resolve(room);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
@ -6261,7 +6283,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* @param {string} roomId the id of the room.
|
* @param {string} roomId the id of the room.
|
||||||
* @return {object} the rule or undefined.
|
* @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
|
// There can be only room-kind push rule per room
|
||||||
// and its id is the room id.
|
// and its id is the room id.
|
||||||
if (this.pushRules) {
|
if (this.pushRules) {
|
||||||
@ -6282,7 +6304,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* @return {Promise} Resolves: result object
|
* @return {Promise} Resolves: result object
|
||||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
* @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 promise: Promise<unknown> | undefined;
|
||||||
let hasDontNotifyRule = false;
|
let hasDontNotifyRule = false;
|
||||||
|
|
||||||
@ -6818,7 +6840,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
.filter(([key, value]) => {
|
.filter(([key, value]) => {
|
||||||
return primTypes.includes(typeof value);
|
return primTypes.includes(typeof value);
|
||||||
})
|
})
|
||||||
.reduce((obj, [key, value]) => {
|
.reduce<Record<string, any>>((obj, [key, value]) => {
|
||||||
obj[key] = value;
|
obj[key] = value;
|
||||||
return obj;
|
return obj;
|
||||||
}, {});
|
}, {});
|
||||||
@ -7841,7 +7863,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
$roomId: roomId,
|
$roomId: roomId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const content = {
|
const content: IContent = {
|
||||||
[ReceiptType.FullyRead]: rmEventId,
|
[ReceiptType.FullyRead]: rmEventId,
|
||||||
[ReceiptType.Read]: rrEventId,
|
[ReceiptType.Read]: rrEventId,
|
||||||
};
|
};
|
||||||
@ -8540,7 +8562,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* an error response ({@link module:http-api.MatrixError}).
|
* an error response ({@link module:http-api.MatrixError}).
|
||||||
*/
|
*/
|
||||||
public downloadKeysForUsers(userIds: string[], opts: { token?: string } = {}): Promise<IDownloadKeyResult> {
|
public downloadKeysForUsers(userIds: string[], opts: { token?: string } = {}): Promise<IDownloadKeyResult> {
|
||||||
const content: any = {
|
const content: IQueryKeysRequest = {
|
||||||
device_keys: {},
|
device_keys: {},
|
||||||
};
|
};
|
||||||
if ('token' in opts) {
|
if ('token' in opts) {
|
||||||
@ -8582,7 +8604,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
queries[userId] = query;
|
queries[userId] = query;
|
||||||
query[deviceId] = keyAlgorithm;
|
query[deviceId] = keyAlgorithm;
|
||||||
}
|
}
|
||||||
const content: any = { one_time_keys: queries };
|
const content: IClaimKeysRequest = { one_time_keys: queries };
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
content.timeout = timeout;
|
content.timeout = timeout;
|
||||||
}
|
}
|
||||||
@ -8874,7 +8896,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
const med = p[1].toLowerCase();
|
const med = p[1].toLowerCase();
|
||||||
const unhashed = `${addr} ${med}`;
|
const unhashed = `${addr} ${med}`;
|
||||||
// Map the unhashed values to a known (case-sensitive) address. We use
|
// 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];
|
localMapping[unhashed] = p[0];
|
||||||
return unhashed;
|
return unhashed;
|
||||||
});
|
});
|
||||||
@ -8883,12 +8905,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
throw new Error("Unsupported identity server: unknown hash algorithm");
|
throw new Error("Unsupported identity server: unknown hash algorithm");
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await this.http.idServerRequest(
|
const response = await this.http.idServerRequest<{
|
||||||
Method.Post, "/lookup",
|
mappings: { [address: string]: string };
|
||||||
params, IdentityPrefix.V2, identityAccessToken,
|
}>(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 }[] = [];
|
const foundAddresses: { address: string, mxid: string }[] = [];
|
||||||
for (const hashed of Object.keys(response['mappings'])) {
|
for (const hashed of Object.keys(response['mappings'])) {
|
||||||
@ -9028,7 +9049,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
messages: contentMap,
|
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]);
|
obj[key] = Object.keys(contentMap[key]);
|
||||||
return obj;
|
return obj;
|
||||||
}, {});
|
}, {});
|
||||||
|
@ -84,6 +84,7 @@ export class CrossSigningInfo {
|
|||||||
const res = new CrossSigningInfo(userId);
|
const res = new CrossSigningInfo(userId);
|
||||||
for (const prop in obj) {
|
for (const prop in obj) {
|
||||||
if (obj.hasOwnProperty(prop)) {
|
if (obj.hasOwnProperty(prop)) {
|
||||||
|
// @ts-ignore - ts doesn't like this and nor should we
|
||||||
res[prop] = obj[prop];
|
res[prop] = obj[prop];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ import { IAccountDataClient } from "./SecretStorage";
|
|||||||
|
|
||||||
interface ICrossSigningKeys {
|
interface ICrossSigningKeys {
|
||||||
authUpload: IBootstrapCrossSigningOpts["authUploadDeviceSigningKeys"];
|
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) {
|
if (this.crossSigningKeys) {
|
||||||
const keys: Partial<CrossSigningKeys> = {};
|
const keys: Partial<CrossSigningKeys> = {};
|
||||||
for (const [name, key] of Object.entries(this.crossSigningKeys.keys)) {
|
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
|
// 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 { IOlmDevice, IOutboundGroupSessionKey } from "./algorithms/megolm";
|
||||||
import { IMegolmSessionData } from "./index";
|
import { IMegolmSessionData } from "./index";
|
||||||
import { OlmGroupSessionExtraData } from "../@types/crypto";
|
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
|
// 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.
|
// reasonable approximation to the biggest plaintext we can encrypt.
|
||||||
const MAX_PLAINTEXT_LENGTH = 65536 * 3 / 4;
|
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 {
|
function checkPayloadLength(payloadString: string): void {
|
||||||
if (payloadString === undefined) {
|
if (payloadString === undefined) {
|
||||||
throw new Error("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,
|
// 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
|
// 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.
|
// 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). " +
|
throw new PayloadTooLargeError(`Message too long (${payloadString.length} bytes). ` +
|
||||||
"The maximum for an encrypted message is " +
|
`The maximum for an encrypted message is ${MAX_PLAINTEXT_LENGTH} bytes.`);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,6 +127,8 @@ interface IInboundGroupSessionKey {
|
|||||||
}
|
}
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
|
type OneTimeKeys = { curve25519: { [keyId: string]: string } };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the olm cryptography functions. Each OlmDevice has a single
|
* Manages the olm cryptography functions. Each OlmDevice has a single
|
||||||
* OlmAccount and a number of OlmSessions.
|
* OlmAccount and a number of OlmSessions.
|
||||||
@ -452,7 +455,7 @@ export class OlmDevice {
|
|||||||
* @return {Promise<string>} base64-encoded signature
|
* @return {Promise<string>} base64-encoded signature
|
||||||
*/
|
*/
|
||||||
public async sign(message: string): Promise<string> {
|
public async sign(message: string): Promise<string> {
|
||||||
let result;
|
let result: string;
|
||||||
await this.cryptoStore.doTxn(
|
await this.cryptoStore.doTxn(
|
||||||
'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT],
|
'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
@ -460,7 +463,7 @@ export class OlmDevice {
|
|||||||
result = account.sign(message);
|
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
|
* <tt>curve25519</tt>, which is itself an object mapping key id to Curve25519
|
||||||
* key.
|
* key.
|
||||||
*/
|
*/
|
||||||
public async getOneTimeKeys(): Promise<{ curve25519: { [keyId: string]: string } }> {
|
public async getOneTimeKeys(): Promise<OneTimeKeys> {
|
||||||
let result;
|
let result: OneTimeKeys;
|
||||||
await this.cryptoStore.doTxn(
|
await this.cryptoStore.doTxn(
|
||||||
'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT],
|
'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
@ -481,7 +484,7 @@ export class OlmDevice {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return result;
|
return result!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -821,10 +824,10 @@ export class OlmDevice {
|
|||||||
theirDeviceIdentityKey: string,
|
theirDeviceIdentityKey: string,
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
payloadString: string,
|
payloadString: string,
|
||||||
): Promise<string> {
|
): Promise<IMessage> {
|
||||||
checkPayloadLength(payloadString);
|
checkPayloadLength(payloadString);
|
||||||
|
|
||||||
let res;
|
let res: IMessage;
|
||||||
await this.cryptoStore.doTxn(
|
await this.cryptoStore.doTxn(
|
||||||
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
|
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
@ -840,7 +843,7 @@ export class OlmDevice {
|
|||||||
},
|
},
|
||||||
logger.withPrefix("[encryptMessage]"),
|
logger.withPrefix("[encryptMessage]"),
|
||||||
);
|
);
|
||||||
return res;
|
return res!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -860,7 +863,7 @@ export class OlmDevice {
|
|||||||
messageType: number,
|
messageType: number,
|
||||||
ciphertext: string,
|
ciphertext: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
let payloadString;
|
let payloadString: string;
|
||||||
await this.cryptoStore.doTxn(
|
await this.cryptoStore.doTxn(
|
||||||
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
|
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
@ -877,7 +880,7 @@ export class OlmDevice {
|
|||||||
},
|
},
|
||||||
logger.withPrefix("[decryptMessage]"),
|
logger.withPrefix("[decryptMessage]"),
|
||||||
);
|
);
|
||||||
return payloadString;
|
return payloadString!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -902,7 +905,7 @@ export class OlmDevice {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let matches;
|
let matches: boolean;
|
||||||
await this.cryptoStore.doTxn(
|
await this.cryptoStore.doTxn(
|
||||||
'readonly', [IndexedDBCryptoStore.STORE_SESSIONS],
|
'readonly', [IndexedDBCryptoStore.STORE_SESSIONS],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
@ -912,7 +915,7 @@ export class OlmDevice {
|
|||||||
},
|
},
|
||||||
logger.withPrefix("[matchesSession]"),
|
logger.withPrefix("[matchesSession]"),
|
||||||
);
|
);
|
||||||
return matches;
|
return matches!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async recordSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void> {
|
public async recordSessionProblem(deviceKey: string, type: string, fixed: boolean): Promise<void> {
|
||||||
@ -1293,7 +1296,7 @@ export class OlmDevice {
|
|||||||
result = null;
|
result = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let res;
|
let res: ReturnType<InboundGroupSession["decrypt"]>;
|
||||||
try {
|
try {
|
||||||
res = session.decrypt(body);
|
res = session.decrypt(body);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -1313,8 +1316,8 @@ export class OlmDevice {
|
|||||||
|
|
||||||
let plaintext: string = res.plaintext;
|
let plaintext: string = res.plaintext;
|
||||||
if (plaintext === undefined) {
|
if (plaintext === undefined) {
|
||||||
// Compatibility for older olm versions.
|
// @ts-ignore - Compatibility for older olm versions.
|
||||||
plaintext = res;
|
plaintext = res as string;
|
||||||
} else {
|
} else {
|
||||||
// Check if we have seen this message index before to detect replay attacks.
|
// 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
|
// 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.unverified": "The sender has disabled encrypting to unverified devices.",
|
||||||
"m.blacklisted": "The sender has blocked you.",
|
"m.blacklisted": "The sender has blocked you.",
|
||||||
"m.unauthorised": "You are not authorised to read the message.",
|
"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 { logger } from '../logger';
|
||||||
import * as olmlib from './olmlib';
|
import * as olmlib from './olmlib';
|
||||||
import { encodeBase64 } from './olmlib';
|
|
||||||
import { randomString } from '../randomstring';
|
import { randomString } from '../randomstring';
|
||||||
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from './aes';
|
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from './aes';
|
||||||
import { ICryptoCallbacks } from ".";
|
import { ICryptoCallbacks, IEncryptedContent } from ".";
|
||||||
import { IContent, MatrixEvent } from "../models/event";
|
import { IContent, MatrixEvent } from "../models/event";
|
||||||
import { ClientEvent, ClientEventHandlerMap, MatrixClient } from "../client";
|
import { ClientEvent, ClientEventHandlerMap, MatrixClient } from "../client";
|
||||||
import { IAddSecretStorageKeyOpts, ISecretStorageKeyInfo } from './api';
|
import { IAddSecretStorageKeyOpts, ISecretStorageKeyInfo } from './api';
|
||||||
@ -61,8 +60,7 @@ interface IDecryptors {
|
|||||||
|
|
||||||
interface ISecretInfo {
|
interface ISecretInfo {
|
||||||
encrypted: {
|
encrypted: {
|
||||||
// eslint-disable-next-line camelcase
|
[keyId: string]: IEncryptedPayload;
|
||||||
key_id: IEncryptedPayload;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,23 +316,11 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
|
|||||||
`the keys it is encrypted with are for a supported algorithm`);
|
`the keys it is encrypted with are for a supported algorithm`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let keyId: string;
|
// fetch private key from app
|
||||||
let decryption;
|
const [keyId, decryption] = await this.getSecretStorageKey(keys, name);
|
||||||
try {
|
const encInfo = secretInfo.encrypted[keyId];
|
||||||
// fetch private key from app
|
|
||||||
[keyId, decryption] = await this.getSecretStorageKey(keys, name);
|
|
||||||
|
|
||||||
const encInfo = secretInfo.encrypted[keyId];
|
return decryption.decrypt(encInfo);
|
||||||
|
|
||||||
// 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);
|
const secretInfo = await this.accountDataAdapter.getAccountDataFromServer<ISecretInfo>(name);
|
||||||
if (!secretInfo?.encrypted) return null;
|
if (!secretInfo?.encrypted) return null;
|
||||||
|
|
||||||
const ret = {};
|
const ret: Record<string, ISecretStorageKeyInfo> = {};
|
||||||
|
|
||||||
// filter secret encryption keys with supported algorithm
|
// filter secret encryption keys with supported algorithm
|
||||||
for (const keyId of Object.keys(secretInfo.encrypted)) {
|
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,
|
requesting_device_id: this.baseApis.deviceId,
|
||||||
request_id: requestId,
|
request_id: requestId,
|
||||||
};
|
};
|
||||||
const toDevice = {};
|
const toDevice: Record<string, typeof cancelData> = {};
|
||||||
for (const device of devices) {
|
for (const device of devices) {
|
||||||
toDevice[device] = cancelData;
|
toDevice[device] = cancelData;
|
||||||
}
|
}
|
||||||
@ -412,7 +398,7 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
|
|||||||
request_id: requestId,
|
request_id: requestId,
|
||||||
[ToDeviceMessageId]: uuidv4(),
|
[ToDeviceMessageId]: uuidv4(),
|
||||||
};
|
};
|
||||||
const toDevice = {};
|
const toDevice: Record<string, typeof requestData> = {};
|
||||||
for (const device of devices) {
|
for (const device of devices) {
|
||||||
toDevice[device] = requestData;
|
toDevice[device] = requestData;
|
||||||
}
|
}
|
||||||
@ -490,9 +476,9 @@ export class SecretStorage<B extends MatrixClient | undefined = MatrixClient> {
|
|||||||
secret: secret,
|
secret: secret,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const encryptedContent = {
|
const encryptedContent: IEncryptedContent = {
|
||||||
algorithm: olmlib.OLM_ALGORITHM,
|
algorithm: olmlib.OLM_ALGORITHM,
|
||||||
sender_key: this.baseApis.crypto!.olmDevice.deviceCurve25519Key,
|
sender_key: this.baseApis.crypto!.olmDevice.deviceCurve25519Key!,
|
||||||
ciphertext: {},
|
ciphertext: {},
|
||||||
[ToDeviceMessageId]: uuidv4(),
|
[ToDeviceMessageId]: uuidv4(),
|
||||||
};
|
};
|
||||||
|
@ -23,8 +23,14 @@ limitations under the License.
|
|||||||
import { MatrixClient } from "../../client";
|
import { MatrixClient } from "../../client";
|
||||||
import { Room } from "../../models/room";
|
import { Room } from "../../models/room";
|
||||||
import { OlmDevice } from "../OlmDevice";
|
import { OlmDevice } from "../OlmDevice";
|
||||||
import { MatrixEvent, RoomMember } from "../../matrix";
|
import { IContent, MatrixEvent, RoomMember } from "../../matrix";
|
||||||
import { Crypto, IEventDecryptionResult, IMegolmSessionData, IncomingRoomKeyRequest } from "..";
|
import {
|
||||||
|
Crypto,
|
||||||
|
IEncryptedContent,
|
||||||
|
IEventDecryptionResult,
|
||||||
|
IMegolmSessionData,
|
||||||
|
IncomingRoomKeyRequest,
|
||||||
|
} from "..";
|
||||||
import { DeviceInfo } from "../deviceinfo";
|
import { DeviceInfo } from "../deviceinfo";
|
||||||
import { IRoomEncryption } from "../RoomList";
|
import { IRoomEncryption } from "../RoomList";
|
||||||
|
|
||||||
@ -108,7 +114,7 @@ export abstract class EncryptionAlgorithm {
|
|||||||
*
|
*
|
||||||
* @return {Promise} Promise which resolves to the new event body
|
* @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.
|
* 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 { DeviceInfo } from "../deviceinfo";
|
||||||
import { IOlmSessionResult } from "../olmlib";
|
import { IOlmSessionResult } from "../olmlib";
|
||||||
import { DeviceInfoMap } from "../DeviceList";
|
import { DeviceInfoMap } from "../DeviceList";
|
||||||
import { MatrixEvent } from "../../models/event";
|
import { IContent, MatrixEvent } from "../../models/event";
|
||||||
import { EventType, MsgType, ToDeviceMessageId } from '../../@types/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 { RoomKeyRequestState } from '../OutgoingRoomKeyRequestManager';
|
||||||
import { OlmGroupSessionExtraData } from "../../@types/crypto";
|
import { OlmGroupSessionExtraData } from "../../@types/crypto";
|
||||||
import { MatrixError } from "../../http-api";
|
import { MatrixError } from "../../http-api";
|
||||||
@ -228,7 +234,7 @@ class OutboundSessionInfo {
|
|||||||
* @param {object} params parameters, as per
|
* @param {object} params parameters, as per
|
||||||
* {@link module:crypto/algorithms/EncryptionAlgorithm}
|
* {@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 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
|
// 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
|
// 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,
|
algorithm: olmlib.OLM_ALGORITHM,
|
||||||
sender_key: this.olmDevice.deviceCurve25519Key,
|
sender_key: this.olmDevice.deviceCurve25519Key!,
|
||||||
ciphertext: {},
|
ciphertext: {},
|
||||||
[ToDeviceMessageId]: uuidv4(),
|
[ToDeviceMessageId]: uuidv4(),
|
||||||
};
|
};
|
||||||
@ -1010,7 +1016,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
|||||||
*
|
*
|
||||||
* @return {Promise} Promise which resolves to the new event body
|
* @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}`);
|
logger.log(`Starting to encrypt event for ${this.roomId}`);
|
||||||
|
|
||||||
if (this.encryptionPreparation != null) {
|
if (this.encryptionPreparation != null) {
|
||||||
@ -1045,12 +1051,10 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
|||||||
content: content,
|
content: content,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ciphertext = this.olmDevice.encryptGroupMessage(
|
const ciphertext = this.olmDevice.encryptGroupMessage(session.sessionId, JSON.stringify(payloadJson));
|
||||||
session.sessionId, JSON.stringify(payloadJson),
|
const encryptedContent: IEncryptedContent = {
|
||||||
);
|
|
||||||
const encryptedContent = {
|
|
||||||
algorithm: olmlib.MEGOLM_ALGORITHM,
|
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||||
sender_key: this.olmDevice.deviceCurve25519Key,
|
sender_key: this.olmDevice.deviceCurve25519Key!,
|
||||||
ciphertext: ciphertext,
|
ciphertext: ciphertext,
|
||||||
session_id: session.sessionId,
|
session_id: session.sessionId,
|
||||||
// Include our device ID so that recipients can send us a
|
// Include our device ID so that recipients can send us a
|
||||||
@ -1064,7 +1068,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
|||||||
return encryptedContent;
|
return encryptedContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isVerificationEvent(eventType: string, content: object): boolean {
|
private isVerificationEvent(eventType: string, content: IContent): boolean {
|
||||||
switch (eventType) {
|
switch (eventType) {
|
||||||
case EventType.KeyVerificationCancel:
|
case EventType.KeyVerificationCancel:
|
||||||
case EventType.KeyVerificationDone:
|
case EventType.KeyVerificationDone:
|
||||||
@ -1227,7 +1231,7 @@ class MegolmEncryption extends EncryptionAlgorithm {
|
|||||||
* @param {object} params parameters, as per
|
* @param {object} params parameters, as per
|
||||||
* {@link module:crypto/algorithms/DecryptionAlgorithm}
|
* {@link module:crypto/algorithms/DecryptionAlgorithm}
|
||||||
*/
|
*/
|
||||||
class MegolmDecryption extends DecryptionAlgorithm {
|
export class MegolmDecryption extends DecryptionAlgorithm {
|
||||||
// events which we couldn't decrypt due to unknown sessions /
|
// events which we couldn't decrypt due to unknown sessions /
|
||||||
// indexes, or which we could only decrypt with untrusted keys:
|
// indexes, or which we could only decrypt with untrusted keys:
|
||||||
// map from senderKey|sessionId to Set of MatrixEvents
|
// map from senderKey|sessionId to Set of MatrixEvents
|
||||||
@ -1670,9 +1674,9 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
|||||||
await olmlib.ensureOlmSessionsForDevices(
|
await olmlib.ensureOlmSessionsForDevices(
|
||||||
this.olmDevice, this.baseApis, { [sender]: [device] }, false,
|
this.olmDevice, this.baseApis, { [sender]: [device] }, false,
|
||||||
);
|
);
|
||||||
const encryptedContent = {
|
const encryptedContent: IEncryptedContent = {
|
||||||
algorithm: olmlib.OLM_ALGORITHM,
|
algorithm: olmlib.OLM_ALGORITHM,
|
||||||
sender_key: this.olmDevice.deviceCurve25519Key,
|
sender_key: this.olmDevice.deviceCurve25519Key!,
|
||||||
ciphertext: {},
|
ciphertext: {},
|
||||||
[ToDeviceMessageId]: uuidv4(),
|
[ToDeviceMessageId]: uuidv4(),
|
||||||
};
|
};
|
||||||
@ -1752,9 +1756,9 @@ class MegolmDecryption extends DecryptionAlgorithm {
|
|||||||
body.room_id, body.sender_key, body.session_id,
|
body.room_id, body.sender_key, body.session_id,
|
||||||
);
|
);
|
||||||
}).then((payload) => {
|
}).then((payload) => {
|
||||||
const encryptedContent = {
|
const encryptedContent: IEncryptedContent = {
|
||||||
algorithm: olmlib.OLM_ALGORITHM,
|
algorithm: olmlib.OLM_ALGORITHM,
|
||||||
sender_key: this.olmDevice.deviceCurve25519Key,
|
sender_key: this.olmDevice.deviceCurve25519Key!,
|
||||||
ciphertext: {},
|
ciphertext: {},
|
||||||
[ToDeviceMessageId]: uuidv4(),
|
[ToDeviceMessageId]: uuidv4(),
|
||||||
};
|
};
|
||||||
|
@ -30,13 +30,13 @@ import {
|
|||||||
registerAlgorithm,
|
registerAlgorithm,
|
||||||
} from "./base";
|
} from "./base";
|
||||||
import { Room } from '../../models/room';
|
import { Room } from '../../models/room';
|
||||||
import { MatrixEvent } from "../../models/event";
|
import { IContent, MatrixEvent } from "../../models/event";
|
||||||
import { IEventDecryptionResult } from "../index";
|
import { IEncryptedContent, IEventDecryptionResult, IOlmEncryptedContent } from "../index";
|
||||||
import { IInboundSession } from "../OlmDevice";
|
import { IInboundSession } from "../OlmDevice";
|
||||||
|
|
||||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||||
|
|
||||||
interface IMessage {
|
export interface IMessage {
|
||||||
type: number;
|
type: number;
|
||||||
body: string;
|
body: string;
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ class OlmEncryption extends EncryptionAlgorithm {
|
|||||||
*
|
*
|
||||||
* @return {Promise} Promise which resolves to the new event body
|
* @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.
|
// pick the list of recipients based on the membership list.
|
||||||
//
|
//
|
||||||
// TODO: there is a race condition here! What if a new user turns up
|
// TODO: there is a race condition here! What if a new user turns up
|
||||||
@ -111,9 +111,9 @@ class OlmEncryption extends EncryptionAlgorithm {
|
|||||||
content: content,
|
content: content,
|
||||||
};
|
};
|
||||||
|
|
||||||
const encryptedContent = {
|
const encryptedContent: IEncryptedContent = {
|
||||||
algorithm: olmlib.OLM_ALGORITHM,
|
algorithm: olmlib.OLM_ALGORITHM,
|
||||||
sender_key: this.olmDevice.deviceCurve25519Key,
|
sender_key: this.olmDevice.deviceCurve25519Key!,
|
||||||
ciphertext: {},
|
ciphertext: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ interface BackupAlgorithmClass {
|
|||||||
|
|
||||||
interface BackupAlgorithm {
|
interface BackupAlgorithm {
|
||||||
untrusted: boolean;
|
untrusted: boolean;
|
||||||
encryptSession(data: Record<string, any>): Promise<any>;
|
encryptSession(data: Record<string, any>): Promise<Curve25519SessionData | IEncryptedPayload>;
|
||||||
decryptSessions(ciphertexts: Record<string, IKeyBackupSession>): Promise<IMegolmSessionData[]>;
|
decryptSessions(ciphertexts: Record<string, IKeyBackupSession>): Promise<IMegolmSessionData[]>;
|
||||||
authData: AuthData;
|
authData: AuthData;
|
||||||
keyMatches(key: ArrayLike<number>): Promise<boolean>;
|
keyMatches(key: ArrayLike<number>): Promise<boolean>;
|
||||||
@ -663,7 +663,7 @@ export class Curve25519 implements BackupAlgorithm {
|
|||||||
|
|
||||||
public get untrusted(): boolean { return true; }
|
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);
|
const plainText: Record<string, any> = Object.assign({}, data);
|
||||||
delete plainText.session_id;
|
delete plainText.session_id;
|
||||||
delete plainText.room_id;
|
delete plainText.room_id;
|
||||||
@ -788,7 +788,7 @@ export class Aes256 implements BackupAlgorithm {
|
|||||||
|
|
||||||
public get untrusted(): boolean { return false; }
|
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);
|
const plainText: Record<string, any> = Object.assign({}, data);
|
||||||
delete plainText.session_id;
|
delete plainText.session_id;
|
||||||
delete plainText.room_id;
|
delete plainText.room_id;
|
||||||
|
@ -258,7 +258,7 @@ export class DehydrationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.log("Preparing fallback keys");
|
logger.log("Preparing fallback keys");
|
||||||
const fallbackKeys = {};
|
const fallbackKeys: Record<string, IOneTimeKey> = {};
|
||||||
for (const [keyId, key] of Object.entries(fallbacks.curve25519)) {
|
for (const [keyId, key] of Object.entries(fallbacks.curve25519)) {
|
||||||
const k: IOneTimeKey = { key, fallback: true };
|
const k: IOneTimeKey = { key, fallback: true };
|
||||||
const signature = account.sign(anotherjson.stringify(k));
|
const signature = account.sign(anotherjson.stringify(k));
|
||||||
|
@ -72,7 +72,8 @@ export class DeviceInfo {
|
|||||||
const res = new DeviceInfo(deviceId);
|
const res = new DeviceInfo(deviceId);
|
||||||
for (const prop in obj) {
|
for (const prop in obj) {
|
||||||
if (obj.hasOwnProperty(prop)) {
|
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;
|
return res;
|
||||||
|
@ -88,6 +88,9 @@ import { CryptoStore } from "./store/base";
|
|||||||
import { IVerificationChannel } from "./verification/request/Channel";
|
import { IVerificationChannel } from "./verification/request/Channel";
|
||||||
import { TypedEventEmitter } from "../models/typed-event-emitter";
|
import { TypedEventEmitter } from "../models/typed-event-emitter";
|
||||||
import { IContent } from "../models/event";
|
import { IContent } from "../models/event";
|
||||||
|
import { ISyncResponse } from "../sync-accumulator";
|
||||||
|
import { ISignatures } from "../@types/signed";
|
||||||
|
import { IMessage } from "./algorithms/olm";
|
||||||
|
|
||||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||||
|
|
||||||
@ -100,7 +103,7 @@ const defaultVerificationMethods = {
|
|||||||
// to start.
|
// to start.
|
||||||
[SHOW_QR_CODE_METHOD]: IllegalMethod,
|
[SHOW_QR_CODE_METHOD]: IllegalMethod,
|
||||||
[SCAN_QR_CODE_METHOD]: IllegalMethod,
|
[SCAN_QR_CODE_METHOD]: IllegalMethod,
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* verification method names
|
* verification method names
|
||||||
@ -109,7 +112,7 @@ const defaultVerificationMethods = {
|
|||||||
export const verificationMethods = {
|
export const verificationMethods = {
|
||||||
RECIPROCATE_QR_CODE: ReciprocateQRCode.NAME,
|
RECIPROCATE_QR_CODE: ReciprocateQRCode.NAME,
|
||||||
SAS: SASVerification.NAME,
|
SAS: SASVerification.NAME,
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
export type VerificationMethod = keyof typeof verificationMethods | string;
|
export type VerificationMethod = keyof typeof verificationMethods | string;
|
||||||
|
|
||||||
@ -200,18 +203,13 @@ interface IUserOlmSession {
|
|||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISyncDeviceLists {
|
|
||||||
changed: string[];
|
|
||||||
left: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IRoomKeyRequestRecipient {
|
export interface IRoomKeyRequestRecipient {
|
||||||
userId: string;
|
userId: string;
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISignableObject {
|
interface ISignableObject {
|
||||||
signatures?: object;
|
signatures?: ISignatures;
|
||||||
unsigned?: object;
|
unsigned?: object;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,14 +229,25 @@ export interface IRequestsMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
export interface IEncryptedContent {
|
export interface IOlmEncryptedContent {
|
||||||
algorithm: string;
|
algorithm: typeof olmlib.OLM_ALGORITHM;
|
||||||
sender_key: string;
|
sender_key: string;
|
||||||
ciphertext: Record<string, string>;
|
ciphertext: Record<string, IMessage>;
|
||||||
[ToDeviceMessageId]: string;
|
[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 */
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
|
export type IEncryptedContent = IOlmEncryptedContent | IMegolmEncryptedContent;
|
||||||
|
|
||||||
export enum CryptoEvent {
|
export enum CryptoEvent {
|
||||||
DeviceVerificationChanged = "deviceVerificationChanged",
|
DeviceVerificationChanged = "deviceVerificationChanged",
|
||||||
UserTrustStatusChanged = "userTrustStatusChanged",
|
UserTrustStatusChanged = "userTrustStatusChanged",
|
||||||
@ -382,7 +391,7 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
|||||||
private readonly clientStore: IStore,
|
private readonly clientStore: IStore,
|
||||||
public readonly cryptoStore: CryptoStore,
|
public readonly cryptoStore: CryptoStore,
|
||||||
private readonly roomList: RoomList,
|
private readonly roomList: RoomList,
|
||||||
verificationMethods: Array<keyof typeof defaultVerificationMethods | typeof VerificationBase>,
|
verificationMethods: Array<VerificationMethod | (typeof VerificationBase & { NAME: string })>,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.reEmitter = new TypedReEmitter(this);
|
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
|
* @param {Object} syncDeviceLists device_lists field from /sync, or response from
|
||||||
* /keys/changes
|
* /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
|
// 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
|
// of changes for the interval or will have invalidated everything in willProcessSync
|
||||||
if (!syncData.oldSyncToken) return;
|
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
|
* @param {Object} deviceLists device_lists field from /sync, or response from
|
||||||
* /keys/changes
|
* /keys/changes
|
||||||
*/
|
*/
|
||||||
private async evalDeviceListChanges(deviceLists: ISyncDeviceLists): Promise<void> {
|
private async evalDeviceListChanges(deviceLists: Required<ISyncResponse>["device_lists"]): Promise<void> {
|
||||||
if (deviceLists.changed && Array.isArray(deviceLists.changed)) {
|
if (Array.isArray(deviceLists?.changed)) {
|
||||||
deviceLists.changed.forEach((u) => {
|
deviceLists.changed.forEach((u) => {
|
||||||
this.deviceList.invalidateUserDeviceList(u);
|
this.deviceList.invalidateUserDeviceList(u);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deviceLists.left && Array.isArray(deviceLists.left) &&
|
if (Array.isArray(deviceLists?.left) && deviceLists.left.length) {
|
||||||
deviceLists.left.length) {
|
|
||||||
// Check we really don't share any rooms with these users
|
// Check we really don't share any rooms with these users
|
||||||
// any more: the server isn't required to give us the
|
// any more: the server isn't required to give us the
|
||||||
// exact correct set.
|
// 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,
|
// 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
|
// 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).
|
// is the session it has most recently received a message on).
|
||||||
const encryptedContent = {
|
const encryptedContent: IEncryptedContent = {
|
||||||
algorithm: olmlib.OLM_ALGORITHM,
|
algorithm: olmlib.OLM_ALGORITHM,
|
||||||
sender_key: this.olmDevice.deviceCurve25519Key,
|
sender_key: this.olmDevice.deviceCurve25519Key!,
|
||||||
ciphertext: {},
|
ciphertext: {},
|
||||||
[ToDeviceMessageId]: uuidv4(),
|
[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
|
* @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 sigs = obj.signatures || {};
|
||||||
const unsigned = obj.unsigned;
|
const unsigned = obj.unsigned;
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ import { IClaimOTKsResult, MatrixClient } from "../client";
|
|||||||
import { ISignatures } from "../@types/signed";
|
import { ISignatures } from "../@types/signed";
|
||||||
import { MatrixEvent } from "../models/event";
|
import { MatrixEvent } from "../models/event";
|
||||||
import { EventType } from "../@types/event";
|
import { EventType } from "../@types/event";
|
||||||
|
import { IMessage } from "./algorithms/olm";
|
||||||
|
|
||||||
enum Algorithm {
|
enum Algorithm {
|
||||||
Olm = "m.olm.v1.curve25519-aes-sha2",
|
Olm = "m.olm.v1.curve25519-aes-sha2",
|
||||||
@ -75,7 +76,7 @@ export interface IOlmSessionResult {
|
|||||||
* has been encrypted into `resultsObject`
|
* has been encrypted into `resultsObject`
|
||||||
*/
|
*/
|
||||||
export async function encryptMessageForDevice(
|
export async function encryptMessageForDevice(
|
||||||
resultsObject: Record<string, string>,
|
resultsObject: Record<string, IMessage>,
|
||||||
ourUserId: string,
|
ourUserId: string,
|
||||||
ourDeviceId: string | undefined,
|
ourDeviceId: string | undefined,
|
||||||
olmDevice: OlmDevice,
|
olmDevice: OlmDevice,
|
||||||
@ -124,6 +125,7 @@ export async function encryptMessageForDevice(
|
|||||||
recipient_keys: {
|
recipient_keys: {
|
||||||
"ed25519": recipientDevice.getFingerprint(),
|
"ed25519": recipientDevice.getFingerprint(),
|
||||||
},
|
},
|
||||||
|
...payloadFields,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: technically, a bunch of that stuff only needs to be included for
|
// 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
|
// involved in the session. If we're looking to reduce data transfer in the
|
||||||
// future, we could elide them for subsequent messages.
|
// 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 {
|
interface IExistingOlmSession {
|
||||||
@ -495,7 +493,7 @@ export async function verifySignature(
|
|||||||
* @param {string} pubKey The public key (ignored if key is a seed)
|
* @param {string} pubKey The public key (ignored if key is a seed)
|
||||||
* @returns {string} the signature for the object
|
* @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;
|
let createdKey = false;
|
||||||
if (key instanceof Uint8Array) {
|
if (key instanceof Uint8Array) {
|
||||||
const keyObj = new global.Olm.PkSigning();
|
const keyObj = new global.Olm.PkSigning();
|
||||||
|
@ -69,7 +69,7 @@ export interface CryptoStore {
|
|||||||
deleteOutgoingRoomKeyRequest(requestId: string, expectedState: number): Promise<OutgoingRoomKeyRequest | null>;
|
deleteOutgoingRoomKeyRequest(requestId: string, expectedState: number): Promise<OutgoingRoomKeyRequest | null>;
|
||||||
|
|
||||||
// Olm Account
|
// 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;
|
storeAccount(txn: unknown, accountPickle: string): void;
|
||||||
getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey> | null) => void): void;
|
getCrossSigningKeys(txn: unknown, func: (keys: Record<string, ICrossSigningKey> | null) => void): void;
|
||||||
getSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(
|
getSecretStorePrivateKey<K extends keyof SecretStorePrivateKeys>(
|
||||||
|
@ -338,20 +338,20 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public unmarkSessionsNeedingBackup(sessions: ISession[]): Promise<void> {
|
public unmarkSessionsNeedingBackup(sessions: ISession[]): Promise<void> {
|
||||||
const sessionsNeedingBackup
|
const sessionsNeedingBackup = getJsonItem<{
|
||||||
= getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
|
[senderKeySessionId: string]: string;
|
||||||
|
}>(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
|
||||||
for (const session of sessions) {
|
for (const session of sessions) {
|
||||||
delete sessionsNeedingBackup[session.senderKey + '/' + session.sessionId];
|
delete sessionsNeedingBackup[session.senderKey + '/' + session.sessionId];
|
||||||
}
|
}
|
||||||
setJsonItem(
|
setJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP, sessionsNeedingBackup);
|
||||||
this.store, KEY_SESSIONS_NEEDING_BACKUP, sessionsNeedingBackup,
|
|
||||||
);
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
public markSessionsNeedingBackup(sessions: ISession[]): Promise<void> {
|
public markSessionsNeedingBackup(sessions: ISession[]): Promise<void> {
|
||||||
const sessionsNeedingBackup
|
const sessionsNeedingBackup = getJsonItem<{
|
||||||
= getJsonItem(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
|
[senderKeySessionId: string]: boolean;
|
||||||
|
}>(this.store, KEY_SESSIONS_NEEDING_BACKUP) || {};
|
||||||
for (const session of sessions) {
|
for (const session of sessions) {
|
||||||
sessionsNeedingBackup[session.senderKey + '/' + session.sessionId] = true;
|
sessionsNeedingBackup[session.senderKey + '/' + session.sessionId] = true;
|
||||||
}
|
}
|
||||||
|
@ -283,21 +283,21 @@ export class QRCodeData {
|
|||||||
private static generateBuffer(qrData: IQrData): Buffer {
|
private static generateBuffer(qrData: IQrData): Buffer {
|
||||||
let buf = Buffer.alloc(0); // we'll concat our way through life
|
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]);
|
const tmpBuf = Buffer.from([b]);
|
||||||
buf = Buffer.concat([buf, tmpBuf]);
|
buf = Buffer.concat([buf, tmpBuf]);
|
||||||
};
|
};
|
||||||
const appendInt = (i): void => {
|
const appendInt = (i: number): void => {
|
||||||
const tmpBuf = Buffer.alloc(2);
|
const tmpBuf = Buffer.alloc(2);
|
||||||
tmpBuf.writeInt16BE(i, 0);
|
tmpBuf.writeInt16BE(i, 0);
|
||||||
buf = Buffer.concat([buf, tmpBuf]);
|
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);
|
const tmpBuf = Buffer.from(s, enc);
|
||||||
if (withLengthPrefix) appendInt(tmpBuf.byteLength);
|
if (withLengthPrefix) appendInt(tmpBuf.byteLength);
|
||||||
buf = Buffer.concat([buf, tmpBuf]);
|
buf = Buffer.concat([buf, tmpBuf]);
|
||||||
};
|
};
|
||||||
const appendEncBase64 = (b64): void => {
|
const appendEncBase64 = (b64: string): void => {
|
||||||
const b = decodeBase64(b64);
|
const b = decodeBase64(b64);
|
||||||
const tmpBuf = Buffer.from(b);
|
const tmpBuf = Buffer.from(b);
|
||||||
buf = Buffer.concat([buf, tmpBuf]);
|
buf = Buffer.concat([buf, tmpBuf]);
|
||||||
@ -307,7 +307,7 @@ export class QRCodeData {
|
|||||||
appendStr(qrData.prefix, "ascii", false);
|
appendStr(qrData.prefix, "ascii", false);
|
||||||
appendByte(qrData.version);
|
appendByte(qrData.version);
|
||||||
appendByte(qrData.mode);
|
appendByte(qrData.mode);
|
||||||
appendStr(qrData.transactionId, "utf-8");
|
appendStr(qrData.transactionId!, "utf-8");
|
||||||
appendEncBase64(qrData.firstKeyB64);
|
appendEncBase64(qrData.firstKeyB64);
|
||||||
appendEncBase64(qrData.secondKeyB64);
|
appendEncBase64(qrData.secondKeyB64);
|
||||||
appendEncBase64(qrData.secretB64);
|
appendEncBase64(qrData.secretB64);
|
||||||
|
@ -140,7 +140,7 @@ function generateEmojiSas(sasBytes: number[]): EmojiMapping[] {
|
|||||||
const sasGenerators = {
|
const sasGenerators = {
|
||||||
decimal: generateDecimalSas,
|
decimal: generateDecimalSas,
|
||||||
emoji: generateEmojiSas,
|
emoji: generateEmojiSas,
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
export interface IGeneratedSas {
|
export interface IGeneratedSas {
|
||||||
decimal?: [number, number, number];
|
decimal?: [number, number, number];
|
||||||
@ -154,11 +154,12 @@ export interface ISasEvent {
|
|||||||
mismatch(): void;
|
mismatch(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateSas(sasBytes: number[], methods: string[]): IGeneratedSas {
|
function generateSas(sasBytes: Uint8Array, methods: string[]): IGeneratedSas {
|
||||||
const sas: IGeneratedSas = {};
|
const sas: IGeneratedSas = {};
|
||||||
for (const method of methods) {
|
for (const method of methods) {
|
||||||
if (method in sasGenerators) {
|
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;
|
return sas;
|
||||||
@ -168,15 +169,14 @@ const macMethods = {
|
|||||||
"hkdf-hmac-sha256": "calculate_mac",
|
"hkdf-hmac-sha256": "calculate_mac",
|
||||||
"org.matrix.msc3783.hkdf-hmac-sha256": "calculate_mac_fixed_base64",
|
"org.matrix.msc3783.hkdf-hmac-sha256": "calculate_mac_fixed_base64",
|
||||||
"hmac-sha256": "calculate_mac_long_kdf",
|
"hmac-sha256": "calculate_mac_long_kdf",
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
type Method = keyof typeof macMethods;
|
type MacMethod = keyof typeof macMethods;
|
||||||
|
|
||||||
function calculateMAC(olmSAS: OlmSAS, method: Method) {
|
function calculateMAC(olmSAS: OlmSAS, method: MacMethod) {
|
||||||
return function(...args): string {
|
return function(input: string, info: string): string {
|
||||||
const macFunction = olmSAS[macMethods[method]];
|
const mac = olmSAS[macMethods[method]](input, info);
|
||||||
const mac: string = macFunction.apply(olmSAS, args);
|
logger.log("SAS calculateMAC:", method, [input, info], mac);
|
||||||
logger.log("SAS calculateMAC:", method, args, mac);
|
|
||||||
return mac;
|
return mac;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -202,15 +202,17 @@ const calculateKeyAgreement = {
|
|||||||
+ sas.channel.transactionId;
|
+ sas.channel.transactionId;
|
||||||
return olmSAS.generate_bytes(sasInfo, bytes);
|
return olmSAS.generate_bytes(sasInfo, bytes);
|
||||||
},
|
},
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
|
type KeyAgreement = keyof typeof calculateKeyAgreement;
|
||||||
|
|
||||||
/* lists of algorithms/methods that are supported. The key agreement, hashes,
|
/* lists of algorithms/methods that are supported. The key agreement, hashes,
|
||||||
* and MAC lists should be sorted in order of preference (most preferred
|
* and MAC lists should be sorted in order of preference (most preferred
|
||||||
* first).
|
* first).
|
||||||
*/
|
*/
|
||||||
const KEY_AGREEMENT_LIST = ["curve25519-hkdf-sha256", "curve25519"];
|
const KEY_AGREEMENT_LIST: KeyAgreement[] = ["curve25519-hkdf-sha256", "curve25519"];
|
||||||
const HASHES_LIST = ["sha256"];
|
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 SAS_LIST = Object.keys(sasGenerators);
|
||||||
|
|
||||||
const KEY_AGREEMENT_SET = new Set(KEY_AGREEMENT_LIST);
|
const KEY_AGREEMENT_SET = new Set(KEY_AGREEMENT_LIST);
|
||||||
@ -299,10 +301,10 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async verifyAndCheckMAC(
|
private async verifyAndCheckMAC(
|
||||||
keyAgreement: string,
|
keyAgreement: KeyAgreement,
|
||||||
sasMethods: string[],
|
sasMethods: string[],
|
||||||
olmSAS: OlmSAS,
|
olmSAS: OlmSAS,
|
||||||
macMethod: Method,
|
macMethod: MacMethod,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const sasBytes = calculateKeyAgreement[keyAgreement](this, olmSAS, 6);
|
const sasBytes = calculateKeyAgreement[keyAgreement](this, olmSAS, 6);
|
||||||
const verifySAS = new Promise<void>((resolve, reject) => {
|
const verifySAS = new Promise<void>((resolve, reject) => {
|
||||||
@ -354,7 +356,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
|
|||||||
throw new SwitchStartEventError(this.startEvent);
|
throw new SwitchStartEventError(this.startEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
let e;
|
let e: MatrixEvent;
|
||||||
try {
|
try {
|
||||||
e = await this.waitForEvent(EventType.KeyVerificationAccept);
|
e = await this.waitForEvent(EventType.KeyVerificationAccept);
|
||||||
} finally {
|
} finally {
|
||||||
@ -445,8 +447,8 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendMAC(olmSAS: OlmSAS, method: Method): Promise<void> {
|
private sendMAC(olmSAS: OlmSAS, method: MacMethod): Promise<void> {
|
||||||
const mac = {};
|
const mac: Record<string, string> = {};
|
||||||
const keyList: string[] = [];
|
const keyList: string[] = [];
|
||||||
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
|
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
|
||||||
+ this.baseApis.getUserId() + this.baseApis.deviceId
|
+ this.baseApis.getUserId() + this.baseApis.deviceId
|
||||||
@ -455,7 +457,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
|
|||||||
|
|
||||||
const deviceKeyId = `ed25519:${this.baseApis.deviceId}`;
|
const deviceKeyId = `ed25519:${this.baseApis.deviceId}`;
|
||||||
mac[deviceKeyId] = calculateMAC(olmSAS, method)(
|
mac[deviceKeyId] = calculateMAC(olmSAS, method)(
|
||||||
this.baseApis.getDeviceEd25519Key(),
|
this.baseApis.getDeviceEd25519Key()!,
|
||||||
baseInfo + deviceKeyId,
|
baseInfo + deviceKeyId,
|
||||||
);
|
);
|
||||||
keyList.push(deviceKeyId);
|
keyList.push(deviceKeyId);
|
||||||
@ -477,7 +479,7 @@ export class SAS extends Base<SasEvent, EventHandlerMap> {
|
|||||||
return this.send(EventType.KeyVerificationMac, { mac, keys });
|
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"
|
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
|
||||||
+ this.userId + this.deviceId
|
+ this.userId + this.deviceId
|
||||||
+ this.baseApis.getUserId() + this.baseApis.deviceId
|
+ this.baseApis.getUserId() + this.baseApis.deviceId
|
||||||
|
@ -148,17 +148,17 @@ export class FilterComponent {
|
|||||||
"types": function(v: string): boolean {
|
"types": function(v: string): boolean {
|
||||||
return matchesWildcard(eventType, v);
|
return matchesWildcard(eventType, v);
|
||||||
},
|
},
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
for (const name in literalKeys) {
|
for (const name in literalKeys) {
|
||||||
const matchFunc = literalKeys[name];
|
const matchFunc = literalKeys[<keyof typeof literalKeys>name];
|
||||||
const notName = "not_" + name;
|
const notName = "not_" + name;
|
||||||
const disallowedValues: string[] = this.filterJson[notName];
|
const disallowedValues = this.filterJson[<`not_${keyof typeof literalKeys}`>notName];
|
||||||
if (disallowedValues?.some(matchFunc)) {
|
if (disallowedValues?.some(matchFunc)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allowedValues: string[] = this.filterJson[name];
|
const allowedValues = this.filterJson[name as keyof typeof literalKeys];
|
||||||
if (allowedValues && !allowedValues.some(matchFunc)) {
|
if (allowedValues && !allowedValues.some(matchFunc)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,8 @@ import { MatrixEvent } from "./models/event";
|
|||||||
* @param {string} keyNesting
|
* @param {string} keyNesting
|
||||||
* @param {*} val
|
* @param {*} val
|
||||||
*/
|
*/
|
||||||
function setProp(obj: object, keyNesting: string, val: any): void {
|
function setProp(obj: Record<string, any>, keyNesting: string, val: any): void {
|
||||||
const nestedKeys = keyNesting.split(".");
|
const nestedKeys = keyNesting.split(".") as [keyof typeof obj];
|
||||||
let currentObj = obj;
|
let currentObj = obj;
|
||||||
for (let i = 0; i < (nestedKeys.length - 1); i++) {
|
for (let i = 0; i < (nestedKeys.length - 1); i++) {
|
||||||
if (!currentObj[nestedKeys[i]]) {
|
if (!currentObj[nestedKeys[i]]) {
|
||||||
@ -70,8 +70,7 @@ interface IStateFilter extends IRoomEventFilter {}
|
|||||||
|
|
||||||
interface IRoomFilter {
|
interface IRoomFilter {
|
||||||
not_rooms?: string[];
|
not_rooms?: string[];
|
||||||
rooms?: string[];
|
rooms?: string[];ephemeral?: IRoomEventFilter;
|
||||||
ephemeral?: IRoomEventFilter;
|
|
||||||
include_leave?: boolean;
|
include_leave?: boolean;
|
||||||
state?: IStateFilter;
|
state?: IStateFilter;
|
||||||
timeline?: IRoomEventFilter;
|
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
|
// '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
|
// field that Element never otherwise looks at, but it will still take up a lot
|
||||||
// of space if we don't intern it.
|
// 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;
|
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;
|
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;
|
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;
|
this.txnId = event.txn_id;
|
||||||
@ -1120,7 +1120,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
|
|||||||
|
|
||||||
for (const key in this.event) {
|
for (const key in this.event) {
|
||||||
if (this.event.hasOwnProperty(key) && !REDACT_KEEP_KEYS.has(key)) {
|
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;
|
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();
|
const content = this.getContent();
|
||||||
for (const key in content) {
|
for (const key in content) {
|
||||||
if (content.hasOwnProperty(key) && !keeps[key]) {
|
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
|
* @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.isRedacted()) return null;
|
||||||
|
|
||||||
if (this.clearEvent?.unsigned) {
|
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)));
|
const ev = new MatrixEvent(JSON.parse(JSON.stringify(this.event)));
|
||||||
for (const [p, v] of Object.entries(this)) {
|
for (const [p, v] of Object.entries(this)) {
|
||||||
if (p !== "event") { // exclude the thing we just cloned
|
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;
|
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
|
// 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.RoomMember]: { 'membership': 1 },
|
||||||
[EventType.RoomCreate]: { 'creator': 1 },
|
[EventType.RoomCreate]: { 'creator': 1 },
|
||||||
[EventType.RoomJoinRules]: { 'join_rule': 1 },
|
[EventType.RoomJoinRules]: { 'join_rule': 1 },
|
||||||
@ -1621,7 +1624,7 @@ const REDACT_KEEP_CONTENT_MAP = {
|
|||||||
'kick': 1, 'redact': 1, 'state_default': 1,
|
'kick': 1, 'redact': 1, 'state_default': 1,
|
||||||
'users': 1, 'users_default': 1,
|
'users': 1, 'users_default': 1,
|
||||||
},
|
},
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fires when an event is decrypted
|
* Fires when an event is decrypted
|
||||||
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
import { UnstableValue } from "matrix-events-sdk";
|
import { UnstableValue } from "matrix-events-sdk";
|
||||||
|
|
||||||
import { MatrixClient } from "../client";
|
import { MatrixClient } from "../client";
|
||||||
import { MatrixEvent } from "./event";
|
import { IContent, MatrixEvent } from "./event";
|
||||||
import { EventTimeline } from "./event-timeline";
|
import { EventTimeline } from "./event-timeline";
|
||||||
import { Preset } from "../@types/partials";
|
import { Preset } from "../@types/partials";
|
||||||
import { globToRegexp } from "../utils";
|
import { globToRegexp } from "../utils";
|
||||||
@ -262,7 +262,7 @@ export class IgnoredInvites {
|
|||||||
*/
|
*/
|
||||||
public async getOrCreateSourceRooms(): Promise<Room[]> {
|
public async getOrCreateSourceRooms(): Promise<Room[]> {
|
||||||
const ignoreInvitesPolicies = this.getIgnoreInvitesPolicies();
|
const ignoreInvitesPolicies = this.getIgnoreInvitesPolicies();
|
||||||
let sources = ignoreInvitesPolicies.sources;
|
let sources: string[] = ignoreInvitesPolicies.sources;
|
||||||
|
|
||||||
// Validate `sources`. If it is invalid, trash out the current `sources`
|
// Validate `sources`. If it is invalid, trash out the current `sources`
|
||||||
// and create a new list of sources from `target`.
|
// and create a new list of sources from `target`.
|
||||||
@ -272,11 +272,11 @@ export class IgnoredInvites {
|
|||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
sources = [];
|
sources = [];
|
||||||
}
|
}
|
||||||
let sourceRooms: Room[] = sources
|
let sourceRooms = sources
|
||||||
// `sources` could contain non-string / invalid room ids
|
// `sources` could contain non-string / invalid room ids
|
||||||
.filter(roomId => typeof roomId === "string")
|
.filter(roomId => typeof roomId === "string")
|
||||||
.map(roomId => this.client.getRoom(roomId))
|
.map(roomId => this.client.getRoom(roomId))
|
||||||
.filter(room => !!room);
|
.filter(room => !!room) as Room[];
|
||||||
if (sourceRooms.length != sources.length) {
|
if (sourceRooms.length != sources.length) {
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
}
|
}
|
||||||
@ -327,7 +327,7 @@ export class IgnoredInvites {
|
|||||||
*/
|
*/
|
||||||
private getPoliciesAndIgnoreInvitesPolicies():
|
private getPoliciesAndIgnoreInvitesPolicies():
|
||||||
{policies: {[key: string]: any}, ignoreInvitesPolicies: {[key: string]: any}} {
|
{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]) {
|
for (const key of [POLICIES_ACCOUNT_EVENT_TYPE.name, POLICIES_ACCOUNT_EVENT_TYPE.altName]) {
|
||||||
if (!key) {
|
if (!key) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -22,7 +22,7 @@ import { RoomMember } from "./room-member";
|
|||||||
import { logger } from '../logger';
|
import { logger } from '../logger';
|
||||||
import * as utils from "../utils";
|
import * as utils from "../utils";
|
||||||
import { EventType, UNSTABLE_MSC2716_MARKER } from "../@types/event";
|
import { EventType, UNSTABLE_MSC2716_MARKER } from "../@types/event";
|
||||||
import { MatrixEvent, MatrixEventEvent } from "./event";
|
import { IEvent, MatrixEvent, MatrixEventEvent } from "./event";
|
||||||
import { MatrixClient } from "../client";
|
import { MatrixClient } from "../client";
|
||||||
import { GuestAccess, HistoryVisibility, IJoinRuleEventContent, JoinRule } from "../@types/partials";
|
import { GuestAccess, HistoryVisibility, IJoinRuleEventContent, JoinRule } from "../@types/partials";
|
||||||
import { TypedEventEmitter } from "./typed-event-emitter";
|
import { TypedEventEmitter } from "./typed-event-emitter";
|
||||||
@ -53,6 +53,20 @@ enum OobStatus {
|
|||||||
Finished,
|
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 {
|
export enum RoomStateEvent {
|
||||||
Events = "RoomState.events",
|
Events = "RoomState.events",
|
||||||
Members = "RoomState.members",
|
Members = "RoomState.members",
|
||||||
@ -499,7 +513,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
const beacon = this.beacons.get(beaconIdentifier)!;
|
const beacon = this.beacons.get(beaconIdentifier)!;
|
||||||
|
|
||||||
if (event.isRedacted()) {
|
if (event.isRedacted()) {
|
||||||
if (beacon.beaconInfoId === event.getRedactionEvent()?.['redacts']) {
|
if (beacon.beaconInfoId === (<IEvent>event.getRedactionEvent())?.redacts) {
|
||||||
beacon.destroy();
|
beacon.destroy();
|
||||||
this.beacons.delete(beaconIdentifier);
|
this.beacons.delete(beaconIdentifier);
|
||||||
}
|
}
|
||||||
@ -724,17 +738,17 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
* @param {number} powerLevel The power level of the member
|
* @param {number} powerLevel The power level of the member
|
||||||
* @return {boolean} true if the given power level is sufficient
|
* @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, "");
|
const powerLevelsEvent = this.getStateEvents(EventType.RoomPowerLevels, "");
|
||||||
|
|
||||||
let powerLevels = {};
|
let powerLevels: IPowerLevelsContent = {};
|
||||||
if (powerLevelsEvent) {
|
if (powerLevelsEvent) {
|
||||||
powerLevels = powerLevelsEvent.getContent();
|
powerLevels = powerLevelsEvent.getContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
let requiredLevel = 50;
|
let requiredLevel = 50;
|
||||||
if (utils.isNumber(powerLevels[action])) {
|
if (utils.isNumber(powerLevels[action])) {
|
||||||
requiredLevel = powerLevels[action];
|
requiredLevel = powerLevels[action]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
return powerLevel >= requiredLevel;
|
return powerLevel >= requiredLevel;
|
||||||
@ -807,8 +821,8 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
private maySendEventOfType(eventType: EventType | string, userId: string, state: boolean): boolean {
|
private maySendEventOfType(eventType: EventType | string, userId: string, state: boolean): boolean {
|
||||||
const powerLevelsEvent = this.getStateEvents(EventType.RoomPowerLevels, '');
|
const powerLevelsEvent = this.getStateEvents(EventType.RoomPowerLevels, '');
|
||||||
|
|
||||||
let powerLevels;
|
let powerLevels: IPowerLevelsContent;
|
||||||
let eventsLevels = {};
|
let eventsLevels: Record<EventType | string, number> = {};
|
||||||
|
|
||||||
let stateDefault = 0;
|
let stateDefault = 0;
|
||||||
let eventsDefault = 0;
|
let eventsDefault = 0;
|
||||||
@ -818,20 +832,20 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
eventsLevels = powerLevels.events || {};
|
eventsLevels = powerLevels.events || {};
|
||||||
|
|
||||||
if (Number.isSafeInteger(powerLevels.state_default)) {
|
if (Number.isSafeInteger(powerLevels.state_default)) {
|
||||||
stateDefault = powerLevels.state_default;
|
stateDefault = powerLevels.state_default!;
|
||||||
} else {
|
} else {
|
||||||
stateDefault = 50;
|
stateDefault = 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userPowerLevel = powerLevels.users && powerLevels.users[userId];
|
const userPowerLevel = powerLevels.users && powerLevels.users[userId];
|
||||||
if (Number.isSafeInteger(userPowerLevel)) {
|
if (Number.isSafeInteger(userPowerLevel)) {
|
||||||
powerLevel = userPowerLevel;
|
powerLevel = userPowerLevel!;
|
||||||
} else if (Number.isSafeInteger(powerLevels.users_default)) {
|
} else if (Number.isSafeInteger(powerLevels.users_default)) {
|
||||||
powerLevel = powerLevels.users_default;
|
powerLevel = powerLevels.users_default!;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Number.isSafeInteger(powerLevels.events_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 {
|
public getJoinRule(): JoinRule {
|
||||||
const joinRuleEvent = this.getStateEvents(EventType.RoomJoinRules, "");
|
const joinRuleEvent = this.getStateEvents(EventType.RoomJoinRules, "");
|
||||||
const joinRuleContent = joinRuleEvent?.getContent<IJoinRuleEventContent>() ?? {};
|
const joinRuleContent: Partial<IJoinRuleEventContent> = joinRuleEvent?.getContent() ?? {};
|
||||||
return joinRuleContent["join_rule"] || JoinRule.Invite;
|
return joinRuleContent["join_rule"] || JoinRule.Invite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@ limitations under the License.
|
|||||||
|
|
||||||
export interface IRoomSummary {
|
export interface IRoomSummary {
|
||||||
"m.heroes": string[];
|
"m.heroes": string[];
|
||||||
"m.joined_member_count": number;
|
"m.joined_member_count"?: number;
|
||||||
"m.invited_member_count": number;
|
"m.invited_member_count"?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IInfo {
|
interface IInfo {
|
||||||
|
@ -1312,10 +1312,10 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
|||||||
const joinedCount = summary["m.joined_member_count"];
|
const joinedCount = summary["m.joined_member_count"];
|
||||||
const invitedCount = summary["m.invited_member_count"];
|
const invitedCount = summary["m.invited_member_count"];
|
||||||
if (Number.isInteger(joinedCount)) {
|
if (Number.isInteger(joinedCount)) {
|
||||||
this.currentState.setJoinedMemberCount(joinedCount);
|
this.currentState.setJoinedMemberCount(joinedCount!);
|
||||||
}
|
}
|
||||||
if (Number.isInteger(invitedCount)) {
|
if (Number.isInteger(invitedCount)) {
|
||||||
this.currentState.setInvitedMemberCount(invitedCount);
|
this.currentState.setInvitedMemberCount(invitedCount!);
|
||||||
}
|
}
|
||||||
if (Array.isArray(heroes)) {
|
if (Array.isArray(heroes)) {
|
||||||
// be cautious about trusting server values,
|
// be cautious about trusting server values,
|
||||||
@ -1826,7 +1826,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
|
|||||||
timelineSet.getFilter(),
|
timelineSet.getFilter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
timelineSet.getLiveTimeline().setPaginationToken(end, Direction.Backward);
|
timelineSet.getLiveTimeline().setPaginationToken(end ?? null, Direction.Backward);
|
||||||
|
|
||||||
if (!events.length) return;
|
if (!events.length) return;
|
||||||
|
|
||||||
|
@ -518,13 +518,13 @@ export class PushProcessor {
|
|||||||
* @return {object} The push rule, or null if no such rule was found
|
* @return {object} The push rule, or null if no such rule was found
|
||||||
*/
|
*/
|
||||||
public getPushRuleById(ruleId: string): IPushRule | null {
|
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;
|
if (this.client.pushRules?.[scope] === undefined) continue;
|
||||||
|
|
||||||
for (const kind of RULEKINDS_IN_ORDER) {
|
for (const kind of RULEKINDS_IN_ORDER) {
|
||||||
if (this.client.pushRules[scope][kind] === undefined) continue;
|
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;
|
if (rule.rule_id === ruleId) return rule;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
|
|||||||
* @see module:scheduler~queueAlgorithm
|
* @see module:scheduler~queueAlgorithm
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// 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)
|
// enqueue messages or events that associate with another event (redactions and relations)
|
||||||
if (event.getType() === EventType.RoomMessage || event.hasAssociation()) {
|
if (event.getType() === EventType.RoomMessage || event.hasAssociation()) {
|
||||||
// put these events in the 'message' queue.
|
// put these events in the 'message' queue.
|
||||||
|
@ -22,7 +22,7 @@ import { ClientEvent, IStoredClientOpts, MatrixClient, PendingEventOrdering } fr
|
|||||||
import { ISyncStateData, SyncState, _createAndReEmitRoom } from "./sync";
|
import { ISyncStateData, SyncState, _createAndReEmitRoom } from "./sync";
|
||||||
import { MatrixEvent } from "./models/event";
|
import { MatrixEvent } from "./models/event";
|
||||||
import { Crypto } from "./crypto";
|
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 { MatrixError } from "./http-api";
|
||||||
import {
|
import {
|
||||||
Extension,
|
Extension,
|
||||||
@ -44,7 +44,18 @@ import { RoomMemberEvent } from "./models/room-member";
|
|||||||
// keepAlive is successful but the server /sync fails.
|
// keepAlive is successful but the server /sync fails.
|
||||||
const FAILED_SYNC_ERROR_THRESHOLD = 3;
|
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 constructor(private readonly crypto: Crypto) {}
|
||||||
|
|
||||||
public name(): string {
|
public name(): string {
|
||||||
@ -55,7 +66,7 @@ class ExtensionE2EE implements Extension {
|
|||||||
return ExtensionState.PreProcess;
|
return ExtensionState.PreProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRequest(isInitial: boolean): object | undefined {
|
public onRequest(isInitial: boolean): ExtensionE2EERequest | undefined {
|
||||||
if (!isInitial) {
|
if (!isInitial) {
|
||||||
return undefined;
|
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
|
// Handle device list updates
|
||||||
if (data["device_lists"]) {
|
if (data["device_lists"]) {
|
||||||
await this.crypto.handleDeviceListChanges({
|
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;
|
private nextBatch: string | null = null;
|
||||||
|
|
||||||
public constructor(private readonly client: MatrixClient) {}
|
public constructor(private readonly client: MatrixClient) {}
|
||||||
@ -105,8 +127,8 @@ class ExtensionToDevice implements Extension {
|
|||||||
return ExtensionState.PreProcess;
|
return ExtensionState.PreProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRequest(isInitial: boolean): object {
|
public onRequest(isInitial: boolean): ExtensionToDeviceRequest {
|
||||||
const extReq = {
|
const extReq: ExtensionToDeviceRequest = {
|
||||||
since: this.nextBatch !== null ? this.nextBatch : undefined,
|
since: this.nextBatch !== null ? this.nextBatch : undefined,
|
||||||
};
|
};
|
||||||
if (isInitial) {
|
if (isInitial) {
|
||||||
@ -116,11 +138,10 @@ class ExtensionToDevice implements Extension {
|
|||||||
return extReq;
|
return extReq;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onResponse(data: object): Promise<void> {
|
public async onResponse(data: ExtensionToDeviceResponse): Promise<void> {
|
||||||
const cancelledKeyVerificationTxns: string[] = [];
|
const cancelledKeyVerificationTxns: string[] = [];
|
||||||
data["events"] = data["events"] || [];
|
data.events
|
||||||
data["events"]
|
?.map(this.client.getEventMapper())
|
||||||
.map(this.client.getEventMapper())
|
|
||||||
.map((toDeviceEvent) => { // map is a cheap inline forEach
|
.map((toDeviceEvent) => { // map is a cheap inline forEach
|
||||||
// We want to flag m.key.verification.start events as cancelled
|
// We want to flag m.key.verification.start events as cancelled
|
||||||
// if there's an accompanying m.key.verification.cancel event, so
|
// 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 constructor(private readonly client: MatrixClient) {}
|
||||||
|
|
||||||
public name(): string {
|
public name(): string {
|
||||||
@ -180,7 +210,7 @@ class ExtensionAccountData implements Extension {
|
|||||||
return ExtensionState.PostProcess;
|
return ExtensionState.PostProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRequest(isInitial: boolean): object | undefined {
|
public onRequest(isInitial: boolean): ExtensionAccountDataRequest | undefined {
|
||||||
if (!isInitial) {
|
if (!isInitial) {
|
||||||
return undefined;
|
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) {
|
if (data.global && data.global.length > 0) {
|
||||||
this.processGlobalAccountData(data.global);
|
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 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());
|
m[c.getType()] = this.client.store.getAccountData(c.getType());
|
||||||
return m;
|
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 constructor(private readonly client: MatrixClient) {}
|
||||||
|
|
||||||
public name(): string {
|
public name(): string {
|
||||||
@ -244,7 +282,7 @@ class ExtensionTyping implements Extension {
|
|||||||
return ExtensionState.PostProcess;
|
return ExtensionState.PostProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRequest(isInitial: boolean): object | undefined {
|
public onRequest(isInitial: boolean): ExtensionTypingRequest | undefined {
|
||||||
if (!isInitial) {
|
if (!isInitial) {
|
||||||
return undefined; // don't send a JSON object for subsequent requests, we don't need to.
|
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 {
|
public onResponse(data: ExtensionTypingResponse): void {
|
||||||
if (!data || !data.rooms) {
|
if (!data?.rooms) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const roomId in data.rooms) {
|
for (const roomId in data.rooms) {
|
||||||
processEphemeralEvents(
|
processEphemeralEvents(this.client, roomId, [data.rooms[roomId]]);
|
||||||
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 constructor(private readonly client: MatrixClient) {}
|
||||||
|
|
||||||
public name(): string {
|
public name(): string {
|
||||||
@ -277,7 +321,7 @@ class ExtensionReceipts implements Extension {
|
|||||||
return ExtensionState.PostProcess;
|
return ExtensionState.PostProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onRequest(isInitial: boolean): object | undefined {
|
public onRequest(isInitial: boolean): ExtensionReceiptsRequest | undefined {
|
||||||
if (isInitial) {
|
if (isInitial) {
|
||||||
return {
|
return {
|
||||||
enabled: true,
|
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.
|
return undefined; // don't send a JSON object for subsequent requests, we don't need to.
|
||||||
}
|
}
|
||||||
|
|
||||||
public onResponse(data: {rooms: Record<string, IMinimalEvent>}): void {
|
public onResponse(data: ExtensionReceiptsResponse): void {
|
||||||
if (!data || !data.rooms) {
|
if (!data?.rooms) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,7 +380,7 @@ export class SlidingSyncSdk {
|
|||||||
|
|
||||||
this.slidingSync.on(SlidingSyncEvent.Lifecycle, this.onLifecycle.bind(this));
|
this.slidingSync.on(SlidingSyncEvent.Lifecycle, this.onLifecycle.bind(this));
|
||||||
this.slidingSync.on(SlidingSyncEvent.RoomData, this.onRoomData.bind(this));
|
this.slidingSync.on(SlidingSyncEvent.RoomData, this.onRoomData.bind(this));
|
||||||
const extensions: Extension[] = [
|
const extensions: Extension<any, any>[] = [
|
||||||
new ExtensionToDevice(this.client),
|
new ExtensionToDevice(this.client),
|
||||||
new ExtensionAccountData(this.client),
|
new ExtensionAccountData(this.client),
|
||||||
new ExtensionTyping(this.client),
|
new ExtensionTyping(this.client),
|
||||||
@ -533,7 +577,7 @@ export class SlidingSyncSdk {
|
|||||||
// room::decryptCriticalEvent is in charge of decrypting all the events
|
// room::decryptCriticalEvent is in charge of decrypting all the events
|
||||||
// required for a client to function properly
|
// required for a client to function properly
|
||||||
let timelineEvents = mapEvents(this.client, room.roomId, roomData.timeline, false);
|
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
|
// TODO: handle threaded / beacon events
|
||||||
|
|
||||||
@ -967,12 +1011,14 @@ function ensureNameEvent(client: MatrixClient, roomId: string, roomData: MSC3575
|
|||||||
return roomData;
|
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,
|
// Helper functions which set up JS SDK structs are below and are identical to the sync v2 counterparts,
|
||||||
// just outside the class.
|
// just outside the class.
|
||||||
function mapEvents(client: MatrixClient, roomId: string | undefined, events: object[], decrypt = true): MatrixEvent[] {
|
function mapEvents(client: MatrixClient, roomId: string | undefined, events: object[], decrypt = true): MatrixEvent[] {
|
||||||
const mapper = client.getEventMapper({ decrypt });
|
const mapper = client.getEventMapper({ decrypt });
|
||||||
return (events as Array<IStrippedState | IRoomEvent | IStateEvent | IMinimalEvent>).map(function(e) {
|
return (events as TaggedEvent[]).map(function(e) {
|
||||||
e["room_id"] = roomId;
|
e.room_id = roomId;
|
||||||
return mapper(e);
|
return mapper(e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ export interface MSC3575SlidingSyncResponse {
|
|||||||
txn_id?: string;
|
txn_id?: string;
|
||||||
lists: ListResponse[];
|
lists: ListResponse[];
|
||||||
rooms: Record<string, MSC3575RoomData>;
|
rooms: Record<string, MSC3575RoomData>;
|
||||||
extensions: object;
|
extensions: Record<string, object>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SlidingSyncState {
|
export enum SlidingSyncState {
|
||||||
@ -265,7 +265,7 @@ export enum ExtensionState {
|
|||||||
/**
|
/**
|
||||||
* An interface that must be satisfied to register extensions
|
* 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.
|
* The extension name to go under 'extensions' in the request body.
|
||||||
* @returns The JSON key.
|
* @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)
|
* @param isInitial True when this is part of the initial request (send sticky params)
|
||||||
* @returns The request JSON to send.
|
* @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.
|
* A function which is called when there is response JSON under this extension.
|
||||||
* @param data The response JSON under the extension name.
|
* @param data The response JSON under the extension name.
|
||||||
*/
|
*/
|
||||||
onResponse(data: object);
|
onResponse(data: Res): void;
|
||||||
/**
|
/**
|
||||||
* Controls when onResponse should be called.
|
* Controls when onResponse should be called.
|
||||||
* @returns The state when it 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.
|
// a defer to resolve/reject depending on whether they were successfully sent or not.
|
||||||
private txnIdDefers: (IDeferred<string> & { txnId: string})[] = [];
|
private txnIdDefers: (IDeferred<string> & { txnId: string})[] = [];
|
||||||
// map of extension name to req/resp handler
|
// 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 desiredRoomSubscriptions = new Set<string>(); // the *desired* room subscriptions
|
||||||
private confirmedRoomSubscriptions = new Set<string>();
|
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.
|
* Register an extension to send with the /sync request.
|
||||||
* @param ext The extension to register.
|
* @param ext The extension to register.
|
||||||
*/
|
*/
|
||||||
public registerExtension(ext: Extension): void {
|
public registerExtension(ext: Extension<any, any>): void {
|
||||||
if (this.extensions[ext.name()]) {
|
if (this.extensions[ext.name()]) {
|
||||||
throw new Error(`registerExtension: ${ext.name()} already exists as an extension`);
|
throw new Error(`registerExtension: ${ext.name()} already exists as an extension`);
|
||||||
}
|
}
|
||||||
this.extensions[ext.name()] = ext;
|
this.extensions[ext.name()] = ext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getExtensionRequest(isInitial: boolean): object {
|
private getExtensionRequest(isInitial: boolean): Record<string, object | undefined> {
|
||||||
const ext = {};
|
const ext: Record<string, object | undefined> = {};
|
||||||
Object.keys(this.extensions).forEach((extName) => {
|
Object.keys(this.extensions).forEach((extName) => {
|
||||||
ext[extName] = this.extensions[extName].onRequest(isInitial);
|
ext[extName] = this.extensions[extName].onRequest(isInitial);
|
||||||
});
|
});
|
||||||
return ext;
|
return ext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onPreExtensionsResponse(ext: object): void {
|
private onPreExtensionsResponse(ext: Record<string, object>): void {
|
||||||
Object.keys(ext).forEach((extName) => {
|
Object.keys(ext).forEach((extName) => {
|
||||||
if (this.extensions[extName].when() == ExtensionState.PreProcess) {
|
if (this.extensions[extName].when() == ExtensionState.PreProcess) {
|
||||||
this.extensions[extName].onResponse(ext[extName]);
|
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) => {
|
Object.keys(ext).forEach((extName) => {
|
||||||
if (this.extensions[extName].when() == ExtensionState.PostProcess) {
|
if (this.extensions[extName].when() == ExtensionState.PostProcess) {
|
||||||
this.extensions[extName].onResponse(ext[extName]);
|
this.extensions[extName].onResponse(ext[extName]);
|
||||||
|
@ -123,7 +123,7 @@ export interface IStore {
|
|||||||
* @param {string} token The token associated with these events.
|
* @param {string} token The token associated with these events.
|
||||||
* @param {boolean} toStart True if these are paginated results.
|
* @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.
|
* Store a filter.
|
||||||
|
@ -315,10 +315,10 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
|
|
||||||
const minStateKeyProm = reqAsCursorPromise(
|
const minStateKeyProm = reqAsCursorPromise(
|
||||||
roomIndex.openKeyCursor(roomRange, "next"),
|
roomIndex.openKeyCursor(roomRange, "next"),
|
||||||
).then((cursor) => cursor && cursor.primaryKey[1]);
|
).then((cursor) => (<IDBValidKey[]>cursor?.primaryKey)[1]);
|
||||||
const maxStateKeyProm = reqAsCursorPromise(
|
const maxStateKeyProm = reqAsCursorPromise(
|
||||||
roomIndex.openKeyCursor(roomRange, "prev"),
|
roomIndex.openKeyCursor(roomRange, "prev"),
|
||||||
).then((cursor) => cursor && cursor.primaryKey[1]);
|
).then((cursor) => (<IDBValidKey[]>cursor?.primaryKey)[1]);
|
||||||
const [minStateKey, maxStateKey] = await Promise.all(
|
const [minStateKey, maxStateKey] = await Promise.all(
|
||||||
[minStateKeyProm, maxStateKeyProm]);
|
[minStateKeyProm, maxStateKeyProm]);
|
||||||
|
|
||||||
|
@ -217,7 +217,7 @@ export class MemoryStore implements IStore {
|
|||||||
* @param {string} token The token associated with these events.
|
* @param {string} token The token associated with these events.
|
||||||
* @param {boolean} toStart True if these are paginated results.
|
* @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.
|
// 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 {string} token The token associated with these events.
|
||||||
* @param {boolean} toStart True if these are paginated results.
|
* @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.
|
* Store a filter.
|
||||||
|
@ -116,7 +116,7 @@ interface IAccountData {
|
|||||||
events: IMinimalEvent[];
|
events: IMinimalEvent[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IToDeviceEvent {
|
export interface IToDeviceEvent {
|
||||||
content: IContent;
|
content: IContent;
|
||||||
sender: string;
|
sender: string;
|
||||||
type: string;
|
type: string;
|
||||||
@ -127,8 +127,8 @@ interface IToDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IDeviceLists {
|
interface IDeviceLists {
|
||||||
changed: string[];
|
changed?: string[];
|
||||||
left: string[];
|
left?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISyncResponse {
|
export interface ISyncResponse {
|
||||||
@ -139,6 +139,9 @@ export interface ISyncResponse {
|
|||||||
to_device?: IToDevice;
|
to_device?: IToDevice;
|
||||||
device_lists?: IDeviceLists;
|
device_lists?: IDeviceLists;
|
||||||
device_one_time_keys_count?: Record<string, number>;
|
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 */
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
@ -182,6 +185,12 @@ export interface ISyncData {
|
|||||||
roomsData: IRooms;
|
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
|
* The purpose of this class is to accumulate /sync responses such that a
|
||||||
* complete "initial" JSON response can be returned which accurately represents
|
* complete "initial" JSON response can be returned which accurately represents
|
||||||
@ -473,35 +482,31 @@ export class SyncAccumulator {
|
|||||||
// - existing state which didn't come down /sync.
|
// - existing state which didn't come down /sync.
|
||||||
// - State events under the 'state' key.
|
// - State events under the 'state' key.
|
||||||
// - State events in the 'timeline'.
|
// - 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);
|
||||||
setState(currentData._currentState, e);
|
});
|
||||||
});
|
data.timeline?.events?.forEach((e, index) => {
|
||||||
}
|
// this nops if 'e' isn't a state event
|
||||||
if (data.timeline && data.timeline.events) {
|
setState(currentData._currentState, e);
|
||||||
data.timeline.events.forEach((e, index) => {
|
// append the event to the timeline. The back-pagination token
|
||||||
// this nops if 'e' isn't a state event
|
// corresponds to the first event in the timeline
|
||||||
setState(currentData._currentState, e);
|
let transformedEvent: TaggedEvent;
|
||||||
// append the event to the timeline. The back-pagination token
|
if (!fromDatabase) {
|
||||||
// corresponds to the first event in the timeline
|
transformedEvent = Object.assign({}, e);
|
||||||
let transformedEvent: IRoomEvent & { _localTs?: number };
|
if (transformedEvent.unsigned !== undefined) {
|
||||||
if (!fromDatabase) {
|
transformedEvent.unsigned = Object.assign({}, transformedEvent.unsigned);
|
||||||
transformedEvent = Object.assign({}, e);
|
|
||||||
if (transformedEvent.unsigned !== undefined) {
|
|
||||||
transformedEvent.unsigned = Object.assign({}, transformedEvent.unsigned);
|
|
||||||
}
|
|
||||||
const age = e.unsigned ? e.unsigned.age : e.age;
|
|
||||||
if (age !== undefined) transformedEvent._localTs = Date.now() - age;
|
|
||||||
} else {
|
|
||||||
transformedEvent = e;
|
|
||||||
}
|
}
|
||||||
|
const age = e.unsigned ? e.unsigned.age : e.age;
|
||||||
|
if (age !== undefined) transformedEvent._localTs = Date.now() - age;
|
||||||
|
} else {
|
||||||
|
transformedEvent = e;
|
||||||
|
}
|
||||||
|
|
||||||
currentData._timeline.push({
|
currentData._timeline.push({
|
||||||
event: transformedEvent,
|
event: transformedEvent,
|
||||||
token: index === 0 ? (data.timeline.prev_batch ?? null) : null,
|
token: index === 0 ? (data.timeline.prev_batch ?? null) : null,
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
// attempt to prune the timeline by jumping between events which have
|
// attempt to prune the timeline by jumping between events which have
|
||||||
// pagination tokens.
|
// pagination tokens.
|
||||||
@ -581,7 +586,7 @@ export class SyncAccumulator {
|
|||||||
room_id: roomId,
|
room_id: roomId,
|
||||||
content: {
|
content: {
|
||||||
// $event_id: { "m.read": { $user_id: $json } }
|
// $event_id: { "m.read": { $user_id: $json } }
|
||||||
},
|
} as IContent,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [userId, receiptData] of Object.entries(roomData._readReceipts)) {
|
for (const [userId, receiptData] of Object.entries(roomData._readReceipts)) {
|
||||||
@ -626,8 +631,8 @@ export class SyncAccumulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let transformedEvent: (IRoomEvent | IStateEvent) & { _localTs?: number };
|
let transformedEvent: (IRoomEvent | IStateEvent) & { _localTs?: number };
|
||||||
if (!forDatabase && msgData.event["_localTs"]) {
|
if (!forDatabase && isTaggedEvent(msgData.event)) {
|
||||||
// This means we have to copy each event so we can fix it up to
|
// 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
|
// set a correct 'age' parameter whilst keeping the local timestamp
|
||||||
// on our stored event. If this turns out to be a bottleneck, it could
|
// 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
|
// be optimised either by doing this in the main process after the data
|
||||||
@ -641,7 +646,7 @@ export class SyncAccumulator {
|
|||||||
}
|
}
|
||||||
delete transformedEvent._localTs;
|
delete transformedEvent._localTs;
|
||||||
transformedEvent.unsigned = transformedEvent.unsigned || {};
|
transformedEvent.unsigned = transformedEvent.unsigned || {};
|
||||||
transformedEvent.unsigned.age = Date.now() - msgData.event["_localTs"];
|
transformedEvent.unsigned.age = Date.now() - msgData.event._localTs!;
|
||||||
} else {
|
} else {
|
||||||
transformedEvent = msgData.event;
|
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 : "");
|
return `FILTER_SYNC_${userId}` + (suffix ? "_" + suffix : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function debuglog(...params): void {
|
function debuglog(...params: any[]): void {
|
||||||
if (!DEBUG) return;
|
if (!DEBUG) return;
|
||||||
logger.log(...params);
|
logger.log(...params);
|
||||||
}
|
}
|
||||||
@ -1093,7 +1093,7 @@ export class SyncApi {
|
|||||||
// handle non-room account_data
|
// handle non-room account_data
|
||||||
if (Array.isArray(data.account_data?.events)) {
|
if (Array.isArray(data.account_data?.events)) {
|
||||||
const events = data.account_data.events.map(client.getEventMapper());
|
const events = data.account_data.events.map(client.getEventMapper());
|
||||||
const prevEventsMap = events.reduce((m, c) => {
|
const prevEventsMap = events.reduce<Record<string, MatrixEvent | undefined>>((m, c) => {
|
||||||
m[c.getType()!] = client.store.getAccountData(c.getType());
|
m[c.getType()!] = client.store.getAccountData(c.getType());
|
||||||
return m;
|
return m;
|
||||||
}, {});
|
}, {});
|
||||||
@ -1473,12 +1473,13 @@ export class SyncApi {
|
|||||||
this.opts.crypto.updateOneTimeKeyCount(currentCount);
|
this.opts.crypto.updateOneTimeKeyCount(currentCount);
|
||||||
}
|
}
|
||||||
if (this.opts.crypto &&
|
if (this.opts.crypto &&
|
||||||
(data["device_unused_fallback_key_types"] ||
|
(data.device_unused_fallback_key_types ||
|
||||||
data["org.matrix.msc2732.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
|
// The presence of device_unused_fallback_key_types indicates that the
|
||||||
// server supports fallback keys. If there's no unused
|
// server supports fallback keys. If there's no unused
|
||||||
// signed_curve25519 fallback key we need a new one.
|
// 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"];
|
data["org.matrix.msc2732.device_unused_fallback_key_types"];
|
||||||
this.opts.crypto.setNeedsNewFallback(
|
this.opts.crypto.setNeedsNewFallback(
|
||||||
Array.isArray(unusedFallbackKeys) &&
|
Array.isArray(unusedFallbackKeys) &&
|
||||||
@ -1607,9 +1608,10 @@ export class SyncApi {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const mapper = this.client.getEventMapper({ decrypt });
|
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) {
|
if (room) {
|
||||||
e["room_id"] = room.roomId;
|
e.room_id = room.roomId;
|
||||||
}
|
}
|
||||||
return mapper(e);
|
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
|
* @param {*} value the value to test
|
||||||
* @return {boolean} whether or not value is a finite number without type-coercion
|
* @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);
|
return typeof value === 'number' && isFinite(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -428,8 +428,8 @@ export interface IDeferred<T> {
|
|||||||
|
|
||||||
// Returns a Deferred
|
// Returns a Deferred
|
||||||
export function defer<T = void>(): IDeferred<T> {
|
export function defer<T = void>(): IDeferred<T> {
|
||||||
let resolve;
|
let resolve!: IDeferred<T>["resolve"];
|
||||||
let reject;
|
let reject!: IDeferred<T>["reject"];
|
||||||
|
|
||||||
const promise = new Promise<T>((_resolve, _reject) => {
|
const promise = new Promise<T>((_resolve, _reject) => {
|
||||||
resolve = _resolve;
|
resolve = _resolve;
|
||||||
@ -665,18 +665,22 @@ export function compare(a: string, b: string): number {
|
|||||||
* @param {Object} source
|
* @param {Object} source
|
||||||
* @returns the target object
|
* @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)) {
|
for (const [sourceKey, sourceValue] of Object.entries(source)) {
|
||||||
if (target[sourceKey] instanceof Object && sourceValue) {
|
if (target[sourceKey] instanceof Object && sourceValue) {
|
||||||
recursivelyAssign(target[sourceKey], sourceValue);
|
recursivelyAssign(target[sourceKey], sourceValue);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ((sourceValue !== null && sourceValue !== undefined) || !ignoreNullish) {
|
if ((sourceValue !== null && sourceValue !== undefined) || !ignoreNullish) {
|
||||||
target[sourceKey] = sourceValue;
|
target[sourceKey as keyof T1] = sourceValue;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return target;
|
return target as T1 & T2;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContentTimestampWithFallback(event: MatrixEvent): number {
|
function getContentTimestampWithFallback(event: MatrixEvent): number {
|
||||||
|
@ -27,7 +27,7 @@ import { parse as parseSdp, write as writeSdp } from "sdp-transform";
|
|||||||
|
|
||||||
import { logger } from '../logger';
|
import { logger } from '../logger';
|
||||||
import * as utils from '../utils';
|
import * as utils from '../utils';
|
||||||
import { MatrixEvent } from '../models/event';
|
import { IContent, MatrixEvent } from '../models/event';
|
||||||
import { EventType, ToDeviceMessageId } from '../@types/event';
|
import { EventType, ToDeviceMessageId } from '../@types/event';
|
||||||
import { RoomMember } from '../models/room-member';
|
import { RoomMember } from '../models/room-member';
|
||||||
import { randomString } from '../randomstring';
|
import { randomString } from '../randomstring';
|
||||||
@ -1101,7 +1101,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
this.terminate(CallParty.Local, reason, !suppressEvent);
|
this.terminate(CallParty.Local, reason, !suppressEvent);
|
||||||
// We don't want to send hangup here if we didn't even get to sending an invite
|
// 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;
|
if ([CallState.Fledgling, CallState.WaitLocalMedia].includes(this.state)) return;
|
||||||
const content = {};
|
const content: IContent = {};
|
||||||
// Don't send UserHangup reason to older clients
|
// Don't send UserHangup reason to older clients
|
||||||
if ((this.opponentVersion && this.opponentVersion !== 0) || reason !== CallErrorCode.UserHangup) {
|
if ((this.opponentVersion && this.opponentVersion !== 0) || reason !== CallErrorCode.UserHangup) {
|
||||||
content["reason"] = reason;
|
content["reason"] = reason;
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"noImplicitAny": false,
|
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|
@ -1423,9 +1423,9 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.21"
|
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":
|
"@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.13"
|
version "3.2.14"
|
||||||
resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.13.tgz#0109fde93bcc61def851f79826c9384c073b5175"
|
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":
|
"@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3":
|
||||||
version "2.1.8-no-fsevents.3"
|
version "2.1.8-no-fsevents.3"
|
||||||
|
Reference in New Issue
Block a user