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) {
timelineFireCount++;
expect(room.roomId).toEqual("!erufh:bar");
expect(room?.roomId).toEqual("!erufh:bar");
});
client!.on(RoomEvent.Name, function(room) {
roomNameInvokeCount++;

View File

@ -368,7 +368,7 @@ describe("MatrixClient event timelines", function() {
expect(tl!.getEvents().length).toEqual(4);
for (let i = 0; i < 4; 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))
.toEqual("start_token");
@ -406,7 +406,7 @@ describe("MatrixClient event timelines", function() {
}).then(function(tl) {
expect(tl!.getEvents().length).toEqual(2);
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))
.toEqual("f_1_1");
// expect(tl.getPaginationToken(EventTimeline.FORWARDS))
@ -767,8 +767,8 @@ describe("MatrixClient event timelines", function() {
httpBackend = testClient.httpBackend;
await startClient(httpBackend, client);
const room = client.getRoom(roomId);
const timelineSet = room.getTimelineSets()[0];
const room = client.getRoom(roomId)!;
const timelineSet = room.getTimelineSets()[0]!;
await expect(client.getLatestTimeline(timelineSet)).rejects.toBeTruthy();
});
@ -786,7 +786,7 @@ describe("MatrixClient event timelines", function() {
httpBackend = testClient.httpBackend;
return startClient(httpBackend, client).then(() => {
const room = client.getRoom(roomId);
const room = client.getRoom(roomId)!;
const timelineSet = room.getTimelineSets()[0];
expect(client.getLatestTimeline(timelineSet)).rejects.toBeFalsy();
});
@ -849,7 +849,7 @@ describe("MatrixClient event timelines", function() {
expect(tl!.getEvents().length).toEqual(4);
for (let i = 0; i < 4; 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))
.toEqual("start_token");
@ -1129,8 +1129,8 @@ describe("MatrixClient event timelines", function() {
});
it("should allow fetching all threads", async function() {
const room = client.getRoom(roomId);
const timelineSets = await room?.createThreadsTimelineSets();
const room = client.getRoom(roomId)!;
const timelineSets = await room.createThreadsTimelineSets();
expect(timelineSets).not.toBeNull();
respondToThreads();
respondToThreads();
@ -1185,14 +1185,14 @@ describe("MatrixClient event timelines", function() {
});
it("should allow fetching all threads", async function() {
const room = client.getRoom(roomId);
const room = client.getRoom(roomId)!;
respondToFilter();
respondToSync();
respondToFilter();
respondToSync();
const timelineSetsPromise = room?.createThreadsTimelineSets();
const timelineSetsPromise = room.createThreadsTimelineSets();
expect(timelineSetsPromise).not.toBeNull();
await flushHttp(timelineSetsPromise!);
respondToFilter();
@ -1218,7 +1218,7 @@ describe("MatrixClient event timelines", function() {
const [allThreads] = timelineSets!;
respondToThreads().check((request) => {
expect(request.queryParams.filter).toEqual(JSON.stringify({
expect(request.queryParams?.filter).toEqual(JSON.stringify({
"lazy_load_members": true,
}));
});
@ -1244,7 +1244,7 @@ describe("MatrixClient event timelines", function() {
state: [],
next_batch: null,
}).check((request) => {
expect(request.queryParams.from).toEqual(RANDOM_TOKEN);
expect(request.queryParams?.from).toEqual(RANDOM_TOKEN);
});
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);
// check member
const member = room.timeline[1].sender;
expect(member.userId).toEqual(userId);
expect(member.name).toEqual(userName);
expect(member?.userId).toEqual(userId);
expect(member?.name).toEqual(userName);
httpBackend!.flush("/sync", 1).then(function() {
done();
@ -327,11 +327,11 @@ describe("MatrixClient room timelines", function() {
client!.scrollback(room).then(function() {
expect(room.timeline.length).toEqual(5);
const joinMsg = room.timeline[0];
expect(joinMsg.sender.name).toEqual("Old Alice");
expect(joinMsg.sender?.name).toEqual("Old Alice");
const oldMsg = room.timeline[1];
expect(oldMsg.sender.name).toEqual("Old Alice");
expect(oldMsg.sender?.name).toEqual("Old Alice");
const newMsg = room.timeline[3];
expect(newMsg.sender.name).toEqual(userName);
expect(newMsg.sender?.name).toEqual(userName);
// still have a sync to flush
httpBackend!.flush("/sync", 1).then(() => {
@ -468,8 +468,8 @@ describe("MatrixClient room timelines", function() {
]).then(function() {
const preNameEvent = room.timeline[room.timeline.length - 3];
const postNameEvent = room.timeline[room.timeline.length - 1];
expect(preNameEvent.sender.name).toEqual(userName);
expect(postNameEvent.sender.name).toEqual("New Name");
expect(preNameEvent.sender?.name).toEqual(userName);
expect(postNameEvent.sender?.name).toEqual("New Name");
});
});
});

View File

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

View File

@ -1,5 +1,7 @@
import { UNREAD_THREAD_NOTIFICATIONS } from "../../src/@types/sync";
import { Filter, IFilterDefinition } from "../../src/filter";
import { mkEvent } from "../test-utils/test-utils";
import { EventType } from "../../src";
describe("Filter", function() {
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 ia = new InteractiveAuth({
authData: null,
matrixClient: getFakeClient(),
stateUpdated,
doRequest,

View File

@ -14,7 +14,10 @@ See the License for the specific language governing permissions and
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', () => {
it('should create copies of itself', () => {
@ -84,4 +87,36 @@ describe('MatrixEvent', () => {
expect(ev.getWireContent().body).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 { RoomMember, RoomMemberEvent } from "../../src/models/room-member";
import { RoomState } from "../../src";
import { EventType, RoomState } from "../../src";
describe("RoomMember", function() {
const roomId = "!foo:bar";
@ -142,33 +142,72 @@ describe("RoomMember", function() {
expect(emitCount).toEqual(1);
});
it("should not honor string power levels.",
function() {
const event = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": "5",
},
it("should not honor string power levels.", function() {
const event = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
user: userA,
content: {
users_default: 20,
users: {
"@alice:bar": "5",
},
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);
},
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);
});
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() {
@ -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() {
const joinEvent = utils.mkMembership({
event: true,

View File

@ -303,92 +303,92 @@ describe("RoomState", function() {
state.setStateEvents(events, { timelineWasEmpty: true });
expect(emitCount).toEqual(1);
});
});
describe('beacon events', () => {
it('adds new beacon info events to state and emits', () => {
const beaconEvent = makeBeaconInfoEvent(userA, roomId);
const emitSpy = jest.spyOn(state, 'emit');
describe('beacon events', () => {
it('adds new beacon info events to state and emits', () => {
const beaconEvent = makeBeaconInfoEvent(userA, roomId);
const emitSpy = jest.spyOn(state, 'emit');
state.setStateEvents([beaconEvent]);
state.setStateEvents([beaconEvent]);
expect(state.beacons.size).toEqual(1);
const beaconInstance = state.beacons.get(`${roomId}_${userA}`);
expect(beaconInstance).toBeTruthy();
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.New, beaconEvent, beaconInstance);
});
expect(state.beacons.size).toEqual(1);
const beaconInstance = state.beacons.get(`${roomId}_${userA}`);
expect(beaconInstance).toBeTruthy();
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.New, beaconEvent, beaconInstance);
});
it('does not add redacted beacon info events to state', () => {
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId);
const redactionEvent = new MatrixEvent({ type: 'm.room.redaction' });
redactedBeaconEvent.makeRedacted(redactionEvent);
const emitSpy = jest.spyOn(state, 'emit');
it('does not add redacted beacon info events to state', () => {
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId);
const redactionEvent = new MatrixEvent({ type: 'm.room.redaction' });
redactedBeaconEvent.makeRedacted(redactionEvent);
const emitSpy = jest.spyOn(state, 'emit');
state.setStateEvents([redactedBeaconEvent]);
state.setStateEvents([redactedBeaconEvent]);
// no beacon added
expect(state.beacons.size).toEqual(0);
expect(state.beacons.get(getBeaconInfoIdentifier(redactedBeaconEvent))).toBeFalsy();
// no new beacon emit
expect(filterEmitCallsByEventType(BeaconEvent.New, emitSpy).length).toBeFalsy();
});
// no beacon added
expect(state.beacons.size).toEqual(0);
expect(state.beacons.get(getBeaconInfoIdentifier(redactedBeaconEvent))).toBeFalsy();
// no new beacon emit
expect(filterEmitCallsByEventType(BeaconEvent.New, emitSpy).length).toBeFalsy();
});
it('updates existing beacon info events in state', () => {
const beaconId = '$beacon1';
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const updatedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, beaconId);
it('updates existing beacon info events in state', () => {
const beaconId = '$beacon1';
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const updatedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, beaconId);
state.setStateEvents([beaconEvent]);
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
expect(beaconInstance?.isLive).toEqual(true);
state.setStateEvents([beaconEvent]);
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
expect(beaconInstance?.isLive).toEqual(true);
state.setStateEvents([updatedBeaconEvent]);
state.setStateEvents([updatedBeaconEvent]);
// same Beacon
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(beaconInstance);
// updated liveness
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))?.isLive).toEqual(false);
});
// same Beacon
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(beaconInstance);
// updated liveness
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))?.isLive).toEqual(false);
});
it('destroys and removes redacted beacon events', () => {
const beaconId = '$beacon1';
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const redactionEvent = new MatrixEvent({ type: 'm.room.redaction', redacts: beaconEvent.getId() });
redactedBeaconEvent.makeRedacted(redactionEvent);
it('destroys and removes redacted beacon events', () => {
const beaconId = '$beacon1';
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const redactionEvent = new MatrixEvent({ type: 'm.room.redaction', redacts: beaconEvent.getId() });
redactedBeaconEvent.makeRedacted(redactionEvent);
state.setStateEvents([beaconEvent]);
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
const destroySpy = jest.spyOn(beaconInstance as Beacon, 'destroy');
expect(beaconInstance?.isLive).toEqual(true);
state.setStateEvents([beaconEvent]);
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
const destroySpy = jest.spyOn(beaconInstance as Beacon, 'destroy');
expect(beaconInstance?.isLive).toEqual(true);
state.setStateEvents([redactedBeaconEvent]);
state.setStateEvents([redactedBeaconEvent]);
expect(destroySpy).toHaveBeenCalled();
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(undefined);
});
expect(destroySpy).toHaveBeenCalled();
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(undefined);
});
it('updates live beacon ids once after setting state events', () => {
const liveBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, '$beacon1');
const deadBeaconEvent = makeBeaconInfoEvent(userB, roomId, { isLive: false }, '$beacon2');
it('updates live beacon ids once after setting state events', () => {
const liveBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, '$beacon1');
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
expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(1);
// called once
expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(1);
// live beacon is now not live
const updatedLiveBeaconEvent = makeBeaconInfoEvent(
userA, roomId, { isLive: false }, liveBeaconEvent.getId(),
);
// live beacon is now not live
const updatedLiveBeaconEvent = makeBeaconInfoEvent(
userA, roomId, { isLive: false }, liveBeaconEvent.getId(),
);
state.setStateEvents([updatedLiveBeaconEvent]);
state.setStateEvents([updatedLiveBeaconEvent]);
expect(state.hasLiveBeacons).toBe(false);
expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(3);
expect(emitSpy).toHaveBeenCalledWith(RoomStateEvent.BeaconLiveness, state, false);
});
expect(state.hasLiveBeacons).toBe(false);
expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(3);
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,
JoinRule,
MatrixEvent,
MatrixEventEvent,
PendingEventOrdering,
RelationType,
RoomEvent,
@ -40,6 +41,7 @@ import { emitPromise } from "../test-utils/test-utils";
import { ReceiptType } from "../../src/@types/read_receipts";
import { FeatureSupport, Thread, ThreadEvent } from "../../src/models/thread";
import { WrappedReceipt } from "../../src/models/read-receipt";
import { Crypto } from "../../src/crypto";
describe("Room", function() {
const roomId = "!foo:bar";
@ -337,12 +339,12 @@ describe("Room", function() {
expect(event.getId()).toEqual(localEventId);
expect(event.status).toEqual(EventStatus.SENDING);
expect(emitRoom).toEqual(room);
expect(oldEventId).toBe(null);
expect(oldStatus).toBe(null);
expect(oldEventId).toBeUndefined();
expect(oldStatus).toBeUndefined();
break;
case 1:
expect(event.getId()).toEqual(remoteEventId);
expect(event.status).toBe(null);
expect(event.status).toBeNull();
expect(emitRoom).toEqual(room);
expect(oldEventId).toEqual(localEventId);
expect(oldStatus).toBe(EventStatus.SENDING);
@ -371,7 +373,7 @@ describe("Room", function() {
delete eventJson["event_id"];
const localEvent = new MatrixEvent(Object.assign({ event_id: "$temp" }, eventJson));
localEvent.status = EventStatus.SENDING;
expect(localEvent.getTxnId()).toBeNull();
expect(localEvent.getTxnId()).toBeUndefined();
expect(room.timeline.length).toEqual(0);
// 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.
const remoteEvent = new MatrixEvent(Object.assign({ event_id: realEventId }, eventJson));
expect(remoteEvent.getTxnId()).toBeNull();
expect(remoteEvent.getTxnId()).toBeUndefined();
room.addLiveEvents([remoteEvent]);
// the duplicate strategy code should ensure we don't add a 2nd event to the live timeline
expect(room.timeline.length).toEqual(1);
@ -1535,6 +1537,36 @@ describe("Room", function() {
[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() {
@ -1721,7 +1753,7 @@ describe("Room", function() {
});
room.updateMyMembership(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
room.updateMyMembership(JoinRule.Invite);
expect(events.length).toEqual(0);
@ -2636,4 +2668,42 @@ describe("Room", function() {
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);
// 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': " +
"The database connection is closing.");
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
*/
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);
}
@ -6800,12 +6800,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @param {boolean} options.emit Emits "event.decrypted" if set to true
*/
public decryptEventIfNeeded(event: MatrixEvent, options?: IDecryptOptions): Promise<void> {
if (event.shouldAttemptDecryption()) {
event.attemptDecryption(this.crypto, options);
if (event.shouldAttemptDecryption() && this.isCryptoEnabled()) {
event.attemptDecryption(this.crypto!, options);
}
if (event.isBeingDecrypted()) {
return event.getDecryptionPromise();
return event.getDecryptionPromise()!;
} else {
return Promise.resolve();
}
@ -9036,8 +9036,8 @@ export function fixNotificationCountOnDecryption(cli: MatrixClient, event: Matri
// TODO: Handle mentions received while the client is offline
// See also https://github.com/vector-im/element-web/issues/9069
const hasReadEvent = isThreadEvent
? room.getThread(event.threadRootId).hasUserReadEvent(cli.getUserId(), event.getId())
: room.hasUserReadEvent(cli.getUserId(), event.getId());
? room.getThread(event.threadRootId)?.hasUserReadEvent(cli.getUserId()!, event.getId())
: room.hasUserReadEvent(cli.getUserId()!, event.getId());
if (!hasReadEvent) {
let newCount = currentCount;

View File

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

View File

@ -107,8 +107,8 @@ export class Filter {
}
private definition: IFilterDefinition = {};
private roomFilter: FilterComponent;
private roomTimelineFilter: FilterComponent;
private roomFilter?: FilterComponent;
private roomTimelineFilter?: FilterComponent;
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)
* @return {?string} The filter ID
*/
getFilterId(): string | null {
public getFilterId(): string | undefined {
return this.filterId;
}
@ -124,7 +124,7 @@ export class Filter {
* Get the JSON body of the filter.
* @return {Object} The filter definition
*/
getDefinition(): IFilterDefinition {
public getDefinition(): IFilterDefinition {
return this.definition;
}
@ -132,7 +132,7 @@ export class Filter {
* Set the JSON body of the filter
* @param {Object} definition The filter definition
*/
setDefinition(definition: IFilterDefinition) {
public setDefinition(definition: IFilterDefinition) {
this.definition = definition;
// This is all ported from synapse's FilterCollection()
@ -201,7 +201,7 @@ export class Filter {
* Get the room.timeline filter component of the filter
* @return {FilterComponent} room timeline filter component
*/
getRoomTimelineFilterComponent(): FilterComponent {
public getRoomTimelineFilterComponent(): FilterComponent | undefined {
return this.roomTimelineFilter;
}
@ -211,15 +211,21 @@ export class Filter {
* @param {MatrixEvent[]} events the list of events being filtered
* @return {MatrixEvent[]} the list of events which match the filter
*/
filterRoomTimeline(events: MatrixEvent[]): MatrixEvent[] {
return this.roomTimelineFilter.filter(this.roomFilter.filter(events));
public filterRoomTimeline(events: MatrixEvent[]): MatrixEvent[] {
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.
* @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);
}
@ -234,14 +240,14 @@ export class Filter {
...this.definition?.room,
timeline: {
...this.definition?.room?.timeline,
[UNREAD_THREAD_NOTIFICATIONS.name]: !!enabled,
[UNREAD_THREAD_NOTIFICATIONS.name]: enabled,
},
},
};
}
setLazyLoadMembers(enabled: boolean): void {
setProp(this.definition, "room.state.lazy_load_members", !!enabled);
public setLazyLoadMembers(enabled: boolean): void {
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
* in responses.
*/
setIncludeLeaveRooms(includeLeave: boolean) {
public setIncludeLeaveRooms(includeLeave: boolean) {
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));
}
} catch (err) {
if (err.name === "AbortError") {
if ((<Error>err).name === "AbortError") {
defer.reject(err);
return;
}
defer.reject(new ConnectionError("request failed", err));
defer.reject(new ConnectionError("request failed", <Error>err));
}
break;
}
@ -207,7 +207,7 @@ export class MatrixHttpApi<O extends IHttpOpts> extends FetchHttpApi<O> {
base: this.opts.baseUrl,
path: MediaPrefix.R0 + "/upload",
params: {
access_token: this.opts.accessToken,
access_token: this.opts.accessToken!,
},
};
}

View File

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

View File

@ -75,13 +75,22 @@ export interface IAddLiveEventOptions
type EmittedEvents = RoomEvent.Timeline | RoomEvent.TimelineReset;
export type EventTimelineSetHandlerMap = {
[RoomEvent.Timeline]:
(event: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed: boolean, data: IRoomTimelineData) => void;
[RoomEvent.TimelineReset]: (room: Room, eventTimelineSet: EventTimelineSet, resetAllTimelines: boolean) => void;
[RoomEvent.Timeline]: (
event: MatrixEvent,
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> {
public readonly relations?: RelationsContainer;
public readonly relations: RelationsContainer;
private readonly timelineSupport: boolean;
private readonly displayPendingEvents: boolean;
private liveTimeline: EventTimeline;
@ -145,7 +154,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
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
* @return {module:models/event-timeline~EventTimeline} timeline
*/
public eventIdToTimeline(eventId: string): EventTimeline {
public eventIdToTimeline(eventId: string): EventTimeline | undefined {
return this._eventIdToTimeline.get(eventId);
}
@ -268,15 +277,13 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
if (forwardPaginationToken) {
// Now set the forward pagination token on the old live timeline
// so it can be forward-paginated.
oldTimeline.setPaginationToken(
forwardPaginationToken, EventTimeline.FORWARDS,
);
oldTimeline.setPaginationToken(forwardPaginationToken, EventTimeline.FORWARDS);
}
// make sure we set the pagination token before firing timelineReset,
// otherwise clients which start back-paginating will fail, and then get
// 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.
this.liveTimeline = newTimeline;
@ -352,7 +359,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
events: MatrixEvent[],
toStartOfTimeline: boolean,
timeline: EventTimeline,
paginationToken: string,
paginationToken?: string,
): void {
if (!timeline) {
throw new Error(
@ -544,7 +551,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
);
return;
}
timeline.setPaginationToken(paginationToken, direction);
timeline.setPaginationToken(paginationToken ?? null, direction);
}
}
@ -579,7 +586,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
roomState?: RoomState,
): void {
let duplicateStrategy = duplicateStrategyOrOpts as DuplicateStrategy || DuplicateStrategy.Ignore;
let timelineWasEmpty: boolean;
let timelineWasEmpty: boolean | undefined;
if (typeof (duplicateStrategyOrOpts) === 'object') {
({
duplicateStrategy = DuplicateStrategy.Ignore,
@ -681,7 +688,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
roomState?: RoomState,
): void {
let toStartOfTimeline = !!toStartOfTimelineOrOpts;
let timelineWasEmpty: boolean;
let timelineWasEmpty: boolean | undefined;
if (typeof (toStartOfTimelineOrOpts) === 'object') {
({ toStartOfTimeline, fromCache = false, roomState, timelineWasEmpty } = toStartOfTimelineOrOpts);
} else if (toStartOfTimelineOrOpts !== undefined) {
@ -794,10 +801,9 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
}
if (timeline1 === timeline2) {
// both events are in the same timeline - figure out their
// relative indices
let idx1: number;
let idx2: number;
// both events are in the same timeline - figure out their relative indices
let idx1: number | undefined = undefined;
let idx2: number | undefined = undefined;
const events = timeline1.getEvents();
for (let idx = 0; idx < events.length &&
(idx1 === undefined || idx2 === undefined); idx++) {
@ -809,7 +815,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
idx2 = idx;
}
}
return idx1 - idx2;
return idx1! - idx2!;
}
// 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.VisibilityChange]: (event: MatrixEvent, visible: boolean) => 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.RelationsCreated]: (relationType: string, eventType: string) => void;
} & ThreadEventHandlerMap;
export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHandlerMap> {
private pushActions: IActionsObject = null;
private _replacingEvent: MatrixEvent = null;
private _localRedactionEvent: MatrixEvent = null;
private pushActions: IActionsObject | null = null;
private _replacingEvent: MatrixEvent | null = null;
private _localRedactionEvent: MatrixEvent | null = null;
private _isCancelled = false;
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
* getSenderKey()
*/
private senderCurve25519Key: string = null;
private senderCurve25519Key: string | null = null;
/* ed25519 key which the sender of this event (for olm) or the creator of
* 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
* senderCurve25519Key and claimedEd25519Key.
@ -237,12 +237,12 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
/* 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
* 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
* 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,
* allows for a unique ID which does not change when the event comes back down sync.
*/
private txnId: string = null;
private txnId?: string;
/**
* @experimental
* A reference to the thread this event belongs to
*/
private thread: Thread = null;
private threadId: string;
private thread?: Thread;
private threadId?: string;
/* Set an approximate timestamp for the event relative the local clock.
* 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;
// XXX: these should be read-only
public sender: RoomMember = null;
public target: RoomMember = null;
public status: EventStatus = null;
public error: MatrixError = null;
public sender: RoomMember | null = null;
public target: RoomMember | null = null;
public status: EventStatus | null = null;
public error: MatrixError | null = null;
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,
* `Crypto` will set this the `VerificationRequest` for the event
* so it can be easily accessed from the timeline.
*/
public verificationRequest: VerificationRequest = null;
public verificationRequest?: VerificationRequest;
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]);
});
this.txnId = event.txn_id || null;
this.txnId = event.txn_id;
this.localTimestamp = Date.now() - (this.getAge() ?? 0);
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.
*/
public isBeingDecrypted(): boolean {
return this._decryptionPromise != null;
return this.decryptionPromise != null;
}
public getDecryptionPromise(): Promise<void> {
return this._decryptionPromise;
public getDecryptionPromise(): Promise<void> | null {
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
// new info.
//
if (this._decryptionPromise) {
if (this.decryptionPromise) {
logger.log(
`Event ${this.getId()} already being decrypted; queueing a retry`,
);
this.retryDecryption = true;
return this._decryptionPromise;
return this.decryptionPromise;
}
this._decryptionPromise = this.decryptionLoop(crypto, options);
return this._decryptionPromise;
this.decryptionPromise = this.decryptionLoop(crypto, options);
return this.decryptionPromise;
}
/**
@ -768,9 +768,9 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
private async decryptionLoop(crypto: Crypto, options: IDecryptOptions = {}): Promise<void> {
// 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
// `_decryptionPromise`).
// `decryptionPromise`).
await Promise.resolve();
// eslint-disable-next-line no-constant-condition
@ -778,7 +778,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
this.retryDecryption = false;
let res: IEventDecryptionResult;
let err: Error;
let err: Error | undefined = undefined;
try {
if (!crypto) {
res = this.badEncryptedMessage("Encryption not enabled");
@ -789,7 +789,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
}
}
} catch (e) {
if (e.name !== "DecryptionError") {
if ((<Error>e).name !== "DecryptionError") {
// not a decryption error: log the whole exception as an error
// (and don't bother with a retry)
const re = options.isRetry ? 're' : '';
@ -799,25 +799,25 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
`Error ${re}decrypting event ` +
`(id=${this.getId()}): ${e.stack || e}`,
);
this._decryptionPromise = null;
this.decryptionPromise = null;
this.retryDecryption = false;
return;
}
err = e;
err = e as Error;
// see if we have a retry queued.
//
// 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:
//
// * A: we check retryDecryption here and see that it is
// false
// * B: we get a second call to attemptDecryption, which sees
// that _decryptionPromise is set so sets
// that decryptionPromise is set so sets
// retryDecryption
// * A: we continue below, clear _decryptionPromise, and
// * A: we continue below, clear decryptionPromise, and
// never do the retry.
//
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
// 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
// there isn't an `Event.decrypted` on the way.
//
// see also notes on retryDecryption above.
//
this._decryptionPromise = null;
this.decryptionPromise = null;
this.retryDecryption = false;
this.setClearData(res);
@ -889,10 +889,8 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
*/
private setClearData(decryptionResult: IEventDecryptionResult): void {
this.clearEvent = decryptionResult.clearEvent;
this.senderCurve25519Key =
decryptionResult.senderCurve25519Key || null;
this.claimedEd25519Key =
decryptionResult.claimedEd25519Key || null;
this.senderCurve25519Key = decryptionResult.senderCurve25519Key ?? null;
this.claimedEd25519Key = decryptionResult.claimedEd25519Key ?? null;
this.forwardingCurve25519KeyChain =
decryptionResult.forwardingCurve25519KeyChain || [];
this.untrusted = decryptionResult.untrusted || false;
@ -941,7 +939,9 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
*
* @return {Object<string, string>}
*/
public getKeysClaimed(): Record<"ed25519", string> {
public getKeysClaimed(): Partial<Record<"ed25519", string>> {
if (!this.claimedEd25519Key) return {};
return {
ed25519: this.claimedEd25519Key,
};
@ -992,8 +992,8 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
*
* @return {boolean}
*/
public isKeySourceUntrusted(): boolean {
return this.untrusted;
public isKeySourceUntrusted(): boolean | undefined {
return !!this.untrusted;
}
public getUnsigned(): IUnsigned {
@ -1035,10 +1035,10 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
* by a visibility event being redacted).
*/
public applyVisibilityEvent(visibilityChange?: IVisibilityChange): void {
const visible = visibilityChange ? visibilityChange.visible : true;
const reason = visibilityChange ? visibilityChange.reason : null;
const visible = visibilityChange?.visible ?? true;
const reason = visibilityChange?.reason ?? null;
let change = false;
if (this.visibility.visible !== visibilityChange.visible) {
if (this.visibility.visible !== visible) {
change = true;
} else if (!this.visibility.visible && this.visibility["reason"] !== reason) {
change = true;
@ -1049,7 +1049,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
} else {
this.visibility = Object.freeze({
visible: false,
reason: reason,
reason,
});
}
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 (this.isEncrypted()) {
this.clearEvent = null;
this.clearEvent = undefined;
}
const keeps = REDACT_KEEP_CONTENT_MAP[this.getType()] || {};
@ -1217,7 +1217,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
*
* @param {Object} pushActions push actions
*/
public setPushActions(pushActions: IActionsObject): void {
public setPushActions(pushActions: IActionsObject | null): void {
this.pushActions = pushActions;
}
@ -1265,7 +1265,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
*
* @param {String} status The new status
*/
public setStatus(status: EventStatus): void {
public setStatus(status: EventStatus | null): void {
this.status = status;
this.emit(MatrixEventEvent.Status, this, status);
}
@ -1283,7 +1283,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
* given type
* @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
// encrypted rooms, so we have to check `getWireContent` for this.
const relation = this.getWireContent()?.["m.relates_to"];
@ -1326,7 +1326,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
return;
}
if (this._replacingEvent !== newEvent) {
this._replacingEvent = newEvent;
this._replacingEvent = newEvent ?? null;
this.emit(MatrixEventEvent.Replaced, this);
this.invalidateExtensibleEvent();
}
@ -1339,7 +1339,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
*
* @return {EventStatus}
*/
public getAssociatedStatus(): EventStatus | undefined {
public getAssociatedStatus(): EventStatus | null {
if (this._replacingEvent) {
return this._replacingEvent.status;
} else if (this._localRedactionEvent) {
@ -1373,7 +1373,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
*
* @return {MatrixEvent?}
*/
public replacingEvent(): MatrixEvent | undefined {
public replacingEvent(): MatrixEvent | null {
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.
* @return {MatrixEvent} the event
*/
public localRedactionEvent(): MatrixEvent | undefined {
public localRedactionEvent(): MatrixEvent | null {
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 {EventStatus} status The new status
*/
private onEventStatus = (event: MatrixEvent, status: EventStatus) => {
private onEventStatus = (event: MatrixEvent, status: EventStatus | null) => {
if (!event.isSending()) {
// Sending is done, so we don't need to listen anymore
event.removeListener(MatrixEventEvent.Status, this.onEventStatus);

View File

@ -35,7 +35,7 @@ export enum RoomMemberEvent {
}
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.PowerLevel]: (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> {
private _isOutOfBand = false;
private _modified: number;
public _requestedProfileInfo: boolean; // used by sync.ts
private modified = -1;
public requestedProfileInfo = false; // used by sync.ts
// XXX these should be read-only
public typing = false;
@ -52,14 +52,12 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
public rawDisplayName: string;
public powerLevel = 0;
public powerLevelNorm = 0;
public user?: User = null;
public membership: string = null;
public user?: User;
public membership?: string;
public disambiguate = false;
public events: {
member?: MatrixEvent;
} = {
member: null,
};
} = {};
/**
* Construct a new room member.
@ -119,7 +117,7 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
* @fires module:client~MatrixClient#event:"RoomMember.membership"
*/
public setMembershipEvent(event: MatrixEvent, roomState?: RoomState): void {
const displayName = event.getDirectionalContent().displayname;
const displayName = event.getDirectionalContent().displayname ?? "";
if (event.getType() !== EventType.RoomMember) {
return;
@ -141,23 +139,14 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
);
}
this.disambiguate = shouldDisambiguate(
this.userId,
displayName,
roomState,
);
this.disambiguate = shouldDisambiguate(this.userId, displayName, roomState);
const oldName = this.name;
this.name = calculateDisplayName(
this.userId,
displayName,
roomState,
this.disambiguate,
);
this.name = calculateDisplayName(this.userId, displayName, this.disambiguate);
// not quite raw: we strip direction override chars so it can safely be inserted into
// 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)) {
this.rawDisplayName = this.userId;
}
@ -180,15 +169,15 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
* @fires module:client~MatrixClient#event:"RoomMember.powerLevel"
*/
public setPowerLevelEvent(powerLevelEvent: MatrixEvent): void {
if (powerLevelEvent.getType() !== "m.room.power_levels") {
if (powerLevelEvent.getType() !== EventType.RoomPowerLevels || powerLevelEvent.getStateKey() !== "") {
return;
}
const evContent = powerLevelEvent.getDirectionalContent();
let maxLevel = evContent.users_default || 0;
const users = evContent.users || {};
Object.values(users).forEach(function(lvl: number) {
const users: { [userId: string]: number } = evContent.users || {};
Object.values(users).forEach((lvl: number) => {
maxLevel = Math.max(maxLevel, lvl);
});
const oldPowerLevel = this.powerLevel;
@ -244,7 +233,7 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
* Update the last modified time to the current time.
*/
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
*/
public getLastModifiedTime(): number {
return this._modified;
return this.modified;
}
public isKicked(): boolean {
return this.membership === "leave" &&
this.events.member.getSender() !== this.events.member.getStateKey();
return this.membership === "leave"
&& 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
* @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,
// we don't really know, but more likely to not be a direct chat
if (this.events.member) {
@ -280,7 +270,7 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
const memberEvent = this.events.member;
let memberContent = memberEvent.getContent();
let inviteSender = memberEvent.getSender();
let inviteSender: string | undefined = memberEvent.getSender();
if (memberContent.membership === "join") {
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
* @return {string} the mxc avatar url
*/
public getMxcAvatarUrl(): string | null {
public getMxcAvatarUrl(): string | undefined {
if (this.events.member) {
return this.events.member.getDirectionalContent().avatar_url;
} else if (this.user) {
return this.user.avatarUrl;
}
return null;
}
}
const MXID_PATTERN = /@.+:.+/;
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;
// First check if the displayname is something we consider truthy
@ -377,14 +366,13 @@ function shouldDisambiguate(selfUserId: string, displayName: string, roomState?:
function calculateDisplayName(
selfUserId: string,
displayName: string,
roomState: RoomState,
displayName: string | undefined,
disambiguate: boolean,
): string {
if (disambiguate) return utils.removeDirectionOverrideChars(displayName) + " (" + selfUserId + ")";
if (!displayName || displayName === selfUserId) return selfUserId;
if (disambiguate) return utils.removeDirectionOverrideChars(displayName) + " (" + selfUserId + ")";
// First check if the displayname is something we consider truthy
// after stripping it of zero width characters and padding spaces
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.Update]: (state: RoomState) => 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;
};
@ -82,17 +82,17 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
private displayNameToUserIds = new Map<string, string[]>();
private userIdsToDisplayNames: Record<string, string> = {};
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
// once set, we know the server supports the summary api
// and we should only trust that
// we could also only trust that before OOB members
// are loaded but doesn't seem worth the hassle atm
private summaryJoinedMemberCount: number = null;
private summaryJoinedMemberCount: number |null = null;
// same for invited member count
private invitedMemberCount: number = null;
private summaryInvitedMemberCount: number = null;
private modified: number;
private invitedMemberCount: number | null = null;
private summaryInvitedMemberCount: number | null = null;
private modified = -1;
// XXX: Should be read-only
public members: Record<string, RoomMember> = {}; // userId: RoomMember
@ -232,7 +232,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
if (sentinel === undefined) {
sentinel = new RoomMember(this.roomId, userId);
const member = this.members[userId];
if (member) {
if (member?.events.member) {
sentinel.setMembershipEvent(member.events.member, this);
}
this.sentinels[userId] = sentinel;
@ -257,9 +257,9 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
return stateKey === undefined ? [] : null;
}
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;
}
@ -306,8 +306,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
// copy markOutOfBand flags
this.getMembers().forEach((member) => {
if (member.isOutOfBand()) {
const copyMember = copy.getMember(member.userId);
copyMember.markOutOfBand();
copy.getMember(member.userId)?.markOutOfBand();
}
});
}
@ -324,8 +323,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
*/
public setUnknownStateEvents(events: MatrixEvent[]): void {
const unknownStateEvents = events.filter((event) => {
return !this.events.has(event.getType()) ||
!this.events.get(event.getType()).has(event.getStateKey());
return !this.events.has(event.getType()) || !this.events.get(event.getType())!.has(event.getStateKey()!);
});
this.setStateEvents(unknownStateEvents);
@ -349,12 +347,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
// update the core event dict
stateEvents.forEach((event) => {
if (event.getRoomId() !== this.roomId) {
return;
}
if (!event.isState()) {
return;
}
if (event.getRoomId() !== this.roomId || !event.isState()) return;
if (M_BEACON_INFO.matches(event.getType())) {
this.setBeacon(event);
@ -363,7 +356,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
const lastStateEvent = this.getStateEventMatching(event);
this.setStateEvent(event);
if (event.getType() === EventType.RoomMember) {
this.updateDisplayNameCache(event.getStateKey(), event.getContent().displayname);
this.updateDisplayNameCache(event.getStateKey()!, event.getContent().displayname ?? "");
this.updateThirdPartyTokenCache(event);
}
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
// clashing names rather than progressively which only catches 1 of them).
stateEvents.forEach((event) => {
if (event.getRoomId() !== this.roomId) {
return;
}
if (!event.isState()) {
return;
}
if (event.getRoomId() !== this.roomId || !event.isState()) return;
if (event.getType() === EventType.RoomMember) {
const userId = event.getStateKey();
const userId = event.getStateKey()!;
// leave events apparently elide the displayname or avatar_url,
// 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) => {
const relatedToEventId = event.getRelation()?.event_id;
// not related to a beacon we know about
// discard
if (!beaconByEventIdDict[relatedToEventId]) {
return;
}
// not related to a beacon we know about; discard
if (!relatedToEventId || !beaconByEventIdDict[relatedToEventId]) return;
matrixClient.decryptEventIfNeeded(event);
@ -501,7 +486,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
if (!this.events.has(event.getType())) {
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);
if (this.beacons.has(beaconIdentifier)) {
const beacon = this.beacons.get(beaconIdentifier);
const beacon = this.beacons.get(beaconIdentifier)!;
if (event.isRedacted()) {
if (beacon.beaconInfoId === event.getRedactionEvent()?.['redacts']) {
@ -558,7 +543,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
}
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 {
@ -646,7 +631,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
if (stateEvent.getType() !== EventType.RoomMember) {
return;
}
const userId = stateEvent.getStateKey();
const userId = stateEvent.getStateKey()!;
const existingMember = this.getMember(userId);
// never replace members received as part of the sync
if (existingMember && !existingMember.isOutOfBand()) {
@ -788,7 +773,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
* according to the room's state.
*/
public mayClientSendStateEvent(stateEventType: EventType | string, cli: MatrixClient): boolean {
if (cli.isGuest()) {
if (cli.isGuest() || !cli.credentials.userId) {
return false;
}
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'];
interface IOpts {
storageToken?: string;
pendingEventOrdering?: PendingEventOrdering;
timelineSupport?: boolean;
lazyLoadMembers?: boolean;
@ -158,7 +157,7 @@ export type RoomEventHandlerMap = {
event: MatrixEvent,
room: Room,
oldEventId?: string,
oldStatus?: EventStatus,
oldStatus?: EventStatus | null,
) => void;
[RoomEvent.OldStateUpdated]: (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 readonly pendingEventList?: MatrixEvent[];
// read by megolm via getter; boolean value - null indicates "use global value"
private blacklistUnverifiedDevices: boolean = null;
private selfMembership: string = null;
private blacklistUnverifiedDevices?: boolean;
private selfMembership?: string;
private summaryHeroes: string[] = null;
// flags to stop logspam about missing m.room.create events
private getTypeWarning = false;
@ -232,10 +231,6 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
* The room summary.
*/
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
/**
* 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
*/
private threads = new Map<string, Thread>();
public lastThread: Thread;
public lastThread?: Thread;
/**
* 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 {string} myUserId Required. The ID of the syncing user.
* @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
* 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;
this.name = roomId;
this.normalizedName = roomId;
// all our per-room timeline sets. the first one is the unfiltered ones;
// 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) {
this.pendingEventList = [];
this.client.store.getPendingEvents(this.roomId).then(events => {
const mapper = this.client.getEventMapper({
toDevice: false,
decrypt: false,
});
events.forEach(async (serializedEvent: Partial<IEvent>) => {
const event = new MatrixEvent(serializedEvent);
if (event.getType() === EventType.RoomMessageEncrypted) {
await event.attemptDecryption(this.client.crypto);
const event = mapper(serializedEvent);
if (event.getType() === EventType.RoomMessageEncrypted && this.client.isCryptoEnabled()) {
await event.attemptDecryption(this.client.crypto!);
}
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) {
this.membersPromise = Promise.resolve(false);
} 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
*/
public decryptCriticalEvents(): Promise<void> {
const readReceiptEventId = this.getEventReadUpTo(this.client.getUserId(), true);
public async decryptCriticalEvents(): Promise<void> {
if (!this.client.isCryptoEnabled()) return;
const readReceiptEventId = this.getEventReadUpTo(this.client.getUserId()!, true);
const events = this.getLiveTimeline().getEvents();
const readReceiptTimelineIndex = events.findIndex(matrixEvent => {
return matrixEvent.event.event_id === readReceiptEventId;
@ -410,9 +409,9 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
.slice(readReceiptTimelineIndex)
.filter(event => event.shouldAttemptDecryption())
.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
*/
public decryptAllEvents(): Promise<void> {
public async decryptAllEvents(): Promise<void> {
if (!this.client.isCryptoEnabled()) return;
const decryptionPromises = this
.getUnfilteredTimelineSet()
.getLiveTimeline()
.getEvents()
.filter(event => event.shouldAttemptDecryption())
.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
* @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, "");
if (!createEvent) {
if (!this.getVersionWarning) {
@ -454,9 +455,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
}
return '1';
}
const ver = createEvent.getContent()['room_version'];
if (ver === undefined) return '1';
return ver;
return createEvent.getContent()['room_version'] ?? '1';
}
/**
@ -535,7 +534,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
logger.log(`[${this.roomId}] Current version: ${currentVersion}`);
logger.log(`[${this.roomId}] Version capability: `, versionCap);
const result = {
const result: IRecommendedVersion = {
version: currentVersion,
needsUpgrade: false,
urgent: false,
@ -645,7 +644,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
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
*/
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
* @return {string} user id of the inviter
*/
public getDMInviter(): string {
public getDMInviter(): string | undefined {
if (this.myUserId) {
const me = this.getMember(this.myUserId);
if (me) {
@ -731,7 +730,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
return this.myUserId;
}
public getAvatarFallbackMember(): RoomMember {
public getAvatarFallbackMember(): RoomMember | undefined {
const memberCount = this.getInvitedAndJoinedMemberCount();
if (memberCount > 2) {
return;
@ -789,7 +788,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
private async loadMembersFromServer(): Promise<IStateEventWithRoomId[]> {
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;
}
@ -836,12 +835,12 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
this.currentState.setOutOfBandMembers(result.memberEvents);
// now the members are loaded, start to track the e2e devices if needed
if (this.client.isCryptoEnabled() && this.client.isRoomEncrypted(this.roomId)) {
this.client.crypto.trackRoomDevices(this.roomId);
this.client.crypto!.trackRoomDevices(this.roomId);
}
return result.fromServer;
}).catch((err) => {
// allow retries on fail
this.membersPromise = null;
this.membersPromise = undefined;
this.currentState.markOutOfBandMembersFailed();
throw err;
});
@ -850,7 +849,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
if (fromServer) {
const oobMembers = this.currentState.getMembers()
.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}`
+ ` members for room ${this.roomId}`);
const store = this.client.store;
@ -881,7 +880,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
await this.loadMembersIfNeeded();
await this.client.store.clearOutOfBandMembers(this.roomId);
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.
*/
public getBlacklistUnverifiedDevices(): boolean {
return this.blacklistUnverifiedDevices;
return !!this.blacklistUnverifiedDevices;
}
/**
@ -1457,8 +1456,8 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
/**
* @experimental
*/
public getThread(eventId: string): Thread {
return this.threads.get(eventId);
public getThread(eventId: string): Thread | null {
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
* @param {Filter} filter The filter to be applied to this timelineSet
* @param {Object=} opts Configuration options
* @param {*} opts.storageToken Optional.
* @return {EventTimelineSet} The timelineSet
*/
public getOrCreateFilteredTimelineSet(
@ -1799,7 +1797,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
const threadRelationship = rootEvent
.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
if (threadRelationship.current_user_participated) {
if (threadRelationship?.current_user_participated) {
this.threadsTimelineSets[1].addLiveEvent(rootEvent, {
duplicateStrategy: DuplicateStrategy.Ignore,
fromCache: false,
@ -2035,7 +2033,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
const redactId = event.event.redacts;
// if we know about this event, redact its contents now.
const redactedEvent = this.findEventById(redactId);
const redactedEvent = redactId ? this.findEventById(redactId) : undefined;
if (redactedEvent) {
redactedEvent.makeRedacted(event);
@ -2043,7 +2041,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
if (redactedEvent.isState()) {
const currentStateEvent = this.currentState.getStateEvents(
redactedEvent.getType(),
redactedEvent.getStateKey(),
redactedEvent.getStateKey()!,
);
if (currentStateEvent.getId() === redactedEvent.getId()) {
this.currentState.setStateEvents([redactedEvent]);
@ -2059,7 +2057,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
// they are based on are changed.
// 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
// 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);
this.txnToEvent[txnId] = event;
if (this.opts.pendingEventOrdering === PendingEventOrdering.Detached) {
if (this.pendingEventList) {
if (this.pendingEventList.some((e) => e.status === EventStatus.NOT_SENT)) {
logger.warn("Setting event as NOT_SENT due to messages in the same state");
event.setStatus(EventStatus.NOT_SENT);
@ -2199,8 +2197,8 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
if (event.isRedaction()) {
const redactId = event.event.redacts;
let redactedEvent = this.pendingEventList?.find(e => e.getId() === redactId);
if (!redactedEvent) {
let redactedEvent = this.pendingEventList.find(e => e.getId() === redactId);
if (!redactedEvent && redactId) {
redactedEvent = this.findEventById(redactId);
}
if (redactedEvent) {
@ -2212,7 +2210,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
for (let i = 0; i < this.timelineSets.length; i++) {
const timelineSet = this.timelineSets[i];
if (timelineSet.getFilter()) {
if (timelineSet.getFilter().filterRoomTimeline([event]).length) {
if (timelineSet.getFilter()!.filterRoomTimeline([event]).length) {
timelineSet.addEventToTimeline(event,
timelineSet.getLiveTimeline(), {
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 (newStatus == EventStatus.SENT && !newEventId) {
throw new Error("updatePendingEvent called with status=SENT, " +
"but no new event id");
throw new Error("updatePendingEvent called with status=SENT, but no new event id");
}
// SENT races against /sync, so we have to special-case it.
if (newStatus == EventStatus.SENT) {
const timeline = this.getTimelineForEvent(newEventId);
const timeline = this.getTimelineForEvent(newEventId!);
if (timeline) {
// we've already received the event via the event stream.
// nothing more to do here, assuming the transaction ID was correctly matched.
// Let's check that.
const remoteEvent = this.findEventById(newEventId);
const remoteTxnId = remoteEvent.getUnsigned().transaction_id;
if (!remoteTxnId) {
const remoteEvent = this.findEventById(newEventId!);
const remoteTxnId = remoteEvent?.getUnsigned().transaction_id;
if (!remoteTxnId && remoteEvent) {
// 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 echo yet. Handle it now.
@ -2395,18 +2392,18 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
if (newStatus == EventStatus.SENT) {
// update the event id
event.replaceLocalEventId(newEventId);
event.replaceLocalEventId(newEventId!);
const { shouldLiveInRoom, threadId } = this.eventShouldLiveIn(event);
const thread = this.getThread(threadId);
thread?.timelineSet.replaceEventId(oldEventId, newEventId);
const thread = threadId ? this.getThread(threadId) : undefined;
thread?.timelineSet.replaceEventId(oldEventId, newEventId!);
if (shouldLiveInRoom) {
// if the event was already in the timeline (which will be the case if
// opts.pendingEventOrdering==chronological), we need to update the
// timeline map.
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) {
@ -2414,7 +2411,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
if (this.pendingEventList) {
const removedEvent = this.getPendingEvent(oldEventId);
this.removePendingEvent(oldEventId);
if (removedEvent.isRedaction()) {
if (removedEvent?.isRedaction()) {
this.revertRedactionLocalEcho(removedEvent);
}
}
@ -2449,7 +2446,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
* they will go to the end of the timeline.
*
* @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'.
*/
public addLiveEvents(events: MatrixEvent[], addLiveEventOptions?: IAddLiveEventOptions): void;
@ -2462,8 +2459,8 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
duplicateStrategyOrOpts?: DuplicateStrategy | IAddLiveEventOptions,
fromCache = false,
): void {
let duplicateStrategy = duplicateStrategyOrOpts as DuplicateStrategy;
let timelineWasEmpty = false;
let duplicateStrategy: DuplicateStrategy | undefined = duplicateStrategyOrOpts as DuplicateStrategy;
let timelineWasEmpty: boolean | undefined = false;
if (typeof (duplicateStrategyOrOpts) === 'object') {
({
duplicateStrategy,
@ -2679,7 +2676,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
const membershipEvent = this.currentState.getStateEvents(EventType.RoomMember, this.myUserId);
if (membershipEvent) {
const membership = membershipEvent.getContent().membership;
this.updateMyMembership(membership);
this.updateMyMembership(membership!);
if (membership === "invite") {
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.
let otherNames: string[] = null;
let otherNames: string[] = [];
if (this.summaryHeroes) {
// if we have a summary, the member state events
// should be in the room state
otherNames = [];
// if we have a summary, the member state events should be in the room state
this.summaryHeroes.forEach((userId) => {
// filter service members
if (excludedUserIds.includes(userId)) {

View File

@ -38,13 +38,13 @@ export type UserEventHandlerMap = {
};
export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
private modified: number;
private modified = -1;
// XXX these should be read-only
public displayName: string;
public rawDisplayName: string;
public avatarUrl: string;
public presenceStatusMsg: string = null;
public displayName?: string;
public rawDisplayName?: string;
public avatarUrl?: string;
public presenceStatusMsg?: string;
public presence = "offline";
public lastActiveAgo = 0;
public lastPresenceTs = 0;
@ -52,10 +52,7 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
public events: {
presence?: MatrixEvent;
profile?: MatrixEvent;
} = {
presence: null,
profile: null,
};
} = {};
/**
* 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();
this.displayName = userId;
this.rawDisplayName = userId;
this.avatarUrl = null;
this.updateModifiedTime();
}
@ -150,11 +146,7 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
*/
public setDisplayName(name: string): void {
const oldName = this.displayName;
if (typeof name === "string") {
this.displayName = name;
} else {
this.displayName = undefined;
}
this.displayName = name;
if (name !== oldName) {
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.
* @param {string} name The new display name.
*/
public setRawDisplayName(name: string): void {
if (typeof name === "string") {
this.rawDisplayName = name;
} else {
this.rawDisplayName = undefined;
}
public setRawDisplayName(name?: string): void {
this.rawDisplayName = name;
}
/**
@ -178,7 +166,7 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
* as there is no underlying MatrixEvent to emit with.
* @param {string} url The new avatar URL.
*/
public setAvatarUrl(url: string): void {
public setAvatarUrl(url?: string): void {
const oldUrl = this.avatarUrl;
this.avatarUrl = url;
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
// if they have one (the m.room.member invites don't contain this).
room.getMembersWithMembership("invite").forEach(function(member) {
if (member._requestedProfileInfo) return;
member._requestedProfileInfo = true;
if (member.requestedProfileInfo) return;
member.requestedProfileInfo = true;
// try to get a cached copy first.
const user = client.getUser(member.userId);
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
* is no saved sync data.
*/
getSavedSync(): Promise<ISavedSync>;
getSavedSync(): Promise<ISavedSync | null>;
/**
* @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>;
isNewlyCreated(): Promise<boolean>;
setSyncData(syncData: ISyncResponse): Promise<void>;
getSavedSync(): Promise<ISavedSync>;
getSavedSync(): Promise<ISavedSync | null>;
getNextBatchToken(): Promise<string>;
clearDatabase(): Promise<void>;
getOutOfBandMembers(roomId: string): Promise<IStateEventWithRoomId[] | null>;

View File

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

View File

@ -23,13 +23,13 @@ import { IIndexedDBBackend, UserTuple } from "./indexeddb-backend";
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage";
export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend {
private worker: Worker;
private worker?: Worker;
private nextSeq = 0;
// The currently in-flight requests to the actual backend
private inFlight: Record<number, IDeferred<any>> = {}; // seq: promise
// Once we start connecting, we keep the promise and re-use it
// 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
@ -44,7 +44,7 @@ export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend {
*/
constructor(
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> {
if (this.startPromise === null) {
if (!this.startPromise) {
this.worker = this.workerFactory();
this.worker.onmessage = this.onWorkerMessage;
// 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");
});
}
@ -168,7 +168,7 @@ export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend {
this.inFlight[seq] = def;
this.worker.postMessage({ command, seq, args });
this.worker?.postMessage({ command, seq, args });
return def.promise;
});

View File

@ -20,7 +20,7 @@ import { logger } from '../logger';
interface ICmd {
command: string;
seq: number;
args?: any[];
args: any[];
}
/**
@ -39,7 +39,7 @@ interface ICmd {
*
*/
export class IndexedDBStoreWorker {
private backend: LocalIndexedDBStoreBackend = null;
private backend?: LocalIndexedDBStoreBackend;
/**
* @param {function} postMessage The web worker postMessage function that
@ -58,59 +58,59 @@ export class IndexedDBStoreWorker {
let prom;
switch (msg.command) {
case '_setupWorker':
case 'setupWorker':
// this is the 'indexedDB' global (where global != window
// because it's a web worker and there is no window).
this.backend = new LocalIndexedDBStoreBackend(indexedDB, msg.args[0]);
prom = Promise.resolve();
break;
case 'connect':
prom = this.backend.connect();
prom = this.backend?.connect();
break;
case 'isNewlyCreated':
prom = this.backend.isNewlyCreated();
prom = this.backend?.isNewlyCreated();
break;
case 'clearDatabase':
prom = this.backend.clearDatabase();
prom = this.backend?.clearDatabase();
break;
case 'getSavedSync':
prom = this.backend.getSavedSync(false);
prom = this.backend?.getSavedSync(false);
break;
case 'setSyncData':
prom = this.backend.setSyncData(msg.args[0]);
prom = this.backend?.setSyncData(msg.args[0]);
break;
case 'syncToDatabase':
prom = this.backend.syncToDatabase(msg.args[0]);
prom = this.backend?.syncToDatabase(msg.args[0]);
break;
case 'getUserPresenceEvents':
prom = this.backend.getUserPresenceEvents();
prom = this.backend?.getUserPresenceEvents();
break;
case 'getNextBatchToken':
prom = this.backend.getNextBatchToken();
prom = this.backend?.getNextBatchToken();
break;
case 'getOutOfBandMembers':
prom = this.backend.getOutOfBandMembers(msg.args[0]);
prom = this.backend?.getOutOfBandMembers(msg.args[0]);
break;
case 'clearOutOfBandMembers':
prom = this.backend.clearOutOfBandMembers(msg.args[0]);
prom = this.backend?.clearOutOfBandMembers(msg.args[0]);
break;
case 'setOutOfBandMembers':
prom = this.backend.setOutOfBandMembers(msg.args[0], msg.args[1]);
prom = this.backend?.setOutOfBandMembers(msg.args[0], msg.args[1]);
break;
case 'getClientOptions':
prom = this.backend.getClientOptions();
prom = this.backend?.getClientOptions();
break;
case 'storeClientOptions':
prom = this.backend.storeClientOptions(msg.args[0]);
prom = this.backend?.storeClientOptions(msg.args[0]);
break;
case 'saveToDeviceBatches':
prom = this.backend.saveToDeviceBatches(msg.args[0]);
prom = this.backend?.saveToDeviceBatches(msg.args[0]);
break;
case 'getOldestToDeviceBatch':
prom = this.backend.getOldestToDeviceBatch();
prom = this.backend?.getOldestToDeviceBatch();
break;
case 'removeToDeviceBatch':
prom = this.backend.removeToDeviceBatch(msg.args[0]);
prom = this.backend?.removeToDeviceBatch(msg.args[0]);
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
* is no saved sync data.
*/
public getSavedSync = this.degradable((): Promise<ISavedSync> => {
public getSavedSync = this.degradable((): Promise<ISavedSync | null> => {
return this.backend.getSavedSync();
}, "getSavedSync");
@ -292,16 +292,16 @@ export class IndexedDBStore extends MemoryStore {
*/
private degradable<A extends Array<any>, R = void>(
func: DegradableFn<A, R>,
fallback?: string,
fallback?: keyof MemoryStore,
): DegradableFn<A, R> {
const fallbackFn = super[fallback];
const fallbackFn = fallback ? super[fallback] as Function : null;
return async (...args) => {
try {
return await func.call(this, ...args);
} catch (e) {
logger.error("IndexedDBStore failure, degrading to MemoryStore", e);
this.emitter.emit("degraded", e);
this.emitter.emit("degraded", e as Error);
try {
// We try to delete IndexedDB after degrading since this store is only a
// 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 { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage";
function isValidFilterId(filterId: string): boolean {
function isValidFilterId(filterId?: string | number | null): boolean {
const isValidStr = typeof filterId === "string" &&
!!filterId &&
filterId !== "undefined" && // exclude these as we've serialized undefined in localStorage before
@ -55,13 +55,13 @@ export interface IOpts {
export class MemoryStore implements IStore {
private rooms: Record<string, Room> = {}; // roomId: Room
private users: Record<string, User> = {}; // userId: User
private syncToken: string = null;
private syncToken: string | null = null;
// userId: {
// filterId: Filter
// }
private filters: Record<string, Record<string, Filter>> = {};
public accountData: Record<string, MatrixEvent> = {}; // type : content
protected readonly localStorage: Storage;
protected readonly localStorage?: Storage;
private oobMembers: Record<string, IStateEventWithRoomId[]> = {}; // roomId: [member events]
private pendingEvents: { [roomId: string]: Partial<IEvent>[] } = {};
private clientOptions = {};
@ -115,7 +115,7 @@ export class MemoryStore implements IStore {
* @param {RoomState} state
* @param {RoomMember} member
*/
private onRoomMember = (event: MatrixEvent, state: RoomState, member: RoomMember) => {
private onRoomMember = (event: MatrixEvent | null, state: RoomState, member: RoomMember) => {
if (member.membership === "invite") {
// We do NOT add invited members because people love to typo user IDs
// which would then show up in these lists (!)
@ -126,9 +126,7 @@ export class MemoryStore implements IStore {
if (member.name) {
user.setDisplayName(member.name);
if (member.events.member) {
user.setRawDisplayName(
member.events.member.getDirectionalContent().displayname,
);
user.setRawDisplayName(member.events.member.getDirectionalContent().displayname);
}
}
if (member.events.member && member.events.member.getContent().avatar_url) {
@ -227,9 +225,7 @@ export class MemoryStore implements IStore {
* @param {Filter} filter
*/
public storeFilter(filter: Filter): void {
if (!filter?.userId) {
return;
}
if (!filter?.userId || !filter?.filterId) return;
if (!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
* is no saved sync data.
*/
public getSavedSync(): Promise<ISavedSync> {
public getSavedSync(): Promise<ISavedSync | 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
// if they have one (the m.room.member invites don't contain this).
room.getMembersWithMembership("invite").forEach(function(member) {
if (member._requestedProfileInfo) return;
member._requestedProfileInfo = true;
if (member.requestedProfileInfo) return;
member.requestedProfileInfo = true;
// try to get a cached copy first.
const user = client.getUser(member.userId);
let promise;

View File

@ -2210,7 +2210,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
this.opponentPartyId = msg.party_id || null;
}
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> {