You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-30 04:23:07 +03:00
Make more of the code conform to Strict TSC (#2756)
This commit is contained in:
committed by
GitHub
parent
f70f6db926
commit
12a4d2a749
@ -201,7 +201,7 @@ describe("MatrixClient events", function() {
|
|||||||
});
|
});
|
||||||
client!.on(RoomEvent.Timeline, function(event, room) {
|
client!.on(RoomEvent.Timeline, function(event, room) {
|
||||||
timelineFireCount++;
|
timelineFireCount++;
|
||||||
expect(room.roomId).toEqual("!erufh:bar");
|
expect(room?.roomId).toEqual("!erufh:bar");
|
||||||
});
|
});
|
||||||
client!.on(RoomEvent.Name, function(room) {
|
client!.on(RoomEvent.Name, function(room) {
|
||||||
roomNameInvokeCount++;
|
roomNameInvokeCount++;
|
||||||
|
@ -368,7 +368,7 @@ describe("MatrixClient event timelines", function() {
|
|||||||
expect(tl!.getEvents().length).toEqual(4);
|
expect(tl!.getEvents().length).toEqual(4);
|
||||||
for (let i = 0; i < 4; i++) {
|
for (let i = 0; i < 4; i++) {
|
||||||
expect(tl!.getEvents()[i].event).toEqual(EVENTS[i]);
|
expect(tl!.getEvents()[i].event).toEqual(EVENTS[i]);
|
||||||
expect(tl!.getEvents()[i]?.sender.name).toEqual(userName);
|
expect(tl!.getEvents()[i]?.sender?.name).toEqual(userName);
|
||||||
}
|
}
|
||||||
expect(tl!.getPaginationToken(EventTimeline.BACKWARDS))
|
expect(tl!.getPaginationToken(EventTimeline.BACKWARDS))
|
||||||
.toEqual("start_token");
|
.toEqual("start_token");
|
||||||
@ -406,7 +406,7 @@ describe("MatrixClient event timelines", function() {
|
|||||||
}).then(function(tl) {
|
}).then(function(tl) {
|
||||||
expect(tl!.getEvents().length).toEqual(2);
|
expect(tl!.getEvents().length).toEqual(2);
|
||||||
expect(tl!.getEvents()[1].event).toEqual(EVENTS[0]);
|
expect(tl!.getEvents()[1].event).toEqual(EVENTS[0]);
|
||||||
expect(tl!.getEvents()[1]?.sender.name).toEqual(userName);
|
expect(tl!.getEvents()[1]?.sender?.name).toEqual(userName);
|
||||||
expect(tl!.getPaginationToken(EventTimeline.BACKWARDS))
|
expect(tl!.getPaginationToken(EventTimeline.BACKWARDS))
|
||||||
.toEqual("f_1_1");
|
.toEqual("f_1_1");
|
||||||
// expect(tl.getPaginationToken(EventTimeline.FORWARDS))
|
// expect(tl.getPaginationToken(EventTimeline.FORWARDS))
|
||||||
@ -767,8 +767,8 @@ describe("MatrixClient event timelines", function() {
|
|||||||
httpBackend = testClient.httpBackend;
|
httpBackend = testClient.httpBackend;
|
||||||
await startClient(httpBackend, client);
|
await startClient(httpBackend, client);
|
||||||
|
|
||||||
const room = client.getRoom(roomId);
|
const room = client.getRoom(roomId)!;
|
||||||
const timelineSet = room.getTimelineSets()[0];
|
const timelineSet = room.getTimelineSets()[0]!;
|
||||||
await expect(client.getLatestTimeline(timelineSet)).rejects.toBeTruthy();
|
await expect(client.getLatestTimeline(timelineSet)).rejects.toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -786,7 +786,7 @@ describe("MatrixClient event timelines", function() {
|
|||||||
httpBackend = testClient.httpBackend;
|
httpBackend = testClient.httpBackend;
|
||||||
|
|
||||||
return startClient(httpBackend, client).then(() => {
|
return startClient(httpBackend, client).then(() => {
|
||||||
const room = client.getRoom(roomId);
|
const room = client.getRoom(roomId)!;
|
||||||
const timelineSet = room.getTimelineSets()[0];
|
const timelineSet = room.getTimelineSets()[0];
|
||||||
expect(client.getLatestTimeline(timelineSet)).rejects.toBeFalsy();
|
expect(client.getLatestTimeline(timelineSet)).rejects.toBeFalsy();
|
||||||
});
|
});
|
||||||
@ -849,7 +849,7 @@ describe("MatrixClient event timelines", function() {
|
|||||||
expect(tl!.getEvents().length).toEqual(4);
|
expect(tl!.getEvents().length).toEqual(4);
|
||||||
for (let i = 0; i < 4; i++) {
|
for (let i = 0; i < 4; i++) {
|
||||||
expect(tl!.getEvents()[i].event).toEqual(EVENTS[i]);
|
expect(tl!.getEvents()[i].event).toEqual(EVENTS[i]);
|
||||||
expect(tl!.getEvents()[i]?.sender.name).toEqual(userName);
|
expect(tl!.getEvents()[i]?.sender?.name).toEqual(userName);
|
||||||
}
|
}
|
||||||
expect(tl!.getPaginationToken(EventTimeline.BACKWARDS))
|
expect(tl!.getPaginationToken(EventTimeline.BACKWARDS))
|
||||||
.toEqual("start_token");
|
.toEqual("start_token");
|
||||||
@ -1129,8 +1129,8 @@ describe("MatrixClient event timelines", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should allow fetching all threads", async function() {
|
it("should allow fetching all threads", async function() {
|
||||||
const room = client.getRoom(roomId);
|
const room = client.getRoom(roomId)!;
|
||||||
const timelineSets = await room?.createThreadsTimelineSets();
|
const timelineSets = await room.createThreadsTimelineSets();
|
||||||
expect(timelineSets).not.toBeNull();
|
expect(timelineSets).not.toBeNull();
|
||||||
respondToThreads();
|
respondToThreads();
|
||||||
respondToThreads();
|
respondToThreads();
|
||||||
@ -1185,14 +1185,14 @@ describe("MatrixClient event timelines", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should allow fetching all threads", async function() {
|
it("should allow fetching all threads", async function() {
|
||||||
const room = client.getRoom(roomId);
|
const room = client.getRoom(roomId)!;
|
||||||
|
|
||||||
respondToFilter();
|
respondToFilter();
|
||||||
respondToSync();
|
respondToSync();
|
||||||
respondToFilter();
|
respondToFilter();
|
||||||
respondToSync();
|
respondToSync();
|
||||||
|
|
||||||
const timelineSetsPromise = room?.createThreadsTimelineSets();
|
const timelineSetsPromise = room.createThreadsTimelineSets();
|
||||||
expect(timelineSetsPromise).not.toBeNull();
|
expect(timelineSetsPromise).not.toBeNull();
|
||||||
await flushHttp(timelineSetsPromise!);
|
await flushHttp(timelineSetsPromise!);
|
||||||
respondToFilter();
|
respondToFilter();
|
||||||
@ -1218,7 +1218,7 @@ describe("MatrixClient event timelines", function() {
|
|||||||
const [allThreads] = timelineSets!;
|
const [allThreads] = timelineSets!;
|
||||||
|
|
||||||
respondToThreads().check((request) => {
|
respondToThreads().check((request) => {
|
||||||
expect(request.queryParams.filter).toEqual(JSON.stringify({
|
expect(request.queryParams?.filter).toEqual(JSON.stringify({
|
||||||
"lazy_load_members": true,
|
"lazy_load_members": true,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
@ -1244,7 +1244,7 @@ describe("MatrixClient event timelines", function() {
|
|||||||
state: [],
|
state: [],
|
||||||
next_batch: null,
|
next_batch: null,
|
||||||
}).check((request) => {
|
}).check((request) => {
|
||||||
expect(request.queryParams.from).toEqual(RANDOM_TOKEN);
|
expect(request.queryParams?.from).toEqual(RANDOM_TOKEN);
|
||||||
});
|
});
|
||||||
|
|
||||||
allThreads.getLiveTimeline().setPaginationToken(RANDOM_TOKEN, Direction.Backward);
|
allThreads.getLiveTimeline().setPaginationToken(RANDOM_TOKEN, Direction.Backward);
|
||||||
|
@ -160,8 +160,8 @@ describe("MatrixClient room timelines", function() {
|
|||||||
expect(room.timeline[1].status).toEqual(EventStatus.SENDING);
|
expect(room.timeline[1].status).toEqual(EventStatus.SENDING);
|
||||||
// check member
|
// check member
|
||||||
const member = room.timeline[1].sender;
|
const member = room.timeline[1].sender;
|
||||||
expect(member.userId).toEqual(userId);
|
expect(member?.userId).toEqual(userId);
|
||||||
expect(member.name).toEqual(userName);
|
expect(member?.name).toEqual(userName);
|
||||||
|
|
||||||
httpBackend!.flush("/sync", 1).then(function() {
|
httpBackend!.flush("/sync", 1).then(function() {
|
||||||
done();
|
done();
|
||||||
@ -327,11 +327,11 @@ describe("MatrixClient room timelines", function() {
|
|||||||
client!.scrollback(room).then(function() {
|
client!.scrollback(room).then(function() {
|
||||||
expect(room.timeline.length).toEqual(5);
|
expect(room.timeline.length).toEqual(5);
|
||||||
const joinMsg = room.timeline[0];
|
const joinMsg = room.timeline[0];
|
||||||
expect(joinMsg.sender.name).toEqual("Old Alice");
|
expect(joinMsg.sender?.name).toEqual("Old Alice");
|
||||||
const oldMsg = room.timeline[1];
|
const oldMsg = room.timeline[1];
|
||||||
expect(oldMsg.sender.name).toEqual("Old Alice");
|
expect(oldMsg.sender?.name).toEqual("Old Alice");
|
||||||
const newMsg = room.timeline[3];
|
const newMsg = room.timeline[3];
|
||||||
expect(newMsg.sender.name).toEqual(userName);
|
expect(newMsg.sender?.name).toEqual(userName);
|
||||||
|
|
||||||
// still have a sync to flush
|
// still have a sync to flush
|
||||||
httpBackend!.flush("/sync", 1).then(() => {
|
httpBackend!.flush("/sync", 1).then(() => {
|
||||||
@ -468,8 +468,8 @@ describe("MatrixClient room timelines", function() {
|
|||||||
]).then(function() {
|
]).then(function() {
|
||||||
const preNameEvent = room.timeline[room.timeline.length - 3];
|
const preNameEvent = room.timeline[room.timeline.length - 3];
|
||||||
const postNameEvent = room.timeline[room.timeline.length - 1];
|
const postNameEvent = room.timeline[room.timeline.length - 1];
|
||||||
expect(preNameEvent.sender.name).toEqual(userName);
|
expect(preNameEvent.sender?.name).toEqual(userName);
|
||||||
expect(postNameEvent.sender.name).toEqual("New Name");
|
expect(postNameEvent.sender?.name).toEqual("New Name");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1276,9 +1276,9 @@ describe("MatrixClient syncing", () => {
|
|||||||
client!.on(RoomEvent.TimelineReset, (room) => {
|
client!.on(RoomEvent.TimelineReset, (room) => {
|
||||||
resetCallCount++;
|
resetCallCount++;
|
||||||
|
|
||||||
const tl = room.getLiveTimeline();
|
const tl = room?.getLiveTimeline();
|
||||||
expect(tl.getEvents().length).toEqual(0);
|
expect(tl?.getEvents().length).toEqual(0);
|
||||||
const tok = tl.getPaginationToken(EventTimeline.BACKWARDS);
|
const tok = tl?.getPaginationToken(EventTimeline.BACKWARDS);
|
||||||
expect(tok).toEqual("newerTok");
|
expect(tok).toEqual("newerTok");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { UNREAD_THREAD_NOTIFICATIONS } from "../../src/@types/sync";
|
import { UNREAD_THREAD_NOTIFICATIONS } from "../../src/@types/sync";
|
||||||
import { Filter, IFilterDefinition } from "../../src/filter";
|
import { Filter, IFilterDefinition } from "../../src/filter";
|
||||||
|
import { mkEvent } from "../test-utils/test-utils";
|
||||||
|
import { EventType } from "../../src";
|
||||||
|
|
||||||
describe("Filter", function() {
|
describe("Filter", function() {
|
||||||
const filterId = "f1lt3ring15g00d4ursoul";
|
const filterId = "f1lt3ring15g00d4ursoul";
|
||||||
@ -57,4 +59,27 @@ describe("Filter", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("filterRoomTimeline", () => {
|
||||||
|
it("should return input if no roomTimelineFilter and roomFilter", () => {
|
||||||
|
const events = [mkEvent({ type: EventType.Sticker, content: {}, event: true })];
|
||||||
|
expect(new Filter(undefined).filterRoomTimeline(events)).toStrictEqual(events);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should filter using components when present", () => {
|
||||||
|
const definition: IFilterDefinition = {
|
||||||
|
room: {
|
||||||
|
timeline: {
|
||||||
|
types: [EventType.Sticker],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const filter = Filter.fromJson(userId, filterId, definition);
|
||||||
|
const events = [
|
||||||
|
mkEvent({ type: EventType.Sticker, content: {}, event: true }),
|
||||||
|
mkEvent({ type: EventType.RoomMessage, content: {}, event: true }),
|
||||||
|
];
|
||||||
|
expect(filter.filterRoomTimeline(events)).toStrictEqual([events[0]]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -259,7 +259,6 @@ describe("InteractiveAuth", () => {
|
|||||||
const requestEmailToken = jest.fn();
|
const requestEmailToken = jest.fn();
|
||||||
|
|
||||||
const ia = new InteractiveAuth({
|
const ia = new InteractiveAuth({
|
||||||
authData: null,
|
|
||||||
matrixClient: getFakeClient(),
|
matrixClient: getFakeClient(),
|
||||||
stateUpdated,
|
stateUpdated,
|
||||||
doRequest,
|
doRequest,
|
||||||
|
@ -14,7 +14,10 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MatrixEvent } from "../../../src/models/event";
|
import { MatrixEvent, MatrixEventEvent } from "../../../src/models/event";
|
||||||
|
import { emitPromise } from "../../test-utils/test-utils";
|
||||||
|
import { EventType } from "../../../src";
|
||||||
|
import { Crypto } from "../../../src/crypto";
|
||||||
|
|
||||||
describe('MatrixEvent', () => {
|
describe('MatrixEvent', () => {
|
||||||
it('should create copies of itself', () => {
|
it('should create copies of itself', () => {
|
||||||
@ -84,4 +87,36 @@ describe('MatrixEvent', () => {
|
|||||||
expect(ev.getWireContent().body).toBeUndefined();
|
expect(ev.getWireContent().body).toBeUndefined();
|
||||||
expect(ev.getWireContent().ciphertext).toBeUndefined();
|
expect(ev.getWireContent().ciphertext).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should abort decryption if fails with an error other than a DecryptionError", async () => {
|
||||||
|
const ev = new MatrixEvent({
|
||||||
|
type: EventType.RoomMessageEncrypted,
|
||||||
|
content: {
|
||||||
|
body: "Test",
|
||||||
|
},
|
||||||
|
event_id: "$event1:server",
|
||||||
|
});
|
||||||
|
await ev.attemptDecryption({
|
||||||
|
decryptEvent: jest.fn().mockRejectedValue(new Error("Not a DecryptionError")),
|
||||||
|
} as unknown as Crypto);
|
||||||
|
expect(ev.isEncrypted()).toBeTruthy();
|
||||||
|
expect(ev.isBeingDecrypted()).toBeFalsy();
|
||||||
|
expect(ev.isDecryptionFailure()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("applyVisibilityEvent", () => {
|
||||||
|
it("should emit VisibilityChange if a change was made", async () => {
|
||||||
|
const ev = new MatrixEvent({
|
||||||
|
type: "m.room.message",
|
||||||
|
content: {
|
||||||
|
body: "Test",
|
||||||
|
},
|
||||||
|
event_id: "$event1:server",
|
||||||
|
});
|
||||||
|
|
||||||
|
const prom = emitPromise(ev, MatrixEventEvent.VisibilityChange);
|
||||||
|
ev.applyVisibilityEvent({ visible: false, eventId: ev.getId(), reason: null });
|
||||||
|
await prom;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import * as utils from "../test-utils/test-utils";
|
import * as utils from "../test-utils/test-utils";
|
||||||
import { RoomMember, RoomMemberEvent } from "../../src/models/room-member";
|
import { RoomMember, RoomMemberEvent } from "../../src/models/room-member";
|
||||||
import { RoomState } from "../../src";
|
import { EventType, RoomState } from "../../src";
|
||||||
|
|
||||||
describe("RoomMember", function() {
|
describe("RoomMember", function() {
|
||||||
const roomId = "!foo:bar";
|
const roomId = "!foo:bar";
|
||||||
@ -142,33 +142,72 @@ describe("RoomMember", function() {
|
|||||||
expect(emitCount).toEqual(1);
|
expect(emitCount).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not honor string power levels.",
|
it("should not honor string power levels.", function() {
|
||||||
function() {
|
const event = utils.mkEvent({
|
||||||
const event = utils.mkEvent({
|
type: "m.room.power_levels",
|
||||||
type: "m.room.power_levels",
|
room: roomId,
|
||||||
room: roomId,
|
user: userA,
|
||||||
user: userA,
|
content: {
|
||||||
content: {
|
users_default: 20,
|
||||||
users_default: 20,
|
users: {
|
||||||
users: {
|
"@alice:bar": "5",
|
||||||
"@alice:bar": "5",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
event: true,
|
},
|
||||||
});
|
event: true,
|
||||||
let emitCount = 0;
|
|
||||||
|
|
||||||
member.on(RoomMemberEvent.PowerLevel, function(emitEvent, emitMember) {
|
|
||||||
emitCount += 1;
|
|
||||||
expect(emitMember.userId).toEqual('@alice:bar');
|
|
||||||
expect(emitMember.powerLevel).toEqual(20);
|
|
||||||
expect(emitEvent).toEqual(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
member.setPowerLevelEvent(event);
|
|
||||||
expect(member.powerLevel).toEqual(20);
|
|
||||||
expect(emitCount).toEqual(1);
|
|
||||||
});
|
});
|
||||||
|
let emitCount = 0;
|
||||||
|
|
||||||
|
member.on(RoomMemberEvent.PowerLevel, function(emitEvent, emitMember) {
|
||||||
|
emitCount += 1;
|
||||||
|
expect(emitMember.userId).toEqual('@alice:bar');
|
||||||
|
expect(emitMember.powerLevel).toEqual(20);
|
||||||
|
expect(emitEvent).toEqual(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
member.setPowerLevelEvent(event);
|
||||||
|
expect(member.powerLevel).toEqual(20);
|
||||||
|
expect(emitCount).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should no-op if given a non-state or unrelated event", () => {
|
||||||
|
const fn = jest.spyOn(member, "emit");
|
||||||
|
expect(fn).not.toHaveBeenCalledWith(RoomMemberEvent.PowerLevel);
|
||||||
|
member.setPowerLevelEvent(utils.mkEvent({
|
||||||
|
type: EventType.RoomPowerLevels,
|
||||||
|
room: roomId,
|
||||||
|
user: userA,
|
||||||
|
content: {
|
||||||
|
users_default: 20,
|
||||||
|
users: {
|
||||||
|
"@alice:bar": "5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
skey: "invalid",
|
||||||
|
event: true,
|
||||||
|
}));
|
||||||
|
const nonStateEv = utils.mkEvent({
|
||||||
|
type: EventType.RoomPowerLevels,
|
||||||
|
room: roomId,
|
||||||
|
user: userA,
|
||||||
|
content: {
|
||||||
|
users_default: 20,
|
||||||
|
users: {
|
||||||
|
"@alice:bar": "5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
event: true,
|
||||||
|
});
|
||||||
|
delete nonStateEv.event.state_key;
|
||||||
|
member.setPowerLevelEvent(nonStateEv);
|
||||||
|
member.setPowerLevelEvent(utils.mkEvent({
|
||||||
|
type: EventType.Sticker,
|
||||||
|
room: roomId,
|
||||||
|
user: userA,
|
||||||
|
content: {},
|
||||||
|
event: true,
|
||||||
|
}));
|
||||||
|
expect(fn).not.toHaveBeenCalledWith(RoomMemberEvent.PowerLevel);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("setTypingEvent", function() {
|
describe("setTypingEvent", function() {
|
||||||
@ -234,6 +273,79 @@ describe("RoomMember", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("isKicked", () => {
|
||||||
|
it("should return false if membership is not `leave`", () => {
|
||||||
|
const member1 = new RoomMember(roomId, userA);
|
||||||
|
member1.membership = "join";
|
||||||
|
expect(member1.isKicked()).toBeFalsy();
|
||||||
|
|
||||||
|
const member2 = new RoomMember(roomId, userA);
|
||||||
|
member2.membership = "invite";
|
||||||
|
expect(member2.isKicked()).toBeFalsy();
|
||||||
|
|
||||||
|
const member3 = new RoomMember(roomId, userA);
|
||||||
|
expect(member3.isKicked()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if the membership event is unknown", () => {
|
||||||
|
const member = new RoomMember(roomId, userA);
|
||||||
|
member.membership = "leave";
|
||||||
|
expect(member.isKicked()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if the member left of their own accord", () => {
|
||||||
|
const member = new RoomMember(roomId, userA);
|
||||||
|
member.membership = "leave";
|
||||||
|
member.events.member = utils.mkMembership({
|
||||||
|
event: true,
|
||||||
|
sender: userA,
|
||||||
|
mship: "leave",
|
||||||
|
skey: userA,
|
||||||
|
});
|
||||||
|
expect(member.isKicked()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true if the member's leave was sent by another user", () => {
|
||||||
|
const member = new RoomMember(roomId, userA);
|
||||||
|
member.membership = "leave";
|
||||||
|
member.events.member = utils.mkMembership({
|
||||||
|
event: true,
|
||||||
|
sender: userB,
|
||||||
|
mship: "leave",
|
||||||
|
skey: userA,
|
||||||
|
});
|
||||||
|
expect(member.isKicked()).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getDMInviter", () => {
|
||||||
|
it("should return userId of the sender of the invite if is_direct=true", () => {
|
||||||
|
const member = new RoomMember(roomId, userA);
|
||||||
|
member.membership = "invite";
|
||||||
|
member.events.member = utils.mkMembership({
|
||||||
|
event: true,
|
||||||
|
sender: userB,
|
||||||
|
mship: "invite",
|
||||||
|
skey: userA,
|
||||||
|
});
|
||||||
|
member.events.member.event.content!.is_direct = true;
|
||||||
|
expect(member.getDMInviter()).toBe(userB);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not return userId of the sender of the invite if is_direct=false", () => {
|
||||||
|
const member = new RoomMember(roomId, userA);
|
||||||
|
member.membership = "invite";
|
||||||
|
member.events.member = utils.mkMembership({
|
||||||
|
event: true,
|
||||||
|
sender: userB,
|
||||||
|
mship: "invite",
|
||||||
|
skey: userA,
|
||||||
|
});
|
||||||
|
member.events.member.event.content!.is_direct = false;
|
||||||
|
expect(member.getDMInviter()).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("setMembershipEvent", function() {
|
describe("setMembershipEvent", function() {
|
||||||
const joinEvent = utils.mkMembership({
|
const joinEvent = utils.mkMembership({
|
||||||
event: true,
|
event: true,
|
||||||
|
@ -303,92 +303,92 @@ describe("RoomState", function() {
|
|||||||
state.setStateEvents(events, { timelineWasEmpty: true });
|
state.setStateEvents(events, { timelineWasEmpty: true });
|
||||||
expect(emitCount).toEqual(1);
|
expect(emitCount).toEqual(1);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('beacon events', () => {
|
describe('beacon events', () => {
|
||||||
it('adds new beacon info events to state and emits', () => {
|
it('adds new beacon info events to state and emits', () => {
|
||||||
const beaconEvent = makeBeaconInfoEvent(userA, roomId);
|
const beaconEvent = makeBeaconInfoEvent(userA, roomId);
|
||||||
const emitSpy = jest.spyOn(state, 'emit');
|
const emitSpy = jest.spyOn(state, 'emit');
|
||||||
|
|
||||||
state.setStateEvents([beaconEvent]);
|
state.setStateEvents([beaconEvent]);
|
||||||
|
|
||||||
expect(state.beacons.size).toEqual(1);
|
expect(state.beacons.size).toEqual(1);
|
||||||
const beaconInstance = state.beacons.get(`${roomId}_${userA}`);
|
const beaconInstance = state.beacons.get(`${roomId}_${userA}`);
|
||||||
expect(beaconInstance).toBeTruthy();
|
expect(beaconInstance).toBeTruthy();
|
||||||
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.New, beaconEvent, beaconInstance);
|
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.New, beaconEvent, beaconInstance);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not add redacted beacon info events to state', () => {
|
it('does not add redacted beacon info events to state', () => {
|
||||||
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId);
|
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId);
|
||||||
const redactionEvent = new MatrixEvent({ type: 'm.room.redaction' });
|
const redactionEvent = new MatrixEvent({ type: 'm.room.redaction' });
|
||||||
redactedBeaconEvent.makeRedacted(redactionEvent);
|
redactedBeaconEvent.makeRedacted(redactionEvent);
|
||||||
const emitSpy = jest.spyOn(state, 'emit');
|
const emitSpy = jest.spyOn(state, 'emit');
|
||||||
|
|
||||||
state.setStateEvents([redactedBeaconEvent]);
|
state.setStateEvents([redactedBeaconEvent]);
|
||||||
|
|
||||||
// no beacon added
|
// no beacon added
|
||||||
expect(state.beacons.size).toEqual(0);
|
expect(state.beacons.size).toEqual(0);
|
||||||
expect(state.beacons.get(getBeaconInfoIdentifier(redactedBeaconEvent))).toBeFalsy();
|
expect(state.beacons.get(getBeaconInfoIdentifier(redactedBeaconEvent))).toBeFalsy();
|
||||||
// no new beacon emit
|
// no new beacon emit
|
||||||
expect(filterEmitCallsByEventType(BeaconEvent.New, emitSpy).length).toBeFalsy();
|
expect(filterEmitCallsByEventType(BeaconEvent.New, emitSpy).length).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates existing beacon info events in state', () => {
|
it('updates existing beacon info events in state', () => {
|
||||||
const beaconId = '$beacon1';
|
const beaconId = '$beacon1';
|
||||||
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
|
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
|
||||||
const updatedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, beaconId);
|
const updatedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, beaconId);
|
||||||
|
|
||||||
state.setStateEvents([beaconEvent]);
|
state.setStateEvents([beaconEvent]);
|
||||||
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
|
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
|
||||||
expect(beaconInstance?.isLive).toEqual(true);
|
expect(beaconInstance?.isLive).toEqual(true);
|
||||||
|
|
||||||
state.setStateEvents([updatedBeaconEvent]);
|
state.setStateEvents([updatedBeaconEvent]);
|
||||||
|
|
||||||
// same Beacon
|
// same Beacon
|
||||||
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(beaconInstance);
|
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(beaconInstance);
|
||||||
// updated liveness
|
// updated liveness
|
||||||
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))?.isLive).toEqual(false);
|
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))?.isLive).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('destroys and removes redacted beacon events', () => {
|
it('destroys and removes redacted beacon events', () => {
|
||||||
const beaconId = '$beacon1';
|
const beaconId = '$beacon1';
|
||||||
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
|
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
|
||||||
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
|
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
|
||||||
const redactionEvent = new MatrixEvent({ type: 'm.room.redaction', redacts: beaconEvent.getId() });
|
const redactionEvent = new MatrixEvent({ type: 'm.room.redaction', redacts: beaconEvent.getId() });
|
||||||
redactedBeaconEvent.makeRedacted(redactionEvent);
|
redactedBeaconEvent.makeRedacted(redactionEvent);
|
||||||
|
|
||||||
state.setStateEvents([beaconEvent]);
|
state.setStateEvents([beaconEvent]);
|
||||||
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
|
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
|
||||||
const destroySpy = jest.spyOn(beaconInstance as Beacon, 'destroy');
|
const destroySpy = jest.spyOn(beaconInstance as Beacon, 'destroy');
|
||||||
expect(beaconInstance?.isLive).toEqual(true);
|
expect(beaconInstance?.isLive).toEqual(true);
|
||||||
|
|
||||||
state.setStateEvents([redactedBeaconEvent]);
|
state.setStateEvents([redactedBeaconEvent]);
|
||||||
|
|
||||||
expect(destroySpy).toHaveBeenCalled();
|
expect(destroySpy).toHaveBeenCalled();
|
||||||
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(undefined);
|
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates live beacon ids once after setting state events', () => {
|
it('updates live beacon ids once after setting state events', () => {
|
||||||
const liveBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, '$beacon1');
|
const liveBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, '$beacon1');
|
||||||
const deadBeaconEvent = makeBeaconInfoEvent(userB, roomId, { isLive: false }, '$beacon2');
|
const deadBeaconEvent = makeBeaconInfoEvent(userB, roomId, { isLive: false }, '$beacon2');
|
||||||
|
|
||||||
const emitSpy = jest.spyOn(state, 'emit');
|
const emitSpy = jest.spyOn(state, 'emit');
|
||||||
|
|
||||||
state.setStateEvents([liveBeaconEvent, deadBeaconEvent]);
|
state.setStateEvents([liveBeaconEvent, deadBeaconEvent]);
|
||||||
|
|
||||||
// called once
|
// called once
|
||||||
expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(1);
|
expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(1);
|
||||||
|
|
||||||
// live beacon is now not live
|
// live beacon is now not live
|
||||||
const updatedLiveBeaconEvent = makeBeaconInfoEvent(
|
const updatedLiveBeaconEvent = makeBeaconInfoEvent(
|
||||||
userA, roomId, { isLive: false }, liveBeaconEvent.getId(),
|
userA, roomId, { isLive: false }, liveBeaconEvent.getId(),
|
||||||
);
|
);
|
||||||
|
|
||||||
state.setStateEvents([updatedLiveBeaconEvent]);
|
state.setStateEvents([updatedLiveBeaconEvent]);
|
||||||
|
|
||||||
expect(state.hasLiveBeacons).toBe(false);
|
expect(state.hasLiveBeacons).toBe(false);
|
||||||
expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(3);
|
expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(3);
|
||||||
expect(emitSpy).toHaveBeenCalledWith(RoomStateEvent.BeaconLiveness, state, false);
|
expect(emitSpy).toHaveBeenCalledWith(RoomStateEvent.BeaconLiveness, state, false);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1007,4 +1007,20 @@ describe("RoomState", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("mayClientSendStateEvent", () => {
|
||||||
|
it("should return false if the user isn't authenticated", () => {
|
||||||
|
expect(state.mayClientSendStateEvent("m.room.message", {
|
||||||
|
isGuest: jest.fn().mockReturnValue(false),
|
||||||
|
credentials: {},
|
||||||
|
} as unknown as MatrixClient)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false if the user is a guest", () => {
|
||||||
|
expect(state.mayClientSendStateEvent("m.room.message", {
|
||||||
|
isGuest: jest.fn().mockReturnValue(true),
|
||||||
|
credentials: { userId: userA },
|
||||||
|
} as unknown as MatrixClient)).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -27,6 +27,7 @@ import {
|
|||||||
EventType,
|
EventType,
|
||||||
JoinRule,
|
JoinRule,
|
||||||
MatrixEvent,
|
MatrixEvent,
|
||||||
|
MatrixEventEvent,
|
||||||
PendingEventOrdering,
|
PendingEventOrdering,
|
||||||
RelationType,
|
RelationType,
|
||||||
RoomEvent,
|
RoomEvent,
|
||||||
@ -40,6 +41,7 @@ import { emitPromise } from "../test-utils/test-utils";
|
|||||||
import { ReceiptType } from "../../src/@types/read_receipts";
|
import { ReceiptType } from "../../src/@types/read_receipts";
|
||||||
import { FeatureSupport, Thread, ThreadEvent } from "../../src/models/thread";
|
import { FeatureSupport, Thread, ThreadEvent } from "../../src/models/thread";
|
||||||
import { WrappedReceipt } from "../../src/models/read-receipt";
|
import { WrappedReceipt } from "../../src/models/read-receipt";
|
||||||
|
import { Crypto } from "../../src/crypto";
|
||||||
|
|
||||||
describe("Room", function() {
|
describe("Room", function() {
|
||||||
const roomId = "!foo:bar";
|
const roomId = "!foo:bar";
|
||||||
@ -337,12 +339,12 @@ describe("Room", function() {
|
|||||||
expect(event.getId()).toEqual(localEventId);
|
expect(event.getId()).toEqual(localEventId);
|
||||||
expect(event.status).toEqual(EventStatus.SENDING);
|
expect(event.status).toEqual(EventStatus.SENDING);
|
||||||
expect(emitRoom).toEqual(room);
|
expect(emitRoom).toEqual(room);
|
||||||
expect(oldEventId).toBe(null);
|
expect(oldEventId).toBeUndefined();
|
||||||
expect(oldStatus).toBe(null);
|
expect(oldStatus).toBeUndefined();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
expect(event.getId()).toEqual(remoteEventId);
|
expect(event.getId()).toEqual(remoteEventId);
|
||||||
expect(event.status).toBe(null);
|
expect(event.status).toBeNull();
|
||||||
expect(emitRoom).toEqual(room);
|
expect(emitRoom).toEqual(room);
|
||||||
expect(oldEventId).toEqual(localEventId);
|
expect(oldEventId).toEqual(localEventId);
|
||||||
expect(oldStatus).toBe(EventStatus.SENDING);
|
expect(oldStatus).toBe(EventStatus.SENDING);
|
||||||
@ -371,7 +373,7 @@ describe("Room", function() {
|
|||||||
delete eventJson["event_id"];
|
delete eventJson["event_id"];
|
||||||
const localEvent = new MatrixEvent(Object.assign({ event_id: "$temp" }, eventJson));
|
const localEvent = new MatrixEvent(Object.assign({ event_id: "$temp" }, eventJson));
|
||||||
localEvent.status = EventStatus.SENDING;
|
localEvent.status = EventStatus.SENDING;
|
||||||
expect(localEvent.getTxnId()).toBeNull();
|
expect(localEvent.getTxnId()).toBeUndefined();
|
||||||
expect(room.timeline.length).toEqual(0);
|
expect(room.timeline.length).toEqual(0);
|
||||||
|
|
||||||
// first add the local echo. This is done before the /send request is even sent.
|
// first add the local echo. This is done before the /send request is even sent.
|
||||||
@ -386,7 +388,7 @@ describe("Room", function() {
|
|||||||
|
|
||||||
// then /sync returns the remoteEvent, it should de-dupe based on the event ID.
|
// then /sync returns the remoteEvent, it should de-dupe based on the event ID.
|
||||||
const remoteEvent = new MatrixEvent(Object.assign({ event_id: realEventId }, eventJson));
|
const remoteEvent = new MatrixEvent(Object.assign({ event_id: realEventId }, eventJson));
|
||||||
expect(remoteEvent.getTxnId()).toBeNull();
|
expect(remoteEvent.getTxnId()).toBeUndefined();
|
||||||
room.addLiveEvents([remoteEvent]);
|
room.addLiveEvents([remoteEvent]);
|
||||||
// the duplicate strategy code should ensure we don't add a 2nd event to the live timeline
|
// the duplicate strategy code should ensure we don't add a 2nd event to the live timeline
|
||||||
expect(room.timeline.length).toEqual(1);
|
expect(room.timeline.length).toEqual(1);
|
||||||
@ -1535,6 +1537,36 @@ describe("Room", function() {
|
|||||||
[eventA, eventB, eventC],
|
[eventA, eventB, eventC],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should apply redactions eagerly in the pending event list", () => {
|
||||||
|
const client = (new TestClient("@alice:example.com", "alicedevice")).client;
|
||||||
|
const room = new Room(roomId, client, userA, {
|
||||||
|
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||||
|
});
|
||||||
|
|
||||||
|
const eventA = utils.mkMessage({
|
||||||
|
room: roomId,
|
||||||
|
user: userA,
|
||||||
|
msg: "remote 1",
|
||||||
|
event: true,
|
||||||
|
});
|
||||||
|
eventA.status = EventStatus.SENDING;
|
||||||
|
const redactA = utils.mkEvent({
|
||||||
|
room: roomId,
|
||||||
|
user: userA,
|
||||||
|
type: EventType.RoomRedaction,
|
||||||
|
content: {},
|
||||||
|
redacts: eventA.getId(),
|
||||||
|
event: true,
|
||||||
|
});
|
||||||
|
redactA.status = EventStatus.SENDING;
|
||||||
|
|
||||||
|
room.addPendingEvent(eventA, "TXN1");
|
||||||
|
expect(room.getPendingEvents()).toEqual([eventA]);
|
||||||
|
room.addPendingEvent(redactA, "TXN2");
|
||||||
|
expect(room.getPendingEvents()).toEqual([eventA, redactA]);
|
||||||
|
expect(eventA.isRedacted()).toBeTruthy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("updatePendingEvent", function() {
|
describe("updatePendingEvent", function() {
|
||||||
@ -1721,7 +1753,7 @@ describe("Room", function() {
|
|||||||
});
|
});
|
||||||
room.updateMyMembership(JoinRule.Invite);
|
room.updateMyMembership(JoinRule.Invite);
|
||||||
expect(room.getMyMembership()).toEqual(JoinRule.Invite);
|
expect(room.getMyMembership()).toEqual(JoinRule.Invite);
|
||||||
expect(events[0]).toEqual({ membership: "invite", oldMembership: null });
|
expect(events[0]).toEqual({ membership: "invite", oldMembership: undefined });
|
||||||
events.splice(0); //clear
|
events.splice(0); //clear
|
||||||
room.updateMyMembership(JoinRule.Invite);
|
room.updateMyMembership(JoinRule.Invite);
|
||||||
expect(events.length).toEqual(0);
|
expect(events.length).toEqual(0);
|
||||||
@ -2636,4 +2668,42 @@ describe("Room", function() {
|
|||||||
expect(room.hasThreadUnreadNotification()).toBe(false);
|
expect(room.hasThreadUnreadNotification()).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should load pending events from from the store and decrypt if needed", async () => {
|
||||||
|
const client = new TestClient(userA).client;
|
||||||
|
client.crypto = {
|
||||||
|
decryptEvent: jest.fn().mockResolvedValue({ clearEvent: { body: "enc" } }),
|
||||||
|
} as unknown as Crypto;
|
||||||
|
client.store.getPendingEvents = jest.fn(async roomId => [{
|
||||||
|
event_id: "$1:server",
|
||||||
|
type: "m.room.message",
|
||||||
|
content: { body: "1" },
|
||||||
|
sender: "@1:server",
|
||||||
|
room_id: roomId,
|
||||||
|
origin_server_ts: 1,
|
||||||
|
txn_id: "txn1",
|
||||||
|
}, {
|
||||||
|
event_id: "$2:server",
|
||||||
|
type: "m.room.encrypted",
|
||||||
|
content: { body: "2" },
|
||||||
|
sender: "@2:server",
|
||||||
|
room_id: roomId,
|
||||||
|
origin_server_ts: 2,
|
||||||
|
txn_id: "txn2",
|
||||||
|
}]);
|
||||||
|
const room = new Room(roomId, client, userA, {
|
||||||
|
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||||
|
});
|
||||||
|
await emitPromise(room, RoomEvent.LocalEchoUpdated);
|
||||||
|
await emitPromise(client, MatrixEventEvent.Decrypted);
|
||||||
|
await emitPromise(room, RoomEvent.LocalEchoUpdated);
|
||||||
|
const pendingEvents = room.getPendingEvents();
|
||||||
|
expect(pendingEvents).toHaveLength(2);
|
||||||
|
expect(pendingEvents[1].isDecryptionFailure()).toBeFalsy();
|
||||||
|
expect(pendingEvents[1].isBeingDecrypted()).toBeFalsy();
|
||||||
|
expect(pendingEvents[1].isEncrypted()).toBeTruthy();
|
||||||
|
for (const ev of pendingEvents) {
|
||||||
|
expect(room.getPendingEvent(ev.getId())).toBe(ev);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -60,7 +60,7 @@ describe("IndexedDBStore", () => {
|
|||||||
expect(await store.getOutOfBandMembers(roomId)).toHaveLength(1);
|
expect(await store.getOutOfBandMembers(roomId)).toHaveLength(1);
|
||||||
|
|
||||||
// Simulate a broken IDB
|
// Simulate a broken IDB
|
||||||
(store.backend as LocalIndexedDBStoreBackend)["db"].transaction = (): IDBTransaction => {
|
(store.backend as LocalIndexedDBStoreBackend)["db"]!.transaction = (): IDBTransaction => {
|
||||||
const err = new Error("Failed to execute 'transaction' on 'IDBDatabase': " +
|
const err = new Error("Failed to execute 'transaction' on 'IDBDatabase': " +
|
||||||
"The database connection is closing.");
|
"The database connection is closing.");
|
||||||
err.name = "InvalidStateError";
|
err.name = "InvalidStateError";
|
||||||
|
@ -3572,7 +3572,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* @throws Error if the event is not in QUEUED, NOT_SENT or ENCRYPTING state
|
* @throws Error if the event is not in QUEUED, NOT_SENT or ENCRYPTING state
|
||||||
*/
|
*/
|
||||||
public cancelPendingEvent(event: MatrixEvent) {
|
public cancelPendingEvent(event: MatrixEvent) {
|
||||||
if (![EventStatus.QUEUED, EventStatus.NOT_SENT, EventStatus.ENCRYPTING].includes(event.status)) {
|
if (![EventStatus.QUEUED, EventStatus.NOT_SENT, EventStatus.ENCRYPTING].includes(event.status!)) {
|
||||||
throw new Error("cannot cancel an event with status " + event.status);
|
throw new Error("cannot cancel an event with status " + event.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6800,12 +6800,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
|||||||
* @param {boolean} options.emit Emits "event.decrypted" if set to true
|
* @param {boolean} options.emit Emits "event.decrypted" if set to true
|
||||||
*/
|
*/
|
||||||
public decryptEventIfNeeded(event: MatrixEvent, options?: IDecryptOptions): Promise<void> {
|
public decryptEventIfNeeded(event: MatrixEvent, options?: IDecryptOptions): Promise<void> {
|
||||||
if (event.shouldAttemptDecryption()) {
|
if (event.shouldAttemptDecryption() && this.isCryptoEnabled()) {
|
||||||
event.attemptDecryption(this.crypto, options);
|
event.attemptDecryption(this.crypto!, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.isBeingDecrypted()) {
|
if (event.isBeingDecrypted()) {
|
||||||
return event.getDecryptionPromise();
|
return event.getDecryptionPromise()!;
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
@ -9036,8 +9036,8 @@ export function fixNotificationCountOnDecryption(cli: MatrixClient, event: Matri
|
|||||||
// TODO: Handle mentions received while the client is offline
|
// TODO: Handle mentions received while the client is offline
|
||||||
// See also https://github.com/vector-im/element-web/issues/9069
|
// See also https://github.com/vector-im/element-web/issues/9069
|
||||||
const hasReadEvent = isThreadEvent
|
const hasReadEvent = isThreadEvent
|
||||||
? room.getThread(event.threadRootId).hasUserReadEvent(cli.getUserId(), event.getId())
|
? room.getThread(event.threadRootId)?.hasUserReadEvent(cli.getUserId()!, event.getId())
|
||||||
: room.hasUserReadEvent(cli.getUserId(), event.getId());
|
: room.hasUserReadEvent(cli.getUserId()!, event.getId());
|
||||||
|
|
||||||
if (!hasReadEvent) {
|
if (!hasReadEvent) {
|
||||||
let newCount = currentCount;
|
let newCount = currentCount;
|
||||||
|
@ -35,7 +35,7 @@ import * as utils from "./utils";
|
|||||||
*/
|
*/
|
||||||
export function getHttpUriForMxc(
|
export function getHttpUriForMxc(
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
mxc: string,
|
mxc?: string,
|
||||||
width?: number,
|
width?: number,
|
||||||
height?: number,
|
height?: number,
|
||||||
resizeMethod?: string,
|
resizeMethod?: string,
|
||||||
|
@ -107,8 +107,8 @@ export class Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private definition: IFilterDefinition = {};
|
private definition: IFilterDefinition = {};
|
||||||
private roomFilter: FilterComponent;
|
private roomFilter?: FilterComponent;
|
||||||
private roomTimelineFilter: FilterComponent;
|
private roomTimelineFilter?: FilterComponent;
|
||||||
|
|
||||||
constructor(public readonly userId: string | undefined | null, public filterId?: string) {}
|
constructor(public readonly userId: string | undefined | null, public filterId?: string) {}
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ export class Filter {
|
|||||||
* Get the ID of this filter on your homeserver (if known)
|
* Get the ID of this filter on your homeserver (if known)
|
||||||
* @return {?string} The filter ID
|
* @return {?string} The filter ID
|
||||||
*/
|
*/
|
||||||
getFilterId(): string | null {
|
public getFilterId(): string | undefined {
|
||||||
return this.filterId;
|
return this.filterId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ export class Filter {
|
|||||||
* Get the JSON body of the filter.
|
* Get the JSON body of the filter.
|
||||||
* @return {Object} The filter definition
|
* @return {Object} The filter definition
|
||||||
*/
|
*/
|
||||||
getDefinition(): IFilterDefinition {
|
public getDefinition(): IFilterDefinition {
|
||||||
return this.definition;
|
return this.definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ export class Filter {
|
|||||||
* Set the JSON body of the filter
|
* Set the JSON body of the filter
|
||||||
* @param {Object} definition The filter definition
|
* @param {Object} definition The filter definition
|
||||||
*/
|
*/
|
||||||
setDefinition(definition: IFilterDefinition) {
|
public setDefinition(definition: IFilterDefinition) {
|
||||||
this.definition = definition;
|
this.definition = definition;
|
||||||
|
|
||||||
// This is all ported from synapse's FilterCollection()
|
// This is all ported from synapse's FilterCollection()
|
||||||
@ -201,7 +201,7 @@ export class Filter {
|
|||||||
* Get the room.timeline filter component of the filter
|
* Get the room.timeline filter component of the filter
|
||||||
* @return {FilterComponent} room timeline filter component
|
* @return {FilterComponent} room timeline filter component
|
||||||
*/
|
*/
|
||||||
getRoomTimelineFilterComponent(): FilterComponent {
|
public getRoomTimelineFilterComponent(): FilterComponent | undefined {
|
||||||
return this.roomTimelineFilter;
|
return this.roomTimelineFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,15 +211,21 @@ export class Filter {
|
|||||||
* @param {MatrixEvent[]} events the list of events being filtered
|
* @param {MatrixEvent[]} events the list of events being filtered
|
||||||
* @return {MatrixEvent[]} the list of events which match the filter
|
* @return {MatrixEvent[]} the list of events which match the filter
|
||||||
*/
|
*/
|
||||||
filterRoomTimeline(events: MatrixEvent[]): MatrixEvent[] {
|
public filterRoomTimeline(events: MatrixEvent[]): MatrixEvent[] {
|
||||||
return this.roomTimelineFilter.filter(this.roomFilter.filter(events));
|
if (this.roomFilter) {
|
||||||
|
events = this.roomFilter.filter(events);
|
||||||
|
}
|
||||||
|
if (this.roomTimelineFilter) {
|
||||||
|
events = this.roomTimelineFilter.filter(events);
|
||||||
|
}
|
||||||
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the max number of events to return for each room's timeline.
|
* Set the max number of events to return for each room's timeline.
|
||||||
* @param {Number} limit The max number of events to return for each room.
|
* @param {Number} limit The max number of events to return for each room.
|
||||||
*/
|
*/
|
||||||
setTimelineLimit(limit: number) {
|
public setTimelineLimit(limit: number) {
|
||||||
setProp(this.definition, "room.timeline.limit", limit);
|
setProp(this.definition, "room.timeline.limit", limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,14 +240,14 @@ export class Filter {
|
|||||||
...this.definition?.room,
|
...this.definition?.room,
|
||||||
timeline: {
|
timeline: {
|
||||||
...this.definition?.room?.timeline,
|
...this.definition?.room?.timeline,
|
||||||
[UNREAD_THREAD_NOTIFICATIONS.name]: !!enabled,
|
[UNREAD_THREAD_NOTIFICATIONS.name]: enabled,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setLazyLoadMembers(enabled: boolean): void {
|
public setLazyLoadMembers(enabled: boolean): void {
|
||||||
setProp(this.definition, "room.state.lazy_load_members", !!enabled);
|
setProp(this.definition, "room.state.lazy_load_members", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -249,7 +255,7 @@ export class Filter {
|
|||||||
* @param {boolean} includeLeave True to make rooms the user has left appear
|
* @param {boolean} includeLeave True to make rooms the user has left appear
|
||||||
* in responses.
|
* in responses.
|
||||||
*/
|
*/
|
||||||
setIncludeLeaveRooms(includeLeave: boolean) {
|
public setIncludeLeaveRooms(includeLeave: boolean) {
|
||||||
setProp(this.definition, "room.include_leave", includeLeave);
|
setProp(this.definition, "room.include_leave", includeLeave);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,11 +112,11 @@ export class MatrixHttpApi<O extends IHttpOpts> extends FetchHttpApi<O> {
|
|||||||
defer.resolve(JSON.parse(xhr.responseText));
|
defer.resolve(JSON.parse(xhr.responseText));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.name === "AbortError") {
|
if ((<Error>err).name === "AbortError") {
|
||||||
defer.reject(err);
|
defer.reject(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
defer.reject(new ConnectionError("request failed", err));
|
defer.reject(new ConnectionError("request failed", <Error>err));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -207,7 +207,7 @@ export class MatrixHttpApi<O extends IHttpOpts> extends FetchHttpApi<O> {
|
|||||||
base: this.opts.baseUrl,
|
base: this.opts.baseUrl,
|
||||||
path: MediaPrefix.R0 + "/upload",
|
path: MediaPrefix.R0 + "/upload",
|
||||||
params: {
|
params: {
|
||||||
access_token: this.opts.accessToken,
|
access_token: this.opts.accessToken!,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ export class EventContext {
|
|||||||
* backwards in time
|
* backwards in time
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
public getPaginateToken(backwards = false): string {
|
public getPaginateToken(backwards = false): string | null {
|
||||||
return this.paginateTokens[backwards ? Direction.Backward : Direction.Forward];
|
return this.paginateTokens[backwards ? Direction.Backward : Direction.Forward];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,13 +75,22 @@ export interface IAddLiveEventOptions
|
|||||||
type EmittedEvents = RoomEvent.Timeline | RoomEvent.TimelineReset;
|
type EmittedEvents = RoomEvent.Timeline | RoomEvent.TimelineReset;
|
||||||
|
|
||||||
export type EventTimelineSetHandlerMap = {
|
export type EventTimelineSetHandlerMap = {
|
||||||
[RoomEvent.Timeline]:
|
[RoomEvent.Timeline]: (
|
||||||
(event: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed: boolean, data: IRoomTimelineData) => void;
|
event: MatrixEvent,
|
||||||
[RoomEvent.TimelineReset]: (room: Room, eventTimelineSet: EventTimelineSet, resetAllTimelines: boolean) => void;
|
room: Room | undefined,
|
||||||
|
toStartOfTimeline: boolean | undefined,
|
||||||
|
removed: boolean,
|
||||||
|
data: IRoomTimelineData,
|
||||||
|
) => void;
|
||||||
|
[RoomEvent.TimelineReset]: (
|
||||||
|
room: Room | undefined,
|
||||||
|
eventTimelineSet: EventTimelineSet,
|
||||||
|
resetAllTimelines: boolean,
|
||||||
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTimelineSetHandlerMap> {
|
export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTimelineSetHandlerMap> {
|
||||||
public readonly relations?: RelationsContainer;
|
public readonly relations: RelationsContainer;
|
||||||
private readonly timelineSupport: boolean;
|
private readonly timelineSupport: boolean;
|
||||||
private readonly displayPendingEvents: boolean;
|
private readonly displayPendingEvents: boolean;
|
||||||
private liveTimeline: EventTimeline;
|
private liveTimeline: EventTimeline;
|
||||||
@ -145,7 +154,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
|||||||
|
|
||||||
this.filter = opts.filter;
|
this.filter = opts.filter;
|
||||||
|
|
||||||
this.relations = this.room?.relations ?? new RelationsContainer(room?.client ?? client);
|
this.relations = this.room?.relations ?? new RelationsContainer(room?.client ?? client!);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -212,7 +221,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
|||||||
* @param {String} eventId the eventId being sought
|
* @param {String} eventId the eventId being sought
|
||||||
* @return {module:models/event-timeline~EventTimeline} timeline
|
* @return {module:models/event-timeline~EventTimeline} timeline
|
||||||
*/
|
*/
|
||||||
public eventIdToTimeline(eventId: string): EventTimeline {
|
public eventIdToTimeline(eventId: string): EventTimeline | undefined {
|
||||||
return this._eventIdToTimeline.get(eventId);
|
return this._eventIdToTimeline.get(eventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,15 +277,13 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
|||||||
if (forwardPaginationToken) {
|
if (forwardPaginationToken) {
|
||||||
// Now set the forward pagination token on the old live timeline
|
// Now set the forward pagination token on the old live timeline
|
||||||
// so it can be forward-paginated.
|
// so it can be forward-paginated.
|
||||||
oldTimeline.setPaginationToken(
|
oldTimeline.setPaginationToken(forwardPaginationToken, EventTimeline.FORWARDS);
|
||||||
forwardPaginationToken, EventTimeline.FORWARDS,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure we set the pagination token before firing timelineReset,
|
// make sure we set the pagination token before firing timelineReset,
|
||||||
// otherwise clients which start back-paginating will fail, and then get
|
// otherwise clients which start back-paginating will fail, and then get
|
||||||
// stuck without realising that they *can* back-paginate.
|
// stuck without realising that they *can* back-paginate.
|
||||||
newTimeline.setPaginationToken(backPaginationToken, EventTimeline.BACKWARDS);
|
newTimeline.setPaginationToken(backPaginationToken ?? null, EventTimeline.BACKWARDS);
|
||||||
|
|
||||||
// Now we can swap the live timeline to the new one.
|
// Now we can swap the live timeline to the new one.
|
||||||
this.liveTimeline = newTimeline;
|
this.liveTimeline = newTimeline;
|
||||||
@ -352,7 +359,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
|||||||
events: MatrixEvent[],
|
events: MatrixEvent[],
|
||||||
toStartOfTimeline: boolean,
|
toStartOfTimeline: boolean,
|
||||||
timeline: EventTimeline,
|
timeline: EventTimeline,
|
||||||
paginationToken: string,
|
paginationToken?: string,
|
||||||
): void {
|
): void {
|
||||||
if (!timeline) {
|
if (!timeline) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -544,7 +551,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
timeline.setPaginationToken(paginationToken, direction);
|
timeline.setPaginationToken(paginationToken ?? null, direction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -579,7 +586,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
|||||||
roomState?: RoomState,
|
roomState?: RoomState,
|
||||||
): void {
|
): void {
|
||||||
let duplicateStrategy = duplicateStrategyOrOpts as DuplicateStrategy || DuplicateStrategy.Ignore;
|
let duplicateStrategy = duplicateStrategyOrOpts as DuplicateStrategy || DuplicateStrategy.Ignore;
|
||||||
let timelineWasEmpty: boolean;
|
let timelineWasEmpty: boolean | undefined;
|
||||||
if (typeof (duplicateStrategyOrOpts) === 'object') {
|
if (typeof (duplicateStrategyOrOpts) === 'object') {
|
||||||
({
|
({
|
||||||
duplicateStrategy = DuplicateStrategy.Ignore,
|
duplicateStrategy = DuplicateStrategy.Ignore,
|
||||||
@ -681,7 +688,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
|||||||
roomState?: RoomState,
|
roomState?: RoomState,
|
||||||
): void {
|
): void {
|
||||||
let toStartOfTimeline = !!toStartOfTimelineOrOpts;
|
let toStartOfTimeline = !!toStartOfTimelineOrOpts;
|
||||||
let timelineWasEmpty: boolean;
|
let timelineWasEmpty: boolean | undefined;
|
||||||
if (typeof (toStartOfTimelineOrOpts) === 'object') {
|
if (typeof (toStartOfTimelineOrOpts) === 'object') {
|
||||||
({ toStartOfTimeline, fromCache = false, roomState, timelineWasEmpty } = toStartOfTimelineOrOpts);
|
({ toStartOfTimeline, fromCache = false, roomState, timelineWasEmpty } = toStartOfTimelineOrOpts);
|
||||||
} else if (toStartOfTimelineOrOpts !== undefined) {
|
} else if (toStartOfTimelineOrOpts !== undefined) {
|
||||||
@ -794,10 +801,9 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (timeline1 === timeline2) {
|
if (timeline1 === timeline2) {
|
||||||
// both events are in the same timeline - figure out their
|
// both events are in the same timeline - figure out their relative indices
|
||||||
// relative indices
|
let idx1: number | undefined = undefined;
|
||||||
let idx1: number;
|
let idx2: number | undefined = undefined;
|
||||||
let idx2: number;
|
|
||||||
const events = timeline1.getEvents();
|
const events = timeline1.getEvents();
|
||||||
for (let idx = 0; idx < events.length &&
|
for (let idx = 0; idx < events.length &&
|
||||||
(idx1 === undefined || idx2 === undefined); idx++) {
|
(idx1 === undefined || idx2 === undefined); idx++) {
|
||||||
@ -809,7 +815,7 @@ export class EventTimelineSet extends TypedEventEmitter<EmittedEvents, EventTime
|
|||||||
idx2 = idx;
|
idx2 = idx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return idx1 - idx2;
|
return idx1! - idx2!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the events are in different timelines. Iterate through the
|
// the events are in different timelines. Iterate through the
|
||||||
|
@ -195,15 +195,15 @@ export type MatrixEventHandlerMap = {
|
|||||||
[MatrixEventEvent.BeforeRedaction]: (event: MatrixEvent, redactionEvent: MatrixEvent) => void;
|
[MatrixEventEvent.BeforeRedaction]: (event: MatrixEvent, redactionEvent: MatrixEvent) => void;
|
||||||
[MatrixEventEvent.VisibilityChange]: (event: MatrixEvent, visible: boolean) => void;
|
[MatrixEventEvent.VisibilityChange]: (event: MatrixEvent, visible: boolean) => void;
|
||||||
[MatrixEventEvent.LocalEventIdReplaced]: (event: MatrixEvent) => void;
|
[MatrixEventEvent.LocalEventIdReplaced]: (event: MatrixEvent) => void;
|
||||||
[MatrixEventEvent.Status]: (event: MatrixEvent, status: EventStatus) => void;
|
[MatrixEventEvent.Status]: (event: MatrixEvent, status: EventStatus | null) => void;
|
||||||
[MatrixEventEvent.Replaced]: (event: MatrixEvent) => void;
|
[MatrixEventEvent.Replaced]: (event: MatrixEvent) => void;
|
||||||
[MatrixEventEvent.RelationsCreated]: (relationType: string, eventType: string) => void;
|
[MatrixEventEvent.RelationsCreated]: (relationType: string, eventType: string) => void;
|
||||||
} & ThreadEventHandlerMap;
|
} & ThreadEventHandlerMap;
|
||||||
|
|
||||||
export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHandlerMap> {
|
export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHandlerMap> {
|
||||||
private pushActions: IActionsObject = null;
|
private pushActions: IActionsObject | null = null;
|
||||||
private _replacingEvent: MatrixEvent = null;
|
private _replacingEvent: MatrixEvent | null = null;
|
||||||
private _localRedactionEvent: MatrixEvent = null;
|
private _localRedactionEvent: MatrixEvent | null = null;
|
||||||
private _isCancelled = false;
|
private _isCancelled = false;
|
||||||
private clearEvent?: IClearEvent;
|
private clearEvent?: IClearEvent;
|
||||||
|
|
||||||
@ -222,12 +222,12 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
/* curve25519 key which we believe belongs to the sender of the event. See
|
/* curve25519 key which we believe belongs to the sender of the event. See
|
||||||
* getSenderKey()
|
* getSenderKey()
|
||||||
*/
|
*/
|
||||||
private senderCurve25519Key: string = null;
|
private senderCurve25519Key: string | null = null;
|
||||||
|
|
||||||
/* ed25519 key which the sender of this event (for olm) or the creator of
|
/* ed25519 key which the sender of this event (for olm) or the creator of
|
||||||
* the megolm session (for megolm) claims to own. See getClaimedEd25519Key()
|
* the megolm session (for megolm) claims to own. See getClaimedEd25519Key()
|
||||||
*/
|
*/
|
||||||
private claimedEd25519Key: string = null;
|
private claimedEd25519Key: string | null = null;
|
||||||
|
|
||||||
/* curve25519 keys of devices involved in telling us about the
|
/* curve25519 keys of devices involved in telling us about the
|
||||||
* senderCurve25519Key and claimedEd25519Key.
|
* senderCurve25519Key and claimedEd25519Key.
|
||||||
@ -237,12 +237,12 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
|
|
||||||
/* where the decryption key is untrusted
|
/* where the decryption key is untrusted
|
||||||
*/
|
*/
|
||||||
private untrusted: boolean = null;
|
private untrusted: boolean | null = null;
|
||||||
|
|
||||||
/* if we have a process decrypting this event, a Promise which resolves
|
/* if we have a process decrypting this event, a Promise which resolves
|
||||||
* when it is finished. Normally null.
|
* when it is finished. Normally null.
|
||||||
*/
|
*/
|
||||||
private _decryptionPromise: Promise<void> = null;
|
private decryptionPromise: Promise<void> | null = null;
|
||||||
|
|
||||||
/* flag to indicate if we should retry decrypting this event after the
|
/* flag to indicate if we should retry decrypting this event after the
|
||||||
* first attempt (eg, we have received new data which means that a second
|
* first attempt (eg, we have received new data which means that a second
|
||||||
@ -253,14 +253,14 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
/* The txnId with which this event was sent if it was during this session,
|
/* The txnId with which this event was sent if it was during this session,
|
||||||
* allows for a unique ID which does not change when the event comes back down sync.
|
* allows for a unique ID which does not change when the event comes back down sync.
|
||||||
*/
|
*/
|
||||||
private txnId: string = null;
|
private txnId?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @experimental
|
* @experimental
|
||||||
* A reference to the thread this event belongs to
|
* A reference to the thread this event belongs to
|
||||||
*/
|
*/
|
||||||
private thread: Thread = null;
|
private thread?: Thread;
|
||||||
private threadId: string;
|
private threadId?: string;
|
||||||
|
|
||||||
/* Set an approximate timestamp for the event relative the local clock.
|
/* Set an approximate timestamp for the event relative the local clock.
|
||||||
* This will inherently be approximate because it doesn't take into account
|
* This will inherently be approximate because it doesn't take into account
|
||||||
@ -271,17 +271,17 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
public localTimestamp: number;
|
public localTimestamp: number;
|
||||||
|
|
||||||
// XXX: these should be read-only
|
// XXX: these should be read-only
|
||||||
public sender: RoomMember = null;
|
public sender: RoomMember | null = null;
|
||||||
public target: RoomMember = null;
|
public target: RoomMember | null = null;
|
||||||
public status: EventStatus = null;
|
public status: EventStatus | null = null;
|
||||||
public error: MatrixError = null;
|
public error: MatrixError | null = null;
|
||||||
public forwardLooking = true; // only state events may be backwards looking
|
public forwardLooking = true; // only state events may be backwards looking
|
||||||
|
|
||||||
/* If the event is a `m.key.verification.request` (or to_device `m.key.verification.start`) event,
|
/* If the event is a `m.key.verification.request` (or to_device `m.key.verification.start`) event,
|
||||||
* `Crypto` will set this the `VerificationRequest` for the event
|
* `Crypto` will set this the `VerificationRequest` for the event
|
||||||
* so it can be easily accessed from the timeline.
|
* so it can be easily accessed from the timeline.
|
||||||
*/
|
*/
|
||||||
public verificationRequest: VerificationRequest = null;
|
public verificationRequest?: VerificationRequest;
|
||||||
|
|
||||||
private readonly reEmitter: TypedReEmitter<EmittedEvents, MatrixEventHandlerMap>;
|
private readonly reEmitter: TypedReEmitter<EmittedEvents, MatrixEventHandlerMap>;
|
||||||
|
|
||||||
@ -332,7 +332,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
event.content["m.relates_to"][prop] = internaliseString(event.content["m.relates_to"][prop]);
|
event.content["m.relates_to"][prop] = internaliseString(event.content["m.relates_to"][prop]);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.txnId = event.txn_id || null;
|
this.txnId = event.txn_id;
|
||||||
this.localTimestamp = Date.now() - (this.getAge() ?? 0);
|
this.localTimestamp = Date.now() - (this.getAge() ?? 0);
|
||||||
this.reEmitter = new TypedReEmitter(this);
|
this.reEmitter = new TypedReEmitter(this);
|
||||||
}
|
}
|
||||||
@ -639,11 +639,11 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
* @return {boolean} True if this event is currently being decrypted, else false.
|
* @return {boolean} True if this event is currently being decrypted, else false.
|
||||||
*/
|
*/
|
||||||
public isBeingDecrypted(): boolean {
|
public isBeingDecrypted(): boolean {
|
||||||
return this._decryptionPromise != null;
|
return this.decryptionPromise != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDecryptionPromise(): Promise<void> {
|
public getDecryptionPromise(): Promise<void> | null {
|
||||||
return this._decryptionPromise;
|
return this.decryptionPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -713,16 +713,16 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
// attempts going at the same time, so just set a flag that says we have
|
// attempts going at the same time, so just set a flag that says we have
|
||||||
// new info.
|
// new info.
|
||||||
//
|
//
|
||||||
if (this._decryptionPromise) {
|
if (this.decryptionPromise) {
|
||||||
logger.log(
|
logger.log(
|
||||||
`Event ${this.getId()} already being decrypted; queueing a retry`,
|
`Event ${this.getId()} already being decrypted; queueing a retry`,
|
||||||
);
|
);
|
||||||
this.retryDecryption = true;
|
this.retryDecryption = true;
|
||||||
return this._decryptionPromise;
|
return this.decryptionPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._decryptionPromise = this.decryptionLoop(crypto, options);
|
this.decryptionPromise = this.decryptionLoop(crypto, options);
|
||||||
return this._decryptionPromise;
|
return this.decryptionPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -768,9 +768,9 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
|
|
||||||
private async decryptionLoop(crypto: Crypto, options: IDecryptOptions = {}): Promise<void> {
|
private async decryptionLoop(crypto: Crypto, options: IDecryptOptions = {}): Promise<void> {
|
||||||
// make sure that this method never runs completely synchronously.
|
// make sure that this method never runs completely synchronously.
|
||||||
// (doing so would mean that we would clear _decryptionPromise *before*
|
// (doing so would mean that we would clear decryptionPromise *before*
|
||||||
// it is set in attemptDecryption - and hence end up with a stuck
|
// it is set in attemptDecryption - and hence end up with a stuck
|
||||||
// `_decryptionPromise`).
|
// `decryptionPromise`).
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
// eslint-disable-next-line no-constant-condition
|
||||||
@ -778,7 +778,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
this.retryDecryption = false;
|
this.retryDecryption = false;
|
||||||
|
|
||||||
let res: IEventDecryptionResult;
|
let res: IEventDecryptionResult;
|
||||||
let err: Error;
|
let err: Error | undefined = undefined;
|
||||||
try {
|
try {
|
||||||
if (!crypto) {
|
if (!crypto) {
|
||||||
res = this.badEncryptedMessage("Encryption not enabled");
|
res = this.badEncryptedMessage("Encryption not enabled");
|
||||||
@ -789,7 +789,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.name !== "DecryptionError") {
|
if ((<Error>e).name !== "DecryptionError") {
|
||||||
// not a decryption error: log the whole exception as an error
|
// not a decryption error: log the whole exception as an error
|
||||||
// (and don't bother with a retry)
|
// (and don't bother with a retry)
|
||||||
const re = options.isRetry ? 're' : '';
|
const re = options.isRetry ? 're' : '';
|
||||||
@ -799,25 +799,25 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
`Error ${re}decrypting event ` +
|
`Error ${re}decrypting event ` +
|
||||||
`(id=${this.getId()}): ${e.stack || e}`,
|
`(id=${this.getId()}): ${e.stack || e}`,
|
||||||
);
|
);
|
||||||
this._decryptionPromise = null;
|
this.decryptionPromise = null;
|
||||||
this.retryDecryption = false;
|
this.retryDecryption = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = e;
|
err = e as Error;
|
||||||
|
|
||||||
// see if we have a retry queued.
|
// see if we have a retry queued.
|
||||||
//
|
//
|
||||||
// NB: make sure to keep this check in the same tick of the
|
// NB: make sure to keep this check in the same tick of the
|
||||||
// event loop as `_decryptionPromise = null` below - otherwise we
|
// event loop as `decryptionPromise = null` below - otherwise we
|
||||||
// risk a race:
|
// risk a race:
|
||||||
//
|
//
|
||||||
// * A: we check retryDecryption here and see that it is
|
// * A: we check retryDecryption here and see that it is
|
||||||
// false
|
// false
|
||||||
// * B: we get a second call to attemptDecryption, which sees
|
// * B: we get a second call to attemptDecryption, which sees
|
||||||
// that _decryptionPromise is set so sets
|
// that decryptionPromise is set so sets
|
||||||
// retryDecryption
|
// retryDecryption
|
||||||
// * A: we continue below, clear _decryptionPromise, and
|
// * A: we continue below, clear decryptionPromise, and
|
||||||
// never do the retry.
|
// never do the retry.
|
||||||
//
|
//
|
||||||
if (this.retryDecryption) {
|
if (this.retryDecryption) {
|
||||||
@ -837,13 +837,13 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
// (and set res to a 'badEncryptedMessage'). Either way, we can now set the
|
// (and set res to a 'badEncryptedMessage'). Either way, we can now set the
|
||||||
// cleartext of the event and raise Event.decrypted.
|
// cleartext of the event and raise Event.decrypted.
|
||||||
//
|
//
|
||||||
// make sure we clear '_decryptionPromise' before sending the 'Event.decrypted' event,
|
// make sure we clear 'decryptionPromise' before sending the 'Event.decrypted' event,
|
||||||
// otherwise the app will be confused to see `isBeingDecrypted` still set when
|
// otherwise the app will be confused to see `isBeingDecrypted` still set when
|
||||||
// there isn't an `Event.decrypted` on the way.
|
// there isn't an `Event.decrypted` on the way.
|
||||||
//
|
//
|
||||||
// see also notes on retryDecryption above.
|
// see also notes on retryDecryption above.
|
||||||
//
|
//
|
||||||
this._decryptionPromise = null;
|
this.decryptionPromise = null;
|
||||||
this.retryDecryption = false;
|
this.retryDecryption = false;
|
||||||
this.setClearData(res);
|
this.setClearData(res);
|
||||||
|
|
||||||
@ -889,10 +889,8 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
*/
|
*/
|
||||||
private setClearData(decryptionResult: IEventDecryptionResult): void {
|
private setClearData(decryptionResult: IEventDecryptionResult): void {
|
||||||
this.clearEvent = decryptionResult.clearEvent;
|
this.clearEvent = decryptionResult.clearEvent;
|
||||||
this.senderCurve25519Key =
|
this.senderCurve25519Key = decryptionResult.senderCurve25519Key ?? null;
|
||||||
decryptionResult.senderCurve25519Key || null;
|
this.claimedEd25519Key = decryptionResult.claimedEd25519Key ?? null;
|
||||||
this.claimedEd25519Key =
|
|
||||||
decryptionResult.claimedEd25519Key || null;
|
|
||||||
this.forwardingCurve25519KeyChain =
|
this.forwardingCurve25519KeyChain =
|
||||||
decryptionResult.forwardingCurve25519KeyChain || [];
|
decryptionResult.forwardingCurve25519KeyChain || [];
|
||||||
this.untrusted = decryptionResult.untrusted || false;
|
this.untrusted = decryptionResult.untrusted || false;
|
||||||
@ -941,7 +939,9 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
*
|
*
|
||||||
* @return {Object<string, string>}
|
* @return {Object<string, string>}
|
||||||
*/
|
*/
|
||||||
public getKeysClaimed(): Record<"ed25519", string> {
|
public getKeysClaimed(): Partial<Record<"ed25519", string>> {
|
||||||
|
if (!this.claimedEd25519Key) return {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ed25519: this.claimedEd25519Key,
|
ed25519: this.claimedEd25519Key,
|
||||||
};
|
};
|
||||||
@ -992,8 +992,8 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
*
|
*
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
public isKeySourceUntrusted(): boolean {
|
public isKeySourceUntrusted(): boolean | undefined {
|
||||||
return this.untrusted;
|
return !!this.untrusted;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getUnsigned(): IUnsigned {
|
public getUnsigned(): IUnsigned {
|
||||||
@ -1035,10 +1035,10 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
* by a visibility event being redacted).
|
* by a visibility event being redacted).
|
||||||
*/
|
*/
|
||||||
public applyVisibilityEvent(visibilityChange?: IVisibilityChange): void {
|
public applyVisibilityEvent(visibilityChange?: IVisibilityChange): void {
|
||||||
const visible = visibilityChange ? visibilityChange.visible : true;
|
const visible = visibilityChange?.visible ?? true;
|
||||||
const reason = visibilityChange ? visibilityChange.reason : null;
|
const reason = visibilityChange?.reason ?? null;
|
||||||
let change = false;
|
let change = false;
|
||||||
if (this.visibility.visible !== visibilityChange.visible) {
|
if (this.visibility.visible !== visible) {
|
||||||
change = true;
|
change = true;
|
||||||
} else if (!this.visibility.visible && this.visibility["reason"] !== reason) {
|
} else if (!this.visibility.visible && this.visibility["reason"] !== reason) {
|
||||||
change = true;
|
change = true;
|
||||||
@ -1049,7 +1049,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
} else {
|
} else {
|
||||||
this.visibility = Object.freeze({
|
this.visibility = Object.freeze({
|
||||||
visible: false,
|
visible: false,
|
||||||
reason: reason,
|
reason,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.emit(MatrixEventEvent.VisibilityChange, this, visible);
|
this.emit(MatrixEventEvent.VisibilityChange, this, visible);
|
||||||
@ -1105,7 +1105,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
|
|
||||||
// If the event is encrypted prune the decrypted bits
|
// If the event is encrypted prune the decrypted bits
|
||||||
if (this.isEncrypted()) {
|
if (this.isEncrypted()) {
|
||||||
this.clearEvent = null;
|
this.clearEvent = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keeps = REDACT_KEEP_CONTENT_MAP[this.getType()] || {};
|
const keeps = REDACT_KEEP_CONTENT_MAP[this.getType()] || {};
|
||||||
@ -1217,7 +1217,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
*
|
*
|
||||||
* @param {Object} pushActions push actions
|
* @param {Object} pushActions push actions
|
||||||
*/
|
*/
|
||||||
public setPushActions(pushActions: IActionsObject): void {
|
public setPushActions(pushActions: IActionsObject | null): void {
|
||||||
this.pushActions = pushActions;
|
this.pushActions = pushActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1265,7 +1265,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
*
|
*
|
||||||
* @param {String} status The new status
|
* @param {String} status The new status
|
||||||
*/
|
*/
|
||||||
public setStatus(status: EventStatus): void {
|
public setStatus(status: EventStatus | null): void {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.emit(MatrixEventEvent.Status, this, status);
|
this.emit(MatrixEventEvent.Status, this, status);
|
||||||
}
|
}
|
||||||
@ -1283,7 +1283,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
* given type
|
* given type
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
public isRelation(relType: string = undefined): boolean {
|
public isRelation(relType?: string): boolean {
|
||||||
// Relation info is lifted out of the encrypted content when sent to
|
// Relation info is lifted out of the encrypted content when sent to
|
||||||
// encrypted rooms, so we have to check `getWireContent` for this.
|
// encrypted rooms, so we have to check `getWireContent` for this.
|
||||||
const relation = this.getWireContent()?.["m.relates_to"];
|
const relation = this.getWireContent()?.["m.relates_to"];
|
||||||
@ -1326,7 +1326,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this._replacingEvent !== newEvent) {
|
if (this._replacingEvent !== newEvent) {
|
||||||
this._replacingEvent = newEvent;
|
this._replacingEvent = newEvent ?? null;
|
||||||
this.emit(MatrixEventEvent.Replaced, this);
|
this.emit(MatrixEventEvent.Replaced, this);
|
||||||
this.invalidateExtensibleEvent();
|
this.invalidateExtensibleEvent();
|
||||||
}
|
}
|
||||||
@ -1339,7 +1339,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
*
|
*
|
||||||
* @return {EventStatus}
|
* @return {EventStatus}
|
||||||
*/
|
*/
|
||||||
public getAssociatedStatus(): EventStatus | undefined {
|
public getAssociatedStatus(): EventStatus | null {
|
||||||
if (this._replacingEvent) {
|
if (this._replacingEvent) {
|
||||||
return this._replacingEvent.status;
|
return this._replacingEvent.status;
|
||||||
} else if (this._localRedactionEvent) {
|
} else if (this._localRedactionEvent) {
|
||||||
@ -1373,7 +1373,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
*
|
*
|
||||||
* @return {MatrixEvent?}
|
* @return {MatrixEvent?}
|
||||||
*/
|
*/
|
||||||
public replacingEvent(): MatrixEvent | undefined {
|
public replacingEvent(): MatrixEvent | null {
|
||||||
return this._replacingEvent;
|
return this._replacingEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1398,7 +1398,7 @@ export class MatrixEvent extends TypedEventEmitter<EmittedEvents, MatrixEventHan
|
|||||||
* Returns the event that wants to redact this event, but hasn't been sent yet.
|
* Returns the event that wants to redact this event, but hasn't been sent yet.
|
||||||
* @return {MatrixEvent} the event
|
* @return {MatrixEvent} the event
|
||||||
*/
|
*/
|
||||||
public localRedactionEvent(): MatrixEvent | undefined {
|
public localRedactionEvent(): MatrixEvent | null {
|
||||||
return this._localRedactionEvent;
|
return this._localRedactionEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +160,7 @@ export class Relations extends TypedEventEmitter<RelationsEvent, EventHandlerMap
|
|||||||
* @param {MatrixEvent} event The event whose status has changed
|
* @param {MatrixEvent} event The event whose status has changed
|
||||||
* @param {EventStatus} status The new status
|
* @param {EventStatus} status The new status
|
||||||
*/
|
*/
|
||||||
private onEventStatus = (event: MatrixEvent, status: EventStatus) => {
|
private onEventStatus = (event: MatrixEvent, status: EventStatus | null) => {
|
||||||
if (!event.isSending()) {
|
if (!event.isSending()) {
|
||||||
// Sending is done, so we don't need to listen anymore
|
// Sending is done, so we don't need to listen anymore
|
||||||
event.removeListener(MatrixEventEvent.Status, this.onEventStatus);
|
event.removeListener(MatrixEventEvent.Status, this.onEventStatus);
|
||||||
|
@ -35,7 +35,7 @@ export enum RoomMemberEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type RoomMemberEventHandlerMap = {
|
export type RoomMemberEventHandlerMap = {
|
||||||
[RoomMemberEvent.Membership]: (event: MatrixEvent, member: RoomMember, oldMembership: string | null) => void;
|
[RoomMemberEvent.Membership]: (event: MatrixEvent, member: RoomMember, oldMembership?: string) => void;
|
||||||
[RoomMemberEvent.Name]: (event: MatrixEvent, member: RoomMember, oldName: string | null) => void;
|
[RoomMemberEvent.Name]: (event: MatrixEvent, member: RoomMember, oldName: string | null) => void;
|
||||||
[RoomMemberEvent.PowerLevel]: (event: MatrixEvent, member: RoomMember) => void;
|
[RoomMemberEvent.PowerLevel]: (event: MatrixEvent, member: RoomMember) => void;
|
||||||
[RoomMemberEvent.Typing]: (event: MatrixEvent, member: RoomMember) => void;
|
[RoomMemberEvent.Typing]: (event: MatrixEvent, member: RoomMember) => void;
|
||||||
@ -43,8 +43,8 @@ export type RoomMemberEventHandlerMap = {
|
|||||||
|
|
||||||
export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEventHandlerMap> {
|
export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEventHandlerMap> {
|
||||||
private _isOutOfBand = false;
|
private _isOutOfBand = false;
|
||||||
private _modified: number;
|
private modified = -1;
|
||||||
public _requestedProfileInfo: boolean; // used by sync.ts
|
public requestedProfileInfo = false; // used by sync.ts
|
||||||
|
|
||||||
// XXX these should be read-only
|
// XXX these should be read-only
|
||||||
public typing = false;
|
public typing = false;
|
||||||
@ -52,14 +52,12 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
|
|||||||
public rawDisplayName: string;
|
public rawDisplayName: string;
|
||||||
public powerLevel = 0;
|
public powerLevel = 0;
|
||||||
public powerLevelNorm = 0;
|
public powerLevelNorm = 0;
|
||||||
public user?: User = null;
|
public user?: User;
|
||||||
public membership: string = null;
|
public membership?: string;
|
||||||
public disambiguate = false;
|
public disambiguate = false;
|
||||||
public events: {
|
public events: {
|
||||||
member?: MatrixEvent;
|
member?: MatrixEvent;
|
||||||
} = {
|
} = {};
|
||||||
member: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new room member.
|
* Construct a new room member.
|
||||||
@ -119,7 +117,7 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
|
|||||||
* @fires module:client~MatrixClient#event:"RoomMember.membership"
|
* @fires module:client~MatrixClient#event:"RoomMember.membership"
|
||||||
*/
|
*/
|
||||||
public setMembershipEvent(event: MatrixEvent, roomState?: RoomState): void {
|
public setMembershipEvent(event: MatrixEvent, roomState?: RoomState): void {
|
||||||
const displayName = event.getDirectionalContent().displayname;
|
const displayName = event.getDirectionalContent().displayname ?? "";
|
||||||
|
|
||||||
if (event.getType() !== EventType.RoomMember) {
|
if (event.getType() !== EventType.RoomMember) {
|
||||||
return;
|
return;
|
||||||
@ -141,23 +139,14 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.disambiguate = shouldDisambiguate(
|
this.disambiguate = shouldDisambiguate(this.userId, displayName, roomState);
|
||||||
this.userId,
|
|
||||||
displayName,
|
|
||||||
roomState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const oldName = this.name;
|
const oldName = this.name;
|
||||||
this.name = calculateDisplayName(
|
this.name = calculateDisplayName(this.userId, displayName, this.disambiguate);
|
||||||
this.userId,
|
|
||||||
displayName,
|
|
||||||
roomState,
|
|
||||||
this.disambiguate,
|
|
||||||
);
|
|
||||||
|
|
||||||
// not quite raw: we strip direction override chars so it can safely be inserted into
|
// not quite raw: we strip direction override chars so it can safely be inserted into
|
||||||
// blocks of text without breaking the text direction
|
// blocks of text without breaking the text direction
|
||||||
this.rawDisplayName = utils.removeDirectionOverrideChars(event.getDirectionalContent().displayname);
|
this.rawDisplayName = utils.removeDirectionOverrideChars(event.getDirectionalContent().displayname ?? "");
|
||||||
if (!this.rawDisplayName || !utils.removeHiddenChars(this.rawDisplayName)) {
|
if (!this.rawDisplayName || !utils.removeHiddenChars(this.rawDisplayName)) {
|
||||||
this.rawDisplayName = this.userId;
|
this.rawDisplayName = this.userId;
|
||||||
}
|
}
|
||||||
@ -180,15 +169,15 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
|
|||||||
* @fires module:client~MatrixClient#event:"RoomMember.powerLevel"
|
* @fires module:client~MatrixClient#event:"RoomMember.powerLevel"
|
||||||
*/
|
*/
|
||||||
public setPowerLevelEvent(powerLevelEvent: MatrixEvent): void {
|
public setPowerLevelEvent(powerLevelEvent: MatrixEvent): void {
|
||||||
if (powerLevelEvent.getType() !== "m.room.power_levels") {
|
if (powerLevelEvent.getType() !== EventType.RoomPowerLevels || powerLevelEvent.getStateKey() !== "") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const evContent = powerLevelEvent.getDirectionalContent();
|
const evContent = powerLevelEvent.getDirectionalContent();
|
||||||
|
|
||||||
let maxLevel = evContent.users_default || 0;
|
let maxLevel = evContent.users_default || 0;
|
||||||
const users = evContent.users || {};
|
const users: { [userId: string]: number } = evContent.users || {};
|
||||||
Object.values(users).forEach(function(lvl: number) {
|
Object.values(users).forEach((lvl: number) => {
|
||||||
maxLevel = Math.max(maxLevel, lvl);
|
maxLevel = Math.max(maxLevel, lvl);
|
||||||
});
|
});
|
||||||
const oldPowerLevel = this.powerLevel;
|
const oldPowerLevel = this.powerLevel;
|
||||||
@ -244,7 +233,7 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
|
|||||||
* Update the last modified time to the current time.
|
* Update the last modified time to the current time.
|
||||||
*/
|
*/
|
||||||
private updateModifiedTime() {
|
private updateModifiedTime() {
|
||||||
this._modified = Date.now();
|
this.modified = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -254,12 +243,13 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
|
|||||||
* @return {number} The timestamp
|
* @return {number} The timestamp
|
||||||
*/
|
*/
|
||||||
public getLastModifiedTime(): number {
|
public getLastModifiedTime(): number {
|
||||||
return this._modified;
|
return this.modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isKicked(): boolean {
|
public isKicked(): boolean {
|
||||||
return this.membership === "leave" &&
|
return this.membership === "leave"
|
||||||
this.events.member.getSender() !== this.events.member.getStateKey();
|
&& this.events.member !== undefined
|
||||||
|
&& this.events.member.getSender() !== this.events.member.getStateKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -267,7 +257,7 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
|
|||||||
* the user that invited this member
|
* the user that invited this member
|
||||||
* @return {string} user id of the inviter
|
* @return {string} user id of the inviter
|
||||||
*/
|
*/
|
||||||
public getDMInviter(): string {
|
public getDMInviter(): string | undefined {
|
||||||
// when not available because that room state hasn't been loaded in,
|
// when not available because that room state hasn't been loaded in,
|
||||||
// we don't really know, but more likely to not be a direct chat
|
// we don't really know, but more likely to not be a direct chat
|
||||||
if (this.events.member) {
|
if (this.events.member) {
|
||||||
@ -280,7 +270,7 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
|
|||||||
|
|
||||||
const memberEvent = this.events.member;
|
const memberEvent = this.events.member;
|
||||||
let memberContent = memberEvent.getContent();
|
let memberContent = memberEvent.getContent();
|
||||||
let inviteSender = memberEvent.getSender();
|
let inviteSender: string | undefined = memberEvent.getSender();
|
||||||
|
|
||||||
if (memberContent.membership === "join") {
|
if (memberContent.membership === "join") {
|
||||||
memberContent = memberEvent.getPrevContent();
|
memberContent = memberEvent.getPrevContent();
|
||||||
@ -335,20 +325,19 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
|
|||||||
* get the mxc avatar url, either from a state event, or from a lazily loaded member
|
* get the mxc avatar url, either from a state event, or from a lazily loaded member
|
||||||
* @return {string} the mxc avatar url
|
* @return {string} the mxc avatar url
|
||||||
*/
|
*/
|
||||||
public getMxcAvatarUrl(): string | null {
|
public getMxcAvatarUrl(): string | undefined {
|
||||||
if (this.events.member) {
|
if (this.events.member) {
|
||||||
return this.events.member.getDirectionalContent().avatar_url;
|
return this.events.member.getDirectionalContent().avatar_url;
|
||||||
} else if (this.user) {
|
} else if (this.user) {
|
||||||
return this.user.avatarUrl;
|
return this.user.avatarUrl;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const MXID_PATTERN = /@.+:.+/;
|
const MXID_PATTERN = /@.+:.+/;
|
||||||
const LTR_RTL_PATTERN = /[\u200E\u200F\u202A-\u202F]/;
|
const LTR_RTL_PATTERN = /[\u200E\u200F\u202A-\u202F]/;
|
||||||
|
|
||||||
function shouldDisambiguate(selfUserId: string, displayName: string, roomState?: RoomState): boolean {
|
function shouldDisambiguate(selfUserId: string, displayName?: string, roomState?: RoomState): boolean {
|
||||||
if (!displayName || displayName === selfUserId) return false;
|
if (!displayName || displayName === selfUserId) return false;
|
||||||
|
|
||||||
// First check if the displayname is something we consider truthy
|
// First check if the displayname is something we consider truthy
|
||||||
@ -377,14 +366,13 @@ function shouldDisambiguate(selfUserId: string, displayName: string, roomState?:
|
|||||||
|
|
||||||
function calculateDisplayName(
|
function calculateDisplayName(
|
||||||
selfUserId: string,
|
selfUserId: string,
|
||||||
displayName: string,
|
displayName: string | undefined,
|
||||||
roomState: RoomState,
|
|
||||||
disambiguate: boolean,
|
disambiguate: boolean,
|
||||||
): string {
|
): string {
|
||||||
if (disambiguate) return utils.removeDirectionOverrideChars(displayName) + " (" + selfUserId + ")";
|
|
||||||
|
|
||||||
if (!displayName || displayName === selfUserId) return selfUserId;
|
if (!displayName || displayName === selfUserId) return selfUserId;
|
||||||
|
|
||||||
|
if (disambiguate) return utils.removeDirectionOverrideChars(displayName) + " (" + selfUserId + ")";
|
||||||
|
|
||||||
// First check if the displayname is something we consider truthy
|
// First check if the displayname is something we consider truthy
|
||||||
// after stripping it of zero width characters and padding spaces
|
// after stripping it of zero width characters and padding spaces
|
||||||
if (!utils.removeHiddenChars(displayName)) return selfUserId;
|
if (!utils.removeHiddenChars(displayName)) return selfUserId;
|
||||||
|
@ -68,7 +68,7 @@ export type RoomStateEventHandlerMap = {
|
|||||||
[RoomStateEvent.NewMember]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void;
|
[RoomStateEvent.NewMember]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void;
|
||||||
[RoomStateEvent.Update]: (state: RoomState) => void;
|
[RoomStateEvent.Update]: (state: RoomState) => void;
|
||||||
[RoomStateEvent.BeaconLiveness]: (state: RoomState, hasLiveBeacons: boolean) => void;
|
[RoomStateEvent.BeaconLiveness]: (state: RoomState, hasLiveBeacons: boolean) => void;
|
||||||
[RoomStateEvent.Marker]: (event: MatrixEvent, setStateOptions: IMarkerFoundOptions) => void;
|
[RoomStateEvent.Marker]: (event: MatrixEvent, setStateOptions?: IMarkerFoundOptions) => void;
|
||||||
[BeaconEvent.New]: (event: MatrixEvent, beacon: Beacon) => void;
|
[BeaconEvent.New]: (event: MatrixEvent, beacon: Beacon) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -82,17 +82,17 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
private displayNameToUserIds = new Map<string, string[]>();
|
private displayNameToUserIds = new Map<string, string[]>();
|
||||||
private userIdsToDisplayNames: Record<string, string> = {};
|
private userIdsToDisplayNames: Record<string, string> = {};
|
||||||
private tokenToInvite: Record<string, MatrixEvent> = {}; // 3pid invite state_key to m.room.member invite
|
private tokenToInvite: Record<string, MatrixEvent> = {}; // 3pid invite state_key to m.room.member invite
|
||||||
private joinedMemberCount: number = null; // cache of the number of joined members
|
private joinedMemberCount: number | null = null; // cache of the number of joined members
|
||||||
// joined members count from summary api
|
// joined members count from summary api
|
||||||
// once set, we know the server supports the summary api
|
// once set, we know the server supports the summary api
|
||||||
// and we should only trust that
|
// and we should only trust that
|
||||||
// we could also only trust that before OOB members
|
// we could also only trust that before OOB members
|
||||||
// are loaded but doesn't seem worth the hassle atm
|
// are loaded but doesn't seem worth the hassle atm
|
||||||
private summaryJoinedMemberCount: number = null;
|
private summaryJoinedMemberCount: number |null = null;
|
||||||
// same for invited member count
|
// same for invited member count
|
||||||
private invitedMemberCount: number = null;
|
private invitedMemberCount: number | null = null;
|
||||||
private summaryInvitedMemberCount: number = null;
|
private summaryInvitedMemberCount: number | null = null;
|
||||||
private modified: number;
|
private modified = -1;
|
||||||
|
|
||||||
// XXX: Should be read-only
|
// XXX: Should be read-only
|
||||||
public members: Record<string, RoomMember> = {}; // userId: RoomMember
|
public members: Record<string, RoomMember> = {}; // userId: RoomMember
|
||||||
@ -232,7 +232,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
if (sentinel === undefined) {
|
if (sentinel === undefined) {
|
||||||
sentinel = new RoomMember(this.roomId, userId);
|
sentinel = new RoomMember(this.roomId, userId);
|
||||||
const member = this.members[userId];
|
const member = this.members[userId];
|
||||||
if (member) {
|
if (member?.events.member) {
|
||||||
sentinel.setMembershipEvent(member.events.member, this);
|
sentinel.setMembershipEvent(member.events.member, this);
|
||||||
}
|
}
|
||||||
this.sentinels[userId] = sentinel;
|
this.sentinels[userId] = sentinel;
|
||||||
@ -257,9 +257,9 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
return stateKey === undefined ? [] : null;
|
return stateKey === undefined ? [] : null;
|
||||||
}
|
}
|
||||||
if (stateKey === undefined) { // return all values
|
if (stateKey === undefined) { // return all values
|
||||||
return Array.from(this.events.get(eventType).values());
|
return Array.from(this.events.get(eventType)!.values());
|
||||||
}
|
}
|
||||||
const event = this.events.get(eventType).get(stateKey);
|
const event = this.events.get(eventType)!.get(stateKey);
|
||||||
return event ? event : null;
|
return event ? event : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,8 +306,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
// copy markOutOfBand flags
|
// copy markOutOfBand flags
|
||||||
this.getMembers().forEach((member) => {
|
this.getMembers().forEach((member) => {
|
||||||
if (member.isOutOfBand()) {
|
if (member.isOutOfBand()) {
|
||||||
const copyMember = copy.getMember(member.userId);
|
copy.getMember(member.userId)?.markOutOfBand();
|
||||||
copyMember.markOutOfBand();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -324,8 +323,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
*/
|
*/
|
||||||
public setUnknownStateEvents(events: MatrixEvent[]): void {
|
public setUnknownStateEvents(events: MatrixEvent[]): void {
|
||||||
const unknownStateEvents = events.filter((event) => {
|
const unknownStateEvents = events.filter((event) => {
|
||||||
return !this.events.has(event.getType()) ||
|
return !this.events.has(event.getType()) || !this.events.get(event.getType())!.has(event.getStateKey()!);
|
||||||
!this.events.get(event.getType()).has(event.getStateKey());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setStateEvents(unknownStateEvents);
|
this.setStateEvents(unknownStateEvents);
|
||||||
@ -349,12 +347,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
|
|
||||||
// update the core event dict
|
// update the core event dict
|
||||||
stateEvents.forEach((event) => {
|
stateEvents.forEach((event) => {
|
||||||
if (event.getRoomId() !== this.roomId) {
|
if (event.getRoomId() !== this.roomId || !event.isState()) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!event.isState()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (M_BEACON_INFO.matches(event.getType())) {
|
if (M_BEACON_INFO.matches(event.getType())) {
|
||||||
this.setBeacon(event);
|
this.setBeacon(event);
|
||||||
@ -363,7 +356,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
const lastStateEvent = this.getStateEventMatching(event);
|
const lastStateEvent = this.getStateEventMatching(event);
|
||||||
this.setStateEvent(event);
|
this.setStateEvent(event);
|
||||||
if (event.getType() === EventType.RoomMember) {
|
if (event.getType() === EventType.RoomMember) {
|
||||||
this.updateDisplayNameCache(event.getStateKey(), event.getContent().displayname);
|
this.updateDisplayNameCache(event.getStateKey()!, event.getContent().displayname ?? "");
|
||||||
this.updateThirdPartyTokenCache(event);
|
this.updateThirdPartyTokenCache(event);
|
||||||
}
|
}
|
||||||
this.emit(RoomStateEvent.Events, event, this, lastStateEvent);
|
this.emit(RoomStateEvent.Events, event, this, lastStateEvent);
|
||||||
@ -375,15 +368,10 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
// the given array (e.g. disambiguating display names in one go to do both
|
// the given array (e.g. disambiguating display names in one go to do both
|
||||||
// clashing names rather than progressively which only catches 1 of them).
|
// clashing names rather than progressively which only catches 1 of them).
|
||||||
stateEvents.forEach((event) => {
|
stateEvents.forEach((event) => {
|
||||||
if (event.getRoomId() !== this.roomId) {
|
if (event.getRoomId() !== this.roomId || !event.isState()) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!event.isState()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.getType() === EventType.RoomMember) {
|
if (event.getType() === EventType.RoomMember) {
|
||||||
const userId = event.getStateKey();
|
const userId = event.getStateKey()!;
|
||||||
|
|
||||||
// leave events apparently elide the displayname or avatar_url,
|
// leave events apparently elide the displayname or avatar_url,
|
||||||
// so let's fake one up so that we don't leak user ids
|
// so let's fake one up so that we don't leak user ids
|
||||||
@ -456,11 +444,8 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
|
|
||||||
events.forEach((event: MatrixEvent) => {
|
events.forEach((event: MatrixEvent) => {
|
||||||
const relatedToEventId = event.getRelation()?.event_id;
|
const relatedToEventId = event.getRelation()?.event_id;
|
||||||
// not related to a beacon we know about
|
// not related to a beacon we know about; discard
|
||||||
// discard
|
if (!relatedToEventId || !beaconByEventIdDict[relatedToEventId]) return;
|
||||||
if (!beaconByEventIdDict[relatedToEventId]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
matrixClient.decryptEventIfNeeded(event);
|
matrixClient.decryptEventIfNeeded(event);
|
||||||
|
|
||||||
@ -501,7 +486,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
if (!this.events.has(event.getType())) {
|
if (!this.events.has(event.getType())) {
|
||||||
this.events.set(event.getType(), new Map());
|
this.events.set(event.getType(), new Map());
|
||||||
}
|
}
|
||||||
this.events.get(event.getType()).set(event.getStateKey(), event);
|
this.events.get(event.getType())!.set(event.getStateKey()!, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -511,7 +496,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
const beaconIdentifier = getBeaconInfoIdentifier(event);
|
const beaconIdentifier = getBeaconInfoIdentifier(event);
|
||||||
|
|
||||||
if (this.beacons.has(beaconIdentifier)) {
|
if (this.beacons.has(beaconIdentifier)) {
|
||||||
const beacon = this.beacons.get(beaconIdentifier);
|
const beacon = this.beacons.get(beaconIdentifier)!;
|
||||||
|
|
||||||
if (event.isRedacted()) {
|
if (event.isRedacted()) {
|
||||||
if (beacon.beaconInfoId === event.getRedactionEvent()?.['redacts']) {
|
if (beacon.beaconInfoId === event.getRedactionEvent()?.['redacts']) {
|
||||||
@ -558,7 +543,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getStateEventMatching(event: MatrixEvent): MatrixEvent | null {
|
private getStateEventMatching(event: MatrixEvent): MatrixEvent | null {
|
||||||
return this.events.get(event.getType())?.get(event.getStateKey()) ?? null;
|
return this.events.get(event.getType())?.get(event.getStateKey()!) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateMember(member: RoomMember): void {
|
private updateMember(member: RoomMember): void {
|
||||||
@ -646,7 +631,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
if (stateEvent.getType() !== EventType.RoomMember) {
|
if (stateEvent.getType() !== EventType.RoomMember) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const userId = stateEvent.getStateKey();
|
const userId = stateEvent.getStateKey()!;
|
||||||
const existingMember = this.getMember(userId);
|
const existingMember = this.getMember(userId);
|
||||||
// never replace members received as part of the sync
|
// never replace members received as part of the sync
|
||||||
if (existingMember && !existingMember.isOutOfBand()) {
|
if (existingMember && !existingMember.isOutOfBand()) {
|
||||||
@ -788,7 +773,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
|
|||||||
* according to the room's state.
|
* according to the room's state.
|
||||||
*/
|
*/
|
||||||
public mayClientSendStateEvent(stateEventType: EventType | string, cli: MatrixClient): boolean {
|
public mayClientSendStateEvent(stateEventType: EventType | string, cli: MatrixClient): boolean {
|
||||||
if (cli.isGuest()) {
|
if (cli.isGuest() || !cli.credentials.userId) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.maySendStateEvent(stateEventType, cli.credentials.userId);
|
return this.maySendStateEvent(stateEventType, cli.credentials.userId);
|
||||||
|
@ -69,7 +69,6 @@ export const KNOWN_SAFE_ROOM_VERSION = '9';
|
|||||||
const SAFE_ROOM_VERSIONS = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
const SAFE_ROOM_VERSIONS = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||||
|
|
||||||
interface IOpts {
|
interface IOpts {
|
||||||
storageToken?: string;
|
|
||||||
pendingEventOrdering?: PendingEventOrdering;
|
pendingEventOrdering?: PendingEventOrdering;
|
||||||
timelineSupport?: boolean;
|
timelineSupport?: boolean;
|
||||||
lazyLoadMembers?: boolean;
|
lazyLoadMembers?: boolean;
|
||||||
@ -158,7 +157,7 @@ export type RoomEventHandlerMap = {
|
|||||||
event: MatrixEvent,
|
event: MatrixEvent,
|
||||||
room: Room,
|
room: Room,
|
||||||
oldEventId?: string,
|
oldEventId?: string,
|
||||||
oldStatus?: EventStatus,
|
oldStatus?: EventStatus | null,
|
||||||
) => void;
|
) => void;
|
||||||
[RoomEvent.OldStateUpdated]: (room: Room, previousRoomState: RoomState, roomState: RoomState) => void;
|
[RoomEvent.OldStateUpdated]: (room: Room, previousRoomState: RoomState, roomState: RoomState) => void;
|
||||||
[RoomEvent.CurrentStateUpdated]: (room: Room, previousRoomState: RoomState, roomState: RoomState) => void;
|
[RoomEvent.CurrentStateUpdated]: (room: Room, previousRoomState: RoomState, roomState: RoomState) => void;
|
||||||
@ -201,8 +200,8 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
private timelineNeedsRefresh = false;
|
private timelineNeedsRefresh = false;
|
||||||
private readonly pendingEventList?: MatrixEvent[];
|
private readonly pendingEventList?: MatrixEvent[];
|
||||||
// read by megolm via getter; boolean value - null indicates "use global value"
|
// read by megolm via getter; boolean value - null indicates "use global value"
|
||||||
private blacklistUnverifiedDevices: boolean = null;
|
private blacklistUnverifiedDevices?: boolean;
|
||||||
private selfMembership: string = null;
|
private selfMembership?: string;
|
||||||
private summaryHeroes: string[] = null;
|
private summaryHeroes: string[] = null;
|
||||||
// flags to stop logspam about missing m.room.create events
|
// flags to stop logspam about missing m.room.create events
|
||||||
private getTypeWarning = false;
|
private getTypeWarning = false;
|
||||||
@ -232,10 +231,6 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
* The room summary.
|
* The room summary.
|
||||||
*/
|
*/
|
||||||
public summary: RoomSummary = null;
|
public summary: RoomSummary = null;
|
||||||
/**
|
|
||||||
* A token which a data store can use to remember the state of the room.
|
|
||||||
*/
|
|
||||||
public readonly storageToken?: string;
|
|
||||||
// legacy fields
|
// legacy fields
|
||||||
/**
|
/**
|
||||||
* The live event timeline for this room, with the oldest event at index 0.
|
* The live event timeline for this room, with the oldest event at index 0.
|
||||||
@ -260,7 +255,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
private threads = new Map<string, Thread>();
|
private threads = new Map<string, Thread>();
|
||||||
public lastThread: Thread;
|
public lastThread?: Thread;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mapping of eventId to all visibility changes to apply
|
* A mapping of eventId to all visibility changes to apply
|
||||||
@ -304,9 +299,6 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
* @param {MatrixClient} client Required. The client, used to lazy load members.
|
* @param {MatrixClient} client Required. The client, used to lazy load members.
|
||||||
* @param {string} myUserId Required. The ID of the syncing user.
|
* @param {string} myUserId Required. The ID of the syncing user.
|
||||||
* @param {Object=} opts Configuration options
|
* @param {Object=} opts Configuration options
|
||||||
* @param {*} opts.storageToken Optional. The token which a data store can use
|
|
||||||
* to remember the state of the room. What this means is dependent on the store
|
|
||||||
* implementation.
|
|
||||||
*
|
*
|
||||||
* @param {String=} opts.pendingEventOrdering Controls where pending messages
|
* @param {String=} opts.pendingEventOrdering Controls where pending messages
|
||||||
* appear in a room's timeline. If "<b>chronological</b>", messages will appear
|
* appear in a room's timeline. If "<b>chronological</b>", messages will appear
|
||||||
@ -332,6 +324,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
opts.pendingEventOrdering = opts.pendingEventOrdering || PendingEventOrdering.Chronological;
|
opts.pendingEventOrdering = opts.pendingEventOrdering || PendingEventOrdering.Chronological;
|
||||||
|
|
||||||
this.name = roomId;
|
this.name = roomId;
|
||||||
|
this.normalizedName = roomId;
|
||||||
|
|
||||||
// all our per-room timeline sets. the first one is the unfiltered ones;
|
// all our per-room timeline sets. the first one is the unfiltered ones;
|
||||||
// the subsequent ones are the filtered ones in no particular order.
|
// the subsequent ones are the filtered ones in no particular order.
|
||||||
@ -346,13 +339,17 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
if (this.opts.pendingEventOrdering === PendingEventOrdering.Detached) {
|
if (this.opts.pendingEventOrdering === PendingEventOrdering.Detached) {
|
||||||
this.pendingEventList = [];
|
this.pendingEventList = [];
|
||||||
this.client.store.getPendingEvents(this.roomId).then(events => {
|
this.client.store.getPendingEvents(this.roomId).then(events => {
|
||||||
|
const mapper = this.client.getEventMapper({
|
||||||
|
toDevice: false,
|
||||||
|
decrypt: false,
|
||||||
|
});
|
||||||
events.forEach(async (serializedEvent: Partial<IEvent>) => {
|
events.forEach(async (serializedEvent: Partial<IEvent>) => {
|
||||||
const event = new MatrixEvent(serializedEvent);
|
const event = mapper(serializedEvent);
|
||||||
if (event.getType() === EventType.RoomMessageEncrypted) {
|
if (event.getType() === EventType.RoomMessageEncrypted && this.client.isCryptoEnabled()) {
|
||||||
await event.attemptDecryption(this.client.crypto);
|
await event.attemptDecryption(this.client.crypto!);
|
||||||
}
|
}
|
||||||
event.setStatus(EventStatus.NOT_SENT);
|
event.setStatus(EventStatus.NOT_SENT);
|
||||||
this.addPendingEvent(event, event.getTxnId());
|
this.addPendingEvent(event, event.getTxnId()!);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -361,7 +358,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
if (!this.opts.lazyLoadMembers) {
|
if (!this.opts.lazyLoadMembers) {
|
||||||
this.membersPromise = Promise.resolve(false);
|
this.membersPromise = Promise.resolve(false);
|
||||||
} else {
|
} else {
|
||||||
this.membersPromise = null;
|
this.membersPromise = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,8 +396,10 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
*
|
*
|
||||||
* @returns {Promise} Signals when all events have been decrypted
|
* @returns {Promise} Signals when all events have been decrypted
|
||||||
*/
|
*/
|
||||||
public decryptCriticalEvents(): Promise<void> {
|
public async decryptCriticalEvents(): Promise<void> {
|
||||||
const readReceiptEventId = this.getEventReadUpTo(this.client.getUserId(), true);
|
if (!this.client.isCryptoEnabled()) return;
|
||||||
|
|
||||||
|
const readReceiptEventId = this.getEventReadUpTo(this.client.getUserId()!, true);
|
||||||
const events = this.getLiveTimeline().getEvents();
|
const events = this.getLiveTimeline().getEvents();
|
||||||
const readReceiptTimelineIndex = events.findIndex(matrixEvent => {
|
const readReceiptTimelineIndex = events.findIndex(matrixEvent => {
|
||||||
return matrixEvent.event.event_id === readReceiptEventId;
|
return matrixEvent.event.event_id === readReceiptEventId;
|
||||||
@ -410,9 +409,9 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
.slice(readReceiptTimelineIndex)
|
.slice(readReceiptTimelineIndex)
|
||||||
.filter(event => event.shouldAttemptDecryption())
|
.filter(event => event.shouldAttemptDecryption())
|
||||||
.reverse()
|
.reverse()
|
||||||
.map(event => event.attemptDecryption(this.client.crypto, { isRetry: true }));
|
.map(event => event.attemptDecryption(this.client.crypto!, { isRetry: true }));
|
||||||
|
|
||||||
return Promise.allSettled(decryptionPromises) as unknown as Promise<void>;
|
await Promise.allSettled(decryptionPromises);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -420,16 +419,18 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
*
|
*
|
||||||
* @returns {Promise} Signals when all events have been decrypted
|
* @returns {Promise} Signals when all events have been decrypted
|
||||||
*/
|
*/
|
||||||
public decryptAllEvents(): Promise<void> {
|
public async decryptAllEvents(): Promise<void> {
|
||||||
|
if (!this.client.isCryptoEnabled()) return;
|
||||||
|
|
||||||
const decryptionPromises = this
|
const decryptionPromises = this
|
||||||
.getUnfilteredTimelineSet()
|
.getUnfilteredTimelineSet()
|
||||||
.getLiveTimeline()
|
.getLiveTimeline()
|
||||||
.getEvents()
|
.getEvents()
|
||||||
.filter(event => event.shouldAttemptDecryption())
|
.filter(event => event.shouldAttemptDecryption())
|
||||||
.reverse()
|
.reverse()
|
||||||
.map(event => event.attemptDecryption(this.client.crypto, { isRetry: true }));
|
.map(event => event.attemptDecryption(this.client.crypto!, { isRetry: true }));
|
||||||
|
|
||||||
return Promise.allSettled(decryptionPromises) as unknown as Promise<void>;
|
await Promise.allSettled(decryptionPromises);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -445,7 +446,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
* Gets the version of the room
|
* Gets the version of the room
|
||||||
* @returns {string} The version of the room, or null if it could not be determined
|
* @returns {string} The version of the room, or null if it could not be determined
|
||||||
*/
|
*/
|
||||||
public getVersion(): string | null {
|
public getVersion(): string {
|
||||||
const createEvent = this.currentState.getStateEvents(EventType.RoomCreate, "");
|
const createEvent = this.currentState.getStateEvents(EventType.RoomCreate, "");
|
||||||
if (!createEvent) {
|
if (!createEvent) {
|
||||||
if (!this.getVersionWarning) {
|
if (!this.getVersionWarning) {
|
||||||
@ -454,9 +455,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
}
|
}
|
||||||
return '1';
|
return '1';
|
||||||
}
|
}
|
||||||
const ver = createEvent.getContent()['room_version'];
|
return createEvent.getContent()['room_version'] ?? '1';
|
||||||
if (ver === undefined) return '1';
|
|
||||||
return ver;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -535,7 +534,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
logger.log(`[${this.roomId}] Current version: ${currentVersion}`);
|
logger.log(`[${this.roomId}] Current version: ${currentVersion}`);
|
||||||
logger.log(`[${this.roomId}] Version capability: `, versionCap);
|
logger.log(`[${this.roomId}] Version capability: `, versionCap);
|
||||||
|
|
||||||
const result = {
|
const result: IRecommendedVersion = {
|
||||||
version: currentVersion,
|
version: currentVersion,
|
||||||
needsUpgrade: false,
|
needsUpgrade: false,
|
||||||
urgent: false,
|
urgent: false,
|
||||||
@ -645,7 +644,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.pendingEventList.find(event => event.getId() === eventId);
|
return this.pendingEventList.find(event => event.getId() === eventId) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -677,7 +676,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
* @return {string} the membership type (join | leave | invite) for the logged in user
|
* @return {string} the membership type (join | leave | invite) for the logged in user
|
||||||
*/
|
*/
|
||||||
public getMyMembership(): string {
|
public getMyMembership(): string {
|
||||||
return this.selfMembership;
|
return this.selfMembership ?? "leave";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -685,7 +684,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
* try to find out who invited us
|
* try to find out who invited us
|
||||||
* @return {string} user id of the inviter
|
* @return {string} user id of the inviter
|
||||||
*/
|
*/
|
||||||
public getDMInviter(): string {
|
public getDMInviter(): string | undefined {
|
||||||
if (this.myUserId) {
|
if (this.myUserId) {
|
||||||
const me = this.getMember(this.myUserId);
|
const me = this.getMember(this.myUserId);
|
||||||
if (me) {
|
if (me) {
|
||||||
@ -731,7 +730,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
return this.myUserId;
|
return this.myUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAvatarFallbackMember(): RoomMember {
|
public getAvatarFallbackMember(): RoomMember | undefined {
|
||||||
const memberCount = this.getInvitedAndJoinedMemberCount();
|
const memberCount = this.getInvitedAndJoinedMemberCount();
|
||||||
if (memberCount > 2) {
|
if (memberCount > 2) {
|
||||||
return;
|
return;
|
||||||
@ -789,7 +788,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
|
|
||||||
private async loadMembersFromServer(): Promise<IStateEventWithRoomId[]> {
|
private async loadMembersFromServer(): Promise<IStateEventWithRoomId[]> {
|
||||||
const lastSyncToken = this.client.store.getSyncToken();
|
const lastSyncToken = this.client.store.getSyncToken();
|
||||||
const response = await this.client.members(this.roomId, undefined, "leave", lastSyncToken);
|
const response = await this.client.members(this.roomId, undefined, "leave", lastSyncToken ?? undefined);
|
||||||
return response.chunk;
|
return response.chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -836,12 +835,12 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
this.currentState.setOutOfBandMembers(result.memberEvents);
|
this.currentState.setOutOfBandMembers(result.memberEvents);
|
||||||
// now the members are loaded, start to track the e2e devices if needed
|
// now the members are loaded, start to track the e2e devices if needed
|
||||||
if (this.client.isCryptoEnabled() && this.client.isRoomEncrypted(this.roomId)) {
|
if (this.client.isCryptoEnabled() && this.client.isRoomEncrypted(this.roomId)) {
|
||||||
this.client.crypto.trackRoomDevices(this.roomId);
|
this.client.crypto!.trackRoomDevices(this.roomId);
|
||||||
}
|
}
|
||||||
return result.fromServer;
|
return result.fromServer;
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
// allow retries on fail
|
// allow retries on fail
|
||||||
this.membersPromise = null;
|
this.membersPromise = undefined;
|
||||||
this.currentState.markOutOfBandMembersFailed();
|
this.currentState.markOutOfBandMembersFailed();
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
@ -850,7 +849,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
if (fromServer) {
|
if (fromServer) {
|
||||||
const oobMembers = this.currentState.getMembers()
|
const oobMembers = this.currentState.getMembers()
|
||||||
.filter((m) => m.isOutOfBand())
|
.filter((m) => m.isOutOfBand())
|
||||||
.map((m) => m.events.member.event as IStateEventWithRoomId);
|
.map((m) => m.events.member?.event as IStateEventWithRoomId);
|
||||||
logger.log(`LL: telling store to write ${oobMembers.length}`
|
logger.log(`LL: telling store to write ${oobMembers.length}`
|
||||||
+ ` members for room ${this.roomId}`);
|
+ ` members for room ${this.roomId}`);
|
||||||
const store = this.client.store;
|
const store = this.client.store;
|
||||||
@ -881,7 +880,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
await this.loadMembersIfNeeded();
|
await this.loadMembersIfNeeded();
|
||||||
await this.client.store.clearOutOfBandMembers(this.roomId);
|
await this.client.store.clearOutOfBandMembers(this.roomId);
|
||||||
this.currentState.clearOutOfBandMembers();
|
this.currentState.clearOutOfBandMembers();
|
||||||
this.membersPromise = null;
|
this.membersPromise = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1328,7 +1327,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
* if the global value should be used for this room.
|
* if the global value should be used for this room.
|
||||||
*/
|
*/
|
||||||
public getBlacklistUnverifiedDevices(): boolean {
|
public getBlacklistUnverifiedDevices(): boolean {
|
||||||
return this.blacklistUnverifiedDevices;
|
return !!this.blacklistUnverifiedDevices;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1457,8 +1456,8 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
/**
|
/**
|
||||||
* @experimental
|
* @experimental
|
||||||
*/
|
*/
|
||||||
public getThread(eventId: string): Thread {
|
public getThread(eventId: string): Thread | null {
|
||||||
return this.threads.get(eventId);
|
return this.threads.get(eventId) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1584,7 +1583,6 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
* Add a timelineSet for this room with the given filter
|
* Add a timelineSet for this room with the given filter
|
||||||
* @param {Filter} filter The filter to be applied to this timelineSet
|
* @param {Filter} filter The filter to be applied to this timelineSet
|
||||||
* @param {Object=} opts Configuration options
|
* @param {Object=} opts Configuration options
|
||||||
* @param {*} opts.storageToken Optional.
|
|
||||||
* @return {EventTimelineSet} The timelineSet
|
* @return {EventTimelineSet} The timelineSet
|
||||||
*/
|
*/
|
||||||
public getOrCreateFilteredTimelineSet(
|
public getOrCreateFilteredTimelineSet(
|
||||||
@ -1799,7 +1797,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
|
|
||||||
const threadRelationship = rootEvent
|
const threadRelationship = rootEvent
|
||||||
.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
|
.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
|
||||||
if (threadRelationship.current_user_participated) {
|
if (threadRelationship?.current_user_participated) {
|
||||||
this.threadsTimelineSets[1].addLiveEvent(rootEvent, {
|
this.threadsTimelineSets[1].addLiveEvent(rootEvent, {
|
||||||
duplicateStrategy: DuplicateStrategy.Ignore,
|
duplicateStrategy: DuplicateStrategy.Ignore,
|
||||||
fromCache: false,
|
fromCache: false,
|
||||||
@ -2035,7 +2033,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
const redactId = event.event.redacts;
|
const redactId = event.event.redacts;
|
||||||
|
|
||||||
// if we know about this event, redact its contents now.
|
// if we know about this event, redact its contents now.
|
||||||
const redactedEvent = this.findEventById(redactId);
|
const redactedEvent = redactId ? this.findEventById(redactId) : undefined;
|
||||||
if (redactedEvent) {
|
if (redactedEvent) {
|
||||||
redactedEvent.makeRedacted(event);
|
redactedEvent.makeRedacted(event);
|
||||||
|
|
||||||
@ -2043,7 +2041,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
if (redactedEvent.isState()) {
|
if (redactedEvent.isState()) {
|
||||||
const currentStateEvent = this.currentState.getStateEvents(
|
const currentStateEvent = this.currentState.getStateEvents(
|
||||||
redactedEvent.getType(),
|
redactedEvent.getType(),
|
||||||
redactedEvent.getStateKey(),
|
redactedEvent.getStateKey()!,
|
||||||
);
|
);
|
||||||
if (currentStateEvent.getId() === redactedEvent.getId()) {
|
if (currentStateEvent.getId() === redactedEvent.getId()) {
|
||||||
this.currentState.setStateEvents([redactedEvent]);
|
this.currentState.setStateEvents([redactedEvent]);
|
||||||
@ -2059,7 +2057,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
// they are based on are changed.
|
// they are based on are changed.
|
||||||
|
|
||||||
// Remove any visibility change on this event.
|
// Remove any visibility change on this event.
|
||||||
this.visibilityEvents.delete(redactId);
|
this.visibilityEvents.delete(redactId!);
|
||||||
|
|
||||||
// If this event is a visibility change event, remove it from the
|
// If this event is a visibility change event, remove it from the
|
||||||
// list of visibility changes and update any event affected by it.
|
// list of visibility changes and update any event affected by it.
|
||||||
@ -2183,7 +2181,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
EventTimeline.setEventMetadata(event, this.getLiveTimeline().getState(EventTimeline.FORWARDS), false);
|
EventTimeline.setEventMetadata(event, this.getLiveTimeline().getState(EventTimeline.FORWARDS), false);
|
||||||
|
|
||||||
this.txnToEvent[txnId] = event;
|
this.txnToEvent[txnId] = event;
|
||||||
if (this.opts.pendingEventOrdering === PendingEventOrdering.Detached) {
|
if (this.pendingEventList) {
|
||||||
if (this.pendingEventList.some((e) => e.status === EventStatus.NOT_SENT)) {
|
if (this.pendingEventList.some((e) => e.status === EventStatus.NOT_SENT)) {
|
||||||
logger.warn("Setting event as NOT_SENT due to messages in the same state");
|
logger.warn("Setting event as NOT_SENT due to messages in the same state");
|
||||||
event.setStatus(EventStatus.NOT_SENT);
|
event.setStatus(EventStatus.NOT_SENT);
|
||||||
@ -2199,8 +2197,8 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
|
|
||||||
if (event.isRedaction()) {
|
if (event.isRedaction()) {
|
||||||
const redactId = event.event.redacts;
|
const redactId = event.event.redacts;
|
||||||
let redactedEvent = this.pendingEventList?.find(e => e.getId() === redactId);
|
let redactedEvent = this.pendingEventList.find(e => e.getId() === redactId);
|
||||||
if (!redactedEvent) {
|
if (!redactedEvent && redactId) {
|
||||||
redactedEvent = this.findEventById(redactId);
|
redactedEvent = this.findEventById(redactId);
|
||||||
}
|
}
|
||||||
if (redactedEvent) {
|
if (redactedEvent) {
|
||||||
@ -2212,7 +2210,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
for (let i = 0; i < this.timelineSets.length; i++) {
|
for (let i = 0; i < this.timelineSets.length; i++) {
|
||||||
const timelineSet = this.timelineSets[i];
|
const timelineSet = this.timelineSets[i];
|
||||||
if (timelineSet.getFilter()) {
|
if (timelineSet.getFilter()) {
|
||||||
if (timelineSet.getFilter().filterRoomTimeline([event]).length) {
|
if (timelineSet.getFilter()!.filterRoomTimeline([event]).length) {
|
||||||
timelineSet.addEventToTimeline(event,
|
timelineSet.addEventToTimeline(event,
|
||||||
timelineSet.getLiveTimeline(), {
|
timelineSet.getLiveTimeline(), {
|
||||||
toStartOfTimeline: false,
|
toStartOfTimeline: false,
|
||||||
@ -2227,7 +2225,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit(RoomEvent.LocalEchoUpdated, event, this, null, null);
|
this.emit(RoomEvent.LocalEchoUpdated, event, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2348,20 +2346,19 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
|
|
||||||
// if the message was sent, we expect an event id
|
// if the message was sent, we expect an event id
|
||||||
if (newStatus == EventStatus.SENT && !newEventId) {
|
if (newStatus == EventStatus.SENT && !newEventId) {
|
||||||
throw new Error("updatePendingEvent called with status=SENT, " +
|
throw new Error("updatePendingEvent called with status=SENT, but no new event id");
|
||||||
"but no new event id");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SENT races against /sync, so we have to special-case it.
|
// SENT races against /sync, so we have to special-case it.
|
||||||
if (newStatus == EventStatus.SENT) {
|
if (newStatus == EventStatus.SENT) {
|
||||||
const timeline = this.getTimelineForEvent(newEventId);
|
const timeline = this.getTimelineForEvent(newEventId!);
|
||||||
if (timeline) {
|
if (timeline) {
|
||||||
// we've already received the event via the event stream.
|
// we've already received the event via the event stream.
|
||||||
// nothing more to do here, assuming the transaction ID was correctly matched.
|
// nothing more to do here, assuming the transaction ID was correctly matched.
|
||||||
// Let's check that.
|
// Let's check that.
|
||||||
const remoteEvent = this.findEventById(newEventId);
|
const remoteEvent = this.findEventById(newEventId!);
|
||||||
const remoteTxnId = remoteEvent.getUnsigned().transaction_id;
|
const remoteTxnId = remoteEvent?.getUnsigned().transaction_id;
|
||||||
if (!remoteTxnId) {
|
if (!remoteTxnId && remoteEvent) {
|
||||||
// This code path is mostly relevant for the Sliding Sync proxy.
|
// This code path is mostly relevant for the Sliding Sync proxy.
|
||||||
// The remote event did not contain a transaction ID, so we did not handle
|
// The remote event did not contain a transaction ID, so we did not handle
|
||||||
// the remote echo yet. Handle it now.
|
// the remote echo yet. Handle it now.
|
||||||
@ -2395,18 +2392,18 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
|
|
||||||
if (newStatus == EventStatus.SENT) {
|
if (newStatus == EventStatus.SENT) {
|
||||||
// update the event id
|
// update the event id
|
||||||
event.replaceLocalEventId(newEventId);
|
event.replaceLocalEventId(newEventId!);
|
||||||
|
|
||||||
const { shouldLiveInRoom, threadId } = this.eventShouldLiveIn(event);
|
const { shouldLiveInRoom, threadId } = this.eventShouldLiveIn(event);
|
||||||
const thread = this.getThread(threadId);
|
const thread = threadId ? this.getThread(threadId) : undefined;
|
||||||
thread?.timelineSet.replaceEventId(oldEventId, newEventId);
|
thread?.timelineSet.replaceEventId(oldEventId, newEventId!);
|
||||||
|
|
||||||
if (shouldLiveInRoom) {
|
if (shouldLiveInRoom) {
|
||||||
// if the event was already in the timeline (which will be the case if
|
// if the event was already in the timeline (which will be the case if
|
||||||
// opts.pendingEventOrdering==chronological), we need to update the
|
// opts.pendingEventOrdering==chronological), we need to update the
|
||||||
// timeline map.
|
// timeline map.
|
||||||
for (let i = 0; i < this.timelineSets.length; i++) {
|
for (let i = 0; i < this.timelineSets.length; i++) {
|
||||||
this.timelineSets[i].replaceEventId(oldEventId, newEventId);
|
this.timelineSets[i].replaceEventId(oldEventId, newEventId!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (newStatus == EventStatus.CANCELLED) {
|
} else if (newStatus == EventStatus.CANCELLED) {
|
||||||
@ -2414,7 +2411,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
if (this.pendingEventList) {
|
if (this.pendingEventList) {
|
||||||
const removedEvent = this.getPendingEvent(oldEventId);
|
const removedEvent = this.getPendingEvent(oldEventId);
|
||||||
this.removePendingEvent(oldEventId);
|
this.removePendingEvent(oldEventId);
|
||||||
if (removedEvent.isRedaction()) {
|
if (removedEvent?.isRedaction()) {
|
||||||
this.revertRedactionLocalEcho(removedEvent);
|
this.revertRedactionLocalEcho(removedEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2449,7 +2446,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
* they will go to the end of the timeline.
|
* they will go to the end of the timeline.
|
||||||
*
|
*
|
||||||
* @param {MatrixEvent[]} events A list of events to add.
|
* @param {MatrixEvent[]} events A list of events to add.
|
||||||
* @param {IAddLiveEventOptions} options addLiveEvent options
|
* @param {IAddLiveEventOptions} addLiveEventOptions addLiveEvent options
|
||||||
* @throws If <code>duplicateStrategy</code> is not falsey, 'replace' or 'ignore'.
|
* @throws If <code>duplicateStrategy</code> is not falsey, 'replace' or 'ignore'.
|
||||||
*/
|
*/
|
||||||
public addLiveEvents(events: MatrixEvent[], addLiveEventOptions?: IAddLiveEventOptions): void;
|
public addLiveEvents(events: MatrixEvent[], addLiveEventOptions?: IAddLiveEventOptions): void;
|
||||||
@ -2462,8 +2459,8 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
duplicateStrategyOrOpts?: DuplicateStrategy | IAddLiveEventOptions,
|
duplicateStrategyOrOpts?: DuplicateStrategy | IAddLiveEventOptions,
|
||||||
fromCache = false,
|
fromCache = false,
|
||||||
): void {
|
): void {
|
||||||
let duplicateStrategy = duplicateStrategyOrOpts as DuplicateStrategy;
|
let duplicateStrategy: DuplicateStrategy | undefined = duplicateStrategyOrOpts as DuplicateStrategy;
|
||||||
let timelineWasEmpty = false;
|
let timelineWasEmpty: boolean | undefined = false;
|
||||||
if (typeof (duplicateStrategyOrOpts) === 'object') {
|
if (typeof (duplicateStrategyOrOpts) === 'object') {
|
||||||
({
|
({
|
||||||
duplicateStrategy,
|
duplicateStrategy,
|
||||||
@ -2679,7 +2676,7 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
const membershipEvent = this.currentState.getStateEvents(EventType.RoomMember, this.myUserId);
|
const membershipEvent = this.currentState.getStateEvents(EventType.RoomMember, this.myUserId);
|
||||||
if (membershipEvent) {
|
if (membershipEvent) {
|
||||||
const membership = membershipEvent.getContent().membership;
|
const membership = membershipEvent.getContent().membership;
|
||||||
this.updateMyMembership(membership);
|
this.updateMyMembership(membership!);
|
||||||
|
|
||||||
if (membership === "invite") {
|
if (membership === "invite") {
|
||||||
const strippedStateEvents = membershipEvent.getUnsigned().invite_room_state || [];
|
const strippedStateEvents = membershipEvent.getUnsigned().invite_room_state || [];
|
||||||
@ -2919,11 +2916,9 @@ export class Room extends ReadReceipt<EmittedEvents, RoomEventHandlerMap> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get members that are NOT ourselves and are actually in the room.
|
// get members that are NOT ourselves and are actually in the room.
|
||||||
let otherNames: string[] = null;
|
let otherNames: string[] = [];
|
||||||
if (this.summaryHeroes) {
|
if (this.summaryHeroes) {
|
||||||
// if we have a summary, the member state events
|
// if we have a summary, the member state events should be in the room state
|
||||||
// should be in the room state
|
|
||||||
otherNames = [];
|
|
||||||
this.summaryHeroes.forEach((userId) => {
|
this.summaryHeroes.forEach((userId) => {
|
||||||
// filter service members
|
// filter service members
|
||||||
if (excludedUserIds.includes(userId)) {
|
if (excludedUserIds.includes(userId)) {
|
||||||
|
@ -38,13 +38,13 @@ export type UserEventHandlerMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
|
export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
|
||||||
private modified: number;
|
private modified = -1;
|
||||||
|
|
||||||
// XXX these should be read-only
|
// XXX these should be read-only
|
||||||
public displayName: string;
|
public displayName?: string;
|
||||||
public rawDisplayName: string;
|
public rawDisplayName?: string;
|
||||||
public avatarUrl: string;
|
public avatarUrl?: string;
|
||||||
public presenceStatusMsg: string = null;
|
public presenceStatusMsg?: string;
|
||||||
public presence = "offline";
|
public presence = "offline";
|
||||||
public lastActiveAgo = 0;
|
public lastActiveAgo = 0;
|
||||||
public lastPresenceTs = 0;
|
public lastPresenceTs = 0;
|
||||||
@ -52,10 +52,7 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
|
|||||||
public events: {
|
public events: {
|
||||||
presence?: MatrixEvent;
|
presence?: MatrixEvent;
|
||||||
profile?: MatrixEvent;
|
profile?: MatrixEvent;
|
||||||
} = {
|
} = {};
|
||||||
presence: null,
|
|
||||||
profile: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new User. A User must have an ID and can optionally have extra
|
* Construct a new User. A User must have an ID and can optionally have extra
|
||||||
@ -83,7 +80,6 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
|
|||||||
super();
|
super();
|
||||||
this.displayName = userId;
|
this.displayName = userId;
|
||||||
this.rawDisplayName = userId;
|
this.rawDisplayName = userId;
|
||||||
this.avatarUrl = null;
|
|
||||||
this.updateModifiedTime();
|
this.updateModifiedTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,11 +146,7 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
|
|||||||
*/
|
*/
|
||||||
public setDisplayName(name: string): void {
|
public setDisplayName(name: string): void {
|
||||||
const oldName = this.displayName;
|
const oldName = this.displayName;
|
||||||
if (typeof name === "string") {
|
this.displayName = name;
|
||||||
this.displayName = name;
|
|
||||||
} else {
|
|
||||||
this.displayName = undefined;
|
|
||||||
}
|
|
||||||
if (name !== oldName) {
|
if (name !== oldName) {
|
||||||
this.updateModifiedTime();
|
this.updateModifiedTime();
|
||||||
}
|
}
|
||||||
@ -165,12 +157,8 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
|
|||||||
* in response to this as there is no underlying MatrixEvent to emit with.
|
* in response to this as there is no underlying MatrixEvent to emit with.
|
||||||
* @param {string} name The new display name.
|
* @param {string} name The new display name.
|
||||||
*/
|
*/
|
||||||
public setRawDisplayName(name: string): void {
|
public setRawDisplayName(name?: string): void {
|
||||||
if (typeof name === "string") {
|
this.rawDisplayName = name;
|
||||||
this.rawDisplayName = name;
|
|
||||||
} else {
|
|
||||||
this.rawDisplayName = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -178,7 +166,7 @@ export class User extends TypedEventEmitter<UserEvent, UserEventHandlerMap> {
|
|||||||
* as there is no underlying MatrixEvent to emit with.
|
* as there is no underlying MatrixEvent to emit with.
|
||||||
* @param {string} url The new avatar URL.
|
* @param {string} url The new avatar URL.
|
||||||
*/
|
*/
|
||||||
public setAvatarUrl(url: string): void {
|
public setAvatarUrl(url?: string): void {
|
||||||
const oldUrl = this.avatarUrl;
|
const oldUrl = this.avatarUrl;
|
||||||
this.avatarUrl = url;
|
this.avatarUrl = url;
|
||||||
if (url !== oldUrl) {
|
if (url !== oldUrl) {
|
||||||
|
@ -670,8 +670,8 @@ export class SlidingSyncSdk {
|
|||||||
// For each invited room member we want to give them a displayname/avatar url
|
// For each invited room member we want to give them a displayname/avatar url
|
||||||
// if they have one (the m.room.member invites don't contain this).
|
// if they have one (the m.room.member invites don't contain this).
|
||||||
room.getMembersWithMembership("invite").forEach(function(member) {
|
room.getMembersWithMembership("invite").forEach(function(member) {
|
||||||
if (member._requestedProfileInfo) return;
|
if (member.requestedProfileInfo) return;
|
||||||
member._requestedProfileInfo = true;
|
member.requestedProfileInfo = true;
|
||||||
// try to get a cached copy first.
|
// try to get a cached copy first.
|
||||||
const user = client.getUser(member.userId);
|
const user = client.getUser(member.userId);
|
||||||
let promise: ReturnType<MatrixClient["getProfileInfo"]>;
|
let promise: ReturnType<MatrixClient["getProfileInfo"]>;
|
||||||
|
@ -195,7 +195,7 @@ export interface IStore {
|
|||||||
* client state to where it was at the last save, or null if there
|
* client state to where it was at the last save, or null if there
|
||||||
* is no saved sync data.
|
* is no saved sync data.
|
||||||
*/
|
*/
|
||||||
getSavedSync(): Promise<ISavedSync>;
|
getSavedSync(): Promise<ISavedSync | null>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {Promise} If there is a saved sync, the nextBatch token
|
* @return {Promise} If there is a saved sync, the nextBatch token
|
||||||
|
@ -23,7 +23,7 @@ export interface IIndexedDBBackend {
|
|||||||
syncToDatabase(userTuples: UserTuple[]): Promise<void>;
|
syncToDatabase(userTuples: UserTuple[]): Promise<void>;
|
||||||
isNewlyCreated(): Promise<boolean>;
|
isNewlyCreated(): Promise<boolean>;
|
||||||
setSyncData(syncData: ISyncResponse): Promise<void>;
|
setSyncData(syncData: ISyncResponse): Promise<void>;
|
||||||
getSavedSync(): Promise<ISavedSync>;
|
getSavedSync(): Promise<ISavedSync | null>;
|
||||||
getNextBatchToken(): Promise<string>;
|
getNextBatchToken(): Promise<string>;
|
||||||
clearDatabase(): Promise<void>;
|
clearDatabase(): Promise<void>;
|
||||||
getOutOfBandMembers(roomId: string): Promise<IStateEventWithRoomId[] | null>;
|
getOutOfBandMembers(roomId: string): Promise<IStateEventWithRoomId[] | null>;
|
||||||
|
@ -124,7 +124,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
|
|
||||||
private readonly dbName: string;
|
private readonly dbName: string;
|
||||||
private readonly syncAccumulator: SyncAccumulator;
|
private readonly syncAccumulator: SyncAccumulator;
|
||||||
private db: IDBDatabase = null;
|
private db?: IDBDatabase;
|
||||||
private disconnected = true;
|
private disconnected = true;
|
||||||
private _isNewlyCreated = false;
|
private _isNewlyCreated = false;
|
||||||
private isPersisting = false;
|
private isPersisting = false;
|
||||||
@ -141,8 +141,8 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
* @param {string=} dbName Optional database name. The same name must be used
|
* @param {string=} dbName Optional database name. The same name must be used
|
||||||
* to open the same database.
|
* to open the same database.
|
||||||
*/
|
*/
|
||||||
constructor(private readonly indexedDB: IDBFactory, dbName: string) {
|
constructor(private readonly indexedDB: IDBFactory, dbName = "default") {
|
||||||
this.dbName = "matrix-js-sdk:" + (dbName || "default");
|
this.dbName = "matrix-js-sdk:" + dbName;
|
||||||
this.syncAccumulator = new SyncAccumulator();
|
this.syncAccumulator = new SyncAccumulator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,7 +188,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
// add a poorly-named listener for when deleteDatabase is called
|
// add a poorly-named listener for when deleteDatabase is called
|
||||||
// so we can close our db connections.
|
// so we can close our db connections.
|
||||||
this.db.onversionchange = () => {
|
this.db.onversionchange = () => {
|
||||||
this.db.close();
|
this.db?.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.init();
|
return this.init();
|
||||||
@ -229,7 +229,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
*/
|
*/
|
||||||
public getOutOfBandMembers(roomId: string): Promise<IStateEventWithRoomId[] | null> {
|
public getOutOfBandMembers(roomId: string): Promise<IStateEventWithRoomId[] | null> {
|
||||||
return new Promise<IStateEventWithRoomId[] | null>((resolve, reject) => {
|
return new Promise<IStateEventWithRoomId[] | null>((resolve, reject) => {
|
||||||
const tx = this.db.transaction(["oob_membership_events"], "readonly");
|
const tx = this.db!.transaction(["oob_membership_events"], "readonly");
|
||||||
const store = tx.objectStore("oob_membership_events");
|
const store = tx.objectStore("oob_membership_events");
|
||||||
const roomIndex = store.index("room");
|
const roomIndex = store.index("room");
|
||||||
const range = IDBKeyRange.only(roomId);
|
const range = IDBKeyRange.only(roomId);
|
||||||
@ -279,7 +279,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
public async setOutOfBandMembers(roomId: string, membershipEvents: IStateEventWithRoomId[]): Promise<void> {
|
public async setOutOfBandMembers(roomId: string, membershipEvents: IStateEventWithRoomId[]): Promise<void> {
|
||||||
logger.log(`LL: backend about to store ${membershipEvents.length}` +
|
logger.log(`LL: backend about to store ${membershipEvents.length}` +
|
||||||
` members for ${roomId}`);
|
` members for ${roomId}`);
|
||||||
const tx = this.db.transaction(["oob_membership_events"], "readwrite");
|
const tx = this.db!.transaction(["oob_membership_events"], "readwrite");
|
||||||
const store = tx.objectStore("oob_membership_events");
|
const store = tx.objectStore("oob_membership_events");
|
||||||
membershipEvents.forEach((e) => {
|
membershipEvents.forEach((e) => {
|
||||||
store.put(e);
|
store.put(e);
|
||||||
@ -306,7 +306,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
// keys in the store.
|
// keys in the store.
|
||||||
// this should be way faster than deleting every member
|
// this should be way faster than deleting every member
|
||||||
// individually for a large room.
|
// individually for a large room.
|
||||||
const readTx = this.db.transaction(
|
const readTx = this.db!.transaction(
|
||||||
["oob_membership_events"],
|
["oob_membership_events"],
|
||||||
"readonly");
|
"readonly");
|
||||||
const store = readTx.objectStore("oob_membership_events");
|
const store = readTx.objectStore("oob_membership_events");
|
||||||
@ -322,7 +322,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
const [minStateKey, maxStateKey] = await Promise.all(
|
const [minStateKey, maxStateKey] = await Promise.all(
|
||||||
[minStateKeyProm, maxStateKeyProm]);
|
[minStateKeyProm, maxStateKeyProm]);
|
||||||
|
|
||||||
const writeTx = this.db.transaction(
|
const writeTx = this.db!.transaction(
|
||||||
["oob_membership_events"],
|
["oob_membership_events"],
|
||||||
"readwrite");
|
"readwrite");
|
||||||
const writeStore = writeTx.objectStore("oob_membership_events");
|
const writeStore = writeTx.objectStore("oob_membership_events");
|
||||||
@ -374,7 +374,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
* client state to where it was at the last save, or null if there
|
* client state to where it was at the last save, or null if there
|
||||||
* is no saved sync data.
|
* is no saved sync data.
|
||||||
*/
|
*/
|
||||||
public getSavedSync(copy = true): Promise<ISavedSync> {
|
public getSavedSync(copy = true): Promise<ISavedSync | null> {
|
||||||
const data = this.syncAccumulator.getJSON();
|
const data = this.syncAccumulator.getJSON();
|
||||||
if (!data.nextBatch) return Promise.resolve(null);
|
if (!data.nextBatch) return Promise.resolve(null);
|
||||||
if (copy) {
|
if (copy) {
|
||||||
@ -431,7 +431,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
logger.log("Persisting sync data up to", nextBatch);
|
logger.log("Persisting sync data up to", nextBatch);
|
||||||
return utils.promiseTry<void>(() => {
|
return utils.promiseTry<void>(() => {
|
||||||
const txn = this.db.transaction(["sync"], "readwrite");
|
const txn = this.db!.transaction(["sync"], "readwrite");
|
||||||
const store = txn.objectStore("sync");
|
const store = txn.objectStore("sync");
|
||||||
store.put({
|
store.put({
|
||||||
clobber: "-", // constant key so will always clobber
|
clobber: "-", // constant key so will always clobber
|
||||||
@ -452,7 +452,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
*/
|
*/
|
||||||
private persistAccountData(accountData: IMinimalEvent[]): Promise<void> {
|
private persistAccountData(accountData: IMinimalEvent[]): Promise<void> {
|
||||||
return utils.promiseTry<void>(() => {
|
return utils.promiseTry<void>(() => {
|
||||||
const txn = this.db.transaction(["accountData"], "readwrite");
|
const txn = this.db!.transaction(["accountData"], "readwrite");
|
||||||
const store = txn.objectStore("accountData");
|
const store = txn.objectStore("accountData");
|
||||||
for (let i = 0; i < accountData.length; i++) {
|
for (let i = 0; i < accountData.length; i++) {
|
||||||
store.put(accountData[i]); // put == UPSERT
|
store.put(accountData[i]); // put == UPSERT
|
||||||
@ -471,7 +471,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
*/
|
*/
|
||||||
private persistUserPresenceEvents(tuples: UserTuple[]): Promise<void> {
|
private persistUserPresenceEvents(tuples: UserTuple[]): Promise<void> {
|
||||||
return utils.promiseTry<void>(() => {
|
return utils.promiseTry<void>(() => {
|
||||||
const txn = this.db.transaction(["users"], "readwrite");
|
const txn = this.db!.transaction(["users"], "readwrite");
|
||||||
const store = txn.objectStore("users");
|
const store = txn.objectStore("users");
|
||||||
for (const tuple of tuples) {
|
for (const tuple of tuples) {
|
||||||
store.put({
|
store.put({
|
||||||
@ -491,7 +491,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
*/
|
*/
|
||||||
public getUserPresenceEvents(): Promise<UserTuple[]> {
|
public getUserPresenceEvents(): Promise<UserTuple[]> {
|
||||||
return utils.promiseTry<UserTuple[]>(() => {
|
return utils.promiseTry<UserTuple[]>(() => {
|
||||||
const txn = this.db.transaction(["users"], "readonly");
|
const txn = this.db!.transaction(["users"], "readonly");
|
||||||
const store = txn.objectStore("users");
|
const store = txn.objectStore("users");
|
||||||
return selectQuery(store, undefined, (cursor) => {
|
return selectQuery(store, undefined, (cursor) => {
|
||||||
return [cursor.value.userId, cursor.value.event];
|
return [cursor.value.userId, cursor.value.event];
|
||||||
@ -506,7 +506,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
private loadAccountData(): Promise<IMinimalEvent[]> {
|
private loadAccountData(): Promise<IMinimalEvent[]> {
|
||||||
logger.log(`LocalIndexedDBStoreBackend: loading account data...`);
|
logger.log(`LocalIndexedDBStoreBackend: loading account data...`);
|
||||||
return utils.promiseTry<IMinimalEvent[]>(() => {
|
return utils.promiseTry<IMinimalEvent[]>(() => {
|
||||||
const txn = this.db.transaction(["accountData"], "readonly");
|
const txn = this.db!.transaction(["accountData"], "readonly");
|
||||||
const store = txn.objectStore("accountData");
|
const store = txn.objectStore("accountData");
|
||||||
return selectQuery(store, undefined, (cursor) => {
|
return selectQuery(store, undefined, (cursor) => {
|
||||||
return cursor.value;
|
return cursor.value;
|
||||||
@ -524,7 +524,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
private loadSyncData(): Promise<ISyncData> {
|
private loadSyncData(): Promise<ISyncData> {
|
||||||
logger.log(`LocalIndexedDBStoreBackend: loading sync data...`);
|
logger.log(`LocalIndexedDBStoreBackend: loading sync data...`);
|
||||||
return utils.promiseTry<ISyncData>(() => {
|
return utils.promiseTry<ISyncData>(() => {
|
||||||
const txn = this.db.transaction(["sync"], "readonly");
|
const txn = this.db!.transaction(["sync"], "readonly");
|
||||||
const store = txn.objectStore("sync");
|
const store = txn.objectStore("sync");
|
||||||
return selectQuery(store, undefined, (cursor) => {
|
return selectQuery(store, undefined, (cursor) => {
|
||||||
return cursor.value;
|
return cursor.value;
|
||||||
@ -540,7 +540,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
|
|
||||||
public getClientOptions(): Promise<IStartClientOpts> {
|
public getClientOptions(): Promise<IStartClientOpts> {
|
||||||
return Promise.resolve().then(() => {
|
return Promise.resolve().then(() => {
|
||||||
const txn = this.db.transaction(["client_options"], "readonly");
|
const txn = this.db!.transaction(["client_options"], "readonly");
|
||||||
const store = txn.objectStore("client_options");
|
const store = txn.objectStore("client_options");
|
||||||
return selectQuery(store, undefined, (cursor) => {
|
return selectQuery(store, undefined, (cursor) => {
|
||||||
return cursor.value?.options;
|
return cursor.value?.options;
|
||||||
@ -549,7 +549,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async storeClientOptions(options: IStartClientOpts): Promise<void> {
|
public async storeClientOptions(options: IStartClientOpts): Promise<void> {
|
||||||
const txn = this.db.transaction(["client_options"], "readwrite");
|
const txn = this.db!.transaction(["client_options"], "readwrite");
|
||||||
const store = txn.objectStore("client_options");
|
const store = txn.objectStore("client_options");
|
||||||
store.put({
|
store.put({
|
||||||
clobber: "-", // constant key so will always clobber
|
clobber: "-", // constant key so will always clobber
|
||||||
@ -559,7 +559,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async saveToDeviceBatches(batches: ToDeviceBatchWithTxnId[]): Promise<void> {
|
public async saveToDeviceBatches(batches: ToDeviceBatchWithTxnId[]): Promise<void> {
|
||||||
const txn = this.db.transaction(["to_device_queue"], "readwrite");
|
const txn = this.db!.transaction(["to_device_queue"], "readwrite");
|
||||||
const store = txn.objectStore("to_device_queue");
|
const store = txn.objectStore("to_device_queue");
|
||||||
for (const batch of batches) {
|
for (const batch of batches) {
|
||||||
store.add(batch);
|
store.add(batch);
|
||||||
@ -568,7 +568,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getOldestToDeviceBatch(): Promise<IndexedToDeviceBatch | null> {
|
public async getOldestToDeviceBatch(): Promise<IndexedToDeviceBatch | null> {
|
||||||
const txn = this.db.transaction(["to_device_queue"], "readonly");
|
const txn = this.db!.transaction(["to_device_queue"], "readonly");
|
||||||
const store = txn.objectStore("to_device_queue");
|
const store = txn.objectStore("to_device_queue");
|
||||||
const cursor = await reqAsCursorPromise(store.openCursor());
|
const cursor = await reqAsCursorPromise(store.openCursor());
|
||||||
if (!cursor) return null;
|
if (!cursor) return null;
|
||||||
@ -584,7 +584,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async removeToDeviceBatch(id: number): Promise<void> {
|
public async removeToDeviceBatch(id: number): Promise<void> {
|
||||||
const txn = this.db.transaction(["to_device_queue"], "readwrite");
|
const txn = this.db!.transaction(["to_device_queue"], "readwrite");
|
||||||
const store = txn.objectStore("to_device_queue");
|
const store = txn.objectStore("to_device_queue");
|
||||||
store.delete(id);
|
store.delete(id);
|
||||||
await txnAsPromise(txn);
|
await txnAsPromise(txn);
|
||||||
|
@ -23,13 +23,13 @@ import { IIndexedDBBackend, UserTuple } from "./indexeddb-backend";
|
|||||||
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage";
|
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage";
|
||||||
|
|
||||||
export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend {
|
export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend {
|
||||||
private worker: Worker;
|
private worker?: Worker;
|
||||||
private nextSeq = 0;
|
private nextSeq = 0;
|
||||||
// The currently in-flight requests to the actual backend
|
// The currently in-flight requests to the actual backend
|
||||||
private inFlight: Record<number, IDeferred<any>> = {}; // seq: promise
|
private inFlight: Record<number, IDeferred<any>> = {}; // seq: promise
|
||||||
// Once we start connecting, we keep the promise and re-use it
|
// Once we start connecting, we keep the promise and re-use it
|
||||||
// if we try to connect again
|
// if we try to connect again
|
||||||
private startPromise: Promise<void> = null;
|
private startPromise?: Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An IndexedDB store backend where the actual backend sits in a web
|
* An IndexedDB store backend where the actual backend sits in a web
|
||||||
@ -44,7 +44,7 @@ export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workerFactory: () => Worker,
|
private readonly workerFactory: () => Worker,
|
||||||
private readonly dbName: string,
|
private readonly dbName?: string,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -147,12 +147,12 @@ export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ensureStarted(): Promise<void> {
|
private ensureStarted(): Promise<void> {
|
||||||
if (this.startPromise === null) {
|
if (!this.startPromise) {
|
||||||
this.worker = this.workerFactory();
|
this.worker = this.workerFactory();
|
||||||
this.worker.onmessage = this.onWorkerMessage;
|
this.worker.onmessage = this.onWorkerMessage;
|
||||||
|
|
||||||
// tell the worker the db name.
|
// tell the worker the db name.
|
||||||
this.startPromise = this.doCmd('_setupWorker', [this.dbName]).then(() => {
|
this.startPromise = this.doCmd('setupWorker', [this.dbName]).then(() => {
|
||||||
logger.log("IndexedDB worker is ready");
|
logger.log("IndexedDB worker is ready");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -168,7 +168,7 @@ export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
|
|
||||||
this.inFlight[seq] = def;
|
this.inFlight[seq] = def;
|
||||||
|
|
||||||
this.worker.postMessage({ command, seq, args });
|
this.worker?.postMessage({ command, seq, args });
|
||||||
|
|
||||||
return def.promise;
|
return def.promise;
|
||||||
});
|
});
|
||||||
|
@ -20,7 +20,7 @@ import { logger } from '../logger';
|
|||||||
interface ICmd {
|
interface ICmd {
|
||||||
command: string;
|
command: string;
|
||||||
seq: number;
|
seq: number;
|
||||||
args?: any[];
|
args: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,7 +39,7 @@ interface ICmd {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export class IndexedDBStoreWorker {
|
export class IndexedDBStoreWorker {
|
||||||
private backend: LocalIndexedDBStoreBackend = null;
|
private backend?: LocalIndexedDBStoreBackend;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {function} postMessage The web worker postMessage function that
|
* @param {function} postMessage The web worker postMessage function that
|
||||||
@ -58,59 +58,59 @@ export class IndexedDBStoreWorker {
|
|||||||
let prom;
|
let prom;
|
||||||
|
|
||||||
switch (msg.command) {
|
switch (msg.command) {
|
||||||
case '_setupWorker':
|
case 'setupWorker':
|
||||||
// this is the 'indexedDB' global (where global != window
|
// this is the 'indexedDB' global (where global != window
|
||||||
// because it's a web worker and there is no window).
|
// because it's a web worker and there is no window).
|
||||||
this.backend = new LocalIndexedDBStoreBackend(indexedDB, msg.args[0]);
|
this.backend = new LocalIndexedDBStoreBackend(indexedDB, msg.args[0]);
|
||||||
prom = Promise.resolve();
|
prom = Promise.resolve();
|
||||||
break;
|
break;
|
||||||
case 'connect':
|
case 'connect':
|
||||||
prom = this.backend.connect();
|
prom = this.backend?.connect();
|
||||||
break;
|
break;
|
||||||
case 'isNewlyCreated':
|
case 'isNewlyCreated':
|
||||||
prom = this.backend.isNewlyCreated();
|
prom = this.backend?.isNewlyCreated();
|
||||||
break;
|
break;
|
||||||
case 'clearDatabase':
|
case 'clearDatabase':
|
||||||
prom = this.backend.clearDatabase();
|
prom = this.backend?.clearDatabase();
|
||||||
break;
|
break;
|
||||||
case 'getSavedSync':
|
case 'getSavedSync':
|
||||||
prom = this.backend.getSavedSync(false);
|
prom = this.backend?.getSavedSync(false);
|
||||||
break;
|
break;
|
||||||
case 'setSyncData':
|
case 'setSyncData':
|
||||||
prom = this.backend.setSyncData(msg.args[0]);
|
prom = this.backend?.setSyncData(msg.args[0]);
|
||||||
break;
|
break;
|
||||||
case 'syncToDatabase':
|
case 'syncToDatabase':
|
||||||
prom = this.backend.syncToDatabase(msg.args[0]);
|
prom = this.backend?.syncToDatabase(msg.args[0]);
|
||||||
break;
|
break;
|
||||||
case 'getUserPresenceEvents':
|
case 'getUserPresenceEvents':
|
||||||
prom = this.backend.getUserPresenceEvents();
|
prom = this.backend?.getUserPresenceEvents();
|
||||||
break;
|
break;
|
||||||
case 'getNextBatchToken':
|
case 'getNextBatchToken':
|
||||||
prom = this.backend.getNextBatchToken();
|
prom = this.backend?.getNextBatchToken();
|
||||||
break;
|
break;
|
||||||
case 'getOutOfBandMembers':
|
case 'getOutOfBandMembers':
|
||||||
prom = this.backend.getOutOfBandMembers(msg.args[0]);
|
prom = this.backend?.getOutOfBandMembers(msg.args[0]);
|
||||||
break;
|
break;
|
||||||
case 'clearOutOfBandMembers':
|
case 'clearOutOfBandMembers':
|
||||||
prom = this.backend.clearOutOfBandMembers(msg.args[0]);
|
prom = this.backend?.clearOutOfBandMembers(msg.args[0]);
|
||||||
break;
|
break;
|
||||||
case 'setOutOfBandMembers':
|
case 'setOutOfBandMembers':
|
||||||
prom = this.backend.setOutOfBandMembers(msg.args[0], msg.args[1]);
|
prom = this.backend?.setOutOfBandMembers(msg.args[0], msg.args[1]);
|
||||||
break;
|
break;
|
||||||
case 'getClientOptions':
|
case 'getClientOptions':
|
||||||
prom = this.backend.getClientOptions();
|
prom = this.backend?.getClientOptions();
|
||||||
break;
|
break;
|
||||||
case 'storeClientOptions':
|
case 'storeClientOptions':
|
||||||
prom = this.backend.storeClientOptions(msg.args[0]);
|
prom = this.backend?.storeClientOptions(msg.args[0]);
|
||||||
break;
|
break;
|
||||||
case 'saveToDeviceBatches':
|
case 'saveToDeviceBatches':
|
||||||
prom = this.backend.saveToDeviceBatches(msg.args[0]);
|
prom = this.backend?.saveToDeviceBatches(msg.args[0]);
|
||||||
break;
|
break;
|
||||||
case 'getOldestToDeviceBatch':
|
case 'getOldestToDeviceBatch':
|
||||||
prom = this.backend.getOldestToDeviceBatch();
|
prom = this.backend?.getOldestToDeviceBatch();
|
||||||
break;
|
break;
|
||||||
case 'removeToDeviceBatch':
|
case 'removeToDeviceBatch':
|
||||||
prom = this.backend.removeToDeviceBatch(msg.args[0]);
|
prom = this.backend?.removeToDeviceBatch(msg.args[0]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ export class IndexedDBStore extends MemoryStore {
|
|||||||
* client state to where it was at the last save, or null if there
|
* client state to where it was at the last save, or null if there
|
||||||
* is no saved sync data.
|
* is no saved sync data.
|
||||||
*/
|
*/
|
||||||
public getSavedSync = this.degradable((): Promise<ISavedSync> => {
|
public getSavedSync = this.degradable((): Promise<ISavedSync | null> => {
|
||||||
return this.backend.getSavedSync();
|
return this.backend.getSavedSync();
|
||||||
}, "getSavedSync");
|
}, "getSavedSync");
|
||||||
|
|
||||||
@ -292,16 +292,16 @@ export class IndexedDBStore extends MemoryStore {
|
|||||||
*/
|
*/
|
||||||
private degradable<A extends Array<any>, R = void>(
|
private degradable<A extends Array<any>, R = void>(
|
||||||
func: DegradableFn<A, R>,
|
func: DegradableFn<A, R>,
|
||||||
fallback?: string,
|
fallback?: keyof MemoryStore,
|
||||||
): DegradableFn<A, R> {
|
): DegradableFn<A, R> {
|
||||||
const fallbackFn = super[fallback];
|
const fallbackFn = fallback ? super[fallback] as Function : null;
|
||||||
|
|
||||||
return async (...args) => {
|
return async (...args) => {
|
||||||
try {
|
try {
|
||||||
return await func.call(this, ...args);
|
return await func.call(this, ...args);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("IndexedDBStore failure, degrading to MemoryStore", e);
|
logger.error("IndexedDBStore failure, degrading to MemoryStore", e);
|
||||||
this.emitter.emit("degraded", e);
|
this.emitter.emit("degraded", e as Error);
|
||||||
try {
|
try {
|
||||||
// We try to delete IndexedDB after degrading since this store is only a
|
// We try to delete IndexedDB after degrading since this store is only a
|
||||||
// cache (the app will still function correctly without the data).
|
// cache (the app will still function correctly without the data).
|
||||||
|
@ -32,7 +32,7 @@ import { ISyncResponse } from "../sync-accumulator";
|
|||||||
import { IStateEventWithRoomId } from "../@types/search";
|
import { IStateEventWithRoomId } from "../@types/search";
|
||||||
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage";
|
import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage";
|
||||||
|
|
||||||
function isValidFilterId(filterId: string): boolean {
|
function isValidFilterId(filterId?: string | number | null): boolean {
|
||||||
const isValidStr = typeof filterId === "string" &&
|
const isValidStr = typeof filterId === "string" &&
|
||||||
!!filterId &&
|
!!filterId &&
|
||||||
filterId !== "undefined" && // exclude these as we've serialized undefined in localStorage before
|
filterId !== "undefined" && // exclude these as we've serialized undefined in localStorage before
|
||||||
@ -55,13 +55,13 @@ export interface IOpts {
|
|||||||
export class MemoryStore implements IStore {
|
export class MemoryStore implements IStore {
|
||||||
private rooms: Record<string, Room> = {}; // roomId: Room
|
private rooms: Record<string, Room> = {}; // roomId: Room
|
||||||
private users: Record<string, User> = {}; // userId: User
|
private users: Record<string, User> = {}; // userId: User
|
||||||
private syncToken: string = null;
|
private syncToken: string | null = null;
|
||||||
// userId: {
|
// userId: {
|
||||||
// filterId: Filter
|
// filterId: Filter
|
||||||
// }
|
// }
|
||||||
private filters: Record<string, Record<string, Filter>> = {};
|
private filters: Record<string, Record<string, Filter>> = {};
|
||||||
public accountData: Record<string, MatrixEvent> = {}; // type : content
|
public accountData: Record<string, MatrixEvent> = {}; // type : content
|
||||||
protected readonly localStorage: Storage;
|
protected readonly localStorage?: Storage;
|
||||||
private oobMembers: Record<string, IStateEventWithRoomId[]> = {}; // roomId: [member events]
|
private oobMembers: Record<string, IStateEventWithRoomId[]> = {}; // roomId: [member events]
|
||||||
private pendingEvents: { [roomId: string]: Partial<IEvent>[] } = {};
|
private pendingEvents: { [roomId: string]: Partial<IEvent>[] } = {};
|
||||||
private clientOptions = {};
|
private clientOptions = {};
|
||||||
@ -115,7 +115,7 @@ export class MemoryStore implements IStore {
|
|||||||
* @param {RoomState} state
|
* @param {RoomState} state
|
||||||
* @param {RoomMember} member
|
* @param {RoomMember} member
|
||||||
*/
|
*/
|
||||||
private onRoomMember = (event: MatrixEvent, state: RoomState, member: RoomMember) => {
|
private onRoomMember = (event: MatrixEvent | null, state: RoomState, member: RoomMember) => {
|
||||||
if (member.membership === "invite") {
|
if (member.membership === "invite") {
|
||||||
// We do NOT add invited members because people love to typo user IDs
|
// We do NOT add invited members because people love to typo user IDs
|
||||||
// which would then show up in these lists (!)
|
// which would then show up in these lists (!)
|
||||||
@ -126,9 +126,7 @@ export class MemoryStore implements IStore {
|
|||||||
if (member.name) {
|
if (member.name) {
|
||||||
user.setDisplayName(member.name);
|
user.setDisplayName(member.name);
|
||||||
if (member.events.member) {
|
if (member.events.member) {
|
||||||
user.setRawDisplayName(
|
user.setRawDisplayName(member.events.member.getDirectionalContent().displayname);
|
||||||
member.events.member.getDirectionalContent().displayname,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (member.events.member && member.events.member.getContent().avatar_url) {
|
if (member.events.member && member.events.member.getContent().avatar_url) {
|
||||||
@ -227,9 +225,7 @@ export class MemoryStore implements IStore {
|
|||||||
* @param {Filter} filter
|
* @param {Filter} filter
|
||||||
*/
|
*/
|
||||||
public storeFilter(filter: Filter): void {
|
public storeFilter(filter: Filter): void {
|
||||||
if (!filter?.userId) {
|
if (!filter?.userId || !filter?.filterId) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.filters[filter.userId]) {
|
if (!this.filters[filter.userId]) {
|
||||||
this.filters[filter.userId] = {};
|
this.filters[filter.userId] = {};
|
||||||
}
|
}
|
||||||
@ -352,7 +348,7 @@ export class MemoryStore implements IStore {
|
|||||||
* client state to where it was at the last save, or null if there
|
* client state to where it was at the last save, or null if there
|
||||||
* is no saved sync data.
|
* is no saved sync data.
|
||||||
*/
|
*/
|
||||||
public getSavedSync(): Promise<ISavedSync> {
|
public getSavedSync(): Promise<ISavedSync | null> {
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1615,8 +1615,8 @@ export class SyncApi {
|
|||||||
// For each invited room member we want to give them a displayname/avatar url
|
// For each invited room member we want to give them a displayname/avatar url
|
||||||
// if they have one (the m.room.member invites don't contain this).
|
// if they have one (the m.room.member invites don't contain this).
|
||||||
room.getMembersWithMembership("invite").forEach(function(member) {
|
room.getMembersWithMembership("invite").forEach(function(member) {
|
||||||
if (member._requestedProfileInfo) return;
|
if (member.requestedProfileInfo) return;
|
||||||
member._requestedProfileInfo = true;
|
member.requestedProfileInfo = true;
|
||||||
// try to get a cached copy first.
|
// try to get a cached copy first.
|
||||||
const user = client.getUser(member.userId);
|
const user = client.getUser(member.userId);
|
||||||
let promise;
|
let promise;
|
||||||
|
@ -2210,7 +2210,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
this.opponentPartyId = msg.party_id || null;
|
this.opponentPartyId = msg.party_id || null;
|
||||||
}
|
}
|
||||||
this.opponentCaps = msg.capabilities || {} as CallCapabilities;
|
this.opponentCaps = msg.capabilities || {} as CallCapabilities;
|
||||||
this.opponentMember = ev.sender;
|
this.opponentMember = ev.sender!; // XXX: we should use ev.getSender() and just store the userId
|
||||||
}
|
}
|
||||||
|
|
||||||
private async addBufferedIceCandidates(): Promise<void> {
|
private async addBufferedIceCandidates(): Promise<void> {
|
||||||
|
Reference in New Issue
Block a user