1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-30 04:23:07 +03:00

Make more of the code conform to Strict TSC (#2756)

This commit is contained in:
Michael Telatynski
2022-10-14 15:57:08 +01:00
committed by GitHub
parent f70f6db926
commit 12a4d2a749
33 changed files with 675 additions and 454 deletions

View File

@ -201,7 +201,7 @@ describe("MatrixClient events", function() {
}); });
client!.on(RoomEvent.Timeline, function(event, room) { client!.on(RoomEvent.Timeline, function(event, room) {
timelineFireCount++; timelineFireCount++;
expect(room.roomId).toEqual("!erufh:bar"); expect(room?.roomId).toEqual("!erufh:bar");
}); });
client!.on(RoomEvent.Name, function(room) { client!.on(RoomEvent.Name, function(room) {
roomNameInvokeCount++; roomNameInvokeCount++;

View File

@ -368,7 +368,7 @@ describe("MatrixClient event timelines", function() {
expect(tl!.getEvents().length).toEqual(4); expect(tl!.getEvents().length).toEqual(4);
for (let i = 0; i < 4; i++) { for (let i = 0; i < 4; i++) {
expect(tl!.getEvents()[i].event).toEqual(EVENTS[i]); expect(tl!.getEvents()[i].event).toEqual(EVENTS[i]);
expect(tl!.getEvents()[i]?.sender.name).toEqual(userName); expect(tl!.getEvents()[i]?.sender?.name).toEqual(userName);
} }
expect(tl!.getPaginationToken(EventTimeline.BACKWARDS)) expect(tl!.getPaginationToken(EventTimeline.BACKWARDS))
.toEqual("start_token"); .toEqual("start_token");
@ -406,7 +406,7 @@ describe("MatrixClient event timelines", function() {
}).then(function(tl) { }).then(function(tl) {
expect(tl!.getEvents().length).toEqual(2); expect(tl!.getEvents().length).toEqual(2);
expect(tl!.getEvents()[1].event).toEqual(EVENTS[0]); expect(tl!.getEvents()[1].event).toEqual(EVENTS[0]);
expect(tl!.getEvents()[1]?.sender.name).toEqual(userName); expect(tl!.getEvents()[1]?.sender?.name).toEqual(userName);
expect(tl!.getPaginationToken(EventTimeline.BACKWARDS)) expect(tl!.getPaginationToken(EventTimeline.BACKWARDS))
.toEqual("f_1_1"); .toEqual("f_1_1");
// expect(tl.getPaginationToken(EventTimeline.FORWARDS)) // expect(tl.getPaginationToken(EventTimeline.FORWARDS))
@ -767,8 +767,8 @@ describe("MatrixClient event timelines", function() {
httpBackend = testClient.httpBackend; httpBackend = testClient.httpBackend;
await startClient(httpBackend, client); await startClient(httpBackend, client);
const room = client.getRoom(roomId); const room = client.getRoom(roomId)!;
const timelineSet = room.getTimelineSets()[0]; const timelineSet = room.getTimelineSets()[0]!;
await expect(client.getLatestTimeline(timelineSet)).rejects.toBeTruthy(); await expect(client.getLatestTimeline(timelineSet)).rejects.toBeTruthy();
}); });
@ -786,7 +786,7 @@ describe("MatrixClient event timelines", function() {
httpBackend = testClient.httpBackend; httpBackend = testClient.httpBackend;
return startClient(httpBackend, client).then(() => { return startClient(httpBackend, client).then(() => {
const room = client.getRoom(roomId); const room = client.getRoom(roomId)!;
const timelineSet = room.getTimelineSets()[0]; const timelineSet = room.getTimelineSets()[0];
expect(client.getLatestTimeline(timelineSet)).rejects.toBeFalsy(); expect(client.getLatestTimeline(timelineSet)).rejects.toBeFalsy();
}); });
@ -849,7 +849,7 @@ describe("MatrixClient event timelines", function() {
expect(tl!.getEvents().length).toEqual(4); expect(tl!.getEvents().length).toEqual(4);
for (let i = 0; i < 4; i++) { for (let i = 0; i < 4; i++) {
expect(tl!.getEvents()[i].event).toEqual(EVENTS[i]); expect(tl!.getEvents()[i].event).toEqual(EVENTS[i]);
expect(tl!.getEvents()[i]?.sender.name).toEqual(userName); expect(tl!.getEvents()[i]?.sender?.name).toEqual(userName);
} }
expect(tl!.getPaginationToken(EventTimeline.BACKWARDS)) expect(tl!.getPaginationToken(EventTimeline.BACKWARDS))
.toEqual("start_token"); .toEqual("start_token");
@ -1129,8 +1129,8 @@ describe("MatrixClient event timelines", function() {
}); });
it("should allow fetching all threads", async function() { it("should allow fetching all threads", async function() {
const room = client.getRoom(roomId); const room = client.getRoom(roomId)!;
const timelineSets = await room?.createThreadsTimelineSets(); const timelineSets = await room.createThreadsTimelineSets();
expect(timelineSets).not.toBeNull(); expect(timelineSets).not.toBeNull();
respondToThreads(); respondToThreads();
respondToThreads(); respondToThreads();
@ -1185,14 +1185,14 @@ describe("MatrixClient event timelines", function() {
}); });
it("should allow fetching all threads", async function() { it("should allow fetching all threads", async function() {
const room = client.getRoom(roomId); const room = client.getRoom(roomId)!;
respondToFilter(); respondToFilter();
respondToSync(); respondToSync();
respondToFilter(); respondToFilter();
respondToSync(); respondToSync();
const timelineSetsPromise = room?.createThreadsTimelineSets(); const timelineSetsPromise = room.createThreadsTimelineSets();
expect(timelineSetsPromise).not.toBeNull(); expect(timelineSetsPromise).not.toBeNull();
await flushHttp(timelineSetsPromise!); await flushHttp(timelineSetsPromise!);
respondToFilter(); respondToFilter();
@ -1218,7 +1218,7 @@ describe("MatrixClient event timelines", function() {
const [allThreads] = timelineSets!; const [allThreads] = timelineSets!;
respondToThreads().check((request) => { respondToThreads().check((request) => {
expect(request.queryParams.filter).toEqual(JSON.stringify({ expect(request.queryParams?.filter).toEqual(JSON.stringify({
"lazy_load_members": true, "lazy_load_members": true,
})); }));
}); });
@ -1244,7 +1244,7 @@ describe("MatrixClient event timelines", function() {
state: [], state: [],
next_batch: null, next_batch: null,
}).check((request) => { }).check((request) => {
expect(request.queryParams.from).toEqual(RANDOM_TOKEN); expect(request.queryParams?.from).toEqual(RANDOM_TOKEN);
}); });
allThreads.getLiveTimeline().setPaginationToken(RANDOM_TOKEN, Direction.Backward); allThreads.getLiveTimeline().setPaginationToken(RANDOM_TOKEN, Direction.Backward);

View File

@ -160,8 +160,8 @@ describe("MatrixClient room timelines", function() {
expect(room.timeline[1].status).toEqual(EventStatus.SENDING); expect(room.timeline[1].status).toEqual(EventStatus.SENDING);
// check member // check member
const member = room.timeline[1].sender; const member = room.timeline[1].sender;
expect(member.userId).toEqual(userId); expect(member?.userId).toEqual(userId);
expect(member.name).toEqual(userName); expect(member?.name).toEqual(userName);
httpBackend!.flush("/sync", 1).then(function() { httpBackend!.flush("/sync", 1).then(function() {
done(); done();
@ -327,11 +327,11 @@ describe("MatrixClient room timelines", function() {
client!.scrollback(room).then(function() { client!.scrollback(room).then(function() {
expect(room.timeline.length).toEqual(5); expect(room.timeline.length).toEqual(5);
const joinMsg = room.timeline[0]; const joinMsg = room.timeline[0];
expect(joinMsg.sender.name).toEqual("Old Alice"); expect(joinMsg.sender?.name).toEqual("Old Alice");
const oldMsg = room.timeline[1]; const oldMsg = room.timeline[1];
expect(oldMsg.sender.name).toEqual("Old Alice"); expect(oldMsg.sender?.name).toEqual("Old Alice");
const newMsg = room.timeline[3]; const newMsg = room.timeline[3];
expect(newMsg.sender.name).toEqual(userName); expect(newMsg.sender?.name).toEqual(userName);
// still have a sync to flush // still have a sync to flush
httpBackend!.flush("/sync", 1).then(() => { httpBackend!.flush("/sync", 1).then(() => {
@ -468,8 +468,8 @@ describe("MatrixClient room timelines", function() {
]).then(function() { ]).then(function() {
const preNameEvent = room.timeline[room.timeline.length - 3]; const preNameEvent = room.timeline[room.timeline.length - 3];
const postNameEvent = room.timeline[room.timeline.length - 1]; const postNameEvent = room.timeline[room.timeline.length - 1];
expect(preNameEvent.sender.name).toEqual(userName); expect(preNameEvent.sender?.name).toEqual(userName);
expect(postNameEvent.sender.name).toEqual("New Name"); expect(postNameEvent.sender?.name).toEqual("New Name");
}); });
}); });
}); });

View File

@ -1276,9 +1276,9 @@ describe("MatrixClient syncing", () => {
client!.on(RoomEvent.TimelineReset, (room) => { client!.on(RoomEvent.TimelineReset, (room) => {
resetCallCount++; resetCallCount++;
const tl = room.getLiveTimeline(); const tl = room?.getLiveTimeline();
expect(tl.getEvents().length).toEqual(0); expect(tl?.getEvents().length).toEqual(0);
const tok = tl.getPaginationToken(EventTimeline.BACKWARDS); const tok = tl?.getPaginationToken(EventTimeline.BACKWARDS);
expect(tok).toEqual("newerTok"); expect(tok).toEqual("newerTok");
}); });

View File

@ -1,5 +1,7 @@
import { UNREAD_THREAD_NOTIFICATIONS } from "../../src/@types/sync"; import { UNREAD_THREAD_NOTIFICATIONS } from "../../src/@types/sync";
import { Filter, IFilterDefinition } from "../../src/filter"; import { Filter, IFilterDefinition } from "../../src/filter";
import { mkEvent } from "../test-utils/test-utils";
import { EventType } from "../../src";
describe("Filter", function() { describe("Filter", function() {
const filterId = "f1lt3ring15g00d4ursoul"; const filterId = "f1lt3ring15g00d4ursoul";
@ -57,4 +59,27 @@ describe("Filter", function() {
}); });
}); });
}); });
describe("filterRoomTimeline", () => {
it("should return input if no roomTimelineFilter and roomFilter", () => {
const events = [mkEvent({ type: EventType.Sticker, content: {}, event: true })];
expect(new Filter(undefined).filterRoomTimeline(events)).toStrictEqual(events);
});
it("should filter using components when present", () => {
const definition: IFilterDefinition = {
room: {
timeline: {
types: [EventType.Sticker],
},
},
};
const filter = Filter.fromJson(userId, filterId, definition);
const events = [
mkEvent({ type: EventType.Sticker, content: {}, event: true }),
mkEvent({ type: EventType.RoomMessage, content: {}, event: true }),
];
expect(filter.filterRoomTimeline(events)).toStrictEqual([events[0]]);
});
});
}); });

View File

@ -259,7 +259,6 @@ describe("InteractiveAuth", () => {
const requestEmailToken = jest.fn(); const requestEmailToken = jest.fn();
const ia = new InteractiveAuth({ const ia = new InteractiveAuth({
authData: null,
matrixClient: getFakeClient(), matrixClient: getFakeClient(),
stateUpdated, stateUpdated,
doRequest, doRequest,

View File

@ -14,7 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { MatrixEvent } from "../../../src/models/event"; import { MatrixEvent, MatrixEventEvent } from "../../../src/models/event";
import { emitPromise } from "../../test-utils/test-utils";
import { EventType } from "../../../src";
import { Crypto } from "../../../src/crypto";
describe('MatrixEvent', () => { describe('MatrixEvent', () => {
it('should create copies of itself', () => { it('should create copies of itself', () => {
@ -84,4 +87,36 @@ describe('MatrixEvent', () => {
expect(ev.getWireContent().body).toBeUndefined(); expect(ev.getWireContent().body).toBeUndefined();
expect(ev.getWireContent().ciphertext).toBeUndefined(); expect(ev.getWireContent().ciphertext).toBeUndefined();
}); });
it("should abort decryption if fails with an error other than a DecryptionError", async () => {
const ev = new MatrixEvent({
type: EventType.RoomMessageEncrypted,
content: {
body: "Test",
},
event_id: "$event1:server",
});
await ev.attemptDecryption({
decryptEvent: jest.fn().mockRejectedValue(new Error("Not a DecryptionError")),
} as unknown as Crypto);
expect(ev.isEncrypted()).toBeTruthy();
expect(ev.isBeingDecrypted()).toBeFalsy();
expect(ev.isDecryptionFailure()).toBeFalsy();
});
describe("applyVisibilityEvent", () => {
it("should emit VisibilityChange if a change was made", async () => {
const ev = new MatrixEvent({
type: "m.room.message",
content: {
body: "Test",
},
event_id: "$event1:server",
});
const prom = emitPromise(ev, MatrixEventEvent.VisibilityChange);
ev.applyVisibilityEvent({ visible: false, eventId: ev.getId(), reason: null });
await prom;
});
});
}); });

View File

@ -16,7 +16,7 @@ limitations under the License.
import * as utils from "../test-utils/test-utils"; import * as utils from "../test-utils/test-utils";
import { RoomMember, RoomMemberEvent } from "../../src/models/room-member"; import { RoomMember, RoomMemberEvent } from "../../src/models/room-member";
import { RoomState } from "../../src"; import { EventType, RoomState } from "../../src";
describe("RoomMember", function() { describe("RoomMember", function() {
const roomId = "!foo:bar"; const roomId = "!foo:bar";
@ -142,33 +142,72 @@ describe("RoomMember", function() {
expect(emitCount).toEqual(1); expect(emitCount).toEqual(1);
}); });
it("should not honor string power levels.", it("should not honor string power levels.", function() {
function() { const event = utils.mkEvent({
const event = utils.mkEvent({ type: "m.room.power_levels",
type: "m.room.power_levels", room: roomId,
room: roomId, user: userA,
user: userA, content: {
content: { users_default: 20,
users_default: 20, users: {
users: { "@alice:bar": "5",
"@alice:bar": "5",
},
}, },
event: true, },
}); event: true,
let emitCount = 0;
member.on(RoomMemberEvent.PowerLevel, function(emitEvent, emitMember) {
emitCount += 1;
expect(emitMember.userId).toEqual('@alice:bar');
expect(emitMember.powerLevel).toEqual(20);
expect(emitEvent).toEqual(event);
});
member.setPowerLevelEvent(event);
expect(member.powerLevel).toEqual(20);
expect(emitCount).toEqual(1);
}); });
let emitCount = 0;
member.on(RoomMemberEvent.PowerLevel, function(emitEvent, emitMember) {
emitCount += 1;
expect(emitMember.userId).toEqual('@alice:bar');
expect(emitMember.powerLevel).toEqual(20);
expect(emitEvent).toEqual(event);
});
member.setPowerLevelEvent(event);
expect(member.powerLevel).toEqual(20);
expect(emitCount).toEqual(1);
});
it("should no-op if given a non-state or unrelated event", () => {
const fn = jest.spyOn(member, "emit");
expect(fn).not.toHaveBeenCalledWith(RoomMemberEvent.PowerLevel);
member.setPowerLevelEvent(utils.mkEvent({
type: EventType.RoomPowerLevels,
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": "5",
},
},
skey: "invalid",
event: true,
}));
const nonStateEv = utils.mkEvent({
type: EventType.RoomPowerLevels,
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": "5",
},
},
event: true,
});
delete nonStateEv.event.state_key;
member.setPowerLevelEvent(nonStateEv);
member.setPowerLevelEvent(utils.mkEvent({
type: EventType.Sticker,
room: roomId,
user: userA,
content: {},
event: true,
}));
expect(fn).not.toHaveBeenCalledWith(RoomMemberEvent.PowerLevel);
});
}); });
describe("setTypingEvent", function() { describe("setTypingEvent", function() {
@ -234,6 +273,79 @@ describe("RoomMember", function() {
}); });
}); });
describe("isKicked", () => {
it("should return false if membership is not `leave`", () => {
const member1 = new RoomMember(roomId, userA);
member1.membership = "join";
expect(member1.isKicked()).toBeFalsy();
const member2 = new RoomMember(roomId, userA);
member2.membership = "invite";
expect(member2.isKicked()).toBeFalsy();
const member3 = new RoomMember(roomId, userA);
expect(member3.isKicked()).toBeFalsy();
});
it("should return false if the membership event is unknown", () => {
const member = new RoomMember(roomId, userA);
member.membership = "leave";
expect(member.isKicked()).toBeFalsy();
});
it("should return false if the member left of their own accord", () => {
const member = new RoomMember(roomId, userA);
member.membership = "leave";
member.events.member = utils.mkMembership({
event: true,
sender: userA,
mship: "leave",
skey: userA,
});
expect(member.isKicked()).toBeFalsy();
});
it("should return true if the member's leave was sent by another user", () => {
const member = new RoomMember(roomId, userA);
member.membership = "leave";
member.events.member = utils.mkMembership({
event: true,
sender: userB,
mship: "leave",
skey: userA,
});
expect(member.isKicked()).toBeTruthy();
});
});
describe("getDMInviter", () => {
it("should return userId of the sender of the invite if is_direct=true", () => {
const member = new RoomMember(roomId, userA);
member.membership = "invite";
member.events.member = utils.mkMembership({
event: true,
sender: userB,
mship: "invite",
skey: userA,
});
member.events.member.event.content!.is_direct = true;
expect(member.getDMInviter()).toBe(userB);
});
it("should not return userId of the sender of the invite if is_direct=false", () => {
const member = new RoomMember(roomId, userA);
member.membership = "invite";
member.events.member = utils.mkMembership({
event: true,
sender: userB,
mship: "invite",
skey: userA,
});
member.events.member.event.content!.is_direct = false;
expect(member.getDMInviter()).toBeUndefined();
});
});
describe("setMembershipEvent", function() { describe("setMembershipEvent", function() {
const joinEvent = utils.mkMembership({ const joinEvent = utils.mkMembership({
event: true, event: true,

View File

@ -303,92 +303,92 @@ describe("RoomState", function() {
state.setStateEvents(events, { timelineWasEmpty: true }); state.setStateEvents(events, { timelineWasEmpty: true });
expect(emitCount).toEqual(1); expect(emitCount).toEqual(1);
}); });
});
describe('beacon events', () => { describe('beacon events', () => {
it('adds new beacon info events to state and emits', () => { it('adds new beacon info events to state and emits', () => {
const beaconEvent = makeBeaconInfoEvent(userA, roomId); const beaconEvent = makeBeaconInfoEvent(userA, roomId);
const emitSpy = jest.spyOn(state, 'emit'); const emitSpy = jest.spyOn(state, 'emit');
state.setStateEvents([beaconEvent]); state.setStateEvents([beaconEvent]);
expect(state.beacons.size).toEqual(1); expect(state.beacons.size).toEqual(1);
const beaconInstance = state.beacons.get(`${roomId}_${userA}`); const beaconInstance = state.beacons.get(`${roomId}_${userA}`);
expect(beaconInstance).toBeTruthy(); expect(beaconInstance).toBeTruthy();
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.New, beaconEvent, beaconInstance); expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.New, beaconEvent, beaconInstance);
}); });
it('does not add redacted beacon info events to state', () => { it('does not add redacted beacon info events to state', () => {
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId); const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId);
const redactionEvent = new MatrixEvent({ type: 'm.room.redaction' }); const redactionEvent = new MatrixEvent({ type: 'm.room.redaction' });
redactedBeaconEvent.makeRedacted(redactionEvent); redactedBeaconEvent.makeRedacted(redactionEvent);
const emitSpy = jest.spyOn(state, 'emit'); const emitSpy = jest.spyOn(state, 'emit');
state.setStateEvents([redactedBeaconEvent]); state.setStateEvents([redactedBeaconEvent]);
// no beacon added // no beacon added
expect(state.beacons.size).toEqual(0); expect(state.beacons.size).toEqual(0);
expect(state.beacons.get(getBeaconInfoIdentifier(redactedBeaconEvent))).toBeFalsy(); expect(state.beacons.get(getBeaconInfoIdentifier(redactedBeaconEvent))).toBeFalsy();
// no new beacon emit // no new beacon emit
expect(filterEmitCallsByEventType(BeaconEvent.New, emitSpy).length).toBeFalsy(); expect(filterEmitCallsByEventType(BeaconEvent.New, emitSpy).length).toBeFalsy();
}); });
it('updates existing beacon info events in state', () => { it('updates existing beacon info events in state', () => {
const beaconId = '$beacon1'; const beaconId = '$beacon1';
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId); const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const updatedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, beaconId); const updatedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, beaconId);
state.setStateEvents([beaconEvent]); state.setStateEvents([beaconEvent]);
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent)); const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
expect(beaconInstance?.isLive).toEqual(true); expect(beaconInstance?.isLive).toEqual(true);
state.setStateEvents([updatedBeaconEvent]); state.setStateEvents([updatedBeaconEvent]);
// same Beacon // same Beacon
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(beaconInstance); expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(beaconInstance);
// updated liveness // updated liveness
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))?.isLive).toEqual(false); expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))?.isLive).toEqual(false);
}); });
it('destroys and removes redacted beacon events', () => { it('destroys and removes redacted beacon events', () => {
const beaconId = '$beacon1'; const beaconId = '$beacon1';
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId); const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const redactedBeaconEvent = 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() }); const redactionEvent = new MatrixEvent({ type: 'm.room.redaction', redacts: beaconEvent.getId() });
redactedBeaconEvent.makeRedacted(redactionEvent); redactedBeaconEvent.makeRedacted(redactionEvent);
state.setStateEvents([beaconEvent]); state.setStateEvents([beaconEvent]);
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent)); const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
const destroySpy = jest.spyOn(beaconInstance as Beacon, 'destroy'); const destroySpy = jest.spyOn(beaconInstance as Beacon, 'destroy');
expect(beaconInstance?.isLive).toEqual(true); expect(beaconInstance?.isLive).toEqual(true);
state.setStateEvents([redactedBeaconEvent]); state.setStateEvents([redactedBeaconEvent]);
expect(destroySpy).toHaveBeenCalled(); expect(destroySpy).toHaveBeenCalled();
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(undefined); expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(undefined);
}); });
it('updates live beacon ids once after setting state events', () => { it('updates live beacon ids once after setting state events', () => {
const liveBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, '$beacon1'); const liveBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, '$beacon1');
const deadBeaconEvent = makeBeaconInfoEvent(userB, roomId, { isLive: false }, '$beacon2'); const deadBeaconEvent = makeBeaconInfoEvent(userB, roomId, { isLive: false }, '$beacon2');
const emitSpy = jest.spyOn(state, 'emit'); const emitSpy = jest.spyOn(state, 'emit');
state.setStateEvents([liveBeaconEvent, deadBeaconEvent]); state.setStateEvents([liveBeaconEvent, deadBeaconEvent]);
// called once // called once
expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(1); expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(1);
// live beacon is now not live // live beacon is now not live
const updatedLiveBeaconEvent = makeBeaconInfoEvent( const updatedLiveBeaconEvent = makeBeaconInfoEvent(
userA, roomId, { isLive: false }, liveBeaconEvent.getId(), userA, roomId, { isLive: false }, liveBeaconEvent.getId(),
); );
state.setStateEvents([updatedLiveBeaconEvent]); state.setStateEvents([updatedLiveBeaconEvent]);
expect(state.hasLiveBeacons).toBe(false); expect(state.hasLiveBeacons).toBe(false);
expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(3); expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(3);
expect(emitSpy).toHaveBeenCalledWith(RoomStateEvent.BeaconLiveness, state, false); expect(emitSpy).toHaveBeenCalledWith(RoomStateEvent.BeaconLiveness, state, false);
});
}); });
}); });
@ -1007,4 +1007,20 @@ describe("RoomState", function() {
}); });
}); });
}); });
describe("mayClientSendStateEvent", () => {
it("should return false if the user isn't authenticated", () => {
expect(state.mayClientSendStateEvent("m.room.message", {
isGuest: jest.fn().mockReturnValue(false),
credentials: {},
} as unknown as MatrixClient)).toBeFalsy();
});
it("should return false if the user is a guest", () => {
expect(state.mayClientSendStateEvent("m.room.message", {
isGuest: jest.fn().mockReturnValue(true),
credentials: { userId: userA },
} as unknown as MatrixClient)).toBeFalsy();
});
});
}); });

View File

@ -27,6 +27,7 @@ import {
EventType, EventType,
JoinRule, JoinRule,
MatrixEvent, MatrixEvent,
MatrixEventEvent,
PendingEventOrdering, PendingEventOrdering,
RelationType, RelationType,
RoomEvent, RoomEvent,
@ -40,6 +41,7 @@ import { emitPromise } from "../test-utils/test-utils";
import { ReceiptType } from "../../src/@types/read_receipts"; import { ReceiptType } from "../../src/@types/read_receipts";
import { FeatureSupport, Thread, ThreadEvent } from "../../src/models/thread"; import { FeatureSupport, Thread, ThreadEvent } from "../../src/models/thread";
import { WrappedReceipt } from "../../src/models/read-receipt"; import { WrappedReceipt } from "../../src/models/read-receipt";
import { Crypto } from "../../src/crypto";
describe("Room", function() { describe("Room", function() {
const roomId = "!foo:bar"; const roomId = "!foo:bar";
@ -337,12 +339,12 @@ describe("Room", function() {
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).toBe(null); expect(oldEventId).toBeUndefined();
expect(oldStatus).toBe(null); expect(oldStatus).toBeUndefined();
break; break;
case 1: case 1:
expect(event.getId()).toEqual(remoteEventId); expect(event.getId()).toEqual(remoteEventId);
expect(event.status).toBe(null); 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);
@ -371,7 +373,7 @@ describe("Room", function() {
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));
localEvent.status = EventStatus.SENDING; localEvent.status = EventStatus.SENDING;
expect(localEvent.getTxnId()).toBeNull(); expect(localEvent.getTxnId()).toBeUndefined();
expect(room.timeline.length).toEqual(0); expect(room.timeline.length).toEqual(0);
// first add the local echo. This is done before the /send request is even sent. // first add the local echo. This is done before the /send request is even sent.
@ -386,7 +388,7 @@ describe("Room", function() {
// then /sync returns the remoteEvent, it should de-dupe based on the event ID. // then /sync returns the remoteEvent, it should de-dupe based on the event ID.
const remoteEvent = new MatrixEvent(Object.assign({ event_id: realEventId }, eventJson)); const remoteEvent = new MatrixEvent(Object.assign({ event_id: realEventId }, eventJson));
expect(remoteEvent.getTxnId()).toBeNull(); expect(remoteEvent.getTxnId()).toBeUndefined();
room.addLiveEvents([remoteEvent]); room.addLiveEvents([remoteEvent]);
// the duplicate strategy code should ensure we don't add a 2nd event to the live timeline // the duplicate strategy code should ensure we don't add a 2nd event to the live timeline
expect(room.timeline.length).toEqual(1); expect(room.timeline.length).toEqual(1);
@ -1535,6 +1537,36 @@ describe("Room", function() {
[eventA, eventB, eventC], [eventA, eventB, eventC],
); );
}); });
it("should apply redactions eagerly in the pending event list", () => {
const client = (new TestClient("@alice:example.com", "alicedevice")).client;
const room = new Room(roomId, client, userA, {
pendingEventOrdering: PendingEventOrdering.Detached,
});
const eventA = utils.mkMessage({
room: roomId,
user: userA,
msg: "remote 1",
event: true,
});
eventA.status = EventStatus.SENDING;
const redactA = utils.mkEvent({
room: roomId,
user: userA,
type: EventType.RoomRedaction,
content: {},
redacts: eventA.getId(),
event: true,
});
redactA.status = EventStatus.SENDING;
room.addPendingEvent(eventA, "TXN1");
expect(room.getPendingEvents()).toEqual([eventA]);
room.addPendingEvent(redactA, "TXN2");
expect(room.getPendingEvents()).toEqual([eventA, redactA]);
expect(eventA.isRedacted()).toBeTruthy();
});
}); });
describe("updatePendingEvent", function() { describe("updatePendingEvent", function() {
@ -1721,7 +1753,7 @@ describe("Room", function() {
}); });
room.updateMyMembership(JoinRule.Invite); room.updateMyMembership(JoinRule.Invite);
expect(room.getMyMembership()).toEqual(JoinRule.Invite); expect(room.getMyMembership()).toEqual(JoinRule.Invite);
expect(events[0]).toEqual({ membership: "invite", oldMembership: null }); expect(events[0]).toEqual({ membership: "invite", oldMembership: undefined });
events.splice(0); //clear events.splice(0); //clear
room.updateMyMembership(JoinRule.Invite); room.updateMyMembership(JoinRule.Invite);
expect(events.length).toEqual(0); expect(events.length).toEqual(0);
@ -2636,4 +2668,42 @@ describe("Room", function() {
expect(room.hasThreadUnreadNotification()).toBe(false); expect(room.hasThreadUnreadNotification()).toBe(false);
}); });
}); });
it("should load pending events from from the store and decrypt if needed", async () => {
const client = new TestClient(userA).client;
client.crypto = {
decryptEvent: jest.fn().mockResolvedValue({ clearEvent: { body: "enc" } }),
} as unknown as Crypto;
client.store.getPendingEvents = jest.fn(async roomId => [{
event_id: "$1:server",
type: "m.room.message",
content: { body: "1" },
sender: "@1:server",
room_id: roomId,
origin_server_ts: 1,
txn_id: "txn1",
}, {
event_id: "$2:server",
type: "m.room.encrypted",
content: { body: "2" },
sender: "@2:server",
room_id: roomId,
origin_server_ts: 2,
txn_id: "txn2",
}]);
const room = new Room(roomId, client, userA, {
pendingEventOrdering: PendingEventOrdering.Detached,
});
await emitPromise(room, RoomEvent.LocalEchoUpdated);
await emitPromise(client, MatrixEventEvent.Decrypted);
await emitPromise(room, RoomEvent.LocalEchoUpdated);
const pendingEvents = room.getPendingEvents();
expect(pendingEvents).toHaveLength(2);
expect(pendingEvents[1].isDecryptionFailure()).toBeFalsy();
expect(pendingEvents[1].isBeingDecrypted()).toBeFalsy();
expect(pendingEvents[1].isEncrypted()).toBeTruthy();
for (const ev of pendingEvents) {
expect(room.getPendingEvent(ev.getId())).toBe(ev);
}
});
}); });

View File

@ -60,7 +60,7 @@ describe("IndexedDBStore", () => {
expect(await store.getOutOfBandMembers(roomId)).toHaveLength(1); expect(await store.getOutOfBandMembers(roomId)).toHaveLength(1);
// Simulate a broken IDB // Simulate a broken IDB
(store.backend as LocalIndexedDBStoreBackend)["db"].transaction = (): IDBTransaction => { (store.backend as LocalIndexedDBStoreBackend)["db"]!.transaction = (): IDBTransaction => {
const err = new Error("Failed to execute 'transaction' on 'IDBDatabase': " + const err = new Error("Failed to execute 'transaction' on 'IDBDatabase': " +
"The database connection is closing."); "The database connection is closing.");
err.name = "InvalidStateError"; err.name = "InvalidStateError";

View File

@ -3572,7 +3572,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @throws Error if the event is not in QUEUED, NOT_SENT or ENCRYPTING state * @throws Error if the event is not in QUEUED, NOT_SENT or ENCRYPTING state
*/ */
public cancelPendingEvent(event: MatrixEvent) { public cancelPendingEvent(event: MatrixEvent) {
if (![EventStatus.QUEUED, EventStatus.NOT_SENT, EventStatus.ENCRYPTING].includes(event.status)) { if (![EventStatus.QUEUED, EventStatus.NOT_SENT, EventStatus.ENCRYPTING].includes(event.status!)) {
throw new Error("cannot cancel an event with status " + event.status); throw new Error("cannot cancel an event with status " + event.status);
} }
@ -6800,12 +6800,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @param {boolean} options.emit Emits "event.decrypted" if set to true * @param {boolean} options.emit Emits "event.decrypted" if set to true
*/ */
public decryptEventIfNeeded(event: MatrixEvent, options?: IDecryptOptions): Promise<void> { public decryptEventIfNeeded(event: MatrixEvent, options?: IDecryptOptions): Promise<void> {
if (event.shouldAttemptDecryption()) { if (event.shouldAttemptDecryption() && this.isCryptoEnabled()) {
event.attemptDecryption(this.crypto, options); event.attemptDecryption(this.crypto!, options);
} }
if (event.isBeingDecrypted()) { if (event.isBeingDecrypted()) {
return event.getDecryptionPromise(); return event.getDecryptionPromise()!;
} else { } else {
return Promise.resolve(); return Promise.resolve();
} }
@ -9036,8 +9036,8 @@ export function fixNotificationCountOnDecryption(cli: MatrixClient, event: Matri
// TODO: Handle mentions received while the client is offline // TODO: Handle mentions received while the client is offline
// See also https://github.com/vector-im/element-web/issues/9069 // See also https://github.com/vector-im/element-web/issues/9069
const hasReadEvent = isThreadEvent const hasReadEvent = isThreadEvent
? room.getThread(event.threadRootId).hasUserReadEvent(cli.getUserId(), event.getId()) ? room.getThread(event.threadRootId)?.hasUserReadEvent(cli.getUserId()!, event.getId())
: room.hasUserReadEvent(cli.getUserId(), event.getId()); : room.hasUserReadEvent(cli.getUserId()!, event.getId());
if (!hasReadEvent) { if (!hasReadEvent) {
let newCount = currentCount; let newCount = currentCount;

View File

@ -35,7 +35,7 @@ import * as utils from "./utils";
*/ */
export function getHttpUriForMxc( export function getHttpUriForMxc(
baseUrl: string, baseUrl: string,
mxc: string, mxc?: string,
width?: number, width?: number,
height?: number, height?: number,
resizeMethod?: string, resizeMethod?: string,

View File

@ -107,8 +107,8 @@ export class Filter {
} }
private definition: IFilterDefinition = {}; private definition: IFilterDefinition = {};
private roomFilter: FilterComponent; private roomFilter?: FilterComponent;
private roomTimelineFilter: FilterComponent; private roomTimelineFilter?: FilterComponent;
constructor(public readonly userId: string | undefined | null, public filterId?: string) {} constructor(public readonly userId: string | undefined | null, public filterId?: string) {}
@ -116,7 +116,7 @@ export class Filter {
* Get the ID of this filter on your homeserver (if known) * Get the ID of this filter on your homeserver (if known)
* @return {?string} The filter ID * @return {?string} The filter ID
*/ */
getFilterId(): string | null { public getFilterId(): string | undefined {
return this.filterId; return this.filterId;
} }
@ -124,7 +124,7 @@ export class Filter {
* Get the JSON body of the filter. * Get the JSON body of the filter.
* @return {Object} The filter definition * @return {Object} The filter definition
*/ */
getDefinition(): IFilterDefinition { public getDefinition(): IFilterDefinition {
return this.definition; return this.definition;
} }
@ -132,7 +132,7 @@ export class Filter {
* Set the JSON body of the filter * Set the JSON body of the filter
* @param {Object} definition The filter definition * @param {Object} definition The filter definition
*/ */
setDefinition(definition: IFilterDefinition) { public setDefinition(definition: IFilterDefinition) {
this.definition = definition; this.definition = definition;
// This is all ported from synapse's FilterCollection() // This is all ported from synapse's FilterCollection()
@ -201,7 +201,7 @@ export class Filter {
* Get the room.timeline filter component of the filter * Get the room.timeline filter component of the filter
* @return {FilterComponent} room timeline filter component * @return {FilterComponent} room timeline filter component
*/ */
getRoomTimelineFilterComponent(): FilterComponent { public getRoomTimelineFilterComponent(): FilterComponent | undefined {
return this.roomTimelineFilter; return this.roomTimelineFilter;
} }
@ -211,15 +211,21 @@ export class Filter {
* @param {MatrixEvent[]} events the list of events being filtered * @param {MatrixEvent[]} events the list of events being filtered
* @return {MatrixEvent[]} the list of events which match the filter * @return {MatrixEvent[]} the list of events which match the filter
*/ */
filterRoomTimeline(events: MatrixEvent[]): MatrixEvent[] { public filterRoomTimeline(events: MatrixEvent[]): MatrixEvent[] {
return this.roomTimelineFilter.filter(this.roomFilter.filter(events)); if (this.roomFilter) {
events = this.roomFilter.filter(events);
}
if (this.roomTimelineFilter) {
events = this.roomTimelineFilter.filter(events);
}
return events;
} }
/** /**
* Set the max number of events to return for each room's timeline. * Set the max number of events to return for each room's timeline.
* @param {Number} limit The max number of events to return for each room. * @param {Number} limit The max number of events to return for each room.
*/ */
setTimelineLimit(limit: number) { public setTimelineLimit(limit: number) {
setProp(this.definition, "room.timeline.limit", limit); setProp(this.definition, "room.timeline.limit", limit);
} }
@ -234,14 +240,14 @@ export class Filter {
...this.definition?.room, ...this.definition?.room,
timeline: { timeline: {
...this.definition?.room?.timeline, ...this.definition?.room?.timeline,
[UNREAD_THREAD_NOTIFICATIONS.name]: !!enabled, [UNREAD_THREAD_NOTIFICATIONS.name]: enabled,
}, },
}, },
}; };
} }
setLazyLoadMembers(enabled: boolean): void { public setLazyLoadMembers(enabled: boolean): void {
setProp(this.definition, "room.state.lazy_load_members", !!enabled); setProp(this.definition, "room.state.lazy_load_members", enabled);
} }
/** /**
@ -249,7 +255,7 @@ export class Filter {
* @param {boolean} includeLeave True to make rooms the user has left appear * @param {boolean} includeLeave True to make rooms the user has left appear
* in responses. * in responses.
*/ */
setIncludeLeaveRooms(includeLeave: boolean) { public setIncludeLeaveRooms(includeLeave: boolean) {
setProp(this.definition, "room.include_leave", includeLeave); setProp(this.definition, "room.include_leave", includeLeave);
} }
} }

View File

@ -112,11 +112,11 @@ export class MatrixHttpApi<O extends IHttpOpts> extends FetchHttpApi<O> {
defer.resolve(JSON.parse(xhr.responseText)); defer.resolve(JSON.parse(xhr.responseText));
} }
} catch (err) { } catch (err) {
if (err.name === "AbortError") { if ((<Error>err).name === "AbortError") {
defer.reject(err); defer.reject(err);
return; return;
} }
defer.reject(new ConnectionError("request failed", err)); defer.reject(new ConnectionError("request failed", <Error>err));
} }
break; break;
} }
@ -207,7 +207,7 @@ export class MatrixHttpApi<O extends IHttpOpts> extends FetchHttpApi<O> {
base: this.opts.baseUrl, base: this.opts.baseUrl,
path: MediaPrefix.R0 + "/upload", path: MediaPrefix.R0 + "/upload",
params: { params: {
access_token: this.opts.accessToken, access_token: this.opts.accessToken!,
}, },
}; };
} }

View File

@ -82,7 +82,7 @@ export class EventContext {
* backwards in time * backwards in time
* @return {string} * @return {string}
*/ */
public getPaginateToken(backwards = false): string { public getPaginateToken(backwards = false): string | null {
return this.paginateTokens[backwards ? Direction.Backward : Direction.Forward]; return this.paginateTokens[backwards ? Direction.Backward : Direction.Forward];
} }

View File

@ -75,13 +75,22 @@ export interface IAddLiveEventOptions
type EmittedEvents = RoomEvent.Timeline | RoomEvent.TimelineReset; type EmittedEvents = RoomEvent.Timeline | RoomEvent.TimelineReset;
export type EventTimelineSetHandlerMap = { export type EventTimelineSetHandlerMap = {
[RoomEvent.Timeline]: [RoomEvent.Timeline]: (
(event: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed: boolean, data: IRoomTimelineData) => void; event: MatrixEvent,
[RoomEvent.TimelineReset]: (room: Room, eventTimelineSet: EventTimelineSet, resetAllTimelines: boolean) => void; room: Room | undefined,
toStartOfTimeline: boolean | undefined,
removed: boolean,
data: IRoomTimelineData,
) => void;
[RoomEvent.TimelineReset]: (
room: Room | undefined,
eventTimelineSet: EventTimelineSet,
resetAllTimelines: boolean,
) => void;
}; };
export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTimelineSetHandlerMap> { export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTimelineSetHandlerMap> {
public readonly relations?: RelationsContainer; public readonly relations: RelationsContainer;
private readonly timelineSupport: boolean; private readonly timelineSupport: boolean;
private readonly displayPendingEvents: boolean; private readonly displayPendingEvents: boolean;
private liveTimeline: EventTimeline; private liveTimeline: EventTimeline;
@ -145,7 +154,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
this.filter = opts.filter; this.filter = opts.filter;
this.relations = this.room?.relations ?? new RelationsContainer(room?.client ?? client); this.relations = this.room?.relations ?? new RelationsContainer(room?.client ?? client!);
} }
/** /**
@ -212,7 +221,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
* @param {String} eventId the eventId being sought * @param {String} eventId the eventId being sought
* @return {module:models/event-timeline~EventTimeline} timeline * @return {module:models/event-timeline~EventTimeline} timeline
*/ */
public eventIdToTimeline(eventId: string): EventTimeline { public eventIdToTimeline(eventId: string): EventTimeline | undefined {
return this._eventIdToTimeline.get(eventId); return this._eventIdToTimeline.get(eventId);
} }
@ -268,15 +277,13 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
if (forwardPaginationToken) { if (forwardPaginationToken) {
// Now set the forward pagination token on the old live timeline // Now set the forward pagination token on the old live timeline
// so it can be forward-paginated. // so it can be forward-paginated.
oldTimeline.setPaginationToken( oldTimeline.setPaginationToken(forwardPaginationToken, EventTimeline.FORWARDS);
forwardPaginationToken, EventTimeline.FORWARDS,
);
} }
// make sure we set the pagination token before firing timelineReset, // make sure we set the pagination token before firing timelineReset,
// otherwise clients which start back-paginating will fail, and then get // otherwise clients which start back-paginating will fail, and then get
// stuck without realising that they *can* back-paginate. // stuck without realising that they *can* back-paginate.
newTimeline.setPaginationToken(backPaginationToken, EventTimeline.BACKWARDS); newTimeline.setPaginationToken(backPaginationToken ?? null, EventTimeline.BACKWARDS);
// Now we can swap the live timeline to the new one. // Now we can swap the live timeline to the new one.
this.liveTimeline = newTimeline; this.liveTimeline = newTimeline;
@ -352,7 +359,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
events: MatrixEvent[], events: MatrixEvent[],
toStartOfTimeline: boolean, toStartOfTimeline: boolean,
timeline: EventTimeline, timeline: EventTimeline,
paginationToken: string, paginationToken?: string,
): void { ): void {
if (!timeline) { if (!timeline) {
throw new Error( throw new Error(
@ -544,7 +551,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
); );
return; return;
} }
timeline.setPaginationToken(paginationToken, direction); timeline.setPaginationToken(paginationToken ?? null, direction);
} }
} }
@ -579,7 +586,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
roomState?: RoomState, roomState?: RoomState,
): void { ): void {
let duplicateStrategy = duplicateStrategyOrOpts as DuplicateStrategy || DuplicateStrategy.Ignore; let duplicateStrategy = duplicateStrategyOrOpts as DuplicateStrategy || DuplicateStrategy.Ignore;
let timelineWasEmpty: boolean; let timelineWasEmpty: boolean | undefined;
if (typeof (duplicateStrategyOrOpts) === 'object') { if (typeof (duplicateStrategyOrOpts) === 'object') {
({ ({
duplicateStrategy = DuplicateStrategy.Ignore, duplicateStrategy = DuplicateStrategy.Ignore,
@ -681,7 +688,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
roomState?: RoomState, roomState?: RoomState,
): void { ): void {
let toStartOfTimeline = !!toStartOfTimelineOrOpts; let toStartOfTimeline = !!toStartOfTimelineOrOpts;
let timelineWasEmpty: boolean; let timelineWasEmpty: boolean | undefined;
if (typeof (toStartOfTimelineOrOpts) === 'object') { if (typeof (toStartOfTimelineOrOpts) === 'object') {
({ toStartOfTimeline, fromCache = false, roomState, timelineWasEmpty } = toStartOfTimelineOrOpts); ({ toStartOfTimeline, fromCache = false, roomState, timelineWasEmpty } = toStartOfTimelineOrOpts);
} else if (toStartOfTimelineOrOpts !== undefined) { } else if (toStartOfTimelineOrOpts !== undefined) {
@ -794,10 +801,9 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
} }
if (timeline1 === timeline2) { if (timeline1 === timeline2) {
// both events are in the same timeline - figure out their // both events are in the same timeline - figure out their relative indices
// relative indices let idx1: number | undefined = undefined;
let idx1: number; let idx2: number | undefined = undefined;
let idx2: number;
const events = timeline1.getEvents(); const events = timeline1.getEvents();
for (let idx = 0; idx < events.length && for (let idx = 0; idx < events.length &&
(idx1 === undefined || idx2 === undefined); idx++) { (idx1 === undefined || idx2 === undefined); idx++) {
@ -809,7 +815,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
idx2 = idx; idx2 = idx;
} }
} }
return idx1 - idx2; return idx1! - idx2!;
} }
// the events are in different timelines. Iterate through the // the events are in different timelines. Iterate through the

View File

@ -195,15 +195,15 @@ export type MatrixEventHandlerMap = {
[MatrixEventEvent.BeforeRedaction]: (event: MatrixEvent, redactionEvent: MatrixEvent) => void; [MatrixEventEvent.BeforeRedaction]: (event: MatrixEvent, redactionEvent: MatrixEvent) => void;
[MatrixEventEvent.VisibilityChange]: (event: MatrixEvent, visible: boolean) => void; [MatrixEventEvent.VisibilityChange]: (event: MatrixEvent, visible: boolean) => void;
[MatrixEventEvent.LocalEventIdReplaced]: (event: MatrixEvent) => void; [MatrixEventEvent.LocalEventIdReplaced]: (event: MatrixEvent) => void;
[MatrixEventEvent.Status]: (event: MatrixEvent, status: EventStatus) => void; [MatrixEventEvent.Status]: (event: MatrixEvent, status: EventStatus | null) => void;
[MatrixEventEvent.Replaced]: (event: MatrixEvent) => void; [MatrixEventEvent.Replaced]: (event: MatrixEvent) => void;
[MatrixEventEvent.RelationsCreated]: (relationType: string, eventType: string) => void; [MatrixEventEvent.RelationsCreated]: (relationType: string, eventType: string) => void;
} & ThreadEventHandlerMap; } & ThreadEventHandlerMap;
export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHandlerMap> { export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHandlerMap> {
private pushActions: IActionsObject = null; private pushActions: IActionsObject | null = null;
private _replacingEvent: MatrixEvent = null; private _replacingEvent: MatrixEvent | null = null;
private _localRedactionEvent: MatrixEvent = null; private _localRedactionEvent: MatrixEvent | null = null;
private _isCancelled = false; private _isCancelled = false;
private clearEvent?: IClearEvent; private clearEvent?: IClearEvent;
@ -222,12 +222,12 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
/* curve25519 key which we believe belongs to the sender of the event. See /* curve25519 key which we believe belongs to the sender of the event. See
* getSenderKey() * getSenderKey()
*/ */
private senderCurve25519Key: string = null; private senderCurve25519Key: string | null = null;
/* ed25519 key which the sender of this event (for olm) or the creator of /* ed25519 key which the sender of this event (for olm) or the creator of
* the megolm session (for megolm) claims to own. See getClaimedEd25519Key() * the megolm session (for megolm) claims to own. See getClaimedEd25519Key()
*/ */
private claimedEd25519Key: string = null; private claimedEd25519Key: string | null = null;
/* curve25519 keys of devices involved in telling us about the /* curve25519 keys of devices involved in telling us about the
* senderCurve25519Key and claimedEd25519Key. * senderCurve25519Key and claimedEd25519Key.
@ -237,12 +237,12 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
/* where the decryption key is untrusted /* where the decryption key is untrusted
*/ */
private untrusted: boolean = null; private untrusted: boolean | null = null;
/* if we have a process decrypting this event, a Promise which resolves /* if we have a process decrypting this event, a Promise which resolves
* when it is finished. Normally null. * when it is finished. Normally null.
*/ */
private _decryptionPromise: Promise<void> = null; private decryptionPromise: Promise<void> | null = null;
/* flag to indicate if we should retry decrypting this event after the /* flag to indicate if we should retry decrypting this event after the
* first attempt (eg, we have received new data which means that a second * first attempt (eg, we have received new data which means that a second
@ -253,14 +253,14 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
/* The txnId with which this event was sent if it was during this session, /* The txnId with which this event was sent if it was during this session,
* allows for a unique ID which does not change when the event comes back down sync. * allows for a unique ID which does not change when the event comes back down sync.
*/ */
private txnId: string = null; private txnId?: string;
/** /**
* @experimental * @experimental
* A reference to the thread this event belongs to * A reference to the thread this event belongs to
*/ */
private thread: Thread = null; private thread?: Thread;
private threadId: string; private threadId?: string;
/* Set an approximate timestamp for the event relative the local clock. /* Set an approximate timestamp for the event relative the local clock.
* This will inherently be approximate because it doesn't take into account * This will inherently be approximate because it doesn't take into account
@ -271,17 +271,17 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
public localTimestamp: number; public localTimestamp: number;
// XXX: these should be read-only // XXX: these should be read-only
public sender: RoomMember = null; public sender: RoomMember | null = null;
public target: RoomMember = null; public target: RoomMember | null = null;
public status: EventStatus = null; public status: EventStatus | null = null;
public error: MatrixError = null; public error: MatrixError | null = null;
public forwardLooking = true; // only state events may be backwards looking public forwardLooking = true; // only state events may be backwards looking
/* If the event is a `m.key.verification.request` (or to_device `m.key.verification.start`) event, /* If the event is a `m.key.verification.request` (or to_device `m.key.verification.start`) event,
* `Crypto` will set this the `VerificationRequest` for the event * `Crypto` will set this the `VerificationRequest` for the event
* so it can be easily accessed from the timeline. * so it can be easily accessed from the timeline.
*/ */
public verificationRequest: VerificationRequest = null; public verificationRequest?: VerificationRequest;
private readonly reEmitter: TypedReEmitter<EmittedEvents, MatrixEventHandlerMap>; private readonly reEmitter: TypedReEmitter<EmittedEvents, MatrixEventHandlerMap>;
@ -332,7 +332,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
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 || null; this.txnId = event.txn_id;
this.localTimestamp = Date.now() - (this.getAge() ?? 0); this.localTimestamp = Date.now() - (this.getAge() ?? 0);
this.reEmitter = new TypedReEmitter(this); this.reEmitter = new TypedReEmitter(this);
} }
@ -639,11 +639,11 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
* @return {boolean} True if this event is currently being decrypted, else false. * @return {boolean} True if this event is currently being decrypted, else false.
*/ */
public isBeingDecrypted(): boolean { public isBeingDecrypted(): boolean {
return this._decryptionPromise != null; return this.decryptionPromise != null;
} }
public getDecryptionPromise(): Promise<void> { public getDecryptionPromise(): Promise<void> | null {
return this._decryptionPromise; return this.decryptionPromise;
} }
/** /**
@ -713,16 +713,16 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
// attempts going at the same time, so just set a flag that says we have // attempts going at the same time, so just set a flag that says we have
// new info. // new info.
// //
if (this._decryptionPromise) { if (this.decryptionPromise) {
logger.log( logger.log(
`Event ${this.getId()} already being decrypted; queueing a retry`, `Event ${this.getId()} already being decrypted; queueing a retry`,
); );
this.retryDecryption = true; this.retryDecryption = true;
return this._decryptionPromise; return this.decryptionPromise;
} }
this._decryptionPromise = this.decryptionLoop(crypto, options); this.decryptionPromise = this.decryptionLoop(crypto, options);
return this._decryptionPromise; return this.decryptionPromise;
} }
/** /**
@ -768,9 +768,9 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
private async decryptionLoop(crypto: Crypto, options: IDecryptOptions = {}): Promise<void> { private async decryptionLoop(crypto: Crypto, options: IDecryptOptions = {}): Promise<void> {
// make sure that this method never runs completely synchronously. // make sure that this method never runs completely synchronously.
// (doing so would mean that we would clear _decryptionPromise *before* // (doing so would mean that we would clear decryptionPromise *before*
// it is set in attemptDecryption - and hence end up with a stuck // it is set in attemptDecryption - and hence end up with a stuck
// `_decryptionPromise`). // `decryptionPromise`).
await Promise.resolve(); await Promise.resolve();
// eslint-disable-next-line no-constant-condition // eslint-disable-next-line no-constant-condition
@ -778,7 +778,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
this.retryDecryption = false; this.retryDecryption = false;
let res: IEventDecryptionResult; let res: IEventDecryptionResult;
let err: Error; let err: Error | undefined = undefined;
try { try {
if (!crypto) { if (!crypto) {
res = this.badEncryptedMessage("Encryption not enabled"); res = this.badEncryptedMessage("Encryption not enabled");
@ -789,7 +789,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
} }
} }
} catch (e) { } catch (e) {
if (e.name !== "DecryptionError") { if ((<Error>e).name !== "DecryptionError") {
// not a decryption error: log the whole exception as an error // not a decryption error: log the whole exception as an error
// (and don't bother with a retry) // (and don't bother with a retry)
const re = options.isRetry ? 're' : ''; const re = options.isRetry ? 're' : '';
@ -799,25 +799,25 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
`Error ${re}decrypting event ` + `Error ${re}decrypting event ` +
`(id=${this.getId()}): ${e.stack || e}`, `(id=${this.getId()}): ${e.stack || e}`,
); );
this._decryptionPromise = null; this.decryptionPromise = null;
this.retryDecryption = false; this.retryDecryption = false;
return; return;
} }
err = e; err = e as Error;
// see if we have a retry queued. // see if we have a retry queued.
// //
// NB: make sure to keep this check in the same tick of the // NB: make sure to keep this check in the same tick of the
// event loop as `_decryptionPromise = null` below - otherwise we // event loop as `decryptionPromise = null` below - otherwise we
// risk a race: // risk a race:
// //
// * A: we check retryDecryption here and see that it is // * A: we check retryDecryption here and see that it is
// false // false
// * B: we get a second call to attemptDecryption, which sees // * B: we get a second call to attemptDecryption, which sees
// that _decryptionPromise is set so sets // that decryptionPromise is set so sets
// retryDecryption // retryDecryption
// * A: we continue below, clear _decryptionPromise, and // * A: we continue below, clear decryptionPromise, and
// never do the retry. // never do the retry.
// //
if (this.retryDecryption) { if (this.retryDecryption) {
@ -837,13 +837,13 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
// (and set res to a 'badEncryptedMessage'). Either way, we can now set the // (and set res to a 'badEncryptedMessage'). Either way, we can now set the
// cleartext of the event and raise Event.decrypted. // cleartext of the event and raise Event.decrypted.
// //
// make sure we clear '_decryptionPromise' before sending the 'Event.decrypted' event, // make sure we clear 'decryptionPromise' before sending the 'Event.decrypted' event,
// otherwise the app will be confused to see `isBeingDecrypted` still set when // otherwise the app will be confused to see `isBeingDecrypted` still set when
// there isn't an `Event.decrypted` on the way. // there isn't an `Event.decrypted` on the way.
// //
// see also notes on retryDecryption above. // see also notes on retryDecryption above.
// //
this._decryptionPromise = null; this.decryptionPromise = null;
this.retryDecryption = false; this.retryDecryption = false;
this.setClearData(res); this.setClearData(res);
@ -889,10 +889,8 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
*/ */
private setClearData(decryptionResult: IEventDecryptionResult): void { private setClearData(decryptionResult: IEventDecryptionResult): void {
this.clearEvent = decryptionResult.clearEvent; this.clearEvent = decryptionResult.clearEvent;
this.senderCurve25519Key = this.senderCurve25519Key = decryptionResult.senderCurve25519Key ?? null;
decryptionResult.senderCurve25519Key || null; this.claimedEd25519Key = decryptionResult.claimedEd25519Key ?? null;
this.claimedEd25519Key =
decryptionResult.claimedEd25519Key || null;
this.forwardingCurve25519KeyChain = this.forwardingCurve25519KeyChain =
decryptionResult.forwardingCurve25519KeyChain || []; decryptionResult.forwardingCurve25519KeyChain || [];
this.untrusted = decryptionResult.untrusted || false; this.untrusted = decryptionResult.untrusted || false;
@ -941,7 +939,9 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
* *
* @return {Object<string, string>} * @return {Object<string, string>}
*/ */
public getKeysClaimed(): Record<"ed25519", string> { public getKeysClaimed(): Partial<Record<"ed25519", string>> {
if (!this.claimedEd25519Key) return {};
return { return {
ed25519: this.claimedEd25519Key, ed25519: this.claimedEd25519Key,
}; };
@ -992,8 +992,8 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
* *
* @return {boolean} * @return {boolean}
*/ */
public isKeySourceUntrusted(): boolean { public isKeySourceUntrusted(): boolean | undefined {
return this.untrusted; return !!this.untrusted;
} }
public getUnsigned(): IUnsigned { public getUnsigned(): IUnsigned {
@ -1035,10 +1035,10 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
* by a visibility event being redacted). * by a visibility event being redacted).
*/ */
public applyVisibilityEvent(visibilityChange?: IVisibilityChange): void { public applyVisibilityEvent(visibilityChange?: IVisibilityChange): void {
const visible = visibilityChange ? visibilityChange.visible : true; const visible = visibilityChange?.visible ?? true;
const reason = visibilityChange ? visibilityChange.reason : null; const reason = visibilityChange?.reason ?? null;
let change = false; let change = false;
if (this.visibility.visible !== visibilityChange.visible) { if (this.visibility.visible !== visible) {
change = true; change = true;
} else if (!this.visibility.visible && this.visibility["reason"] !== reason) { } else if (!this.visibility.visible && this.visibility["reason"] !== reason) {
change = true; change = true;
@ -1049,7 +1049,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
} else { } else {
this.visibility = Object.freeze({ this.visibility = Object.freeze({
visible: false, visible: false,
reason: reason, reason,
}); });
} }
this.emit(MatrixEventEvent.VisibilityChange, this, visible); this.emit(MatrixEventEvent.VisibilityChange, this, visible);
@ -1105,7 +1105,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
// If the event is encrypted prune the decrypted bits // If the event is encrypted prune the decrypted bits
if (this.isEncrypted()) { if (this.isEncrypted()) {
this.clearEvent = null; this.clearEvent = undefined;
} }
const keeps = REDACT_KEEP_CONTENT_MAP[this.getType()] || {}; const keeps = REDACT_KEEP_CONTENT_MAP[this.getType()] || {};
@ -1217,7 +1217,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
* *
* @param {Object} pushActions push actions * @param {Object} pushActions push actions
*/ */
public setPushActions(pushActions: IActionsObject): void { public setPushActions(pushActions: IActionsObject | null): void {
this.pushActions = pushActions; this.pushActions = pushActions;
} }
@ -1265,7 +1265,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
* *
* @param {String} status The new status * @param {String} status The new status
*/ */
public setStatus(status: EventStatus): void { public setStatus(status: EventStatus | null): void {
this.status = status; this.status = status;
this.emit(MatrixEventEvent.Status, this, status); this.emit(MatrixEventEvent.Status, this, status);
} }
@ -1283,7 +1283,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
* given type * given type
* @return {boolean} * @return {boolean}
*/ */
public isRelation(relType: string = undefined): boolean { public isRelation(relType?: string): boolean {
// Relation info is lifted out of the encrypted content when sent to // Relation info is lifted out of the encrypted content when sent to
// encrypted rooms, so we have to check `getWireContent` for this. // encrypted rooms, so we have to check `getWireContent` for this.
const relation = this.getWireContent()?.["m.relates_to"]; const relation = this.getWireContent()?.["m.relates_to"];
@ -1326,7 +1326,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
return; return;
} }
if (this._replacingEvent !== newEvent) { if (this._replacingEvent !== newEvent) {
this._replacingEvent = newEvent; this._replacingEvent = newEvent ?? null;
this.emit(MatrixEventEvent.Replaced, this); this.emit(MatrixEventEvent.Replaced, this);
this.invalidateExtensibleEvent(); this.invalidateExtensibleEvent();
} }
@ -1339,7 +1339,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
* *
* @return {EventStatus} * @return {EventStatus}
*/ */
public getAssociatedStatus(): EventStatus | undefined { public getAssociatedStatus(): EventStatus | null {
if (this._replacingEvent) { if (this._replacingEvent) {
return this._replacingEvent.status; return this._replacingEvent.status;
} else if (this._localRedactionEvent) { } else if (this._localRedactionEvent) {
@ -1373,7 +1373,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
* *
* @return {MatrixEvent?} * @return {MatrixEvent?}
*/ */
public replacingEvent(): MatrixEvent | undefined { public replacingEvent(): MatrixEvent | null {
return this._replacingEvent; return this._replacingEvent;
} }
@ -1398,7 +1398,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
* Returns the event that wants to redact this event, but hasn't been sent yet. * Returns the event that wants to redact this event, but hasn't been sent yet.
* @return {MatrixEvent} the event * @return {MatrixEvent} the event
*/ */
public localRedactionEvent(): MatrixEvent | undefined { public localRedactionEvent(): MatrixEvent | null {
return this._localRedactionEvent; return this._localRedactionEvent;
} }

View File

@ -160,7 +160,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
* @param {MatrixEvent} event The event whose status has changed * @param {MatrixEvent} event The event whose status has changed
* @param {EventStatus} status The new status * @param {EventStatus} status The new status
*/ */
private onEventStatus = (event: MatrixEvent, status: EventStatus) => { private onEventStatus = (event: MatrixEvent, status: EventStatus | null) => {
if (!event.isSending()) { if (!event.isSending()) {
// Sending is done, so we don't need to listen anymore // Sending is done, so we don't need to listen anymore
event.removeListener(MatrixEventEvent.Status, this.onEventStatus); event.removeListener(MatrixEventEvent.Status, this.onEventStatus);

View File

@ -35,7 +35,7 @@ export enum RoomMemberEvent {
} }
export type RoomMemberEventHandlerMap = { export type RoomMemberEventHandlerMap = {
[RoomMemberEvent.Membership]: (event: MatrixEvent, member: RoomMember, oldMembership: string | null) => void; [RoomMemberEvent.Membership]: (event: MatrixEvent, member: RoomMember, oldMembership?: string) => void;
[RoomMemberEvent.Name]: (event: MatrixEvent, member: RoomMember, oldName: string | null) => void; [RoomMemberEvent.Name]: (event: MatrixEvent, member: RoomMember, oldName: string | null) => void;
[RoomMemberEvent.PowerLevel]: (event: MatrixEvent, member: RoomMember) => void; [RoomMemberEvent.PowerLevel]: (event: MatrixEvent, member: RoomMember) => void;
[RoomMemberEvent.Typing]: (event: MatrixEvent, member: RoomMember) => void; [RoomMemberEvent.Typing]: (event: MatrixEvent, member: RoomMember) => void;
@ -43,8 +43,8 @@ export type RoomMemberEventHandlerMap = {
export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEventHandlerMap> { export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEventHandlerMap> {
private _isOutOfBand = false; private _isOutOfBand = false;
private _modified: number; private modified = -1;
public _requestedProfileInfo: boolean; // used by sync.ts public requestedProfileInfo = false; // used by sync.ts
// XXX these should be read-only // XXX these should be read-only
public typing = false; public typing = false;
@ -52,14 +52,12 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
public rawDisplayName: string; public rawDisplayName: string;
public powerLevel = 0; public powerLevel = 0;
public powerLevelNorm = 0; public powerLevelNorm = 0;
public user?: User = null; public user?: User;
public membership: string = null; public membership?: string;
public disambiguate = false; public disambiguate = false;
public events: { public events: {
member?: MatrixEvent; member?: MatrixEvent;
} = { } = {};
member: null,
};
/** /**
* Construct a new room member. * Construct a new room member.
@ -119,7 +117,7 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
* @fires module:client~MatrixClient#event:"RoomMember.membership" * @fires module:client~MatrixClient#event:"RoomMember.membership"
*/ */
public setMembershipEvent(event: MatrixEvent, roomState?: RoomState): void { public setMembershipEvent(event: MatrixEvent, roomState?: RoomState): void {
const displayName = event.getDirectionalContent().displayname; const displayName = event.getDirectionalContent().displayname ?? "";
if (event.getType() !== EventType.RoomMember) { if (event.getType() !== EventType.RoomMember) {
return; return;
@ -141,23 +139,14 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
); );
} }
this.disambiguate = shouldDisambiguate( this.disambiguate = shouldDisambiguate(this.userId, displayName, roomState);
this.userId,
displayName,
roomState,
);
const oldName = this.name; const oldName = this.name;
this.name = calculateDisplayName( this.name = calculateDisplayName(this.userId, displayName, this.disambiguate);
this.userId,
displayName,
roomState,
this.disambiguate,
);
// not quite raw: we strip direction override chars so it can safely be inserted into // not quite raw: we strip direction override chars so it can safely be inserted into
// blocks of text without breaking the text direction // blocks of text without breaking the text direction
this.rawDisplayName = utils.removeDirectionOverrideChars(event.getDirectionalContent().displayname); this.rawDisplayName = utils.removeDirectionOverrideChars(event.getDirectionalContent().displayname ?? "");
if (!this.rawDisplayName || !utils.removeHiddenChars(this.rawDisplayName)) { if (!this.rawDisplayName || !utils.removeHiddenChars(this.rawDisplayName)) {
this.rawDisplayName = this.userId; this.rawDisplayName = this.userId;
} }
@ -180,15 +169,15 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
* @fires module:client~MatrixClient#event:"RoomMember.powerLevel" * @fires module:client~MatrixClient#event:"RoomMember.powerLevel"
*/ */
public setPowerLevelEvent(powerLevelEvent: MatrixEvent): void { public setPowerLevelEvent(powerLevelEvent: MatrixEvent): void {
if (powerLevelEvent.getType() !== "m.room.power_levels") { if (powerLevelEvent.getType() !== EventType.RoomPowerLevels || powerLevelEvent.getStateKey() !== "") {
return; return;
} }
const evContent = powerLevelEvent.getDirectionalContent(); const evContent = powerLevelEvent.getDirectionalContent();
let maxLevel = evContent.users_default || 0; let maxLevel = evContent.users_default || 0;
const users = evContent.users || {}; const users: { [userId: string]: number } = evContent.users || {};
Object.values(users).forEach(function(lvl: number) { Object.values(users).forEach((lvl: number) => {
maxLevel = Math.max(maxLevel, lvl); maxLevel = Math.max(maxLevel, lvl);
}); });
const oldPowerLevel = this.powerLevel; const oldPowerLevel = this.powerLevel;
@ -244,7 +233,7 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
* Update the last modified time to the current time. * Update the last modified time to the current time.
*/ */
private updateModifiedTime() { private updateModifiedTime() {
this._modified = Date.now(); this.modified = Date.now();
} }
/** /**
@ -254,12 +243,13 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
* @return {number} The timestamp * @return {number} The timestamp
*/ */
public getLastModifiedTime(): number { public getLastModifiedTime(): number {
return this._modified; return this.modified;
} }
public isKicked(): boolean { public isKicked(): boolean {
return this.membership === "leave" && return this.membership === "leave"
this.events.member.getSender() !== this.events.member.getStateKey(); && this.events.member !== undefined
&& this.events.member.getSender() !== this.events.member.getStateKey();
} }
/** /**
@ -267,7 +257,7 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
* the user that invited this member * the user that invited this member
* @return {string} user id of the inviter * @return {string} user id of the inviter
*/ */
public getDMInviter(): string { public getDMInviter(): string | undefined {
// when not available because that room state hasn't been loaded in, // when not available because that room state hasn't been loaded in,
// we don't really know, but more likely to not be a direct chat // we don't really know, but more likely to not be a direct chat
if (this.events.member) { if (this.events.member) {
@ -280,7 +270,7 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
const memberEvent = this.events.member; const memberEvent = this.events.member;
let memberContent = memberEvent.getContent(); let memberContent = memberEvent.getContent();
let inviteSender = memberEvent.getSender(); let inviteSender: string | undefined = memberEvent.getSender();
if (memberContent.membership === "join") { if (memberContent.membership === "join") {
memberContent = memberEvent.getPrevContent(); memberContent = memberEvent.getPrevContent();
@ -335,20 +325,19 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
* get the mxc avatar url, either from a state event, or from a lazily loaded member * get the mxc avatar url, either from a state event, or from a lazily loaded member
* @return {string} the mxc avatar url * @return {string} the mxc avatar url
*/ */
public getMxcAvatarUrl(): string | null { public getMxcAvatarUrl(): string | undefined {
if (this.events.member) { if (this.events.member) {
return this.events.member.getDirectionalContent().avatar_url; return this.events.member.getDirectionalContent().avatar_url;
} else if (this.user) { } else if (this.user) {
return this.user.avatarUrl; return this.user.avatarUrl;
} }
return null;
} }
} }
const MXID_PATTERN = /@.+:.+/; const MXID_PATTERN = /@.+:.+/;
const LTR_RTL_PATTERN = /[\u200E\u200F\u202A-\u202F]/; const LTR_RTL_PATTERN = /[\u200E\u200F\u202A-\u202F]/;
function shouldDisambiguate(selfUserId: string, displayName: string, roomState?: RoomState): boolean { function shouldDisambiguate(selfUserId: string, displayName?: string, roomState?: RoomState): boolean {
if (!displayName || displayName === selfUserId) return false; if (!displayName || displayName === selfUserId) return false;
// First check if the displayname is something we consider truthy // First check if the displayname is something we consider truthy
@ -377,14 +366,13 @@ function shouldDisambiguate(selfUserId: string, displayName: string, roomState?:
function calculateDisplayName( function calculateDisplayName(
selfUserId: string, selfUserId: string,
displayName: string, displayName: string | undefined,
roomState: RoomState,
disambiguate: boolean, disambiguate: boolean,
): string { ): string {
if (disambiguate) return utils.removeDirectionOverrideChars(displayName) + " (" + selfUserId + ")";
if (!displayName || displayName === selfUserId) return selfUserId; if (!displayName || displayName === selfUserId) return selfUserId;
if (disambiguate) return utils.removeDirectionOverrideChars(displayName) + " (" + selfUserId + ")";
// First check if the displayname is something we consider truthy // First check if the displayname is something we consider truthy
// after stripping it of zero width characters and padding spaces // after stripping it of zero width characters and padding spaces
if (!utils.removeHiddenChars(displayName)) return selfUserId; if (!utils.removeHiddenChars(displayName)) return selfUserId;

View File

@ -68,7 +68,7 @@ export type RoomStateEventHandlerMap = {
[RoomStateEvent.NewMember]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void; [RoomStateEvent.NewMember]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void;
[RoomStateEvent.Update]: (state: RoomState) => void; [RoomStateEvent.Update]: (state: RoomState) => void;
[RoomStateEvent.BeaconLiveness]: (state: RoomState, hasLiveBeacons: boolean) => void; [RoomStateEvent.BeaconLiveness]: (state: RoomState, hasLiveBeacons: boolean) => void;
[RoomStateEvent.Marker]: (event: MatrixEvent, setStateOptions: IMarkerFoundOptions) => void; [RoomStateEvent.Marker]: (event: MatrixEvent, setStateOptions?: IMarkerFoundOptions) => void;
[BeaconEvent.New]: (event: MatrixEvent, beacon: Beacon) => void; [BeaconEvent.New]: (event: MatrixEvent, beacon: Beacon) => void;
}; };
@ -82,17 +82,17 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
private displayNameToUserIds = new Map<string, string[]>(); private displayNameToUserIds = new Map<string, string[]>();
private userIdsToDisplayNames: Record<string, string> = {}; private userIdsToDisplayNames: Record<string, string> = {};
private tokenToInvite: Record<string, MatrixEvent> = {}; // 3pid invite state_key to m.room.member invite private tokenToInvite: Record<string, MatrixEvent> = {}; // 3pid invite state_key to m.room.member invite
private joinedMemberCount: number = null; // cache of the number of joined members private joinedMemberCount: number | null = null; // cache of the number of joined members
// joined members count from summary api // joined members count from summary api
// once set, we know the server supports the summary api // once set, we know the server supports the summary api
// and we should only trust that // and we should only trust that
// we could also only trust that before OOB members // we could also only trust that before OOB members
// are loaded but doesn't seem worth the hassle atm // are loaded but doesn't seem worth the hassle atm
private summaryJoinedMemberCount: number = null; private summaryJoinedMemberCount: number |null = null;
// same for invited member count // same for invited member count
private invitedMemberCount: number = null; private invitedMemberCount: number | null = null;
private summaryInvitedMemberCount: number = null; private summaryInvitedMemberCount: number | null = null;
private modified: number; private modified = -1;
// XXX: Should be read-only // XXX: Should be read-only
public members: Record<string, RoomMember> = {}; // userId: RoomMember public members: Record<string, RoomMember> = {}; // userId: RoomMember
@ -232,7 +232,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
if (sentinel === undefined) { if (sentinel === undefined) {
sentinel = new RoomMember(this.roomId, userId); sentinel = new RoomMember(this.roomId, userId);
const member = this.members[userId]; const member = this.members[userId];
if (member) { if (member?.events.member) {
sentinel.setMembershipEvent(member.events.member, this); sentinel.setMembershipEvent(member.events.member, this);
} }
this.sentinels[userId] = sentinel; this.sentinels[userId] = sentinel;
@ -257,9 +257,9 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
return stateKey === undefined ? [] : null; return stateKey === undefined ? [] : null;
} }
if (stateKey === undefined) { // return all values if (stateKey === undefined) { // return all values
return Array.from(this.events.get(eventType).values()); return Array.from(this.events.get(eventType)!.values());
} }
const event = this.events.get(eventType).get(stateKey); const event = this.events.get(eventType)!.get(stateKey);
return event ? event : null; return event ? event : null;
} }
@ -306,8 +306,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
// copy markOutOfBand flags // copy markOutOfBand flags
this.getMembers().forEach((member) => { this.getMembers().forEach((member) => {
if (member.isOutOfBand()) { if (member.isOutOfBand()) {
const copyMember = copy.getMember(member.userId); copy.getMember(member.userId)?.markOutOfBand();
copyMember.markOutOfBand();
} }
}); });
} }
@ -324,8 +323,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
*/ */
public setUnknownStateEvents(events: MatrixEvent[]): void { public setUnknownStateEvents(events: MatrixEvent[]): void {
const unknownStateEvents = events.filter((event) => { const unknownStateEvents = events.filter((event) => {
return !this.events.has(event.getType()) || return !this.events.has(event.getType()) || !this.events.get(event.getType())!.has(event.getStateKey()!);
!this.events.get(event.getType()).has(event.getStateKey());
}); });
this.setStateEvents(unknownStateEvents); this.setStateEvents(unknownStateEvents);
@ -349,12 +347,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
// update the core event dict // update the core event dict
stateEvents.forEach((event) => { stateEvents.forEach((event) => {
if (event.getRoomId() !== this.roomId) { if (event.getRoomId() !== this.roomId || !event.isState()) return;
return;
}
if (!event.isState()) {
return;
}
if (M_BEACON_INFO.matches(event.getType())) { if (M_BEACON_INFO.matches(event.getType())) {
this.setBeacon(event); this.setBeacon(event);
@ -363,7 +356,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
const lastStateEvent = this.getStateEventMatching(event); const lastStateEvent = this.getStateEventMatching(event);
this.setStateEvent(event); this.setStateEvent(event);
if (event.getType() === EventType.RoomMember) { if (event.getType() === EventType.RoomMember) {
this.updateDisplayNameCache(event.getStateKey(), event.getContent().displayname); this.updateDisplayNameCache(event.getStateKey()!, event.getContent().displayname ?? "");
this.updateThirdPartyTokenCache(event); this.updateThirdPartyTokenCache(event);
} }
this.emit(RoomStateEvent.Events, event, this, lastStateEvent); this.emit(RoomStateEvent.Events, event, this, lastStateEvent);
@ -375,15 +368,10 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
// the given array (e.g. disambiguating display names in one go to do both // the given array (e.g. disambiguating display names in one go to do both
// clashing names rather than progressively which only catches 1 of them). // clashing names rather than progressively which only catches 1 of them).
stateEvents.forEach((event) => { stateEvents.forEach((event) => {
if (event.getRoomId() !== this.roomId) { if (event.getRoomId() !== this.roomId || !event.isState()) return;
return;
}
if (!event.isState()) {
return;
}
if (event.getType() === EventType.RoomMember) { if (event.getType() === EventType.RoomMember) {
const userId = event.getStateKey(); const userId = event.getStateKey()!;
// leave events apparently elide the displayname or avatar_url, // leave events apparently elide the displayname or avatar_url,
// so let's fake one up so that we don't leak user ids // so let's fake one up so that we don't leak user ids
@ -456,11 +444,8 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
events.forEach((event: MatrixEvent) => { events.forEach((event: MatrixEvent) => {
const relatedToEventId = event.getRelation()?.event_id; const relatedToEventId = event.getRelation()?.event_id;
// not related to a beacon we know about // not related to a beacon we know about; discard
// discard if (!relatedToEventId || !beaconByEventIdDict[relatedToEventId]) return;
if (!beaconByEventIdDict[relatedToEventId]) {
return;
}
matrixClient.decryptEventIfNeeded(event); matrixClient.decryptEventIfNeeded(event);
@ -501,7 +486,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
if (!this.events.has(event.getType())) { if (!this.events.has(event.getType())) {
this.events.set(event.getType(), new Map()); this.events.set(event.getType(), new Map());
} }
this.events.get(event.getType()).set(event.getStateKey(), event); this.events.get(event.getType())!.set(event.getStateKey()!, event);
} }
/** /**
@ -511,7 +496,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
const beaconIdentifier = getBeaconInfoIdentifier(event); const beaconIdentifier = getBeaconInfoIdentifier(event);
if (this.beacons.has(beaconIdentifier)) { if (this.beacons.has(beaconIdentifier)) {
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 === event.getRedactionEvent()?.['redacts']) {
@ -558,7 +543,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
} }
private getStateEventMatching(event: MatrixEvent): MatrixEvent | null { private getStateEventMatching(event: MatrixEvent): MatrixEvent | null {
return this.events.get(event.getType())?.get(event.getStateKey()) ?? null; return this.events.get(event.getType())?.get(event.getStateKey()!) ?? null;
} }
private updateMember(member: RoomMember): void { private updateMember(member: RoomMember): void {
@ -646,7 +631,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
if (stateEvent.getType() !== EventType.RoomMember) { if (stateEvent.getType() !== EventType.RoomMember) {
return; return;
} }
const userId = stateEvent.getStateKey(); const userId = stateEvent.getStateKey()!;
const existingMember = this.getMember(userId); const existingMember = this.getMember(userId);
// never replace members received as part of the sync // never replace members received as part of the sync
if (existingMember && !existingMember.isOutOfBand()) { if (existingMember && !existingMember.isOutOfBand()) {
@ -788,7 +773,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
* according to the room's state. * according to the room's state.
*/ */
public mayClientSendStateEvent(stateEventType: EventType | string, cli: MatrixClient): boolean { public mayClientSendStateEvent(stateEventType: EventType | string, cli: MatrixClient): boolean {
if (cli.isGuest()) { if (cli.isGuest() || !cli.credentials.userId) {
return false; return false;
} }
return this.maySendStateEvent(stateEventType, cli.credentials.userId); return this.maySendStateEvent(stateEventType, cli.credentials.userId);

View File

@ -69,7 +69,6 @@ export const KNOWN_SAFE_ROOM_VERSION = '9';
const SAFE_ROOM_VERSIONS = ['1', '2', '3', '4', '5', '6', '7', '8', '9']; const SAFE_ROOM_VERSIONS = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
interface IOpts { interface IOpts {
storageToken?: string;
pendingEventOrdering?: PendingEventOrdering; pendingEventOrdering?: PendingEventOrdering;
timelineSupport?: boolean; timelineSupport?: boolean;
lazyLoadMembers?: boolean; lazyLoadMembers?: boolean;
@ -158,7 +157,7 @@ export type RoomEventHandlerMap = {
event: MatrixEvent, event: MatrixEvent,
room: Room, room: Room,
oldEventId?: string, oldEventId?: string,
oldStatus?: EventStatus, oldStatus?: EventStatus | null,
) => void; ) => void;
[RoomEvent.OldStateUpdated]: (room: Room, previousRoomState: RoomState, roomState: RoomState) => void; [RoomEvent.OldStateUpdated]: (room: Room, previousRoomState: RoomState, roomState: RoomState) => void;
[RoomEvent.CurrentStateUpdated]: (room: Room, previousRoomState: RoomState, roomState: RoomState) => void; [RoomEvent.CurrentStateUpdated]: (room: Room, previousRoomState: RoomState, roomState: RoomState) => void;
@ -201,8 +200,8 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
private timelineNeedsRefresh = false; private timelineNeedsRefresh = false;
private readonly pendingEventList?: MatrixEvent[]; private readonly pendingEventList?: MatrixEvent[];
// read by megolm via getter; boolean value - null indicates "use global value" // read by megolm via getter; boolean value - null indicates "use global value"
private blacklistUnverifiedDevices: boolean = null; private blacklistUnverifiedDevices?: boolean;
private selfMembership: string = null; private selfMembership?: string;
private summaryHeroes: string[] = null; private summaryHeroes: string[] = null;
// flags to stop logspam about missing m.room.create events // flags to stop logspam about missing m.room.create events
private getTypeWarning = false; private getTypeWarning = false;
@ -232,10 +231,6 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
* The room summary. * The room summary.
*/ */
public summary: RoomSummary = null; public summary: RoomSummary = null;
/**
* A token which a data store can use to remember the state of the room.
*/
public readonly storageToken?: string;
// legacy fields // legacy fields
/** /**
* The live event timeline for this room, with the oldest event at index 0. * The live event timeline for this room, with the oldest event at index 0.
@ -260,7 +255,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
* @experimental * @experimental
*/ */
private threads = new Map<string, Thread>(); private threads = new Map<string, Thread>();
public lastThread: Thread; public lastThread?: Thread;
/** /**
* A mapping of eventId to all visibility changes to apply * A mapping of eventId to all visibility changes to apply
@ -304,9 +299,6 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
* @param {MatrixClient} client Required. The client, used to lazy load members. * @param {MatrixClient} client Required. The client, used to lazy load members.
* @param {string} myUserId Required. The ID of the syncing user. * @param {string} myUserId Required. The ID of the syncing user.
* @param {Object=} opts Configuration options * @param {Object=} opts Configuration options
* @param {*} opts.storageToken Optional. The token which a data store can use
* to remember the state of the room. What this means is dependent on the store
* implementation.
* *
* @param {String=} opts.pendingEventOrdering Controls where pending messages * @param {String=} opts.pendingEventOrdering Controls where pending messages
* appear in a room's timeline. If "<b>chronological</b>", messages will appear * appear in a room's timeline. If "<b>chronological</b>", messages will appear
@ -332,6 +324,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
opts.pendingEventOrdering = opts.pendingEventOrdering || PendingEventOrdering.Chronological; opts.pendingEventOrdering = opts.pendingEventOrdering || PendingEventOrdering.Chronological;
this.name = roomId; this.name = roomId;
this.normalizedName = roomId;
// all our per-room timeline sets. the first one is the unfiltered ones; // all our per-room timeline sets. the first one is the unfiltered ones;
// the subsequent ones are the filtered ones in no particular order. // the subsequent ones are the filtered ones in no particular order.
@ -346,13 +339,17 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
if (this.opts.pendingEventOrdering === PendingEventOrdering.Detached) { if (this.opts.pendingEventOrdering === PendingEventOrdering.Detached) {
this.pendingEventList = []; this.pendingEventList = [];
this.client.store.getPendingEvents(this.roomId).then(events => { this.client.store.getPendingEvents(this.roomId).then(events => {
const mapper = this.client.getEventMapper({
toDevice: false,
decrypt: false,
});
events.forEach(async (serializedEvent: Partial<IEvent>) => { events.forEach(async (serializedEvent: Partial<IEvent>) => {
const event = new MatrixEvent(serializedEvent); const event = mapper(serializedEvent);
if (event.getType() === EventType.RoomMessageEncrypted) { if (event.getType() === EventType.RoomMessageEncrypted && this.client.isCryptoEnabled()) {
await event.attemptDecryption(this.client.crypto); await event.attemptDecryption(this.client.crypto!);
} }
event.setStatus(EventStatus.NOT_SENT); event.setStatus(EventStatus.NOT_SENT);
this.addPendingEvent(event, event.getTxnId()); this.addPendingEvent(event, event.getTxnId()!);
}); });
}); });
} }
@ -361,7 +358,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
if (!this.opts.lazyLoadMembers) { if (!this.opts.lazyLoadMembers) {
this.membersPromise = Promise.resolve(false); this.membersPromise = Promise.resolve(false);
} else { } else {
this.membersPromise = null; this.membersPromise = undefined;
} }
} }
@ -399,8 +396,10 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
* *
* @returns {Promise} Signals when all events have been decrypted * @returns {Promise} Signals when all events have been decrypted
*/ */
public decryptCriticalEvents(): Promise<void> { public async decryptCriticalEvents(): Promise<void> {
const readReceiptEventId = this.getEventReadUpTo(this.client.getUserId(), true); if (!this.client.isCryptoEnabled()) return;
const readReceiptEventId = this.getEventReadUpTo(this.client.getUserId()!, true);
const events = this.getLiveTimeline().getEvents(); const events = this.getLiveTimeline().getEvents();
const readReceiptTimelineIndex = events.findIndex(matrixEvent => { const readReceiptTimelineIndex = events.findIndex(matrixEvent => {
return matrixEvent.event.event_id === readReceiptEventId; return matrixEvent.event.event_id === readReceiptEventId;
@ -410,9 +409,9 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
.slice(readReceiptTimelineIndex) .slice(readReceiptTimelineIndex)
.filter(event => event.shouldAttemptDecryption()) .filter(event => event.shouldAttemptDecryption())
.reverse() .reverse()
.map(event => event.attemptDecryption(this.client.crypto, { isRetry: true })); .map(event => event.attemptDecryption(this.client.crypto!, { isRetry: true }));
return Promise.allSettled(decryptionPromises) as unknown as Promise<void>; await Promise.allSettled(decryptionPromises);
} }
/** /**
@ -420,16 +419,18 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
* *
* @returns {Promise} Signals when all events have been decrypted * @returns {Promise} Signals when all events have been decrypted
*/ */
public decryptAllEvents(): Promise<void> { public async decryptAllEvents(): Promise<void> {
if (!this.client.isCryptoEnabled()) return;
const decryptionPromises = this const decryptionPromises = this
.getUnfilteredTimelineSet() .getUnfilteredTimelineSet()
.getLiveTimeline() .getLiveTimeline()
.getEvents() .getEvents()
.filter(event => event.shouldAttemptDecryption()) .filter(event => event.shouldAttemptDecryption())
.reverse() .reverse()
.map(event => event.attemptDecryption(this.client.crypto, { isRetry: true })); .map(event => event.attemptDecryption(this.client.crypto!, { isRetry: true }));
return Promise.allSettled(decryptionPromises) as unknown as Promise<void>; await Promise.allSettled(decryptionPromises);
} }
/** /**
@ -445,7 +446,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
* Gets the version of the room * Gets the version of the room
* @returns {string} The version of the room, or null if it could not be determined * @returns {string} The version of the room, or null if it could not be determined
*/ */
public getVersion(): string | null { public getVersion(): string {
const createEvent = this.currentState.getStateEvents(EventType.RoomCreate, ""); const createEvent = this.currentState.getStateEvents(EventType.RoomCreate, "");
if (!createEvent) { if (!createEvent) {
if (!this.getVersionWarning) { if (!this.getVersionWarning) {
@ -454,9 +455,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
} }
return '1'; return '1';
} }
const ver = createEvent.getContent()['room_version']; return createEvent.getContent()['room_version'] ?? '1';
if (ver === undefined) return '1';
return ver;
} }
/** /**
@ -535,7 +534,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
logger.log(`[${this.roomId}] Current version: ${currentVersion}`); logger.log(`[${this.roomId}] Current version: ${currentVersion}`);
logger.log(`[${this.roomId}] Version capability: `, versionCap); logger.log(`[${this.roomId}] Version capability: `, versionCap);
const result = { const result: IRecommendedVersion = {
version: currentVersion, version: currentVersion,
needsUpgrade: false, needsUpgrade: false,
urgent: false, urgent: false,
@ -645,7 +644,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
return null; return null;
} }
return this.pendingEventList.find(event => event.getId() === eventId); return this.pendingEventList.find(event => event.getId() === eventId) ?? null;
} }
/** /**
@ -677,7 +676,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
* @return {string} the membership type (join | leave | invite) for the logged in user * @return {string} the membership type (join | leave | invite) for the logged in user
*/ */
public getMyMembership(): string { public getMyMembership(): string {
return this.selfMembership; return this.selfMembership ?? "leave";
} }
/** /**
@ -685,7 +684,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
* try to find out who invited us * try to find out who invited us
* @return {string} user id of the inviter * @return {string} user id of the inviter
*/ */
public getDMInviter(): string { public getDMInviter(): string | undefined {
if (this.myUserId) { if (this.myUserId) {
const me = this.getMember(this.myUserId); const me = this.getMember(this.myUserId);
if (me) { if (me) {
@ -731,7 +730,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
return this.myUserId; return this.myUserId;
} }
public getAvatarFallbackMember(): RoomMember { public getAvatarFallbackMember(): RoomMember | undefined {
const memberCount = this.getInvitedAndJoinedMemberCount(); const memberCount = this.getInvitedAndJoinedMemberCount();
if (memberCount > 2) { if (memberCount > 2) {
return; return;
@ -789,7 +788,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
private async loadMembersFromServer(): Promise<IStateEventWithRoomId[]> { private async loadMembersFromServer(): Promise<IStateEventWithRoomId[]> {
const lastSyncToken = this.client.store.getSyncToken(); const lastSyncToken = this.client.store.getSyncToken();
const response = await this.client.members(this.roomId, undefined, "leave", lastSyncToken); const response = await this.client.members(this.roomId, undefined, "leave", lastSyncToken ?? undefined);
return response.chunk; return response.chunk;
} }
@ -836,12 +835,12 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
this.currentState.setOutOfBandMembers(result.memberEvents); this.currentState.setOutOfBandMembers(result.memberEvents);
// now the members are loaded, start to track the e2e devices if needed // now the members are loaded, start to track the e2e devices if needed
if (this.client.isCryptoEnabled() && this.client.isRoomEncrypted(this.roomId)) { if (this.client.isCryptoEnabled() && this.client.isRoomEncrypted(this.roomId)) {
this.client.crypto.trackRoomDevices(this.roomId); this.client.crypto!.trackRoomDevices(this.roomId);
} }
return result.fromServer; return result.fromServer;
}).catch((err) => { }).catch((err) => {
// allow retries on fail // allow retries on fail
this.membersPromise = null; this.membersPromise = undefined;
this.currentState.markOutOfBandMembersFailed(); this.currentState.markOutOfBandMembersFailed();
throw err; throw err;
}); });
@ -850,7 +849,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
if (fromServer) { if (fromServer) {
const oobMembers = this.currentState.getMembers() const oobMembers = this.currentState.getMembers()
.filter((m) => m.isOutOfBand()) .filter((m) => m.isOutOfBand())
.map((m) => m.events.member.event as IStateEventWithRoomId); .map((m) => m.events.member?.event as IStateEventWithRoomId);
logger.log(`LL: telling store to write ${oobMembers.length}` logger.log(`LL: telling store to write ${oobMembers.length}`
+ ` members for room ${this.roomId}`); + ` members for room ${this.roomId}`);
const store = this.client.store; const store = this.client.store;
@ -881,7 +880,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
await this.loadMembersIfNeeded(); await this.loadMembersIfNeeded();
await this.client.store.clearOutOfBandMembers(this.roomId); await this.client.store.clearOutOfBandMembers(this.roomId);
this.currentState.clearOutOfBandMembers(); this.currentState.clearOutOfBandMembers();
this.membersPromise = null; this.membersPromise = undefined;
} }
} }
@ -1328,7 +1327,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
* if the global value should be used for this room. * if the global value should be used for this room.
*/ */
public getBlacklistUnverifiedDevices(): boolean { public getBlacklistUnverifiedDevices(): boolean {
return this.blacklistUnverifiedDevices; return !!this.blacklistUnverifiedDevices;
} }
/** /**
@ -1457,8 +1456,8 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
/** /**
* @experimental * @experimental
*/ */
public getThread(eventId: string): Thread { public getThread(eventId: string): Thread | null {
return this.threads.get(eventId); return this.threads.get(eventId) ?? null;
} }
/** /**
@ -1584,7 +1583,6 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
* Add a timelineSet for this room with the given filter * Add a timelineSet for this room with the given filter
* @param {Filter} filter The filter to be applied to this timelineSet * @param {Filter} filter The filter to be applied to this timelineSet
* @param {Object=} opts Configuration options * @param {Object=} opts Configuration options
* @param {*} opts.storageToken Optional.
* @return {EventTimelineSet} The timelineSet * @return {EventTimelineSet} The timelineSet
*/ */
public getOrCreateFilteredTimelineSet( public getOrCreateFilteredTimelineSet(
@ -1799,7 +1797,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
const threadRelationship = rootEvent const threadRelationship = rootEvent
.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name); .getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
if (threadRelationship.current_user_participated) { if (threadRelationship?.current_user_participated) {
this.threadsTimelineSets[1].addLiveEvent(rootEvent, { this.threadsTimelineSets[1].addLiveEvent(rootEvent, {
duplicateStrategy: DuplicateStrategy.Ignore, duplicateStrategy: DuplicateStrategy.Ignore,
fromCache: false, fromCache: false,
@ -2035,7 +2033,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
const redactId = event.event.redacts; const redactId = event.event.redacts;
// if we know about this event, redact its contents now. // if we know about this event, redact its contents now.
const redactedEvent = this.findEventById(redactId); const redactedEvent = redactId ? this.findEventById(redactId) : undefined;
if (redactedEvent) { if (redactedEvent) {
redactedEvent.makeRedacted(event); redactedEvent.makeRedacted(event);
@ -2043,7 +2041,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
if (redactedEvent.isState()) { if (redactedEvent.isState()) {
const currentStateEvent = this.currentState.getStateEvents( const currentStateEvent = this.currentState.getStateEvents(
redactedEvent.getType(), redactedEvent.getType(),
redactedEvent.getStateKey(), redactedEvent.getStateKey()!,
); );
if (currentStateEvent.getId() === redactedEvent.getId()) { if (currentStateEvent.getId() === redactedEvent.getId()) {
this.currentState.setStateEvents([redactedEvent]); this.currentState.setStateEvents([redactedEvent]);
@ -2059,7 +2057,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
// they are based on are changed. // they are based on are changed.
// Remove any visibility change on this event. // Remove any visibility change on this event.
this.visibilityEvents.delete(redactId); this.visibilityEvents.delete(redactId!);
// If this event is a visibility change event, remove it from the // If this event is a visibility change event, remove it from the
// list of visibility changes and update any event affected by it. // list of visibility changes and update any event affected by it.
@ -2183,7 +2181,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
EventTimeline.setEventMetadata(event, this.getLiveTimeline().getState(EventTimeline.FORWARDS), false); EventTimeline.setEventMetadata(event, this.getLiveTimeline().getState(EventTimeline.FORWARDS), false);
this.txnToEvent[txnId] = event; this.txnToEvent[txnId] = event;
if (this.opts.pendingEventOrdering === PendingEventOrdering.Detached) { if (this.pendingEventList) {
if (this.pendingEventList.some((e) => e.status === EventStatus.NOT_SENT)) { if (this.pendingEventList.some((e) => e.status === EventStatus.NOT_SENT)) {
logger.warn("Setting event as NOT_SENT due to messages in the same state"); logger.warn("Setting event as NOT_SENT due to messages in the same state");
event.setStatus(EventStatus.NOT_SENT); event.setStatus(EventStatus.NOT_SENT);
@ -2199,8 +2197,8 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
if (event.isRedaction()) { if (event.isRedaction()) {
const redactId = event.event.redacts; const redactId = event.event.redacts;
let redactedEvent = this.pendingEventList?.find(e => e.getId() === redactId); let redactedEvent = this.pendingEventList.find(e => e.getId() === redactId);
if (!redactedEvent) { if (!redactedEvent && redactId) {
redactedEvent = this.findEventById(redactId); redactedEvent = this.findEventById(redactId);
} }
if (redactedEvent) { if (redactedEvent) {
@ -2212,7 +2210,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
for (let i = 0; i < this.timelineSets.length; i++) { for (let i = 0; i < this.timelineSets.length; i++) {
const timelineSet = this.timelineSets[i]; const timelineSet = this.timelineSets[i];
if (timelineSet.getFilter()) { if (timelineSet.getFilter()) {
if (timelineSet.getFilter().filterRoomTimeline([event]).length) { if (timelineSet.getFilter()!.filterRoomTimeline([event]).length) {
timelineSet.addEventToTimeline(event, timelineSet.addEventToTimeline(event,
timelineSet.getLiveTimeline(), { timelineSet.getLiveTimeline(), {
toStartOfTimeline: false, toStartOfTimeline: false,
@ -2227,7 +2225,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
} }
} }
this.emit(RoomEvent.LocalEchoUpdated, event, this, null, null); this.emit(RoomEvent.LocalEchoUpdated, event, this);
} }
/** /**
@ -2348,20 +2346,19 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
// if the message was sent, we expect an event id // if the message was sent, we expect an event id
if (newStatus == EventStatus.SENT && !newEventId) { if (newStatus == EventStatus.SENT && !newEventId) {
throw new Error("updatePendingEvent called with status=SENT, " + throw new Error("updatePendingEvent called with status=SENT, but no new event id");
"but no new event id");
} }
// SENT races against /sync, so we have to special-case it. // SENT races against /sync, so we have to special-case it.
if (newStatus == EventStatus.SENT) { if (newStatus == EventStatus.SENT) {
const timeline = this.getTimelineForEvent(newEventId); const timeline = this.getTimelineForEvent(newEventId!);
if (timeline) { if (timeline) {
// we've already received the event via the event stream. // we've already received the event via the event stream.
// nothing more to do here, assuming the transaction ID was correctly matched. // nothing more to do here, assuming the transaction ID was correctly matched.
// Let's check that. // Let's check that.
const remoteEvent = this.findEventById(newEventId); const remoteEvent = this.findEventById(newEventId!);
const remoteTxnId = remoteEvent.getUnsigned().transaction_id; const remoteTxnId = remoteEvent?.getUnsigned().transaction_id;
if (!remoteTxnId) { if (!remoteTxnId && remoteEvent) {
// This code path is mostly relevant for the Sliding Sync proxy. // This code path is mostly relevant for the Sliding Sync proxy.
// The remote event did not contain a transaction ID, so we did not handle // The remote event did not contain a transaction ID, so we did not handle
// the remote echo yet. Handle it now. // the remote echo yet. Handle it now.
@ -2395,18 +2392,18 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
if (newStatus == EventStatus.SENT) { if (newStatus == EventStatus.SENT) {
// update the event id // update the event id
event.replaceLocalEventId(newEventId); event.replaceLocalEventId(newEventId!);
const { shouldLiveInRoom, threadId } = this.eventShouldLiveIn(event); const { shouldLiveInRoom, threadId } = this.eventShouldLiveIn(event);
const thread = this.getThread(threadId); const thread = threadId ? this.getThread(threadId) : undefined;
thread?.timelineSet.replaceEventId(oldEventId, newEventId); thread?.timelineSet.replaceEventId(oldEventId, newEventId!);
if (shouldLiveInRoom) { if (shouldLiveInRoom) {
// if the event was already in the timeline (which will be the case if // if the event was already in the timeline (which will be the case if
// opts.pendingEventOrdering==chronological), we need to update the // opts.pendingEventOrdering==chronological), we need to update the
// timeline map. // timeline map.
for (let i = 0; i < this.timelineSets.length; i++) { for (let i = 0; i < this.timelineSets.length; i++) {
this.timelineSets[i].replaceEventId(oldEventId, newEventId); this.timelineSets[i].replaceEventId(oldEventId, newEventId!);
} }
} }
} else if (newStatus == EventStatus.CANCELLED) { } else if (newStatus == EventStatus.CANCELLED) {
@ -2414,7 +2411,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
if (this.pendingEventList) { if (this.pendingEventList) {
const removedEvent = this.getPendingEvent(oldEventId); const removedEvent = this.getPendingEvent(oldEventId);
this.removePendingEvent(oldEventId); this.removePendingEvent(oldEventId);
if (removedEvent.isRedaction()) { if (removedEvent?.isRedaction()) {
this.revertRedactionLocalEcho(removedEvent); this.revertRedactionLocalEcho(removedEvent);
} }
} }
@ -2449,7 +2446,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
* they will go to the end of the timeline. * they will go to the end of the timeline.
* *
* @param {MatrixEvent[]} events A list of events to add. * @param {MatrixEvent[]} events A list of events to add.
* @param {IAddLiveEventOptions} options addLiveEvent options * @param {IAddLiveEventOptions} addLiveEventOptions addLiveEvent options
* @throws If <code>duplicateStrategy</code> is not falsey, 'replace' or 'ignore'. * @throws If <code>duplicateStrategy</code> is not falsey, 'replace' or 'ignore'.
*/ */
public addLiveEvents(events: MatrixEvent[], addLiveEventOptions?: IAddLiveEventOptions): void; public addLiveEvents(events: MatrixEvent[], addLiveEventOptions?: IAddLiveEventOptions): void;
@ -2462,8 +2459,8 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
duplicateStrategyOrOpts?: DuplicateStrategy | IAddLiveEventOptions, duplicateStrategyOrOpts?: DuplicateStrategy | IAddLiveEventOptions,
fromCache = false, fromCache = false,
): void { ): void {
let duplicateStrategy = duplicateStrategyOrOpts as DuplicateStrategy; let duplicateStrategy: DuplicateStrategy | undefined = duplicateStrategyOrOpts as DuplicateStrategy;
let timelineWasEmpty = false; let timelineWasEmpty: boolean | undefined = false;
if (typeof (duplicateStrategyOrOpts) === 'object') { if (typeof (duplicateStrategyOrOpts) === 'object') {
({ ({
duplicateStrategy, duplicateStrategy,
@ -2679,7 +2676,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
const membershipEvent = this.currentState.getStateEvents(EventType.RoomMember, this.myUserId); const membershipEvent = this.currentState.getStateEvents(EventType.RoomMember, this.myUserId);
if (membershipEvent) { if (membershipEvent) {
const membership = membershipEvent.getContent().membership; const membership = membershipEvent.getContent().membership;
this.updateMyMembership(membership); this.updateMyMembership(membership!);
if (membership === "invite") { if (membership === "invite") {
const strippedStateEvents = membershipEvent.getUnsigned().invite_room_state || []; const strippedStateEvents = membershipEvent.getUnsigned().invite_room_state || [];
@ -2919,11 +2916,9 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
} }
// get members that are NOT ourselves and are actually in the room. // get members that are NOT ourselves and are actually in the room.
let otherNames: string[] = null; let otherNames: string[] = [];
if (this.summaryHeroes) { if (this.summaryHeroes) {
// if we have a summary, the member state events // if we have a summary, the member state events should be in the room state
// should be in the room state
otherNames = [];
this.summaryHeroes.forEach((userId) => { this.summaryHeroes.forEach((userId) => {
// filter service members // filter service members
if (excludedUserIds.includes(userId)) { if (excludedUserIds.includes(userId)) {

View File

@ -38,13 +38,13 @@ export type UserEventHandlerMap = {
}; };
export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> { export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
private modified: number; private modified = -1;
// XXX these should be read-only // XXX these should be read-only
public displayName: string; public displayName?: string;
public rawDisplayName: string; public rawDisplayName?: string;
public avatarUrl: string; public avatarUrl?: string;
public presenceStatusMsg: string = null; public presenceStatusMsg?: string;
public presence = "offline"; public presence = "offline";
public lastActiveAgo = 0; public lastActiveAgo = 0;
public lastPresenceTs = 0; public lastPresenceTs = 0;
@ -52,10 +52,7 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
public events: { public events: {
presence?: MatrixEvent; presence?: MatrixEvent;
profile?: MatrixEvent; profile?: MatrixEvent;
} = { } = {};
presence: null,
profile: null,
};
/** /**
* Construct a new User. A User must have an ID and can optionally have extra * Construct a new User. A User must have an ID and can optionally have extra
@ -83,7 +80,6 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
super(); super();
this.displayName = userId; this.displayName = userId;
this.rawDisplayName = userId; this.rawDisplayName = userId;
this.avatarUrl = null;
this.updateModifiedTime(); this.updateModifiedTime();
} }
@ -150,11 +146,7 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
*/ */
public setDisplayName(name: string): void { public setDisplayName(name: string): void {
const oldName = this.displayName; const oldName = this.displayName;
if (typeof name === "string") { this.displayName = name;
this.displayName = name;
} else {
this.displayName = undefined;
}
if (name !== oldName) { if (name !== oldName) {
this.updateModifiedTime(); this.updateModifiedTime();
} }
@ -165,12 +157,8 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
* in response to this as there is no underlying MatrixEvent to emit with. * in response to this as there is no underlying MatrixEvent to emit with.
* @param {string} name The new display name. * @param {string} name The new display name.
*/ */
public setRawDisplayName(name: string): void { public setRawDisplayName(name?: string): void {
if (typeof name === "string") { this.rawDisplayName = name;
this.rawDisplayName = name;
} else {
this.rawDisplayName = undefined;
}
} }
/** /**
@ -178,7 +166,7 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
* as there is no underlying MatrixEvent to emit with. * as there is no underlying MatrixEvent to emit with.
* @param {string} url The new avatar URL. * @param {string} url The new avatar URL.
*/ */
public setAvatarUrl(url: string): void { public setAvatarUrl(url?: string): void {
const oldUrl = this.avatarUrl; const oldUrl = this.avatarUrl;
this.avatarUrl = url; this.avatarUrl = url;
if (url !== oldUrl) { if (url !== oldUrl) {

View File

@ -670,8 +670,8 @@ export class SlidingSyncSdk {
// For each invited room member we want to give them a displayname/avatar url // For each invited room member we want to give them a displayname/avatar url
// if they have one (the m.room.member invites don't contain this). // if they have one (the m.room.member invites don't contain this).
room.getMembersWithMembership("invite").forEach(function(member) { room.getMembersWithMembership("invite").forEach(function(member) {
if (member._requestedProfileInfo) return; if (member.requestedProfileInfo) return;
member._requestedProfileInfo = true; member.requestedProfileInfo = true;
// try to get a cached copy first. // try to get a cached copy first.
const user = client.getUser(member.userId); const user = client.getUser(member.userId);
let promise: ReturnType<MatrixClient["getProfileInfo"]>; let promise: ReturnType<MatrixClient["getProfileInfo"]>;

View File

@ -195,7 +195,7 @@ export interface IStore {
* client state to where it was at the last save, or null if there * client state to where it was at the last save, or null if there
* is no saved sync data. * is no saved sync data.
*/ */
getSavedSync(): Promise<ISavedSync>; getSavedSync(): Promise<ISavedSync | null>;
/** /**
* @return {Promise} If there is a saved sync, the nextBatch token * @return {Promise} If there is a saved sync, the nextBatch token

View File

@ -23,7 +23,7 @@ export interface IIndexedDBBackend {
syncToDatabase(userTuples: UserTuple[]): Promise<void>; syncToDatabase(userTuples: UserTuple[]): Promise<void>;
isNewlyCreated(): Promise<boolean>; isNewlyCreated(): Promise<boolean>;
setSyncData(syncData: ISyncResponse): Promise<void>; setSyncData(syncData: ISyncResponse): Promise<void>;
getSavedSync(): Promise<ISavedSync>; getSavedSync(): Promise<ISavedSync | null>;
getNextBatchToken(): Promise<string>; getNextBatchToken(): Promise<string>;
clearDatabase(): Promise<void>; clearDatabase(): Promise<void>;
getOutOfBandMembers(roomId: string): Promise<IStateEventWithRoomId[] | null>; getOutOfBandMembers(roomId: string): Promise<IStateEventWithRoomId[] | null>;

View File

@ -124,7 +124,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
private readonly dbName: string; private readonly dbName: string;
private readonly syncAccumulator: SyncAccumulator; private readonly syncAccumulator: SyncAccumulator;
private db: IDBDatabase = null; private db?: IDBDatabase;
private disconnected = true; private disconnected = true;
private _isNewlyCreated = false; private _isNewlyCreated = false;
private isPersisting = false; private isPersisting = false;
@ -141,8 +141,8 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
* @param {string=} dbName Optional database name. The same name must be used * @param {string=} dbName Optional database name. The same name must be used
* to open the same database. * to open the same database.
*/ */
constructor(private readonly indexedDB: IDBFactory, dbName: string) { constructor(private readonly indexedDB: IDBFactory, dbName = "default") {
this.dbName = "matrix-js-sdk:" + (dbName || "default"); this.dbName = "matrix-js-sdk:" + dbName;
this.syncAccumulator = new SyncAccumulator(); this.syncAccumulator = new SyncAccumulator();
} }
@ -188,7 +188,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
// add a poorly-named listener for when deleteDatabase is called // add a poorly-named listener for when deleteDatabase is called
// so we can close our db connections. // so we can close our db connections.
this.db.onversionchange = () => { this.db.onversionchange = () => {
this.db.close(); this.db?.close();
}; };
return this.init(); return this.init();
@ -229,7 +229,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
*/ */
public getOutOfBandMembers(roomId: string): Promise<IStateEventWithRoomId[] | null> { public getOutOfBandMembers(roomId: string): Promise<IStateEventWithRoomId[] | null> {
return new Promise<IStateEventWithRoomId[] | null>((resolve, reject) => { return new Promise<IStateEventWithRoomId[] | null>((resolve, reject) => {
const tx = this.db.transaction(["oob_membership_events"], "readonly"); const tx = this.db!.transaction(["oob_membership_events"], "readonly");
const store = tx.objectStore("oob_membership_events"); const store = tx.objectStore("oob_membership_events");
const roomIndex = store.index("room"); const roomIndex = store.index("room");
const range = IDBKeyRange.only(roomId); const range = IDBKeyRange.only(roomId);
@ -279,7 +279,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
public async setOutOfBandMembers(roomId: string, membershipEvents: IStateEventWithRoomId[]): Promise<void> { public async setOutOfBandMembers(roomId: string, membershipEvents: IStateEventWithRoomId[]): Promise<void> {
logger.log(`LL: backend about to store ${membershipEvents.length}` + logger.log(`LL: backend about to store ${membershipEvents.length}` +
` members for ${roomId}`); ` members for ${roomId}`);
const tx = this.db.transaction(["oob_membership_events"], "readwrite"); const tx = this.db!.transaction(["oob_membership_events"], "readwrite");
const store = tx.objectStore("oob_membership_events"); const store = tx.objectStore("oob_membership_events");
membershipEvents.forEach((e) => { membershipEvents.forEach((e) => {
store.put(e); store.put(e);
@ -306,7 +306,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
// keys in the store. // keys in the store.
// this should be way faster than deleting every member // this should be way faster than deleting every member
// individually for a large room. // individually for a large room.
const readTx = this.db.transaction( const readTx = this.db!.transaction(
["oob_membership_events"], ["oob_membership_events"],
"readonly"); "readonly");
const store = readTx.objectStore("oob_membership_events"); const store = readTx.objectStore("oob_membership_events");
@ -322,7 +322,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
const [minStateKey, maxStateKey] = await Promise.all( const [minStateKey, maxStateKey] = await Promise.all(
[minStateKeyProm, maxStateKeyProm]); [minStateKeyProm, maxStateKeyProm]);
const writeTx = this.db.transaction( const writeTx = this.db!.transaction(
["oob_membership_events"], ["oob_membership_events"],
"readwrite"); "readwrite");
const writeStore = writeTx.objectStore("oob_membership_events"); const writeStore = writeTx.objectStore("oob_membership_events");
@ -374,7 +374,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
* client state to where it was at the last save, or null if there * client state to where it was at the last save, or null if there
* is no saved sync data. * is no saved sync data.
*/ */
public getSavedSync(copy = true): Promise<ISavedSync> { public getSavedSync(copy = true): Promise<ISavedSync | null> {
const data = this.syncAccumulator.getJSON(); const data = this.syncAccumulator.getJSON();
if (!data.nextBatch) return Promise.resolve(null); if (!data.nextBatch) return Promise.resolve(null);
if (copy) { if (copy) {
@ -431,7 +431,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
): Promise<void> { ): Promise<void> {
logger.log("Persisting sync data up to", nextBatch); logger.log("Persisting sync data up to", nextBatch);
return utils.promiseTry<void>(() => { return utils.promiseTry<void>(() => {
const txn = this.db.transaction(["sync"], "readwrite"); const txn = this.db!.transaction(["sync"], "readwrite");
const store = txn.objectStore("sync"); const store = txn.objectStore("sync");
store.put({ store.put({
clobber: "-", // constant key so will always clobber clobber: "-", // constant key so will always clobber
@ -452,7 +452,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
*/ */
private persistAccountData(accountData: IMinimalEvent[]): Promise<void> { private persistAccountData(accountData: IMinimalEvent[]): Promise<void> {
return utils.promiseTry<void>(() => { return utils.promiseTry<void>(() => {
const txn = this.db.transaction(["accountData"], "readwrite"); const txn = this.db!.transaction(["accountData"], "readwrite");
const store = txn.objectStore("accountData"); const store = txn.objectStore("accountData");
for (let i = 0; i < accountData.length; i++) { for (let i = 0; i < accountData.length; i++) {
store.put(accountData[i]); // put == UPSERT store.put(accountData[i]); // put == UPSERT
@ -471,7 +471,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
*/ */
private persistUserPresenceEvents(tuples: UserTuple[]): Promise<void> { private persistUserPresenceEvents(tuples: UserTuple[]): Promise<void> {
return utils.promiseTry<void>(() => { return utils.promiseTry<void>(() => {
const txn = this.db.transaction(["users"], "readwrite"); const txn = this.db!.transaction(["users"], "readwrite");
const store = txn.objectStore("users"); const store = txn.objectStore("users");
for (const tuple of tuples) { for (const tuple of tuples) {
store.put({ store.put({
@ -491,7 +491,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
*/ */
public getUserPresenceEvents(): Promise<UserTuple[]> { public getUserPresenceEvents(): Promise<UserTuple[]> {
return utils.promiseTry<UserTuple[]>(() => { return utils.promiseTry<UserTuple[]>(() => {
const txn = this.db.transaction(["users"], "readonly"); const txn = this.db!.transaction(["users"], "readonly");
const store = txn.objectStore("users"); const store = txn.objectStore("users");
return selectQuery(store, undefined, (cursor) => { return selectQuery(store, undefined, (cursor) => {
return [cursor.value.userId, cursor.value.event]; return [cursor.value.userId, cursor.value.event];
@ -506,7 +506,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
private loadAccountData(): Promise<IMinimalEvent[]> { private loadAccountData(): Promise<IMinimalEvent[]> {
logger.log(`LocalIndexedDBStoreBackend: loading account data...`); logger.log(`LocalIndexedDBStoreBackend: loading account data...`);
return utils.promiseTry<IMinimalEvent[]>(() => { return utils.promiseTry<IMinimalEvent[]>(() => {
const txn = this.db.transaction(["accountData"], "readonly"); const txn = this.db!.transaction(["accountData"], "readonly");
const store = txn.objectStore("accountData"); const store = txn.objectStore("accountData");
return selectQuery(store, undefined, (cursor) => { return selectQuery(store, undefined, (cursor) => {
return cursor.value; return cursor.value;
@ -524,7 +524,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
private loadSyncData(): Promise<ISyncData> { private loadSyncData(): Promise<ISyncData> {
logger.log(`LocalIndexedDBStoreBackend: loading sync data...`); logger.log(`LocalIndexedDBStoreBackend: loading sync data...`);
return utils.promiseTry<ISyncData>(() => { return utils.promiseTry<ISyncData>(() => {
const txn = this.db.transaction(["sync"], "readonly"); const txn = this.db!.transaction(["sync"], "readonly");
const store = txn.objectStore("sync"); const store = txn.objectStore("sync");
return selectQuery(store, undefined, (cursor) => { return selectQuery(store, undefined, (cursor) => {
return cursor.value; return cursor.value;
@ -540,7 +540,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
public getClientOptions(): Promise<IStartClientOpts> { public getClientOptions(): Promise<IStartClientOpts> {
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
const txn = this.db.transaction(["client_options"], "readonly"); const txn = this.db!.transaction(["client_options"], "readonly");
const store = txn.objectStore("client_options"); const store = txn.objectStore("client_options");
return selectQuery(store, undefined, (cursor) => { return selectQuery(store, undefined, (cursor) => {
return cursor.value?.options; return cursor.value?.options;
@ -549,7 +549,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
} }
public async storeClientOptions(options: IStartClientOpts): Promise<void> { public async storeClientOptions(options: IStartClientOpts): Promise<void> {
const txn = this.db.transaction(["client_options"], "readwrite"); const txn = this.db!.transaction(["client_options"], "readwrite");
const store = txn.objectStore("client_options"); const store = txn.objectStore("client_options");
store.put({ store.put({
clobber: "-", // constant key so will always clobber clobber: "-", // constant key so will always clobber
@ -559,7 +559,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
} }
public async saveToDeviceBatches(batches: ToDeviceBatchWithTxnId[]): Promise<void> { public async saveToDeviceBatches(batches: ToDeviceBatchWithTxnId[]): Promise<void> {
const txn = this.db.transaction(["to_device_queue"], "readwrite"); const txn = this.db!.transaction(["to_device_queue"], "readwrite");
const store = txn.objectStore("to_device_queue"); const store = txn.objectStore("to_device_queue");
for (const batch of batches) { for (const batch of batches) {
store.add(batch); store.add(batch);
@ -568,7 +568,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
} }
public async getOldestToDeviceBatch(): Promise<IndexedToDeviceBatch | null> { public async getOldestToDeviceBatch(): Promise<IndexedToDeviceBatch | null> {
const txn = this.db.transaction(["to_device_queue"], "readonly"); const txn = this.db!.transaction(["to_device_queue"], "readonly");
const store = txn.objectStore("to_device_queue"); const store = txn.objectStore("to_device_queue");
const cursor = await reqAsCursorPromise(store.openCursor()); const cursor = await reqAsCursorPromise(store.openCursor());
if (!cursor) return null; if (!cursor) return null;
@ -584,7 +584,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
} }
public async removeToDeviceBatch(id: number): Promise<void> { public async removeToDeviceBatch(id: number): Promise<void> {
const txn = this.db.transaction(["to_device_queue"], "readwrite"); const txn = this.db!.transaction(["to_device_queue"], "readwrite");
const store = txn.objectStore("to_device_queue"); const store = txn.objectStore("to_device_queue");
store.delete(id); store.delete(id);
await txnAsPromise(txn); await txnAsPromise(txn);

View File

@ -23,13 +23,13 @@ import { IIndexedDBBackend, UserTuple } from "./indexeddb-backend";
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage"; import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage";
export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend { export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend {
private worker: Worker; private worker?: Worker;
private nextSeq = 0; private nextSeq = 0;
// The currently in-flight requests to the actual backend // The currently in-flight requests to the actual backend
private inFlight: Record<number, IDeferred<any>> = {}; // seq: promise private inFlight: Record<number, IDeferred<any>> = {}; // seq: promise
// Once we start connecting, we keep the promise and re-use it // Once we start connecting, we keep the promise and re-use it
// if we try to connect again // if we try to connect again
private startPromise: Promise<void> = null; private startPromise?: Promise<void>;
/** /**
* An IndexedDB store backend where the actual backend sits in a web * An IndexedDB store backend where the actual backend sits in a web
@ -44,7 +44,7 @@ export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend {
*/ */
constructor( constructor(
private readonly workerFactory: () => Worker, private readonly workerFactory: () => Worker,
private readonly dbName: string, private readonly dbName?: string,
) {} ) {}
/** /**
@ -147,12 +147,12 @@ export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend {
} }
private ensureStarted(): Promise<void> { private ensureStarted(): Promise<void> {
if (this.startPromise === null) { if (!this.startPromise) {
this.worker = this.workerFactory(); this.worker = this.workerFactory();
this.worker.onmessage = this.onWorkerMessage; this.worker.onmessage = this.onWorkerMessage;
// tell the worker the db name. // tell the worker the db name.
this.startPromise = this.doCmd('_setupWorker', [this.dbName]).then(() => { this.startPromise = this.doCmd('setupWorker', [this.dbName]).then(() => {
logger.log("IndexedDB worker is ready"); logger.log("IndexedDB worker is ready");
}); });
} }
@ -168,7 +168,7 @@ export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend {
this.inFlight[seq] = def; this.inFlight[seq] = def;
this.worker.postMessage({ command, seq, args }); this.worker?.postMessage({ command, seq, args });
return def.promise; return def.promise;
}); });

View File

@ -20,7 +20,7 @@ import { logger } from '../logger';
interface ICmd { interface ICmd {
command: string; command: string;
seq: number; seq: number;
args?: any[]; args: any[];
} }
/** /**
@ -39,7 +39,7 @@ interface ICmd {
* *
*/ */
export class IndexedDBStoreWorker { export class IndexedDBStoreWorker {
private backend: LocalIndexedDBStoreBackend = null; private backend?: LocalIndexedDBStoreBackend;
/** /**
* @param {function} postMessage The web worker postMessage function that * @param {function} postMessage The web worker postMessage function that
@ -58,59 +58,59 @@ export class IndexedDBStoreWorker {
let prom; let prom;
switch (msg.command) { switch (msg.command) {
case '_setupWorker': case 'setupWorker':
// this is the 'indexedDB' global (where global != window // this is the 'indexedDB' global (where global != window
// because it's a web worker and there is no window). // because it's a web worker and there is no window).
this.backend = new LocalIndexedDBStoreBackend(indexedDB, msg.args[0]); this.backend = new LocalIndexedDBStoreBackend(indexedDB, msg.args[0]);
prom = Promise.resolve(); prom = Promise.resolve();
break; break;
case 'connect': case 'connect':
prom = this.backend.connect(); prom = this.backend?.connect();
break; break;
case 'isNewlyCreated': case 'isNewlyCreated':
prom = this.backend.isNewlyCreated(); prom = this.backend?.isNewlyCreated();
break; break;
case 'clearDatabase': case 'clearDatabase':
prom = this.backend.clearDatabase(); prom = this.backend?.clearDatabase();
break; break;
case 'getSavedSync': case 'getSavedSync':
prom = this.backend.getSavedSync(false); prom = this.backend?.getSavedSync(false);
break; break;
case 'setSyncData': case 'setSyncData':
prom = this.backend.setSyncData(msg.args[0]); prom = this.backend?.setSyncData(msg.args[0]);
break; break;
case 'syncToDatabase': case 'syncToDatabase':
prom = this.backend.syncToDatabase(msg.args[0]); prom = this.backend?.syncToDatabase(msg.args[0]);
break; break;
case 'getUserPresenceEvents': case 'getUserPresenceEvents':
prom = this.backend.getUserPresenceEvents(); prom = this.backend?.getUserPresenceEvents();
break; break;
case 'getNextBatchToken': case 'getNextBatchToken':
prom = this.backend.getNextBatchToken(); prom = this.backend?.getNextBatchToken();
break; break;
case 'getOutOfBandMembers': case 'getOutOfBandMembers':
prom = this.backend.getOutOfBandMembers(msg.args[0]); prom = this.backend?.getOutOfBandMembers(msg.args[0]);
break; break;
case 'clearOutOfBandMembers': case 'clearOutOfBandMembers':
prom = this.backend.clearOutOfBandMembers(msg.args[0]); prom = this.backend?.clearOutOfBandMembers(msg.args[0]);
break; break;
case 'setOutOfBandMembers': case 'setOutOfBandMembers':
prom = this.backend.setOutOfBandMembers(msg.args[0], msg.args[1]); prom = this.backend?.setOutOfBandMembers(msg.args[0], msg.args[1]);
break; break;
case 'getClientOptions': case 'getClientOptions':
prom = this.backend.getClientOptions(); prom = this.backend?.getClientOptions();
break; break;
case 'storeClientOptions': case 'storeClientOptions':
prom = this.backend.storeClientOptions(msg.args[0]); prom = this.backend?.storeClientOptions(msg.args[0]);
break; break;
case 'saveToDeviceBatches': case 'saveToDeviceBatches':
prom = this.backend.saveToDeviceBatches(msg.args[0]); prom = this.backend?.saveToDeviceBatches(msg.args[0]);
break; break;
case 'getOldestToDeviceBatch': case 'getOldestToDeviceBatch':
prom = this.backend.getOldestToDeviceBatch(); prom = this.backend?.getOldestToDeviceBatch();
break; break;
case 'removeToDeviceBatch': case 'removeToDeviceBatch':
prom = this.backend.removeToDeviceBatch(msg.args[0]); prom = this.backend?.removeToDeviceBatch(msg.args[0]);
break; break;
} }

View File

@ -155,7 +155,7 @@ export class IndexedDBStore extends MemoryStore {
* client state to where it was at the last save, or null if there * client state to where it was at the last save, or null if there
* is no saved sync data. * is no saved sync data.
*/ */
public getSavedSync = this.degradable((): Promise<ISavedSync> => { public getSavedSync = this.degradable((): Promise<ISavedSync | null> => {
return this.backend.getSavedSync(); return this.backend.getSavedSync();
}, "getSavedSync"); }, "getSavedSync");
@ -292,16 +292,16 @@ export class IndexedDBStore extends MemoryStore {
*/ */
private degradable<A extends Array<any>, R = void>( private degradable<A extends Array<any>, R = void>(
func: DegradableFn<A, R>, func: DegradableFn<A, R>,
fallback?: string, fallback?: keyof MemoryStore,
): DegradableFn<A, R> { ): DegradableFn<A, R> {
const fallbackFn = super[fallback]; const fallbackFn = fallback ? super[fallback] as Function : null;
return async (...args) => { return async (...args) => {
try { try {
return await func.call(this, ...args); return await func.call(this, ...args);
} catch (e) { } catch (e) {
logger.error("IndexedDBStore failure, degrading to MemoryStore", e); logger.error("IndexedDBStore failure, degrading to MemoryStore", e);
this.emitter.emit("degraded", e); this.emitter.emit("degraded", e as Error);
try { try {
// We try to delete IndexedDB after degrading since this store is only a // We try to delete IndexedDB after degrading since this store is only a
// cache (the app will still function correctly without the data). // cache (the app will still function correctly without the data).

View File

@ -32,7 +32,7 @@ import { ISyncResponse } from "../sync-accumulator";
import { IStateEventWithRoomId } from "../@types/search"; import { IStateEventWithRoomId } from "../@types/search";
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage"; import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage";
function isValidFilterId(filterId: string): boolean { function isValidFilterId(filterId?: string | number | null): boolean {
const isValidStr = typeof filterId === "string" && const isValidStr = typeof filterId === "string" &&
!!filterId && !!filterId &&
filterId !== "undefined" && // exclude these as we've serialized undefined in localStorage before filterId !== "undefined" && // exclude these as we've serialized undefined in localStorage before
@ -55,13 +55,13 @@ export interface IOpts {
export class MemoryStore implements IStore { export class MemoryStore implements IStore {
private rooms: Record<string, Room> = {}; // roomId: Room private rooms: Record<string, Room> = {}; // roomId: Room
private users: Record<string, User> = {}; // userId: User private users: Record<string, User> = {}; // userId: User
private syncToken: string = null; private syncToken: string | null = null;
// userId: { // userId: {
// filterId: Filter // filterId: Filter
// } // }
private filters: Record<string, Record<string, Filter>> = {}; private filters: Record<string, Record<string, Filter>> = {};
public accountData: Record<string, MatrixEvent> = {}; // type : content public accountData: Record<string, MatrixEvent> = {}; // type : content
protected readonly localStorage: Storage; protected readonly localStorage?: Storage;
private oobMembers: Record<string, IStateEventWithRoomId[]> = {}; // roomId: [member events] private oobMembers: Record<string, IStateEventWithRoomId[]> = {}; // roomId: [member events]
private pendingEvents: { [roomId: string]: Partial<IEvent>[] } = {}; private pendingEvents: { [roomId: string]: Partial<IEvent>[] } = {};
private clientOptions = {}; private clientOptions = {};
@ -115,7 +115,7 @@ export class MemoryStore implements IStore {
* @param {RoomState} state * @param {RoomState} state
* @param {RoomMember} member * @param {RoomMember} member
*/ */
private onRoomMember = (event: MatrixEvent, state: RoomState, member: RoomMember) => { private onRoomMember = (event: MatrixEvent | null, state: RoomState, member: RoomMember) => {
if (member.membership === "invite") { if (member.membership === "invite") {
// We do NOT add invited members because people love to typo user IDs // We do NOT add invited members because people love to typo user IDs
// which would then show up in these lists (!) // which would then show up in these lists (!)
@ -126,9 +126,7 @@ export class MemoryStore implements IStore {
if (member.name) { if (member.name) {
user.setDisplayName(member.name); user.setDisplayName(member.name);
if (member.events.member) { if (member.events.member) {
user.setRawDisplayName( user.setRawDisplayName(member.events.member.getDirectionalContent().displayname);
member.events.member.getDirectionalContent().displayname,
);
} }
} }
if (member.events.member && member.events.member.getContent().avatar_url) { if (member.events.member && member.events.member.getContent().avatar_url) {
@ -227,9 +225,7 @@ export class MemoryStore implements IStore {
* @param {Filter} filter * @param {Filter} filter
*/ */
public storeFilter(filter: Filter): void { public storeFilter(filter: Filter): void {
if (!filter?.userId) { if (!filter?.userId || !filter?.filterId) return;
return;
}
if (!this.filters[filter.userId]) { if (!this.filters[filter.userId]) {
this.filters[filter.userId] = {}; this.filters[filter.userId] = {};
} }
@ -352,7 +348,7 @@ export class MemoryStore implements IStore {
* client state to where it was at the last save, or null if there * client state to where it was at the last save, or null if there
* is no saved sync data. * is no saved sync data.
*/ */
public getSavedSync(): Promise<ISavedSync> { public getSavedSync(): Promise<ISavedSync | null> {
return Promise.resolve(null); return Promise.resolve(null);
} }

View File

@ -1615,8 +1615,8 @@ export class SyncApi {
// For each invited room member we want to give them a displayname/avatar url // For each invited room member we want to give them a displayname/avatar url
// if they have one (the m.room.member invites don't contain this). // if they have one (the m.room.member invites don't contain this).
room.getMembersWithMembership("invite").forEach(function(member) { room.getMembersWithMembership("invite").forEach(function(member) {
if (member._requestedProfileInfo) return; if (member.requestedProfileInfo) return;
member._requestedProfileInfo = true; member.requestedProfileInfo = true;
// try to get a cached copy first. // try to get a cached copy first.
const user = client.getUser(member.userId); const user = client.getUser(member.userId);
let promise; let promise;

View File

@ -2210,7 +2210,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
this.opponentPartyId = msg.party_id || null; this.opponentPartyId = msg.party_id || null;
} }
this.opponentCaps = msg.capabilities || {} as CallCapabilities; this.opponentCaps = msg.capabilities || {} as CallCapabilities;
this.opponentMember = ev.sender; this.opponentMember = ev.sender!; // XXX: we should use ev.getSender() and just store the userId
} }
private async addBufferedIceCandidates(): Promise<void> { private async addBufferedIceCandidates(): Promise<void> {