From 2a4ad7bd4c7b541b015cde27873950ce3f0d7a66 Mon Sep 17 00:00:00 2001 From: yousefed Date: Fri, 9 Sep 2022 16:59:53 +0200 Subject: [PATCH 1/4] first e2ee working --- package-lock.json | 90 +++++++++++++++---- packages/matrix-crdt/package.json | 3 +- .../matrix-crdt/src/MatrixProvider.test.ts | 10 ++- .../matrix-crdt/src/matrixRoomManagement.ts | 19 ++-- .../memberReader/MatrixMemberReader.test.ts | 10 ++- .../src/reader/MatrixReader.test.ts | 22 +++-- .../matrix-crdt/src/reader/MatrixReader.ts | 6 +- packages/matrix-crdt/src/setupTests.ts | 8 +- .../src/test-utils/matrixGuestClient.ts | 7 +- .../src/test-utils/matrixTestUtil.ts | 14 +-- 10 files changed, 138 insertions(+), 51 deletions(-) diff --git a/package-lock.json b/package-lock.json index f381963..54c3020 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7372,6 +7372,19 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@vitest/coverage-c8": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-c8/-/coverage-c8-0.23.1.tgz", + "integrity": "sha512-si3vK1h3BHdoGfb5J2jXstthROHwDW+yqVNGO/NOPG8642+d1RO/jFFvF3OSSYFEsxxcDL0uywEVcXCjGuPYdA==", + "dev": true, + "dependencies": { + "c8": "^7.12.0", + "vitest": "0.23.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -25802,6 +25815,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-0.4.0.tgz", + "integrity": "sha512-ql/sBDoJOybTKSIOWrrh8kgUEMjXMwRAkZTD0EwiwxQH/6tTPkZvMIEjp0CRlpi6V5FMiJyvxeRkEi1KrGISoA==", + "dev": true, + "dependencies": { + "acorn": "^8.7.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/strong-log-transformer": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz", @@ -26500,6 +26525,12 @@ "node": ">=8" } }, + "node_modules/tinybench": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.1.5.tgz", + "integrity": "sha512-ak+PZZEuH3mw6CCFOgf5S90YH0MARnZNhxjhjguAmoJimEMAJuNip/rJRd6/wyylHItomVpKTzZk9zrhTrQCoQ==", + "dev": true + }, "node_modules/tinypool": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.2.4.tgz", @@ -27162,19 +27193,21 @@ } }, "node_modules/vitest": { - "version": "0.20.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.20.3.tgz", - "integrity": "sha512-cXMjTbZxBBUUuIF3PUzEGPLJWtIMeURBDXVxckSHpk7xss4JxkiiWh5cnIlfGyfJne2Ii3QpbiRuFL5dMJtljw==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.23.1.tgz", + "integrity": "sha512-kn9pG+h6VA3yj/xRvwgLKEd33rOlzMqJEg3tl5HSm3WUPlkY1Lr1FK8RN1uIqVKvFxmz6HGU3EQW+xW2kazRkQ==", "dev": true, "dependencies": { - "@types/chai": "^4.3.1", + "@types/chai": "^4.3.3", "@types/chai-subset": "^1.3.3", "@types/node": "*", "chai": "^4.3.6", "debug": "^4.3.4", "local-pkg": "^0.4.2", + "strip-literal": "^0.4.0", + "tinybench": "^2.1.3", "tinypool": "^0.2.4", - "tinyspy": "^1.0.0", + "tinyspy": "^1.0.2", "vite": "^2.9.12 || ^3.0.0-0" }, "bin": { @@ -27190,7 +27223,6 @@ "@edge-runtime/vm": "*", "@vitest/browser": "*", "@vitest/ui": "*", - "c8": "*", "happy-dom": "*", "jsdom": "*" }, @@ -27204,9 +27236,6 @@ "@vitest/ui": { "optional": true }, - "c8": { - "optional": true - }, "happy-dom": { "optional": true }, @@ -28377,6 +28406,7 @@ "@types/lodash": "^4.14.178", "@types/qs": "^6.9.7", "@types/simple-peer": "^9.11.3", + "@vitest/coverage-c8": "^0.23.1", "autocannon": "7.4.0", "c8": "^7.12.0", "cross-fetch": "^3.1.4", @@ -28388,7 +28418,7 @@ "rimraf": "^3.0.2", "typescript": "^4.4.4", "vite": "^3.0.0", - "vitest": "^0.20.3", + "vitest": "^0.23.1", "y-protocols": "^1.0.5", "yjs": "^13.5.16" }, @@ -34080,6 +34110,16 @@ } } }, + "@vitest/coverage-c8": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@vitest/coverage-c8/-/coverage-c8-0.23.1.tgz", + "integrity": "sha512-si3vK1h3BHdoGfb5J2jXstthROHwDW+yqVNGO/NOPG8642+d1RO/jFFvF3OSSYFEsxxcDL0uywEVcXCjGuPYdA==", + "dev": true, + "requires": { + "c8": "^7.12.0", + "vitest": "0.23.1" + } + }, "@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -42483,6 +42523,7 @@ "@types/lodash": "^4.14.178", "@types/qs": "^6.9.7", "@types/simple-peer": "^9.11.3", + "@vitest/coverage-c8": "^0.23.1", "another-json": "^0.2.0", "autocannon": "7.4.0", "c8": "^7.12.0", @@ -42497,7 +42538,7 @@ "simple-peer": "^9.11.0", "typescript": "^4.4.4", "vite": "^3.0.0", - "vitest": "^0.20.3", + "vitest": "^0.23.1", "vscode-lib": "^0.1.0", "y-protocols": "^1.0.5", "yjs": "^13.5.16" @@ -48171,6 +48212,15 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, + "strip-literal": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-0.4.0.tgz", + "integrity": "sha512-ql/sBDoJOybTKSIOWrrh8kgUEMjXMwRAkZTD0EwiwxQH/6tTPkZvMIEjp0CRlpi6V5FMiJyvxeRkEi1KrGISoA==", + "dev": true, + "requires": { + "acorn": "^8.7.1" + } + }, "strong-log-transformer": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz", @@ -48706,6 +48756,12 @@ "integrity": "sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==", "dev": true }, + "tinybench": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.1.5.tgz", + "integrity": "sha512-ak+PZZEuH3mw6CCFOgf5S90YH0MARnZNhxjhjguAmoJimEMAJuNip/rJRd6/wyylHItomVpKTzZk9zrhTrQCoQ==", + "dev": true + }, "tinypool": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.2.4.tgz", @@ -49208,19 +49264,21 @@ } }, "vitest": { - "version": "0.20.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.20.3.tgz", - "integrity": "sha512-cXMjTbZxBBUUuIF3PUzEGPLJWtIMeURBDXVxckSHpk7xss4JxkiiWh5cnIlfGyfJne2Ii3QpbiRuFL5dMJtljw==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.23.1.tgz", + "integrity": "sha512-kn9pG+h6VA3yj/xRvwgLKEd33rOlzMqJEg3tl5HSm3WUPlkY1Lr1FK8RN1uIqVKvFxmz6HGU3EQW+xW2kazRkQ==", "dev": true, "requires": { - "@types/chai": "^4.3.1", + "@types/chai": "^4.3.3", "@types/chai-subset": "^1.3.3", "@types/node": "*", "chai": "^4.3.6", "debug": "^4.3.4", "local-pkg": "^0.4.2", + "strip-literal": "^0.4.0", + "tinybench": "^2.1.3", "tinypool": "^0.2.4", - "tinyspy": "^1.0.0", + "tinyspy": "^1.0.2", "vite": "^2.9.12 || ^3.0.0-0" } }, diff --git a/packages/matrix-crdt/package.json b/packages/matrix-crdt/package.json index 77892f6..edc79b0 100644 --- a/packages/matrix-crdt/package.json +++ b/packages/matrix-crdt/package.json @@ -33,7 +33,8 @@ "rimraf": "^3.0.2", "typescript": "^4.4.4", "vite": "^3.0.0", - "vitest": "^0.20.3", + "vitest": "^0.23.1", + "@vitest/coverage-c8": "^0.23.1", "y-protocols": "^1.0.5", "yjs": "^13.5.16" }, diff --git a/packages/matrix-crdt/src/MatrixProvider.test.ts b/packages/matrix-crdt/src/MatrixProvider.test.ts index b12cf67..e9a3e3c 100644 --- a/packages/matrix-crdt/src/MatrixProvider.test.ts +++ b/packages/matrix-crdt/src/MatrixProvider.test.ts @@ -24,8 +24,12 @@ type UnPromisify = T extends Promise ? U : T; async function getRoomAndTwoUsers(opts: { bobIsGuest: boolean; roomAccess: "public-read-write" | "public-read"; + encrypted: boolean; }) { - const setup = await createRandomMatrixClientAndRoom(opts.roomAccess); + const setup = await createRandomMatrixClientAndRoom( + opts.roomAccess, + opts.encrypted + ); const doc = new Y.Doc(); const provider = new MatrixProvider(doc, setup.client, { type: "alias", @@ -133,6 +137,7 @@ it("syncs public room guest", async () => { const users = await getRoomAndTwoUsers({ bobIsGuest: true, roomAccess: "public-read-write", + encrypted: false, }); await validateOneWaySync(users); }, 30000); @@ -141,6 +146,7 @@ it("syncs write-only access", async () => { const users = await getRoomAndTwoUsers({ bobIsGuest: false, roomAccess: "public-read", + encrypted: false, }); await validateOneWaySync(users); }, 30000); @@ -149,6 +155,7 @@ it("syncs two users writing ", async () => { const users = await getRoomAndTwoUsers({ bobIsGuest: false, roomAccess: "public-read-write", + encrypted: false, }); await validateTwoWaySync(users); }, 30000); @@ -157,6 +164,7 @@ it("syncs with intermediate snapshots ", async () => { const users = await getRoomAndTwoUsers({ bobIsGuest: false, roomAccess: "public-read-write", + encrypted: false, }); const { alice, bob } = users; diff --git a/packages/matrix-crdt/src/matrixRoomManagement.ts b/packages/matrix-crdt/src/matrixRoomManagement.ts index 7f2b6ce..80d38f3 100644 --- a/packages/matrix-crdt/src/matrixRoomManagement.ts +++ b/packages/matrix-crdt/src/matrixRoomManagement.ts @@ -5,7 +5,8 @@ export async function createMatrixRoom( matrixClient: any, roomName: string, - access: "public-read-write" | "public-read" + access: "public-read-write" | "public-read", + encrypted: boolean = false ) { try { const initial_state = []; @@ -39,13 +40,15 @@ export async function createMatrixRoom( }); // for e2ee - // initial_state.push({ - // type: "m.room.encryption", - // state_key: "", - // content: { - // algorithm: "m.megolm.v1.aes-sha2", - // }, - // }); + if (encrypted) { + initial_state.push({ + type: "m.room.encryption", + state_key: "", + content: { + algorithm: "m.megolm.v1.aes-sha2", + }, + }); + } const ret = await matrixClient.createRoom({ room_alias_name: roomName, diff --git a/packages/matrix-crdt/src/memberReader/MatrixMemberReader.test.ts b/packages/matrix-crdt/src/memberReader/MatrixMemberReader.test.ts index 85bccb1..4f6f891 100644 --- a/packages/matrix-crdt/src/memberReader/MatrixMemberReader.test.ts +++ b/packages/matrix-crdt/src/memberReader/MatrixMemberReader.test.ts @@ -20,7 +20,10 @@ beforeAll(async () => { }); it("handles room joins", async () => { - const setupA = await createRandomMatrixClientAndRoom("public-read-write"); + const setupA = await createRandomMatrixClientAndRoom( + "public-read-write", + false + ); const userB = await createRandomMatrixClient(); const guestClient = await createMatrixGuestClient(matrixTestConfig); @@ -47,7 +50,10 @@ it("handles room joins", async () => { }, 30000); it("handles room power levels", async () => { - const setupA = await createRandomMatrixClientAndRoom("public-read-write"); + const setupA = await createRandomMatrixClientAndRoom( + "public-read-write", + false + ); const userB = await createRandomMatrixClient(); const guestClient = await createMatrixGuestClient(matrixTestConfig); diff --git a/packages/matrix-crdt/src/reader/MatrixReader.test.ts b/packages/matrix-crdt/src/reader/MatrixReader.test.ts index 3bfb587..667b425 100644 --- a/packages/matrix-crdt/src/reader/MatrixReader.test.ts +++ b/packages/matrix-crdt/src/reader/MatrixReader.test.ts @@ -5,7 +5,10 @@ import { beforeAll, expect, it } from "vitest"; import { autocannonSeparateProcess } from "../benchmark/util"; import { MatrixCRDTEventTranslator } from "../MatrixCRDTEventTranslator"; import { createMatrixGuestClient } from "../test-utils/matrixGuestClient"; -import { createRandomMatrixClientAndRoom } from "../test-utils/matrixTestUtil"; +import { + createRandomMatrixClient, + createRandomMatrixClientAndRoom, +} from "../test-utils/matrixTestUtil"; import { ensureMatrixIsRunning, HOMESERVER_NAME, @@ -52,16 +55,22 @@ function validateMessages(messages: any[], count: number) { it("handles initial and live messages", async () => { let messageId = 0; - const setup = await createRandomMatrixClientAndRoom("public-read"); + const setup = await createRandomMatrixClientAndRoom( + "public-read-write", + true + ); + + const { client, username } = await createRandomMatrixClient(); + // const guestClient = await createMatrixGuestClient(matrixTestConfig); + await client.joinRoom(setup.roomId); // send more than 1 page (30 messages) initially for (let i = 0; i < 40; i++) { await sendMessage(setup.client, setup.roomId, "message " + ++messageId); } - const guestClient = await createMatrixGuestClient(matrixTestConfig); const reader = new MatrixReader( - guestClient, + client, setup.roomId, new MatrixCRDTEventTranslator() ); @@ -81,7 +90,6 @@ it("handles initial and live messages", async () => { while (messageId < 60) { await sendMessage(setup.client, setup.roomId, "message " + ++messageId); } - await new Promise((resolve) => setTimeout(resolve, 1000)); validateMessages(messages, messageId); } finally { @@ -127,7 +135,7 @@ class TestReader { it.skip("handles parallel live messages", async () => { const PARALLEL = 500; let messageId = 0; - const setup = await createRandomMatrixClientAndRoom("public-read"); + const setup = await createRandomMatrixClientAndRoom("public-read", false); const readers = []; try { @@ -161,7 +169,7 @@ it.skip("handles parallel live messages autocannon", async () => { const PARALLEL = 500; let messageId = 0; - const setup = await createRandomMatrixClientAndRoom("public-read"); + const setup = await createRandomMatrixClientAndRoom("public-read", false); const client = await createMatrixGuestClient(matrixTestConfig); const reader = new MatrixReader( diff --git a/packages/matrix-crdt/src/reader/MatrixReader.ts b/packages/matrix-crdt/src/reader/MatrixReader.ts index c347d7f..3a96438 100644 --- a/packages/matrix-crdt/src/reader/MatrixReader.ts +++ b/packages/matrix-crdt/src/reader/MatrixReader.ts @@ -75,9 +75,9 @@ export class MatrixReader extends lifecycle.Disposable { console.error("not expected; Room.timeline on MatrixClient"); // (disable error when testing / developing e2ee support, // in that case startClient is necessary) - throw new Error( - "unexpected, we don't use /sync calls for MatrixReader, startClient should not be used on the Matrix client" - ); + // throw new Error( + // "unexpected, we don't use /sync calls for MatrixReader, startClient should not be used on the Matrix client" + // ); }; /** diff --git a/packages/matrix-crdt/src/setupTests.ts b/packages/matrix-crdt/src/setupTests.ts index 02d119c..0bffc9e 100644 --- a/packages/matrix-crdt/src/setupTests.ts +++ b/packages/matrix-crdt/src/setupTests.ts @@ -1,8 +1,6 @@ -// https://github.com/developit/microbundle/issues/708, otherwise vscode-lib fails -import "regenerator-runtime/runtime.js"; - const { randomFillSync } = require("crypto"); -(global as any).Olm = require("@matrix-org/olm"); +(globalThis as any).Olm = require("@matrix-org/olm"); + // const { Crypto } = require("@peculiar/webcrypto"); // const crypto = new Crypto(); @@ -12,3 +10,5 @@ Object.defineProperty(globalThis, "crypto", { // , subtle: crypto.subtle }, }); + +export {}; diff --git a/packages/matrix-crdt/src/test-utils/matrixGuestClient.ts b/packages/matrix-crdt/src/test-utils/matrixGuestClient.ts index 83f66e5..8467066 100644 --- a/packages/matrix-crdt/src/test-utils/matrixGuestClient.ts +++ b/packages/matrix-crdt/src/test-utils/matrixGuestClient.ts @@ -15,16 +15,15 @@ export async function createMatrixGuestClient(config: { baseUrl: string }) { // hardcoded overwrites (matrixClient as any).canSupportVoip = false; - (matrixClient as any).clientOpts = { - lazyLoadMembers: true, - }; matrixClient.setGuest(true); await matrixClient.initCrypto(); + matrixClient.setCryptoTrustCrossSignedDevices(true); + matrixClient.setGlobalErrorOnUnknownDevices(false); // don't use startClient (because it will sync periodically), when we're in guest / readonly mode // in guest mode we only use the matrixclient to fetch initial room state, but receive updates via WebRTCProvider - // matrixClient.startClient({ lazyLoadMembers: true }); + await matrixClient.startClient({ lazyLoadMembers: true }); return matrixClient; } diff --git a/packages/matrix-crdt/src/test-utils/matrixTestUtil.ts b/packages/matrix-crdt/src/test-utils/matrixTestUtil.ts index 406a2f4..f36f7a3 100644 --- a/packages/matrix-crdt/src/test-utils/matrixTestUtil.ts +++ b/packages/matrix-crdt/src/test-utils/matrixTestUtil.ts @@ -30,11 +30,12 @@ export async function createRandomMatrixClient() { } export async function createRandomMatrixClientAndRoom( - access: "public-read-write" | "public-read" + access: "public-read-write" | "public-read", + encrypted: boolean ) { const { client, username } = await createRandomMatrixClient(); const roomName = "@" + username + "/test"; - const result = await createMatrixRoom(client, roomName, access); + const result = await createMatrixRoom(client, roomName, access, encrypted); if (typeof result === "string" || result.status !== "ok") { throw new Error("couldn't create room"); @@ -93,10 +94,13 @@ export async function createMatrixUser(username: string, password: string) { deviceId: loginResult.device_id, }); - matrixClientLoggedIn.initCrypto(); + await matrixClientLoggedIn.initCrypto(); + matrixClientLoggedIn.setCryptoTrustCrossSignedDevices(true); + matrixClientLoggedIn.setGlobalErrorOnUnknownDevices(false); (matrixClientLoggedIn as any).canSupportVoip = false; - (matrixClientLoggedIn as any).clientOpts = { + + await matrixClientLoggedIn.startClient({ lazyLoadMembers: true, - }; + }); return matrixClientLoggedIn; } From 7d7d89192e99bced1b055c87fffe7a908118f168 Mon Sep 17 00:00:00 2001 From: yousefed Date: Fri, 9 Sep 2022 17:04:24 +0200 Subject: [PATCH 2/4] fix build --- packages/matrix-crdt/src/benchmark/matrix.bench.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/matrix-crdt/src/benchmark/matrix.bench.ts b/packages/matrix-crdt/src/benchmark/matrix.bench.ts index 8d24008..171822b 100644 --- a/packages/matrix-crdt/src/benchmark/matrix.bench.ts +++ b/packages/matrix-crdt/src/benchmark/matrix.bench.ts @@ -1,10 +1,10 @@ import * as http from "http"; import * as https from "https"; import { MatrixClient } from "matrix-js-sdk"; -import * as Y from "yjs"; import { event } from "vscode-lib"; -import { createMatrixGuestClient } from "../test-utils/matrixGuestClient"; +import * as Y from "yjs"; import { MatrixProvider } from "../MatrixProvider"; +import { createMatrixGuestClient } from "../test-utils/matrixGuestClient"; import { createRandomMatrixClientAndRoom } from "../test-utils/matrixTestUtil"; import { HOMESERVER_NAME, @@ -45,7 +45,10 @@ async function readRoom(roomName: string) { } async function loadTest() { - const setup = await createRandomMatrixClientAndRoom("public-read-write"); + const setup = await createRandomMatrixClientAndRoom( + "public-read-write", + false + ); await setRoomContents(setup.client, setup.roomId); const server = await createSimpleServer(() => readRoom(setup.roomName)); From 7be2181f5d6d3cdbcb5c00871bc73b0d09d88987 Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 13 Sep 2022 20:27:15 +0200 Subject: [PATCH 3/4] clean tests and add e2ee test --- .../matrix-crdt/src/MatrixProvider.test.ts | 81 +++++++--- packages/matrix-crdt/src/MatrixProvider.ts | 14 +- .../matrix-crdt/src/benchmark/matrix.bench.ts | 139 ++++++++++++++++- .../matrix-crdt/src/matrixRoomManagement.ts | 49 ++++-- .../memberReader/MatrixMemberReader.test.ts | 46 +++++- .../src/reader/MatrixReader.test.ts | 142 +----------------- .../matrix-crdt/src/reader/MatrixReader.ts | 36 ++--- .../src/test-utils/matrixGuestClient.ts | 7 +- .../src/test-utils/matrixTestUtil.ts | 8 +- .../src/writer/ThrottledMatrixWriter.ts | 8 +- 10 files changed, 317 insertions(+), 213 deletions(-) diff --git a/packages/matrix-crdt/src/MatrixProvider.test.ts b/packages/matrix-crdt/src/MatrixProvider.test.ts index e9a3e3c..95f67f1 100644 --- a/packages/matrix-crdt/src/MatrixProvider.test.ts +++ b/packages/matrix-crdt/src/MatrixProvider.test.ts @@ -2,6 +2,10 @@ import { beforeAll, expect, it } from "vitest"; import { event } from "vscode-lib"; import * as Y from "yjs"; import { MatrixProvider } from "./MatrixProvider"; +import { + PublicRoomPermissionType, + RoomSecuritySetting, +} from "./matrixRoomManagement"; import { createMatrixGuestClient } from "./test-utils/matrixGuestClient"; import { createRandomMatrixClient, @@ -23,13 +27,9 @@ type UnPromisify = T extends Promise ? U : T; async function getRoomAndTwoUsers(opts: { bobIsGuest: boolean; - roomAccess: "public-read-write" | "public-read"; - encrypted: boolean; + security: RoomSecuritySetting; }) { - const setup = await createRandomMatrixClientAndRoom( - opts.roomAccess, - opts.encrypted - ); + const setup = await createRandomMatrixClientAndRoom(opts.security); const doc = new Y.Doc(); const provider = new MatrixProvider(doc, setup.client, { type: "alias", @@ -45,6 +45,11 @@ async function getRoomAndTwoUsers(opts: { alias: "#" + setup.roomName + ":" + HOMESERVER_NAME, }); + if (opts.security.permissions === "private") { + // invite user to private room + await setup.client.invite(setup.roomId, client2.credentials.userId!); + } + return { alice: { doc, @@ -124,6 +129,7 @@ async function validateTwoWaySync( console.log("Bob sending change", bob.client.credentials.userId); expect(bob.provider.canWrite).toBe(true); bob.doc.getMap("test3").set("key", 1); + await bob.provider.waitForFlush(); await event.Event.toPromise(alice.provider.onReceivedEvents); expect(alice.doc.getMap("test3").get("key")).toBe(1); @@ -133,29 +139,62 @@ async function validateTwoWaySync( bob.provider.dispose(); } -it("syncs public room guest", async () => { +for (let permissions of [ + "public-read-write", + "public-read", +] as PublicRoomPermissionType[]) { + it(`Guest is read-only for room set to ${permissions}`, async () => { + const users = await getRoomAndTwoUsers({ + bobIsGuest: true, + security: { + permissions: "public-read-write", + encrypted: false, + }, + }); + await validateOneWaySync(users); + }, 30000); +} + +it("User is read-only for room set to public-read", async () => { const users = await getRoomAndTwoUsers({ - bobIsGuest: true, - roomAccess: "public-read-write", - encrypted: false, + bobIsGuest: false, + security: { + permissions: "public-read", + encrypted: false, + }, }); await validateOneWaySync(users); }, 30000); -it("syncs write-only access", async () => { +it("User can read and write for room set to public-read-write ", async () => { const users = await getRoomAndTwoUsers({ bobIsGuest: false, - roomAccess: "public-read", - encrypted: false, + security: { + permissions: "public-read-write", + encrypted: false, + }, }); - await validateOneWaySync(users); + await validateTwoWaySync(users); }, 30000); -it("syncs two users writing ", async () => { +it("User can read and write for room set to private", async () => { const users = await getRoomAndTwoUsers({ bobIsGuest: false, - roomAccess: "public-read-write", - encrypted: false, + security: { + permissions: "private", + encrypted: false, + }, + }); + await validateTwoWaySync(users); +}, 30000); + +it("User can read and write for room set to private, encrypted", async () => { + const users = await getRoomAndTwoUsers({ + bobIsGuest: false, + security: { + permissions: "private", + encrypted: true, + }, }); await validateTwoWaySync(users); }, 30000); @@ -163,8 +202,10 @@ it("syncs two users writing ", async () => { it("syncs with intermediate snapshots ", async () => { const users = await getRoomAndTwoUsers({ bobIsGuest: false, - roomAccess: "public-read-write", - encrypted: false, + security: { + permissions: "public-read-write", + encrypted: false, + }, }); const { alice, bob } = users; @@ -183,6 +224,8 @@ it("syncs with intermediate snapshots ", async () => { const val = bob.doc.getMap("test").get("contents") as any; expect(val.toJSON()).toEqual(text.toJSON()); + + // validate that the snapshot system has been used expect(bob.provider.totalEventsReceived).toBeLessThan(20); alice.provider.dispose(); diff --git a/packages/matrix-crdt/src/MatrixProvider.ts b/packages/matrix-crdt/src/MatrixProvider.ts index d68b2f8..9d55d30 100644 --- a/packages/matrix-crdt/src/MatrixProvider.ts +++ b/packages/matrix-crdt/src/MatrixProvider.ts @@ -2,20 +2,20 @@ import { MatrixClient } from "matrix-js-sdk"; import { event, lifecycle } from "vscode-lib"; import * as awarenessProtocol from "y-protocols/awareness"; import * as Y from "yjs"; -import { signObject, verifyObject } from "./util/authUtil"; +import { + MatrixCRDTEventTranslator, + MatrixCRDTEventTranslatorOptions, +} from "./MatrixCRDTEventTranslator"; import { MatrixMemberReader } from "./memberReader/MatrixMemberReader"; import { MatrixReader, MatrixReaderOptions } from "./reader/MatrixReader"; import { SignedWebrtcProvider } from "./SignedWebrtcProvider"; +import { signObject, verifyObject } from "./util/authUtil"; +import { arrayBuffersAreEqual } from "./util/binary"; +import { decodeBase64 } from "./util/olmlib"; import { ThrottledMatrixWriter, ThrottledMatrixWriterOptions, } from "./writer/ThrottledMatrixWriter"; -import { decodeBase64 } from "./util/olmlib"; -import { arrayBuffersAreEqual } from "./util/binary"; -import { - MatrixCRDTEventTranslator, - MatrixCRDTEventTranslatorOptions, -} from "./MatrixCRDTEventTranslator"; const DEFAULT_OPTIONS = { enableExperimentalWebrtcSync: false, diff --git a/packages/matrix-crdt/src/benchmark/matrix.bench.ts b/packages/matrix-crdt/src/benchmark/matrix.bench.ts index 171822b..ee96c01 100644 --- a/packages/matrix-crdt/src/benchmark/matrix.bench.ts +++ b/packages/matrix-crdt/src/benchmark/matrix.bench.ts @@ -45,10 +45,10 @@ async function readRoom(roomName: string) { } async function loadTest() { - const setup = await createRandomMatrixClientAndRoom( - "public-read-write", - false - ); + const setup = await createRandomMatrixClientAndRoom({ + permissions: "public-read-write", + encrypted: false, + }); await setRoomContents(setup.client, setup.roomId); const server = await createSimpleServer(() => readRoom(setup.roomName)); @@ -66,3 +66,134 @@ async function loadTest() { // await loadTest(); // }, 30000); loadTest(); + +/* +class TestReader { + private static CREATED = 0; + + public messages: any[] | undefined; + private reader: MatrixReader | undefined; + constructor( + private config: any, + private roomId: string, + private client?: MatrixClient + ) {} + + async initialize() { + const guestClient = + this.client || (await createMatrixGuestClient(matrixTestConfig)); + this.reader = new MatrixReader( + guestClient, + this.roomId, + new MatrixCRDTEventTranslator() + ); + + this.messages = await this.reader.getInitialDocumentUpdateEvents(); + console.log("created", TestReader.CREATED++); + this.reader.onEvents((msgs) => { + // console.log("on message"); + this.messages!.push.apply(this.messages, msgs.events); + }); + this.reader.startPolling(); + } + + dispose() { + this.reader?.dispose(); + } +} + +// Breaks at 500 parallel requests locally +it.skip("handles parallel live messages", async () => { + const PARALLEL = 500; + let messageId = 0; + const setup = await createRandomMatrixClientAndRoom({ + permissions: "public-read", + encrypted: false, + }); + + const readers = []; + try { + const client = await createMatrixGuestClient(matrixTestConfig); + for (let i = 0; i < PARALLEL; i++) { + // const worker = new Worker(__dirname + "/worker.js", { + // workerData: { + // path: "./MatrixReader.test.ts", + // }, + // }); + readers.push(new TestReader(matrixTestConfig, setup.roomId, client)); + } + + // return; + await Promise.all(readers.map((reader) => reader.initialize())); + + while (messageId < 10) { + // console.log("send message"); + await sendMessage(setup.client, setup.roomId, "message " + ++messageId); + } + + await new Promise((resolve) => setTimeout(resolve, 10000)); + readers.map((r) => validateMessages(r.messages!, messageId)); + } finally { + readers.map((r) => r.dispose()); + } +}); + +// gets slow at around 500 messages, but calls to http://localhost:8888/_matrix/static/ also at around 1k +it.skip("handles parallel live messages autocannon", async () => { + const PARALLEL = 500; + + let messageId = 0; + const setup = await createRandomMatrixClientAndRoom({ + permissions: "public-read", + encrypted: false, + }); + + const client = await createMatrixGuestClient(matrixTestConfig); + const reader = new MatrixReader( + client, + setup.roomId, + new MatrixCRDTEventTranslator() + ); + try { + await reader.getInitialDocumentUpdateEvents(); + + const params = { + access_token: client.http.opts.accessToken, + from: reader.latestToken, + room_id: setup.roomId, + timeout: 30000, + }; + + // send some new messages beforehand + while (messageId < 10) { + // console.log("send message"); + await sendMessage(setup.client, setup.roomId, "message " + ++messageId); + } + + const uri = + "http://" + + HOMESERVER_NAME + + "/_matrix/client/r0/events?" + + qs.stringify(params); + autocannonSeparateProcess([ + "-c", + PARALLEL + "", + "-a", + PARALLEL + "", + "-t", + "20", + uri, + ]); + + // send some new messages in parallel / after + while (messageId < 20) { + // console.log("send message"); + await sendMessage(setup.client, setup.roomId, "message " + ++messageId); + } + + await new Promise((resolve) => setTimeout(resolve, 10000)); + } finally { + reader.dispose(); + } +}); +*/ diff --git a/packages/matrix-crdt/src/matrixRoomManagement.ts b/packages/matrix-crdt/src/matrixRoomManagement.ts index 80d38f3..e238f45 100644 --- a/packages/matrix-crdt/src/matrixRoomManagement.ts +++ b/packages/matrix-crdt/src/matrixRoomManagement.ts @@ -1,12 +1,22 @@ +export type PublicRoomPermissionType = "public-read-write" | "public-read"; + +export type RoomSecuritySetting = + | { + permissions: PublicRoomPermissionType; + encrypted: false; // it doesn't make sense to encrypt public rooms + } + | { + permissions: "private"; // TODO: read-only private rooms (using power levels) + encrypted: boolean; + }; + /** * Helper function to create a Matrix room suitable for use with MatrixProvider. - * Access can currently be set to "public-read-write" | "public-read" */ export async function createMatrixRoom( matrixClient: any, roomName: string, - access: "public-read-write" | "public-read", - encrypted: boolean = false + security: RoomSecuritySetting ) { try { const initial_state = []; @@ -26,7 +36,8 @@ export async function createMatrixRoom( initial_state.push({ type: "m.room.join_rules", content: { - join_rule: access === "public-read-write" ? "public" : "invite", + join_rule: + security.permissions === "public-read-write" ? "public" : "invite", }, }); @@ -40,7 +51,7 @@ export async function createMatrixRoom( }); // for e2ee - if (encrypted) { + if (security.encrypted) { initial_state.push({ type: "m.room.encryption", state_key: "", @@ -52,7 +63,7 @@ export async function createMatrixRoom( const ret = await matrixClient.createRoom({ room_alias_name: roomName, - visibility: "public", // Whether this room is visible to the /publicRooms API or not." One of: ["private", "public"] + visibility: security.permissions === "private" ? "private" : "public", // Whether this room is visible to the /publicRooms API or not." One of: ["private", "public"] name: roomName, topic: "", initial_state, @@ -77,9 +88,14 @@ export async function createMatrixRoom( } } -export async function getMatrixRoomAccess(matrixClient: any, roomId: string) { +export async function getMatrixRoomSecurity( + matrixClient: any, + roomId: string +): Promise { let result: any; + // TODO: what about private rooms? + try { result = await matrixClient.getStateEvent(roomId, "m.room.join_rules"); } catch (e) { @@ -90,9 +106,15 @@ export async function getMatrixRoomAccess(matrixClient: any, roomId: string) { } if (result.join_rule === "public") { - return "public-read-write"; + return { + permissions: "public-read-write", + encrypted: false, + }; } else if (result.join_rule === "invite") { - return "public-read"; + return { + permissions: "public-read", + encrypted: false, + }; } else { throw new Error("unsupported join_rule"); } @@ -105,16 +127,21 @@ export async function getMatrixRoomAccess(matrixClient: any, roomId: string) { export async function updateMatrixRoomAccess( matrixClient: any, roomId: string, - access: "public-read-write" | "public-read" + security: RoomSecuritySetting ) { try { await matrixClient.sendStateEvent( roomId, "m.room.join_rules", - { join_rule: access === "public-read-write" ? "public" : "invite" }, + { + join_rule: + security.permissions === "public-read-write" ? "public" : "invite", + }, "" ); + // TODO: set encryption + // TODO: add room to space return { status: "ok" as "ok", roomId }; diff --git a/packages/matrix-crdt/src/memberReader/MatrixMemberReader.test.ts b/packages/matrix-crdt/src/memberReader/MatrixMemberReader.test.ts index 4f6f891..33e35a6 100644 --- a/packages/matrix-crdt/src/memberReader/MatrixMemberReader.test.ts +++ b/packages/matrix-crdt/src/memberReader/MatrixMemberReader.test.ts @@ -19,11 +19,15 @@ beforeAll(async () => { await ensureMatrixIsRunning(); }); +/** + * This test validates whether MatrixMemberReader correctly detects write access + * when users join a room + */ it("handles room joins", async () => { - const setupA = await createRandomMatrixClientAndRoom( - "public-read-write", - false - ); + const setupA = await createRandomMatrixClientAndRoom({ + permissions: "public-read-write", + encrypted: false, + }); const userB = await createRandomMatrixClient(); const guestClient = await createMatrixGuestClient(matrixTestConfig); @@ -37,6 +41,28 @@ it("handles room joins", async () => { await readerC.startPolling(); await memberC.initialize(); + // alternative, now we use startClient, we can sync to something like this: + + // const room = setupA.client.getRoom(setupA.roomId)!; + // expect( + // room.currentState.maySendEvent( + // "m.room.message", + // guestClient.credentials.userId! + // ) + // ).toBe(false); + // expect( + // room.currentState.maySendEvent( + // "m.room.message", + // setupA.client.credentials.userId! + // ) + // ).toBe(true); + // expect( + // room.currentState.maySendEvent( + // "m.room.message", + // userB.client.credentials.userId! + // ) + // ).toBe(false); + expect(memberC.hasWriteAccess(guestClient.credentials.userId!)).toBe(false); expect(memberC.hasWriteAccess(setupA.client.credentials.userId!)).toBe(true); expect(memberC.hasWriteAccess(userB.client.credentials.userId!)).toBe(false); @@ -49,11 +75,15 @@ it("handles room joins", async () => { readerC.dispose(); }, 30000); +/** + * This test checks whether MatrixMemberReader correctly checks + * power level events to see if a user has write access or not + */ it("handles room power levels", async () => { - const setupA = await createRandomMatrixClientAndRoom( - "public-read-write", - false - ); + const setupA = await createRandomMatrixClientAndRoom({ + permissions: "public-read-write", + encrypted: false, + }); const userB = await createRandomMatrixClient(); const guestClient = await createMatrixGuestClient(matrixTestConfig); diff --git a/packages/matrix-crdt/src/reader/MatrixReader.test.ts b/packages/matrix-crdt/src/reader/MatrixReader.test.ts index 667b425..d17b6cb 100644 --- a/packages/matrix-crdt/src/reader/MatrixReader.test.ts +++ b/packages/matrix-crdt/src/reader/MatrixReader.test.ts @@ -1,19 +1,12 @@ import got from "got"; -import { MatrixClient, request } from "matrix-js-sdk"; -import * as qs from "qs"; +import { request } from "matrix-js-sdk"; import { beforeAll, expect, it } from "vitest"; -import { autocannonSeparateProcess } from "../benchmark/util"; import { MatrixCRDTEventTranslator } from "../MatrixCRDTEventTranslator"; -import { createMatrixGuestClient } from "../test-utils/matrixGuestClient"; import { createRandomMatrixClient, createRandomMatrixClientAndRoom, } from "../test-utils/matrixTestUtil"; -import { - ensureMatrixIsRunning, - HOMESERVER_NAME, - matrixTestConfig, -} from "../test-utils/matrixTestUtilServer"; +import { ensureMatrixIsRunning } from "../test-utils/matrixTestUtilServer"; import { sendMessage } from "../util/matrixUtil"; import { MatrixReader } from "./MatrixReader"; @@ -55,10 +48,10 @@ function validateMessages(messages: any[], count: number) { it("handles initial and live messages", async () => { let messageId = 0; - const setup = await createRandomMatrixClientAndRoom( - "public-read-write", - true - ); + const setup = await createRandomMatrixClientAndRoom({ + permissions: "public-read-write", + encrypted: false, + }); const { client, username } = await createRandomMatrixClient(); // const guestClient = await createMatrixGuestClient(matrixTestConfig); @@ -96,126 +89,3 @@ it("handles initial and live messages", async () => { reader.dispose(); } }, 100000); - -class TestReader { - private static CREATED = 0; - - public messages: any[] | undefined; - private reader: MatrixReader | undefined; - constructor( - private config: any, - private roomId: string, - private client?: MatrixClient - ) {} - - async initialize() { - const guestClient = - this.client || (await createMatrixGuestClient(matrixTestConfig)); - this.reader = new MatrixReader( - guestClient, - this.roomId, - new MatrixCRDTEventTranslator() - ); - - this.messages = await this.reader.getInitialDocumentUpdateEvents(); - console.log("created", TestReader.CREATED++); - this.reader.onEvents((msgs) => { - // console.log("on message"); - this.messages!.push.apply(this.messages, msgs.events); - }); - this.reader.startPolling(); - } - - dispose() { - this.reader?.dispose(); - } -} - -// Breaks at 500 parallel requests locally -it.skip("handles parallel live messages", async () => { - const PARALLEL = 500; - let messageId = 0; - const setup = await createRandomMatrixClientAndRoom("public-read", false); - - const readers = []; - try { - const client = await createMatrixGuestClient(matrixTestConfig); - for (let i = 0; i < PARALLEL; i++) { - // const worker = new Worker(__dirname + "/worker.js", { - // workerData: { - // path: "./MatrixReader.test.ts", - // }, - // }); - readers.push(new TestReader(matrixTestConfig, setup.roomId, client)); - } - - // return; - await Promise.all(readers.map((reader) => reader.initialize())); - - while (messageId < 10) { - // console.log("send message"); - await sendMessage(setup.client, setup.roomId, "message " + ++messageId); - } - - await new Promise((resolve) => setTimeout(resolve, 10000)); - readers.map((r) => validateMessages(r.messages!, messageId)); - } finally { - readers.map((r) => r.dispose()); - } -}); - -// gets slow at around 500 messages, but calls to http://localhost:8888/_matrix/static/ also at around 1k -it.skip("handles parallel live messages autocannon", async () => { - const PARALLEL = 500; - - let messageId = 0; - const setup = await createRandomMatrixClientAndRoom("public-read", false); - - const client = await createMatrixGuestClient(matrixTestConfig); - const reader = new MatrixReader( - client, - setup.roomId, - new MatrixCRDTEventTranslator() - ); - try { - await reader.getInitialDocumentUpdateEvents(); - - const params = { - access_token: client.http.opts.accessToken, - from: reader.latestToken, - room_id: setup.roomId, - timeout: 30000, - }; - - // send some new messages beforehand - while (messageId < 10) { - // console.log("send message"); - await sendMessage(setup.client, setup.roomId, "message " + ++messageId); - } - - const uri = - "http://" + - HOMESERVER_NAME + - "/_matrix/client/r0/events?" + - qs.stringify(params); - autocannonSeparateProcess([ - "-c", - PARALLEL + "", - "-a", - PARALLEL + "", - "-t", - "20", - uri, - ]); - - // send some new messages in parallel / after - while (messageId < 20) { - // console.log("send message"); - await sendMessage(setup.client, setup.roomId, "message " + ++messageId); - } - - await new Promise((resolve) => setTimeout(resolve, 10000)); - } finally { - reader.dispose(); - } -}); diff --git a/packages/matrix-crdt/src/reader/MatrixReader.ts b/packages/matrix-crdt/src/reader/MatrixReader.ts index 3a96438..b64028a 100644 --- a/packages/matrix-crdt/src/reader/MatrixReader.ts +++ b/packages/matrix-crdt/src/reader/MatrixReader.ts @@ -3,6 +3,7 @@ import { MatrixClient, MatrixEvent, Method, + Room, RoomEvent, } from "matrix-js-sdk"; import { event, lifecycle } from "vscode-lib"; @@ -54,30 +55,25 @@ export class MatrixReader extends lifecycle.Disposable { /** * Only receives messages from rooms the user has joined, and after startClient() has been called * (i.e.: they're received via the sync API). - * - * At this moment, we only poll for events using the /events endpoint. - * I.e. the Sync API should not be used (and startClient() should not be called). - * - * We do this because we don't want the MatrixClient to keep all events in memory. - * For yjs, this is not necessary, as events are document updates which are accumulated in the yjs - * document, so already stored there. - * - * In a later version, it might be more efficient to call the /sync API manually - * (without relying on the Timeline / sync system in the matrix-js-sdk), - * because it allows us to retrieve events for multiple rooms simultaneously, instead of - * a seperate /events poll per room */ private matrixRoomListener = ( - _event: any, - _room: any, + event: MatrixEvent, + room: Room, _toStartOfTimeline: boolean ) => { - console.error("not expected; Room.timeline on MatrixClient"); - // (disable error when testing / developing e2ee support, - // in that case startClient is necessary) - // throw new Error( - // "unexpected, we don't use /sync calls for MatrixReader, startClient should not be used on the Matrix client" - // ); + // TODO: for e2ee support we now call `startClient`. + // For rooms we've joined, the client will now get messages here, but also by polling /events. + // We should probably remove the polling if it's not necessary anymore, and handle messages in this function, like below: + // + // if (room.roomId !== this.roomId) { + // return; + // } + // console.error("not expected; Room.timeline on MatrixClient"); + // const events = [event.event]; + // const shouldSendSnapshot = this.processIncomingEventsForSnapshot(events); + // if (events.length) { + // this._onEvents.fire({ events: events, shouldSendSnapshot }); + // } }; /** diff --git a/packages/matrix-crdt/src/test-utils/matrixGuestClient.ts b/packages/matrix-crdt/src/test-utils/matrixGuestClient.ts index 8467066..4b5135c 100644 --- a/packages/matrix-crdt/src/test-utils/matrixGuestClient.ts +++ b/packages/matrix-crdt/src/test-utils/matrixGuestClient.ts @@ -20,10 +20,11 @@ export async function createMatrixGuestClient(config: { baseUrl: string }) { await matrixClient.initCrypto(); matrixClient.setCryptoTrustCrossSignedDevices(true); matrixClient.setGlobalErrorOnUnknownDevices(false); - // don't use startClient (because it will sync periodically), when we're in guest / readonly mode - // in guest mode we only use the matrixclient to fetch initial room state, but receive updates via WebRTCProvider - await matrixClient.startClient({ lazyLoadMembers: true }); + await matrixClient.startClient({ + lazyLoadMembers: true, + initialSyncLimit: 0, + }); return matrixClient; } diff --git a/packages/matrix-crdt/src/test-utils/matrixTestUtil.ts b/packages/matrix-crdt/src/test-utils/matrixTestUtil.ts index f36f7a3..d9542f3 100644 --- a/packages/matrix-crdt/src/test-utils/matrixTestUtil.ts +++ b/packages/matrix-crdt/src/test-utils/matrixTestUtil.ts @@ -2,7 +2,7 @@ import * as http from "http"; import * as https from "https"; import Matrix, { createClient, MemoryStore } from "matrix-js-sdk"; import { uuid } from "vscode-lib"; -import { createMatrixRoom } from "../matrixRoomManagement"; +import { createMatrixRoom, RoomSecuritySetting } from "../matrixRoomManagement"; import { matrixTestConfig } from "./matrixTestUtilServer"; const request = require("request"); @@ -30,12 +30,11 @@ export async function createRandomMatrixClient() { } export async function createRandomMatrixClientAndRoom( - access: "public-read-write" | "public-read", - encrypted: boolean + security: RoomSecuritySetting ) { const { client, username } = await createRandomMatrixClient(); const roomName = "@" + username + "/test"; - const result = await createMatrixRoom(client, roomName, access, encrypted); + const result = await createMatrixRoom(client, roomName, security); if (typeof result === "string" || result.status !== "ok") { throw new Error("couldn't create room"); @@ -101,6 +100,7 @@ export async function createMatrixUser(username: string, password: string) { await matrixClientLoggedIn.startClient({ lazyLoadMembers: true, + initialSyncLimit: 0, }); return matrixClientLoggedIn; } diff --git a/packages/matrix-crdt/src/writer/ThrottledMatrixWriter.ts b/packages/matrix-crdt/src/writer/ThrottledMatrixWriter.ts index c5f1748..2c889d5 100644 --- a/packages/matrix-crdt/src/writer/ThrottledMatrixWriter.ts +++ b/packages/matrix-crdt/src/writer/ThrottledMatrixWriter.ts @@ -85,7 +85,13 @@ export class ThrottledMatrixWriter extends lifecycle.Disposable { this.setCanWrite(true); console.log("sent updates"); } catch (e: any) { - if (e.errcode === "M_FORBIDDEN") { + if ( + e.errcode === "M_FORBIDDEN" || + // the following is an error we get when the room uses e2ee and we're invited, but didn't join yet + e.message?.includes( + "Room was previously configured to use encryption, but is no longer." + ) + ) { console.warn("not allowed to edit document", e); this.setCanWrite(false); From 3046507d92786b1bff46067729fabdc973f644aa Mon Sep 17 00:00:00 2001 From: yousefed Date: Tue, 13 Sep 2022 20:47:26 +0200 Subject: [PATCH 4/4] clean up MatrixReader.test.ts --- .../src/reader/MatrixReader.test.ts | 81 +++++++++++++++---- 1 file changed, 67 insertions(+), 14 deletions(-) diff --git a/packages/matrix-crdt/src/reader/MatrixReader.test.ts b/packages/matrix-crdt/src/reader/MatrixReader.test.ts index d17b6cb..e1de1f8 100644 --- a/packages/matrix-crdt/src/reader/MatrixReader.test.ts +++ b/packages/matrix-crdt/src/reader/MatrixReader.test.ts @@ -1,12 +1,17 @@ import got from "got"; -import { request } from "matrix-js-sdk"; +import { MatrixClient, request } from "matrix-js-sdk"; import { beforeAll, expect, it } from "vitest"; import { MatrixCRDTEventTranslator } from "../MatrixCRDTEventTranslator"; +import { RoomSecuritySetting } from "../matrixRoomManagement"; +import { createMatrixGuestClient } from "../test-utils/matrixGuestClient"; import { createRandomMatrixClient, createRandomMatrixClientAndRoom, } from "../test-utils/matrixTestUtil"; -import { ensureMatrixIsRunning } from "../test-utils/matrixTestUtilServer"; +import { + ensureMatrixIsRunning, + matrixTestConfig, +} from "../test-utils/matrixTestUtilServer"; import { sendMessage } from "../util/matrixUtil"; import { MatrixReader } from "./MatrixReader"; @@ -39,6 +44,32 @@ beforeAll(async () => { await ensureMatrixIsRunning(); }); +async function getRoomAndTwoUsers(opts: { + bobIsGuest: boolean; + security: RoomSecuritySetting; +}) { + const setup = await createRandomMatrixClientAndRoom(opts.security); + + const client2 = opts.bobIsGuest + ? await createMatrixGuestClient(matrixTestConfig) + : (await createRandomMatrixClient()).client; + + if (opts.security.permissions === "private") { + // invite user to private room + await setup.client.invite(setup.roomId, client2.credentials.userId!); + } + + return { + alice: { + client: setup.client, + roomId: setup.roomId, + }, + bob: { + client: client2, + }, + }; +} + function validateMessages(messages: any[], count: number) { expect(messages.length).toBe(count); for (let i = 1; i <= count; i++) { @@ -46,25 +77,47 @@ function validateMessages(messages: any[], count: number) { } } -it("handles initial and live messages", async () => { +it("handles initial and live messages (public room)", async () => { let messageId = 0; - const setup = await createRandomMatrixClientAndRoom({ - permissions: "public-read-write", - encrypted: false, + + const { alice, bob } = await getRoomAndTwoUsers({ + bobIsGuest: false, + security: { + permissions: "public-read-write", + encrypted: false, + }, }); - const { client, username } = await createRandomMatrixClient(); - // const guestClient = await createMatrixGuestClient(matrixTestConfig); - await client.joinRoom(setup.roomId); + await validateMessagesSentByAliceReceivedByBob(alice, messageId, bob); +}, 100000); + +it("handles initial and live messages (private encrypted room)", async () => { + let messageId = 0; + + const { alice, bob } = await getRoomAndTwoUsers({ + bobIsGuest: false, + security: { + permissions: "private", + encrypted: true, + }, + }); + + await validateMessagesSentByAliceReceivedByBob(alice, messageId, bob); +}, 100000); +async function validateMessagesSentByAliceReceivedByBob( + alice: { client: MatrixClient; roomId: string }, + messageId: number, + bob: { client: MatrixClient } +) { // send more than 1 page (30 messages) initially for (let i = 0; i < 40; i++) { - await sendMessage(setup.client, setup.roomId, "message " + ++messageId); + await sendMessage(alice.client, alice.roomId, "message " + ++messageId); } const reader = new MatrixReader( - client, - setup.roomId, + bob.client, + alice.roomId, new MatrixCRDTEventTranslator() ); try { @@ -81,11 +134,11 @@ it("handles initial and live messages", async () => { reader.startPolling(); while (messageId < 60) { - await sendMessage(setup.client, setup.roomId, "message " + ++messageId); + await sendMessage(alice.client, alice.roomId, "message " + ++messageId); } await new Promise((resolve) => setTimeout(resolve, 1000)); validateMessages(messages, messageId); } finally { reader.dispose(); } -}, 100000); +}