Skip to content

Commit

Permalink
Merge branch 'develop' into florianduros/elementr/wire-up-rotation
Browse files Browse the repository at this point in the history
  • Loading branch information
florianduros committed Oct 18, 2023
2 parents baf77f6 + 6468d79 commit 6ac2c89
Show file tree
Hide file tree
Showing 14 changed files with 507 additions and 336 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/cypress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
# from creeping in. They take a long time to run and consume 4 concurrent runners.
if: github.event.workflow_run.event == 'merge_group'

uses: matrix-org/matrix-react-sdk/.github/workflows/cypress.yaml@66854039a33ed6cfe1fc635ff2daa8bb261c0b56
uses: matrix-org/matrix-react-sdk/.github/workflows/cypress.yaml@v3.83.0-rc.1
permissions:
actions: read
issues: read
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pull_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
if: github.event.action == 'opened'
steps:
- name: Check membership
uses: tspascoal/get-user-teams-membership@37c08f7b52a72ca95d12af2e7ab2553ca9adf13b # v2
uses: tspascoal/get-user-teams-membership@ba78054988f58bea69b7c6136d563236f8ed2fc0 # v3
id: teams
with:
username: ${{ github.event.pull_request.user.login }}
Expand Down
8 changes: 8 additions & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Summary

- [Introduction](../README.md)

# Deep dive

- [Storage notes](storage-notes.md)
- [Unverified devices](warning-on-unverified-devices.md)
Original file line number Diff line number Diff line change
@@ -1,31 +1,29 @@
Random notes from Matthew on the two possible approaches for warning users about unexpected
unverified devices popping up in their rooms....

Original idea...
================
# Original idea...

Warn when an existing user adds an unknown device to a room.

Warn when a user joins the room with unverified or unknown devices.

Warn when you initial sync if the room has any unverified devices in it.
^ this is good enough if we're doing local storage.
OR, better:
^ this is good enough if we're doing local storage.
OR, better:
Warn when you initial sync if the room has any new undefined devices since you were last there.
=> This means persisting the rooms that devices are in, across initial syncs.
=> This means persisting the rooms that devices are in, across initial syncs.


Updated idea...
===============
# Updated idea...

Warn when the user tries to send a message:
- If the room has unverified devices which the user has not yet been told about in the context of this room
...or in the context of this user? currently all verification is per-user, not per-room.

- If the room has unverified devices which the user has not yet been told about in the context of this room
...or in the context of this user? currently all verification is per-user, not per-room.
...this should be good enough.

- so track whether we have warned the user or not about unverified devices - blocked, unverified, verified, unverified_warned.
- so track whether we have warned the user or not about unverified devices - blocked, unverified, verified, unverified_warned.
throw an error when trying to encrypt if there are pure unverified devices there
app will have to search for the devices which are pure unverified to warn about them - have to do this from MembersList anyway?
- or megolm could warn which devices are causing the problems.
- or megolm could warn which devices are causing the problems.

Why do we wait to establish outbound sessions? It just makes a horrible pause when we first try to send a message... but could otherwise unnecessarily consume resources?
Why do we wait to establish outbound sessions? It just makes a horrible pause when we first try to send a message... but could otherwise unnecessarily consume resources?
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
"eslint-plugin-tsdoc": "^0.2.17",
"eslint-plugin-unicorn": "^48.0.0",
"exorcist": "^2.0.0",
"fake-indexeddb": "^4.0.0",
"fake-indexeddb": "^5.0.0",
"fetch-mock-jest": "^1.5.1",
"jest": "^29.0.0",
"jest-environment-jsdom": "^29.0.0",
Expand Down
110 changes: 108 additions & 2 deletions spec/unit/models/event.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { MockedObject } from "jest-mock";

import { MatrixEvent, MatrixEventEvent } from "../../../src/models/event";
import { emitPromise } from "../../test-utils/test-utils";
import { Crypto, IEventDecryptionResult } from "../../../src/crypto";
import { IAnnotatedPushRule, PushRuleActionName, TweakName } from "../../../src";
import {
IAnnotatedPushRule,
MatrixClient,
PushRuleActionName,
Room,
THREAD_RELATION_TYPE,
TweakName,
} from "../../../src";

describe("MatrixEvent", () => {
it("should create copies of itself", () => {
Expand Down Expand Up @@ -77,17 +86,98 @@ describe("MatrixEvent", () => {
expect(ev.getWireContent().body).toBeUndefined();
expect(ev.getWireContent().ciphertext).toBe("xyz");

const mockClient = {} as unknown as MockedObject<MatrixClient>;
const room = new Room("!roomid:e.xyz", mockClient, "myname");
const redaction = new MatrixEvent({
type: "m.room.redaction",
redacts: ev.getId(),
});

ev.makeRedacted(redaction);
ev.makeRedacted(redaction, room);
expect(ev.getContent().body).toBeUndefined();
expect(ev.getWireContent().body).toBeUndefined();
expect(ev.getWireContent().ciphertext).toBeUndefined();
});

it("should remain in the main timeline when redacted", async () => {
// Given an event in the main timeline
const mockClient = {
supportsThreads: jest.fn().mockReturnValue(true),
decryptEventIfNeeded: jest.fn().mockReturnThis(),
getUserId: jest.fn().mockReturnValue("@user:server"),
} as unknown as MockedObject<MatrixClient>;
const room = new Room("!roomid:e.xyz", mockClient, "myname");
const ev = new MatrixEvent({
type: "m.room.message",
content: {
body: "Test",
},
event_id: "$event1:server",
});

await room.addLiveEvents([ev]);
await room.createThreadsTimelineSets();
expect(ev.threadRootId).toBeUndefined();
expect(mainTimelineLiveEventIds(room)).toEqual(["$event1:server"]);

// When I redact it
const redaction = new MatrixEvent({
type: "m.room.redaction",
redacts: ev.getId(),
});
ev.makeRedacted(redaction, room);

// Then it remains in the main timeline
expect(ev.threadRootId).toBeUndefined();
expect(mainTimelineLiveEventIds(room)).toEqual(["$event1:server"]);
});

it("should move into the main timeline when redacted", async () => {
// Given an event in a thread
const mockClient = {
supportsThreads: jest.fn().mockReturnValue(true),
decryptEventIfNeeded: jest.fn().mockReturnThis(),
getUserId: jest.fn().mockReturnValue("@user:server"),
} as unknown as MockedObject<MatrixClient>;
const room = new Room("!roomid:e.xyz", mockClient, "myname");
const threadRoot = new MatrixEvent({
type: "m.room.message",
content: {
body: "threadRoot",
},
event_id: "$threadroot:server",
});
const ev = new MatrixEvent({
type: "m.room.message",
content: {
"body": "Test",
"m.relates_to": {
rel_type: THREAD_RELATION_TYPE.name,
event_id: "$threadroot:server",
},
},
event_id: "$event1:server",
});

await room.addLiveEvents([threadRoot, ev]);
await room.createThreadsTimelineSets();
expect(ev.threadRootId).toEqual("$threadroot:server");
expect(mainTimelineLiveEventIds(room)).toEqual(["$threadroot:server"]);
expect(threadLiveEventIds(room, 0)).toEqual(["$threadroot:server", "$event1:server"]);

// When I redact it
const redaction = new MatrixEvent({
type: "m.room.redaction",
redacts: ev.getId(),
});
ev.makeRedacted(redaction, room);

// Then it disappears from the thread and appears in the main timeline
expect(ev.threadRootId).toBeUndefined();
expect(mainTimelineLiveEventIds(room)).toEqual(["$threadroot:server", "$event1:server"]);
expect(threadLiveEventIds(room, 0)).not.toContain("$event1:server");
});

describe("applyVisibilityEvent", () => {
it("should emit VisibilityChange if a change was made", async () => {
const ev = new MatrixEvent({
Expand Down Expand Up @@ -330,3 +420,19 @@ describe("MatrixEvent", () => {
expect(stateEvent.threadRootId).toBeUndefined();
});
});

function mainTimelineLiveEventIds(room: Room): Array<string> {
return room
.getLiveTimeline()
.getEvents()
.map((e) => e.getId()!);
}

function threadLiveEventIds(room: Room, threadIndex: number): Array<string> {
return room
.getThreads()
[threadIndex].getUnfilteredTimelineSet()
.getLiveTimeline()
.getEvents()
.map((e) => e.getId()!);
}
63 changes: 63 additions & 0 deletions spec/unit/models/thread.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,69 @@ describe("Thread", () => {
});
});
});

describe("addEvent", () => {
describe("Given server support for threads", () => {
let previousThreadHasServerSideSupport: FeatureSupport;

beforeAll(() => {
previousThreadHasServerSideSupport = Thread.hasServerSideSupport;
Thread.hasServerSideSupport = FeatureSupport.Stable;
});

afterAll(() => {
Thread.hasServerSideSupport = previousThreadHasServerSideSupport;
});

it("Adds events even if they appear out of order", async () => {
// Given a thread exists
const client = createClient();
const user = "@alice:matrix.org";
const room = "!room:z";
const thread = await createThread(client, user, room);
const prevNumEvents = thread.timeline.length;

// When two messages come in but the later one has an older timestamp
const message1 = createThreadMessage(thread.id, user, room, "message1");
const message2 = createThreadMessage(thread.id, user, room, "message2");
message2.localTimestamp -= 10000;

await thread.addEvent(message1, false);
await thread.addEvent(message2, false);

// Then both events end up in the timeline
expect(thread.timeline.length - prevNumEvents).toEqual(2);
const lastEvent = thread.timeline.at(-1)!;
const secondLastEvent = thread.timeline.at(-2)!;
expect(lastEvent).toBe(message2);
expect(secondLastEvent).toBe(message1);
});

it("Adds events to start even if they appear out of order", async () => {
// Given a thread exists
const client = createClient();
const user = "@alice:matrix.org";
const room = "!room:z";
const thread = await createThread(client, user, room);
const prevNumEvents = thread.timeline.length;

// When two messages come in but the later one has an older timestamp
const message1 = createThreadMessage(thread.id, user, room, "message1");
const message2 = createThreadMessage(thread.id, user, room, "message2");
message2.localTimestamp -= 10000;

await thread.addEvent(message1, false);
await thread.addEvent(message2, true);

// Then both events end up in the timeline
expect(thread.timeline.length - prevNumEvents).toEqual(2);
const lastEvent = thread.timeline.at(-1)!;
const firstEvent = thread.timeline.at(0)!;
expect(lastEvent).toBe(message1);
expect(firstEvent).toBe(message2);
});
});
});
});

/**
Expand Down
9 changes: 7 additions & 2 deletions spec/unit/room-state.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { M_BEACON } from "../../src/@types/beacon";
import { MatrixClient } from "../../src/client";
import { DecryptionError } from "../../src/crypto/algorithms";
import { defer } from "../../src/utils";
import { Room } from "../../src/models/room";

describe("RoomState", function () {
const roomId = "!foo:bar";
Expand Down Expand Up @@ -362,9 +363,11 @@ describe("RoomState", function () {
});

it("does not add redacted beacon info events to state", () => {
const mockClient = {} as unknown as MockedObject<MatrixClient>;
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId);
const redactionEvent = new MatrixEvent({ type: "m.room.redaction" });
redactedBeaconEvent.makeRedacted(redactionEvent);
const room = new Room(roomId, mockClient, userA);
redactedBeaconEvent.makeRedacted(redactionEvent, room);
const emitSpy = jest.spyOn(state, "emit");

state.setStateEvents([redactedBeaconEvent]);
Expand Down Expand Up @@ -394,11 +397,13 @@ describe("RoomState", function () {
});

it("destroys and removes redacted beacon events", () => {
const mockClient = {} as unknown as MockedObject<MatrixClient>;
const beaconId = "$beacon1";
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const redactionEvent = new MatrixEvent({ type: "m.room.redaction", redacts: beaconEvent.getId() });
redactedBeaconEvent.makeRedacted(redactionEvent);
const room = new Room(roomId, mockClient, userA);
redactedBeaconEvent.makeRedacted(redactionEvent, room);

state.setStateEvents([beaconEvent]);
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
Expand Down
2 changes: 1 addition & 1 deletion spec/unit/room.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3564,7 +3564,7 @@ describe("Room", function () {
expect(room.polls.get(pollStartEvent.getId()!)).toBeTruthy();

const redactedEvent = new MatrixEvent({ type: "m.room.redaction" });
pollStartEvent.makeRedacted(redactedEvent);
pollStartEvent.makeRedacted(redactedEvent, room);

await flushPromises();

Expand Down
10 changes: 4 additions & 6 deletions src/matrixrtc/MatrixRTCSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,6 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
memberships: this.makeNewMemberships(memberships, myCallMemberEvent, myPrevMembership),
};

let resendDelay = 0;
try {
await this.client.sendStateEvent(
this.room.roomId,
Expand All @@ -438,13 +437,12 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
logger.info(`Sent updated call member event.`);

// check periodically to see if we need to refresh our member event
if (this.isJoined()) resendDelay = MEMBER_EVENT_CHECK_PERIOD;
if (this.isJoined()) {
this.memberEventTimeout = setTimeout(this.triggerCallMembershipEventUpdate, MEMBER_EVENT_CHECK_PERIOD);
}
} catch (e) {
resendDelay = CALL_MEMBER_EVENT_RETRY_DELAY_MIN + Math.random() * 2000;
const resendDelay = CALL_MEMBER_EVENT_RETRY_DELAY_MIN + Math.random() * 2000;
logger.warn(`Failed to send call member event: retrying in ${resendDelay}`);
}

if (resendDelay) {
await new Promise((resolve) => setTimeout(resolve, resendDelay));
await this.triggerCallMembershipEventUpdate();
}
Expand Down
Loading

0 comments on commit 6ac2c89

Please sign in to comment.