From e7b91aae52c71f7df85df91cd22fd4b36fc78ab4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 09:26:22 +0000 Subject: [PATCH 01/30] Remove unused Thread.Ready emit signature --- src/models/event.ts | 2 +- src/models/room.ts | 1 - src/models/thread.ts | 5 ++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/models/event.ts b/src/models/event.ts index 9b04ae0996c..7a7a3567220 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -1559,7 +1559,7 @@ export class MatrixEvent extends EventEmitter { public setThread(thread: Thread): void { this.thread = thread; this.setThreadId(thread.id); - this.reEmitter.reEmit(thread, [ThreadEvent.Ready, ThreadEvent.Update]); + this.reEmitter.reEmit(thread, [ThreadEvent.Update]); } /** diff --git a/src/models/room.ts b/src/models/room.ts index b9f79ba8804..228e5264417 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -1413,7 +1413,6 @@ export class Room extends EventEmitter { this.threads.set(thread.id, thread); this.reEmitter.reEmit(thread, [ ThreadEvent.Update, - ThreadEvent.Ready, "Room.timeline", "Room.timelineReset", ]); diff --git a/src/models/thread.ts b/src/models/thread.ts index 9465cc6a988..4ef80128308 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -27,10 +27,9 @@ import { RoomState } from "./room-state"; export enum ThreadEvent { New = "Thread.new", - Ready = "Thread.ready", Update = "Thread.update", NewReply = "Thread.newReply", - ViewThread = "Thred.viewThread", + ViewThread = "Thread.viewThread", } interface IThreadOpts { @@ -103,7 +102,7 @@ export class Thread extends TypedEventEmitter { ?.capabilities?.[RelationType.Thread]?.enabled; } - onEcho = (event: MatrixEvent) => { + private onEcho = (event: MatrixEvent) => { if (this.timelineSet.eventIdToTimeline(event.getId())) { this.emit(ThreadEvent.Update, this); } From dc36e68fe8500a13348b6688f1b69d00461764cc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 09:27:49 +0000 Subject: [PATCH 02/30] un-type local-storage-events-emitter.ts --- src/store/local-storage-events-emitter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/store/local-storage-events-emitter.ts b/src/store/local-storage-events-emitter.ts index 18f15b59353..2320cc1ef84 100644 --- a/src/store/local-storage-events-emitter.ts +++ b/src/store/local-storage-events-emitter.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { TypedEventEmitter } from "../models/typed-event-emitter"; +import { EventEmitter } from "events"; export enum LocalStorageErrors { Global = 'Global', @@ -33,5 +33,5 @@ export enum LocalStorageErrors { * maybe you should check out your disk, as it's probably dying and your session may die with it. * See: https://github.com/vector-im/element-web/issues/18423 */ -class LocalStorageErrorsEventsEmitter extends TypedEventEmitter {} +class LocalStorageErrorsEventsEmitter extends EventEmitter {} export const localStorageErrorsEventsEmitter = new LocalStorageErrorsEventsEmitter(); From 5021cf008a2af1afcbd7ebe01cd3fbc8526b634a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 09:27:58 +0000 Subject: [PATCH 03/30] tidy re-emitter --- src/ReEmitter.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ReEmitter.ts b/src/ReEmitter.ts index 03c13dd602e..f74a4099ebb 100644 --- a/src/ReEmitter.ts +++ b/src/ReEmitter.ts @@ -19,13 +19,9 @@ limitations under the License. import { EventEmitter } from "events"; export class ReEmitter { - private target: EventEmitter; + constructor(private readonly target: EventEmitter) {} - constructor(target: EventEmitter) { - this.target = target; - } - - reEmit(source: EventEmitter, eventNames: string[]) { + public reEmit(source: EventEmitter, eventNames: string[]): void { for (const eventName of eventNames) { // We include the source as the last argument for event handlers which may need it, // such as read receipt listeners on the client class which won't have the context From 740a682fa774b9cd540c2a60c75352eb12a904d8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 10:21:51 +0000 Subject: [PATCH 04/30] Fix memory leak in the memory store --- src/store/memory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store/memory.ts b/src/store/memory.ts index 7effd9f61d2..01ca13e767e 100644 --- a/src/store/memory.ts +++ b/src/store/memory.ts @@ -185,7 +185,7 @@ export class MemoryStore implements IStore { */ public removeRoom(roomId: string): void { if (this.rooms[roomId]) { - this.rooms[roomId].removeListener("RoomState.members", this.onRoomMember); + this.rooms[roomId].currentState.removeListener("RoomState.members", this.onRoomMember); } delete this.rooms[roomId]; } From 2d729d085526ea8f7d9aaefef128ff16cf97b2bd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 10:22:56 +0000 Subject: [PATCH 05/30] Improve TypedEventEmitter and create TypedReEmitter --- spec/integ/matrix-client-retrying.spec.ts | 4 +- src/ReEmitter.ts | 20 ++++++ src/client.ts | 6 +- src/models/event-timeline-set.ts | 24 +++++-- src/models/room.ts | 87 ++++++++++++++++------- src/models/thread.ts | 32 ++++++--- src/models/typed-event-emitter.ts | 55 +++++++++++--- src/models/user.ts | 21 ++++-- 8 files changed, 187 insertions(+), 62 deletions(-) diff --git a/spec/integ/matrix-client-retrying.spec.ts b/spec/integ/matrix-client-retrying.spec.ts index d0335668f02..9979e571a30 100644 --- a/spec/integ/matrix-client-retrying.spec.ts +++ b/spec/integ/matrix-client-retrying.spec.ts @@ -1,4 +1,4 @@ -import { EventStatus } from "../../src/matrix"; +import { EventStatus, RoomEvents } from "../../src/matrix"; import { MatrixScheduler } from "../../src/scheduler"; import { Room } from "../../src/models/room"; import { TestClient } from "../TestClient"; @@ -95,7 +95,7 @@ describe("MatrixClient retrying", function() { // wait for the localecho of ev1 to be updated const p3 = new Promise((resolve, reject) => { - room.on("Room.localEchoUpdated", (ev0) => { + room.on(RoomEvents.LocalEchoUpdated, (ev0) => { if (ev0 === ev1) { resolve(); } diff --git a/src/ReEmitter.ts b/src/ReEmitter.ts index f74a4099ebb..f9e7dfa6a43 100644 --- a/src/ReEmitter.ts +++ b/src/ReEmitter.ts @@ -18,6 +18,8 @@ limitations under the License. import { EventEmitter } from "events"; +import { ListenerMap, TypedEventEmitter } from "./models/typed-event-emitter"; + export class ReEmitter { constructor(private readonly target: EventEmitter) {} @@ -44,3 +46,21 @@ export class ReEmitter { } } } + +type Common = Omit>; + +export class TypedReEmitter< + Events extends string, + Arguments extends ListenerMap, +> extends ReEmitter { + constructor(target: TypedEventEmitter) { + super(target); + } + + public reEmit & string>( + source: TypedEventEmitter, + eventNames: T[], + ): void { + super.reEmit(source, eventNames); + } +} diff --git a/src/client.ts b/src/client.ts index 64d669b3dcf..6944fb89144 100644 --- a/src/client.ts +++ b/src/client.ts @@ -67,7 +67,7 @@ import { import { DeviceInfo, IDevice } from "./crypto/deviceinfo"; import { decodeRecoveryKey } from './crypto/recoverykey'; import { keyFromAuthData } from './crypto/key_passphrase'; -import { User } from "./models/user"; +import { User, UserEvents } from "./models/user"; import { getHttpUriForMxc } from "./content-repo"; import { SearchResult } from "./models/search-result"; import { @@ -4916,7 +4916,7 @@ export class MatrixClient extends EventEmitter { const user = this.getUser(this.getUserId()); if (user) { user.displayName = name; - user.emit("User.displayName", user.events.presence, user); + user.emit(UserEvents.DisplayName, user.events.presence, user); } return prom; } @@ -4933,7 +4933,7 @@ export class MatrixClient extends EventEmitter { const user = this.getUser(this.getUserId()); if (user) { user.avatarUrl = url; - user.emit("User.avatarUrl", user.events.presence, user); + user.emit(UserEvents.AvatarUrl, user.events.presence, user); } return prom; } diff --git a/src/models/event-timeline-set.ts b/src/models/event-timeline-set.ts index 03408c08ba8..d22b746b9d1 100644 --- a/src/models/event-timeline-set.ts +++ b/src/models/event-timeline-set.ts @@ -18,8 +18,6 @@ limitations under the License. * @module models/event-timeline-set */ -import { EventEmitter } from "events"; - import { EventTimeline } from "./event-timeline"; import { EventStatus, MatrixEvent } from "./event"; import { logger } from '../logger'; @@ -28,6 +26,7 @@ import { Room } from "./room"; import { Filter } from "../filter"; import { EventType, RelationType } from "../@types/event"; import { RoomState } from "./room-state"; +import { TypedEventEmitter } from "./typed-event-emitter"; // var DEBUG = false; const DEBUG = true; @@ -57,7 +56,19 @@ export interface IRoomTimelineData { liveEvent?: boolean; } -export class EventTimelineSet extends EventEmitter { +export enum EventTimelineSetEvents { + RoomTimeline = "Room.timeline", + RoomTimelineReset = "Room.timelineReset", +} + +export type EventHandlerMap = { + [EventTimelineSetEvents.RoomTimeline]: + (event: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed: boolean, data: IRoomTimelineData) => void; + [EventTimelineSetEvents.RoomTimelineReset]: + (room: Room, eventTimelineSet: EventTimelineSet, resetAllTimelines: boolean) => void; +}; + +export class EventTimelineSet extends TypedEventEmitter { private readonly timelineSupport: boolean; private unstableClientRelationAggregation: boolean; private displayPendingEvents: boolean; @@ -247,7 +258,7 @@ export class EventTimelineSet extends EventEmitter { // Now we can swap the live timeline to the new one. this.liveTimeline = newTimeline; - this.emit("Room.timelineReset", this.room, this, resetAllTimelines); + this.emit(EventTimelineSetEvents.RoomTimelineReset, this.room, this, resetAllTimelines); } /** @@ -597,8 +608,7 @@ export class EventTimelineSet extends EventEmitter { timeline: timeline, liveEvent: !toStartOfTimeline && timeline == this.liveTimeline && !fromCache, }; - this.emit("Room.timeline", event, this.room, - Boolean(toStartOfTimeline), false, data); + this.emit(EventTimelineSetEvents.RoomTimeline, event, this.room, Boolean(toStartOfTimeline), false, data); } /** @@ -652,7 +662,7 @@ export class EventTimelineSet extends EventEmitter { const data = { timeline: timeline, }; - this.emit("Room.timeline", removed, this.room, undefined, true, data); + this.emit(EventTimelineSetEvents.RoomTimeline, removed, this.room, undefined, true, data); } return removed; } diff --git a/src/models/room.ts b/src/models/room.ts index 228e5264417..1096ba7f435 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -18,9 +18,7 @@ limitations under the License. * @module models/room */ -import { EventEmitter } from "events"; - -import { EventTimelineSet, DuplicateStrategy } from "./event-timeline-set"; +import { EventTimelineSet, DuplicateStrategy, EventTimelineSetEvents } from "./event-timeline-set"; import { Direction, EventTimeline } from "./event-timeline"; import { getHttpUriForMxc } from "../content-repo"; import * as utils from "../utils"; @@ -29,7 +27,7 @@ import { EventStatus, IEvent, MatrixEvent } from "./event"; import { RoomMember } from "./room-member"; import { IRoomSummary, RoomSummary } from "./room-summary"; import { logger } from '../logger'; -import { ReEmitter } from '../ReEmitter'; +import { TypedReEmitter } from '../ReEmitter'; import { EventType, RoomCreateTypeField, RoomType, UNSTABLE_ELEMENT_FUNCTIONAL_USERS, EVENT_VISIBILITY_CHANGE_TYPE, @@ -38,8 +36,9 @@ import { IRoomVersionsCapability, MatrixClient, PendingEventOrdering, RoomVersio import { GuestAccess, HistoryVisibility, JoinRule, ResizeMethod } from "../@types/partials"; import { Filter } from "../filter"; import { RoomState } from "./room-state"; -import { Thread, ThreadEvent } from "./thread"; +import { Thread, ThreadEvent, EventHandlerMap as ThreadHandlerMap } from "./thread"; import { Method } from "../http-api"; +import { TypedEventEmitter } from "./typed-event-emitter"; // These constants are used as sane defaults when the homeserver doesn't support // the m.room_versions capability. In practice, KNOWN_SAFE_ROOM_VERSION should be @@ -143,8 +142,45 @@ export interface ICreateFilterOpts { prepopulateTimeline?: boolean; } -export class Room extends EventEmitter { - private readonly reEmitter: ReEmitter; +export enum RoomEvents { + MyMembership = "Room.myMembership", + Tags = "Room.tags", + AccountData = "Room.accountData", + Receipt = "Room.receipt", + Name = "Room.name", + Redaction = "Room.redaction", + RedactionCancelled = "Room.redactionCancelled", + LocalEchoUpdated = "Room.localEchoUpdated", +} + +type EmittedEvents = RoomEvents + | ThreadEvent.New + | ThreadEvent.Update + | EventTimelineSetEvents.RoomTimeline + | EventTimelineSetEvents.RoomTimelineReset; + +type EventHandlerMap = { + [RoomEvents.MyMembership]: (room: Room, membership: string, prevMembership?: string) => void; + [RoomEvents.Tags]: (event: MatrixEvent, room: Room) => void; + [RoomEvents.AccountData]: (event: MatrixEvent, room: Room, lastEvent?: MatrixEvent) => void; + [RoomEvents.Receipt]: (event: MatrixEvent, room: Room) => void; + [RoomEvents.Name]: (room: Room) => void; + [RoomEvents.Redaction]: (event: MatrixEvent, room: Room) => void; + [RoomEvents.RedactionCancelled]: (event: MatrixEvent, room: Room) => void; + [RoomEvents.LocalEchoUpdated]: ( + event: MatrixEvent, + room: Room, + oldEventId?: string, + oldStatus?: EventStatus, + ) => void; + [ThreadEvent.New]: (thread: Thread) => void; +} & Pick< + ThreadHandlerMap, + EventTimelineSetEvents.RoomTimeline | EventTimelineSetEvents.RoomTimelineReset | ThreadEvent.Update +>; + +export class Room extends TypedEventEmitter { + private readonly reEmitter: TypedReEmitter; private txnToEvent: Record = {}; // Pending in-flight requests { string: MatrixEvent } // receipts should clobber based on receipt_type and user_id pairs hence // the form of this structure. This is sub-optimal for the exposed APIs @@ -287,7 +323,7 @@ export class Room extends EventEmitter { // In some cases, we add listeners for every displayed Matrix event, so it's // common to have quite a few more than the default limit. this.setMaxListeners(100); - this.reEmitter = new ReEmitter(this); + this.reEmitter = new TypedReEmitter(this); opts.pendingEventOrdering = opts.pendingEventOrdering || PendingEventOrdering.Chronological; @@ -297,7 +333,8 @@ export class Room extends EventEmitter { // the subsequent ones are the filtered ones in no particular order. this.timelineSets = [new EventTimelineSet(this, opts)]; this.reEmitter.reEmit(this.getUnfilteredTimelineSet(), [ - "Room.timeline", "Room.timelineReset", + EventTimelineSetEvents.RoomTimeline, + EventTimelineSetEvents.RoomTimelineReset, ]); this.fixUpLegacyTimelineFields(); @@ -712,7 +749,7 @@ export class Room extends EventEmitter { if (membership === "leave") { this.cleanupAfterLeaving(); } - this.emit("Room.myMembership", this, membership, prevMembership); + this.emit(RoomEvents.MyMembership, this, membership, prevMembership); } } @@ -1285,7 +1322,10 @@ export class Room extends EventEmitter { } const opts = Object.assign({ filter: filter }, this.opts); const timelineSet = new EventTimelineSet(this, opts); - this.reEmitter.reEmit(timelineSet, ["Room.timeline", "Room.timelineReset"]); + this.reEmitter.reEmit(timelineSet, [ + EventTimelineSetEvents.RoomTimeline, + EventTimelineSetEvents.RoomTimelineReset, + ]); this.filteredTimelineSets[filter.filterId] = timelineSet; this.timelineSets.push(timelineSet); @@ -1413,8 +1453,8 @@ export class Room extends EventEmitter { this.threads.set(thread.id, thread); this.reEmitter.reEmit(thread, [ ThreadEvent.Update, - "Room.timeline", - "Room.timelineReset", + EventTimelineSetEvents.RoomTimeline, + EventTimelineSetEvents.RoomTimelineReset, ]); if (!this.lastThread || this.lastThread.rootEvent.localTimestamp < rootEvent.localTimestamp) { @@ -1456,7 +1496,7 @@ export class Room extends EventEmitter { } } - this.emit("Room.redaction", event, this); + this.emit(RoomEvents.Redaction, event, this); // TODO: we stash user displaynames (among other things) in // RoomMember objects which are then attached to other events @@ -1578,7 +1618,7 @@ export class Room extends EventEmitter { } if (redactedEvent) { redactedEvent.markLocallyRedacted(event); - this.emit("Room.redaction", event, this); + this.emit(RoomEvents.Redaction, event, this); } } } else { @@ -1596,7 +1636,7 @@ export class Room extends EventEmitter { } } - this.emit("Room.localEchoUpdated", event, this, null, null); + this.emit(RoomEvents.LocalEchoUpdated, event, this, null, null); } /** @@ -1724,8 +1764,7 @@ export class Room extends EventEmitter { } } - this.emit("Room.localEchoUpdated", localEvent, this, - oldEventId, oldStatus); + this.emit(RoomEvents.LocalEchoUpdated, localEvent, this, oldEventId, oldStatus); } /** @@ -1809,7 +1848,7 @@ export class Room extends EventEmitter { } this.savePendingEvents(); - this.emit("Room.localEchoUpdated", event, this, oldEventId, oldStatus); + this.emit(RoomEvents.LocalEchoUpdated, event, this, oldEventId, oldStatus); } private revertRedactionLocalEcho(redactionEvent: MatrixEvent): void { @@ -1822,7 +1861,7 @@ export class Room extends EventEmitter { if (redactedEvent) { redactedEvent.unmarkLocallyRedacted(); // re-render after undoing redaction - this.emit("Room.redactionCancelled", redactionEvent, this); + this.emit(RoomEvents.RedactionCancelled, redactionEvent, this); // reapply relation now redaction failed if (redactedEvent.isRelation()) { this.aggregateNonLiveRelation(redactedEvent); @@ -1962,7 +2001,7 @@ export class Room extends EventEmitter { }); if (oldName !== this.name) { - this.emit("Room.name", this); + this.emit(RoomEvents.Name, this); } } @@ -2055,7 +2094,7 @@ export class Room extends EventEmitter { this.addReceiptsToStructure(event, synthetic); // send events after we've regenerated the structure & cache, otherwise things that // listened for the event would read stale data. - this.emit("Room.receipt", event, this); + this.emit(RoomEvents.Receipt, event, this); } /** @@ -2189,7 +2228,7 @@ export class Room extends EventEmitter { // XXX: we could do a deep-comparison to see if the tags have really // changed - but do we want to bother? - this.emit("Room.tags", event, this); + this.emit(RoomEvents.Tags, event, this); } /** @@ -2204,7 +2243,7 @@ export class Room extends EventEmitter { } const lastEvent = this.accountData[event.getType()]; this.accountData[event.getType()] = event; - this.emit("Room.accountData", event, this, lastEvent); + this.emit(RoomEvents.AccountData, event, this, lastEvent); } } diff --git a/src/models/thread.ts b/src/models/thread.ts index 4ef80128308..e0a82b32fda 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -14,13 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { MatrixClient } from "../matrix"; -import { ReEmitter } from "../ReEmitter"; +import { EventTimelineSetEvents, MatrixClient, RoomEvents } from "../matrix"; +import { TypedReEmitter } from "../ReEmitter"; import { RelationType } from "../@types/event"; import { IRelationsRequestOpts } from "../@types/requests"; -import { MatrixEvent, IThreadBundledRelationship } from "./event"; +import { IThreadBundledRelationship, MatrixEvent } from "./event"; import { Direction, EventTimeline } from "./event-timeline"; -import { EventTimelineSet } from './event-timeline-set'; +import { EventTimelineSet, EventHandlerMap as EventTimelineSetHandlerMap } from './event-timeline-set'; import { Room } from './room'; import { TypedEventEmitter } from "./typed-event-emitter"; import { RoomState } from "./room-state"; @@ -32,6 +32,16 @@ export enum ThreadEvent { ViewThread = "Thread.viewThread", } +type EmittedEvents = Exclude + | EventTimelineSetEvents.RoomTimeline + | EventTimelineSetEvents.RoomTimelineReset; + +export type EventHandlerMap = { + [ThreadEvent.Update]: (thread: Thread) => void; + [ThreadEvent.NewReply]: (thread: Thread, event: MatrixEvent) => void; + [ThreadEvent.ViewThread]: () => void; +} & Pick; + interface IThreadOpts { initialEvents?: MatrixEvent[]; room: Room; @@ -41,7 +51,7 @@ interface IThreadOpts { /** * @experimental */ -export class Thread extends TypedEventEmitter { +export class Thread extends TypedEventEmitter { /** * A reference to all the events ID at the bottom of the threads */ @@ -49,7 +59,7 @@ export class Thread extends TypedEventEmitter { private _currentUserParticipated = false; - private reEmitter: ReEmitter; + private reEmitter: TypedReEmitter; private lastEvent: MatrixEvent; private replyCount = 0; @@ -74,11 +84,11 @@ export class Thread extends TypedEventEmitter { timelineSupport: true, pendingEvents: true, }); - this.reEmitter = new ReEmitter(this); + this.reEmitter = new TypedReEmitter(this); this.reEmitter.reEmit(this.timelineSet, [ - "Room.timeline", - "Room.timelineReset", + EventTimelineSetEvents.RoomTimeline, + EventTimelineSetEvents.RoomTimelineReset, ]); // If we weren't able to find the root event, it's probably missing @@ -93,8 +103,8 @@ export class Thread extends TypedEventEmitter { opts?.initialEvents?.forEach(event => this.addEvent(event)); - this.room.on("Room.localEchoUpdated", this.onEcho); - this.room.on("Room.timeline", this.onEcho); + this.room.on(RoomEvents.LocalEchoUpdated, this.onEcho); + this.room.on(EventTimelineSetEvents.RoomTimeline, this.onEcho); } public get hasServerSideSupport(): boolean { diff --git a/src/models/typed-event-emitter.ts b/src/models/typed-event-emitter.ts index 5bbe750bace..14c7b822341 100644 --- a/src/models/typed-event-emitter.ts +++ b/src/models/typed-event-emitter.ts @@ -21,6 +21,16 @@ enum EventEmitterEvents { RemoveListener = "removeListener", } +type AnyListener = (...args: any) => any; +export type ListenerMap = { [eventName in E]: AnyListener }; +type EventEmitterEventListener = (eventName: string, listener: AnyListener) => void; + +type Listener< + E extends string, + A extends ListenerMap, + T extends E | EventEmitterEvents, +> = T extends E ? A[T] : EventEmitterEventListener; + /** * Typed Event Emitter class which can act as a Base Model for all our model * and communication events. @@ -28,17 +38,24 @@ enum EventEmitterEvents { * to properly type this, so that our events are not stringly-based and prone * to silly typos. */ -export abstract class TypedEventEmitter extends EventEmitter { - public addListener(event: Events | EventEmitterEvents, listener: (...args: any[]) => void): this { +export abstract class TypedEventEmitter< + Events extends string, + Arguments extends ListenerMap, +> extends EventEmitter { + public addListener( + event: T, + listener: Listener, + ): this { return super.addListener(event, listener); } - public emit(event: Events | EventEmitterEvents, ...args: any[]): boolean { + public emit(event: T, ...args: Parameters): boolean { + // @ts-expect-error TS2488 return super.emit(event, ...args); } public eventNames(): (Events | EventEmitterEvents)[] { - return super.eventNames() as Events[]; + return super.eventNames() as Array; } public listenerCount(event: Events | EventEmitterEvents): number { @@ -49,23 +66,38 @@ export abstract class TypedEventEmitter extends EventEmit return super.listeners(event); } - public off(event: Events | EventEmitterEvents, listener: (...args: any[]) => void): this { + public off( + event: T, + listener: Listener, + ): this { return super.off(event, listener); } - public on(event: Events | EventEmitterEvents, listener: (...args: any[]) => void): this { + public on( + event: T, + listener: Listener, + ): this { return super.on(event, listener); } - public once(event: Events | EventEmitterEvents, listener: (...args: any[]) => void): this { + public once( + event: T, + listener: Listener, + ): this { return super.once(event, listener); } - public prependListener(event: Events | EventEmitterEvents, listener: (...args: any[]) => void): this { + public prependListener( + event: T, + listener: Listener, + ): this { return super.prependListener(event, listener); } - public prependOnceListener(event: Events | EventEmitterEvents, listener: (...args: any[]) => void): this { + public prependOnceListener( + event: T, + listener: Listener, + ): this { return super.prependOnceListener(event, listener); } @@ -73,7 +105,10 @@ export abstract class TypedEventEmitter extends EventEmit return super.removeAllListeners(event); } - public removeListener(event: Events | EventEmitterEvents, listener: (...args: any[]) => void): this { + public removeListener( + event: T, + listener: Listener, + ): this { return super.removeListener(event, listener); } diff --git a/src/models/user.ts b/src/models/user.ts index 613a03a69ea..e39ea47893e 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -18,12 +18,23 @@ limitations under the License. * @module models/user */ -import { EventEmitter } from "events"; - import { MatrixEvent } from "./event"; +import { TypedEventEmitter } from "./typed-event-emitter"; -export class User extends EventEmitter { - // eslint-disable-next-line camelcase +export enum UserEvents { + DisplayName = "User.displayName", + AvatarUrl = "User.avatarUrl", + /* @deprecated */ + _UnstableStatusMessage = "User.unstable_statusMessage", +} + +type EventHandlerMap = { + [UserEvents.DisplayName]: (event: MatrixEvent | undefined, user: User) => void; + [UserEvents.AvatarUrl]: (event: MatrixEvent | undefined, user: User) => void; + [UserEvents._UnstableStatusMessage]: (user: User) => void; +}; + +export class User extends TypedEventEmitter { private modified: number; // XXX these should be read-only @@ -213,7 +224,7 @@ export class User extends EventEmitter { if (!event.getContent()) this.unstable_statusMessage = ""; else this.unstable_statusMessage = event.getContent()["status"]; this.updateModifiedTime(); - this.emit("User.unstable_statusMessage", this); + this.emit(UserEvents._UnstableStatusMessage, this); } } From c2be05cb4b47c8aad4196a2a889f2becf39e9668 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 10:39:41 +0000 Subject: [PATCH 06/30] Type the RoomState & User EventEmitters --- src/models/room-state.ts | 30 ++++++++++++++++++++---------- src/models/user.ts | 18 ++++++++++++------ src/store/memory.ts | 6 +++--- src/sync.ts | 13 ++++++++----- 4 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/models/room-state.ts b/src/models/room-state.ts index e1fa9827093..1d56334bc34 100644 --- a/src/models/room-state.ts +++ b/src/models/room-state.ts @@ -18,8 +18,6 @@ limitations under the License. * @module models/room-state */ -import { EventEmitter } from "events"; - import { RoomMember } from "./room-member"; import { logger } from '../logger'; import * as utils from "../utils"; @@ -27,6 +25,7 @@ import { EventType } from "../@types/event"; import { MatrixEvent } from "./event"; import { MatrixClient } from "../client"; import { GuestAccess, HistoryVisibility, IJoinRuleEventContent, JoinRule } from "../@types/partials"; +import { TypedEventEmitter } from "./typed-event-emitter"; // possible statuses for out-of-band member loading enum OobStatus { @@ -35,7 +34,19 @@ enum OobStatus { Finished, } -export class RoomState extends EventEmitter { +export enum RoomStateEvents { + Events = "RoomState.events", + Members = "RoomState.members", + NewMember = "RoomState.newMember", +} + +type EventHandlerMap = { + [RoomStateEvents.Events]: (event: MatrixEvent, state: RoomState, lastStateEvent: MatrixEvent | null) => void; + [RoomStateEvents.Members]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void; + [RoomStateEvents.NewMember]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void; +}; + +export class RoomState extends TypedEventEmitter { private sentinels: Record = {}; // userId: RoomMember // stores fuzzy matches to a list of userIDs (applies utils.removeHiddenChars to keys) private displayNameToUserIds: Record = {}; @@ -307,7 +318,7 @@ export class RoomState extends EventEmitter { this.updateDisplayNameCache(event.getStateKey(), event.getContent().displayname); this.updateThirdPartyTokenCache(event); } - this.emit("RoomState.events", event, this, lastStateEvent); + this.emit(RoomStateEvents.Events, event, this, lastStateEvent); }); // update higher level data structures. This needs to be done AFTER the @@ -342,7 +353,7 @@ export class RoomState extends EventEmitter { member.setMembershipEvent(event, this); this.updateMember(member); - this.emit("RoomState.members", event, this, member); + this.emit(RoomStateEvents.Members, event, this, member); } else if (event.getType() === EventType.RoomPowerLevels) { // events with unknown state keys should be ignored // and should not aggregate onto members power levels @@ -357,7 +368,7 @@ export class RoomState extends EventEmitter { const oldLastModified = member.getLastModifiedTime(); member.setPowerLevelEvent(event); if (oldLastModified !== member.getLastModifiedTime()) { - this.emit("RoomState.members", event, this, member); + this.emit(RoomStateEvents.Members, event, this, member); } }); @@ -384,7 +395,7 @@ export class RoomState extends EventEmitter { // add member to members before emitting any events, // as event handlers often lookup the member this.members[userId] = member; - this.emit("RoomState.newMember", event, this, member); + this.emit(RoomStateEvents.NewMember, event, this, member); } return member; } @@ -397,8 +408,7 @@ export class RoomState extends EventEmitter { } private getStateEventMatching(event: MatrixEvent): MatrixEvent | null { - if (!this.events.has(event.getType())) return null; - return this.events.get(event.getType()).get(event.getStateKey()); + return this.events.get(event.getType())?.get(event.getStateKey()) ?? null; } private updateMember(member: RoomMember): void { @@ -503,7 +513,7 @@ export class RoomState extends EventEmitter { this.setStateEvent(stateEvent); this.updateMember(member); - this.emit("RoomState.members", stateEvent, this, member); + this.emit(RoomStateEvents.Members, stateEvent, this, member); } /** diff --git a/src/models/user.ts b/src/models/user.ts index e39ea47893e..934e252cd28 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -24,6 +24,9 @@ import { TypedEventEmitter } from "./typed-event-emitter"; export enum UserEvents { DisplayName = "User.displayName", AvatarUrl = "User.avatarUrl", + Presence = "User.presence", + CurrentlyActive = "User.currentlyActive", + LastPresenceTs = "User.lastPresenceTs", /* @deprecated */ _UnstableStatusMessage = "User.unstable_statusMessage", } @@ -31,6 +34,9 @@ export enum UserEvents { type EventHandlerMap = { [UserEvents.DisplayName]: (event: MatrixEvent | undefined, user: User) => void; [UserEvents.AvatarUrl]: (event: MatrixEvent | undefined, user: User) => void; + [UserEvents.Presence]: (event: MatrixEvent | undefined, user: User) => void; + [UserEvents.CurrentlyActive]: (event: MatrixEvent | undefined, user: User) => void; + [UserEvents.LastPresenceTs]: (event: MatrixEvent | undefined, user: User) => void; [UserEvents._UnstableStatusMessage]: (user: User) => void; }; @@ -105,25 +111,25 @@ export class User extends TypedEventEmitter { const firstFire = this.events.presence === null; this.events.presence = event; - const eventsToFire = []; + const eventsToFire: UserEvents[] = []; if (event.getContent().presence !== this.presence || firstFire) { - eventsToFire.push("User.presence"); + eventsToFire.push(UserEvents.Presence); } if (event.getContent().avatar_url && event.getContent().avatar_url !== this.avatarUrl) { - eventsToFire.push("User.avatarUrl"); + eventsToFire.push(UserEvents.AvatarUrl); } if (event.getContent().displayname && event.getContent().displayname !== this.displayName) { - eventsToFire.push("User.displayName"); + eventsToFire.push(UserEvents.DisplayName); } if (event.getContent().currently_active !== undefined && event.getContent().currently_active !== this.currentlyActive) { - eventsToFire.push("User.currentlyActive"); + eventsToFire.push(UserEvents.CurrentlyActive); } this.presence = event.getContent().presence; - eventsToFire.push("User.lastPresenceTs"); + eventsToFire.push(UserEvents.LastPresenceTs); if (event.getContent().status_msg) { this.presenceStatusMsg = event.getContent().status_msg; diff --git a/src/store/memory.ts b/src/store/memory.ts index 01ca13e767e..c3ae49bbde7 100644 --- a/src/store/memory.ts +++ b/src/store/memory.ts @@ -24,7 +24,7 @@ import { Group } from "../models/group"; import { Room } from "../models/room"; import { User } from "../models/user"; import { IEvent, MatrixEvent } from "../models/event"; -import { RoomState } from "../models/room-state"; +import { RoomState, RoomStateEvents } from "../models/room-state"; import { RoomMember } from "../models/room-member"; import { Filter } from "../filter"; import { ISavedSync, IStore } from "./index"; @@ -126,7 +126,7 @@ export class MemoryStore implements IStore { this.rooms[room.roomId] = room; // add listeners for room member changes so we can keep the room member // map up-to-date. - room.currentState.on("RoomState.members", this.onRoomMember); + room.currentState.on(RoomStateEvents.Members, this.onRoomMember); // add existing members room.currentState.getMembers().forEach((m) => { this.onRoomMember(null, room.currentState, m); @@ -185,7 +185,7 @@ export class MemoryStore implements IStore { */ public removeRoom(roomId: string): void { if (this.rooms[roomId]) { - this.rooms[roomId].currentState.removeListener("RoomState.members", this.onRoomMember); + this.rooms[roomId].currentState.removeListener(RoomStateEvents.Members, this.onRoomMember); } delete this.rooms[roomId]; } diff --git a/src/sync.ts b/src/sync.ts index c0da84c44d4..3a7e1d55160 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -53,6 +53,7 @@ import { MatrixError, Method } from "./http-api"; import { ISavedSync } from "./store"; import { EventType } from "./@types/event"; import { IPushRules } from "./@types/PushRules"; +import { RoomStateEvents } from "./models/room-state"; const DEBUG = true; @@ -231,12 +232,14 @@ export class SyncApi { client.reEmitter.reEmit(room.currentState, [ "RoomState.events", "RoomState.members", "RoomState.newMember", ]); - room.currentState.on("RoomState.newMember", function(event, state, member) { + room.currentState.on(RoomStateEvents.NewMember, function(event, state, member) { member.user = client.getUser(member.userId); client.reEmitter.reEmit( member, [ - "RoomMember.name", "RoomMember.typing", "RoomMember.powerLevel", + "RoomMember.name", + "RoomMember.typing", + "RoomMember.powerLevel", "RoomMember.membership", ], ); @@ -249,9 +252,9 @@ export class SyncApi { */ private deregisterStateListeners(room: Room): void { // could do with a better way of achieving this. - room.currentState.removeAllListeners("RoomState.events"); - room.currentState.removeAllListeners("RoomState.members"); - room.currentState.removeAllListeners("RoomState.newMember"); + room.currentState.removeAllListeners(RoomStateEvents.Events); + room.currentState.removeAllListeners(RoomStateEvents.Members); + room.currentState.removeAllListeners(RoomStateEvents.NewMember); } /** From 3ddc9965436c744e173286031007dcc2259b5a5b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 11:01:33 +0000 Subject: [PATCH 07/30] Type the RoomMember EventEmitter --- src/models/room-member.ts | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/models/room-member.ts b/src/models/room-member.ts index fab65ba8809..5fe62a1ba3d 100644 --- a/src/models/room-member.ts +++ b/src/models/room-member.ts @@ -18,16 +18,30 @@ limitations under the License. * @module models/room-member */ -import { EventEmitter } from "events"; - import { getHttpUriForMxc } from "../content-repo"; import * as utils from "../utils"; import { User } from "./user"; import { MatrixEvent } from "./event"; import { RoomState } from "./room-state"; import { logger } from "../logger"; +import { TypedEventEmitter } from "./typed-event-emitter"; +import { EventType } from "../@types/event"; + +export enum RoomMemberEvents { + Membership = "RoomMember.membership", + Name = "RoomMember.name", + PowerLevel = "RoomMember.powerLevel", + Typing = "RoomMember.typing", +} + +type EventHandlerMap = { + [RoomMemberEvents.Membership]: (event: MatrixEvent, member: RoomMember, oldMembership: string | null) => void; + [RoomMemberEvents.Name]: (event: MatrixEvent, member: RoomMember, oldName: string | null) => void; + [RoomMemberEvents.PowerLevel]: (event: MatrixEvent, member: RoomMember) => void; + [RoomMemberEvents.Typing]: (event: MatrixEvent, member: RoomMember) => void; +}; -export class RoomMember extends EventEmitter { +export class RoomMember extends TypedEventEmitter { private _isOutOfBand = false; private _modified: number; public _requestedProfileInfo: boolean; // used by sync.ts @@ -107,7 +121,7 @@ export class RoomMember extends EventEmitter { public setMembershipEvent(event: MatrixEvent, roomState?: RoomState): void { const displayName = event.getDirectionalContent().displayname; - if (event.getType() !== "m.room.member") { + if (event.getType() !== EventType.RoomMember) { return; } @@ -150,11 +164,11 @@ export class RoomMember extends EventEmitter { if (oldMembership !== this.membership) { this.updateModifiedTime(); - this.emit("RoomMember.membership", event, this, oldMembership); + this.emit(RoomMemberEvents.Membership, event, this, oldMembership); } if (oldName !== this.name) { this.updateModifiedTime(); - this.emit("RoomMember.name", event, this, oldName); + this.emit(RoomMemberEvents.Name, event, this, oldName); } } @@ -196,7 +210,7 @@ export class RoomMember extends EventEmitter { // redraw everyone's level if the max has changed) if (oldPowerLevel !== this.powerLevel || oldPowerLevelNorm !== this.powerLevelNorm) { this.updateModifiedTime(); - this.emit("RoomMember.powerLevel", powerLevelEvent, this); + this.emit(RoomMemberEvents.PowerLevel, powerLevelEvent, this); } } @@ -222,7 +236,7 @@ export class RoomMember extends EventEmitter { } if (oldTyping !== this.typing) { this.updateModifiedTime(); - this.emit("RoomMember.typing", event, this); + this.emit(RoomMemberEvents.Typing, event, this); } } From 47a33e8d40ff6afc8e79757405bc5ed5649c9886 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 11:09:03 +0000 Subject: [PATCH 08/30] Type the MatrixCall EventEmitter --- src/models/typed-event-emitter.ts | 8 ++++++-- src/webrtc/call.ts | 24 +++++++++++++++++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/models/typed-event-emitter.ts b/src/models/typed-event-emitter.ts index 14c7b822341..6aed7014ea6 100644 --- a/src/models/typed-event-emitter.ts +++ b/src/models/typed-event-emitter.ts @@ -16,20 +16,24 @@ limitations under the License. import { EventEmitter } from "events"; -enum EventEmitterEvents { +export enum EventEmitterEvents { NewListener = "newListener", RemoveListener = "removeListener", + Error = "error", } type AnyListener = (...args: any) => any; export type ListenerMap = { [eventName in E]: AnyListener }; type EventEmitterEventListener = (eventName: string, listener: AnyListener) => void; +type EventEmitterErrorListener = (error: Error) => void; type Listener< E extends string, A extends ListenerMap, T extends E | EventEmitterEvents, -> = T extends E ? A[T] : EventEmitterEventListener; +> = T extends E ? A[T] + : T extends EventEmitterEvents ? EventEmitterErrorListener + : EventEmitterEventListener; /** * Typed Event Emitter class which can act as a Base Model for all our model diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index da4dc8a4b3e..9f43c9baefa 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -21,8 +21,6 @@ limitations under the License. * @module webrtc/call */ -import { EventEmitter } from 'events'; - import { logger } from '../logger'; import * as utils from '../utils'; import { MatrixEvent } from '../models/event'; @@ -47,6 +45,7 @@ import { import { CallFeed } from './callFeed'; import { MatrixClient } from "../client"; import { ISendEventResponse } from "../@types/requests"; +import { EventEmitterEvents, TypedEventEmitter } from "../models/typed-event-emitter"; // events: hangup, error(err), replaced(call), state(state, oldState) @@ -241,6 +240,21 @@ function genCallID(): string { return Date.now().toString() + randomString(16); } +type EventHandlerMap = { + [CallEvent.DataChannel]: (channel: RTCDataChannel) => void; + [CallEvent.FeedsChanged]: (feeds: CallFeed[]) => void; + [CallEvent.Replaced]: (newCall: MatrixCall) => void; + [CallEvent.Error]: (error: CallError) => void; + [CallEvent.RemoteHoldUnhold]: (onHold: boolean) => void; + [CallEvent.LocalHoldUnhold]: (onHold: boolean) => void; + [CallEvent.LengthChanged]: (length: number) => void; + [CallEvent.State]: (state: CallState, oldState?: CallState) => void; + [CallEvent.Hangup]: () => void; + [CallEvent.AssertedIdentityChanged]: () => void; + /* @deprecated */ + [CallEvent.HoldUnhold]: (onHold: boolean) => void; +}; + /** * Construct a new Matrix Call. * @constructor @@ -252,7 +266,7 @@ function genCallID(): string { * @param {Array} opts.turnServers Optional. A list of TURN servers. * @param {MatrixClient} opts.client The Matrix Client instance to send events to. */ -export class MatrixCall extends EventEmitter { +export class MatrixCall extends TypedEventEmitter { public roomId: string; public callId: string; public state = CallState.Fledgling; @@ -1973,7 +1987,7 @@ export class MatrixCall extends EventEmitter { this.peerConn.close(); } if (shouldEmit) { - this.emit(CallEvent.Hangup, this); + this.emit(CallEvent.Hangup); } } @@ -1995,7 +2009,7 @@ export class MatrixCall extends EventEmitter { } private checkForErrorListener(): void { - if (this.listeners("error").length === 0) { + if (this.listeners(EventEmitterEvents.Error).length === 0) { throw new Error( "You MUST attach an error listener using call.on('error', function() {})", ); From 0f35e8133040944fcdcc01f82aadaeb85a5973dd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 11:44:11 +0000 Subject: [PATCH 09/30] Type the MatrixEvent EventEmitter --- spec/unit/relations.spec.ts | 6 ++-- src/ReEmitter.ts | 6 ++-- src/client.ts | 6 ++-- src/crypto/index.ts | 32 ++++++++++-------- src/models/event-timeline-set.ts | 4 +-- src/models/event.ts | 57 +++++++++++++++++++++----------- src/models/relations.ts | 14 ++++---- src/webrtc/callEventHandler.ts | 6 ++-- 8 files changed, 76 insertions(+), 55 deletions(-) diff --git a/spec/unit/relations.spec.ts b/spec/unit/relations.spec.ts index 27370fba0e5..20e51b63a36 100644 --- a/spec/unit/relations.spec.ts +++ b/spec/unit/relations.spec.ts @@ -15,7 +15,7 @@ limitations under the License. */ import { EventTimelineSet } from "../../src/models/event-timeline-set"; -import { MatrixEvent } from "../../src/models/event"; +import { MatrixEvent, MatrixEventEvents } from "../../src/models/event"; import { Room } from "../../src/models/room"; import { Relations } from "../../src/models/relations"; @@ -103,7 +103,7 @@ describe("Relations", function() { // Add the target event first, then the relation event { const relationsCreated = new Promise(resolve => { - targetEvent.once("Event.relationsCreated", resolve); + targetEvent.once(MatrixEventEvents.RelationsCreated, resolve); }); const timelineSet = new EventTimelineSet(room, { @@ -118,7 +118,7 @@ describe("Relations", function() { // Add the relation event first, then the target event { const relationsCreated = new Promise(resolve => { - targetEvent.once("Event.relationsCreated", resolve); + targetEvent.once(MatrixEventEvents.RelationsCreated, resolve); }); const timelineSet = new EventTimelineSet(room, { diff --git a/src/ReEmitter.ts b/src/ReEmitter.ts index f9e7dfa6a43..330eb730347 100644 --- a/src/ReEmitter.ts +++ b/src/ReEmitter.ts @@ -47,8 +47,6 @@ export class ReEmitter { } } -type Common = Omit>; - export class TypedReEmitter< Events extends string, Arguments extends ListenerMap, @@ -57,8 +55,8 @@ export class TypedReEmitter< super(target); } - public reEmit & string>( - source: TypedEventEmitter, + public reEmit( + source: TypedEventEmitter, eventNames: T[], ): void { super.reEmit(source, eventNames); diff --git a/src/client.ts b/src/client.ts index c00562a659c..18fa865aa0d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -23,7 +23,7 @@ import { EventEmitter } from "events"; import { EmoteEvent, IPartialEvent, MessageEvent, NoticeEvent } from "matrix-events-sdk"; import { ISyncStateData, SyncApi, SyncState } from "./sync"; -import { EventStatus, IContent, IDecryptOptions, IEvent, MatrixEvent } from "./models/event"; +import { EventStatus, IContent, IDecryptOptions, IEvent, MatrixEvent, MatrixEventEvents } from "./models/event"; import { StubStore } from "./store/stub"; import { createNewMatrixCall, MatrixCall } from "./webrtc/call"; import { Filter, IFilterDefinition } from "./filter"; @@ -3660,7 +3660,7 @@ export class MatrixClient extends EventEmitter { const targetId = localEvent.getAssociatedId(); if (targetId && targetId.startsWith("~")) { const target = room.getPendingEvents().find(e => e.getId() === targetId); - target.once("Event.localEventIdReplaced", () => { + target.once(MatrixEventEvents.LocalEventIdReplaced, () => { localEvent.updateAssociatedId(target.getId()); }); } @@ -6510,7 +6510,7 @@ export class MatrixClient extends EventEmitter { const allEvents = originalEvent ? events.concat(originalEvent) : events; await Promise.all(allEvents.map(e => { if (e.isEncrypted()) { - return new Promise(resolve => e.once("Event.decrypted", resolve)); + return new Promise(resolve => e.once(MatrixEventEvents.Decrypted, resolve)); } })); events = events.filter(e => e.getType() === eventType); diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 03650d069ad..c1482c5e6e9 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -34,13 +34,20 @@ import * as algorithms from "./algorithms"; import { createCryptoStoreCacheCallbacks, CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from './CrossSigning'; import { EncryptionSetupBuilder } from "./EncryptionSetup"; import { + ISecretRequest, SECRET_STORAGE_ALGORITHM_V1_AES, SecretStorage, - SecretStorageKeyTuple, - ISecretRequest, SecretStorageKeyObject, + SecretStorageKeyTuple, } from './SecretStorage'; -import { IAddSecretStorageKeyOpts, ICreateSecretStorageOpts, IImportRoomKeysOpts, ISecretStorageKeyInfo } from "./api"; +import { + IAddSecretStorageKeyOpts, + ICreateSecretStorageOpts, + IEncryptedEventInfo, + IImportRoomKeysOpts, + IRecoveryKey, + ISecretStorageKeyInfo +} from "./api"; import { OutgoingRoomKeyRequestManager } from './OutgoingRoomKeyRequestManager'; import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store'; import { ReciprocateQRCode, SCAN_QR_CODE_METHOD, SHOW_QR_CODE_METHOD } from './verification/QRCode'; @@ -52,17 +59,16 @@ import { InRoomChannel, InRoomRequests } from "./verification/request/InRoomChan import { ToDeviceChannel, ToDeviceRequests } from "./verification/request/ToDeviceChannel"; import { IllegalMethod } from "./verification/IllegalMethod"; import { KeySignatureUploadError } from "../errors"; -import { decryptAES, encryptAES, calculateKeyCheck } from './aes'; +import { calculateKeyCheck, decryptAES, encryptAES } from './aes'; import { DehydrationManager, IDeviceKeys, IOneTimeKey } from './dehydration'; import { BackupManager } from "./backup"; import { IStore } from "../store"; import { Room } from "../models/room"; import { RoomMember } from "../models/room-member"; -import { MatrixEvent, EventStatus, IClearEvent, IEvent } from "../models/event"; -import { MatrixClient, IKeysUploadResponse, SessionStore, ISignedKey, ICrossSigningKey } from "../client"; -import type { EncryptionAlgorithm, DecryptionAlgorithm } from "./algorithms/base"; +import { EventStatus, IClearEvent, IEvent, MatrixEvent, MatrixEventEvents } from "../models/event"; +import { ICrossSigningKey, IKeysUploadResponse, ISignedKey, MatrixClient, SessionStore } from "../client"; +import type { DecryptionAlgorithm, EncryptionAlgorithm } from "./algorithms/base"; import type { IRoomEncryption, RoomList } from "./RoomList"; -import { IRecoveryKey, IEncryptedEventInfo } from "./api"; import { IKeyBackupInfo } from "./keybackup"; import { ISyncStateData } from "../sync"; import { CryptoStore } from "./store/base"; @@ -3070,7 +3076,7 @@ export class Crypto extends EventEmitter { event.attemptDecryption(this); } // once the event has been decrypted, try again - event.once('Event.decrypted', (ev) => { + event.once(MatrixEventEvents.Decrypted, (ev) => { this.onToDeviceEvent(ev); }); } @@ -3219,15 +3225,15 @@ export class Crypto extends EventEmitter { reject(new Error("Event status set to CANCELLED.")); } }; - event.once("Event.localEventIdReplaced", eventIdListener); - event.on("Event.status", statusListener); + event.once(MatrixEventEvents.LocalEventIdReplaced, eventIdListener); + event.on(MatrixEventEvents.Status, statusListener); }); } catch (err) { logger.error("error while waiting for the verification event to be sent: " + err.message); return; } finally { - event.removeListener("Event.localEventIdReplaced", eventIdListener); - event.removeListener("Event.status", statusListener); + event.removeListener(MatrixEventEvents.LocalEventIdReplaced, eventIdListener); + event.removeListener(MatrixEventEvents.Status, statusListener); } } let request = requestsMap.getRequest(event); diff --git a/src/models/event-timeline-set.ts b/src/models/event-timeline-set.ts index d22b746b9d1..b8ae0a063d4 100644 --- a/src/models/event-timeline-set.ts +++ b/src/models/event-timeline-set.ts @@ -19,7 +19,7 @@ limitations under the License. */ import { EventTimeline } from "./event-timeline"; -import { EventStatus, MatrixEvent } from "./event"; +import { EventStatus, MatrixEvent, MatrixEventEvents } from "./event"; import { logger } from '../logger'; import { Relations } from './relations'; import { Room } from "./room"; @@ -829,7 +829,7 @@ export class EventTimelineSet extends TypedEventEmitter { + event.once(MatrixEventEvents.Decrypted, () => { this.aggregateRelations(event); }); return; diff --git a/src/models/event.ts b/src/models/event.ts index 7a7a3567220..1402f214933 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -20,24 +20,19 @@ limitations under the License. * @module models/event */ -import { EventEmitter } from 'events'; import { ExtensibleEvent, ExtensibleEvents, Optional } from "matrix-events-sdk"; import { logger } from '../logger'; import { VerificationRequest } from "../crypto/verification/request/VerificationRequest"; -import { - EventType, - MsgType, - RelationType, - EVENT_VISIBILITY_CHANGE_TYPE, -} from "../@types/event"; +import { EVENT_VISIBILITY_CHANGE_TYPE, EventType, MsgType, RelationType } from "../@types/event"; import { Crypto, IEventDecryptionResult } from "../crypto"; import { deepSortedObjectEntries } from "../utils"; import { RoomMember } from "./room-member"; -import { Thread, ThreadEvent } from "./thread"; +import { Thread, ThreadEvent, EventHandlerMap as ThreadEventHandlerMap } from "./thread"; import { IActionsObject } from '../pushprocessor'; -import { ReEmitter } from '../ReEmitter'; +import { TypedReEmitter } from '../ReEmitter'; import { MatrixError } from "../http-api"; +import { TypedEventEmitter } from "./typed-event-emitter"; /** * Enum for event statuses. @@ -209,7 +204,29 @@ export interface IMessageVisibilityHidden { // A singleton implementing `IMessageVisibilityVisible`. const MESSAGE_VISIBLE: IMessageVisibilityVisible = Object.freeze({ visible: true }); -export class MatrixEvent extends EventEmitter { +export enum MatrixEventEvents { + Decrypted = "Event.decrypted", + BeforeRedaction = "Event.beforeRedaction", + VisibilityChange = "Event.visibilityChange", + LocalEventIdReplaced = "Event.localEventIdReplaced", + Status = "Event.status", + Replaced = "Event.replaced", + RelationsCreated = "Event.relationsCreated", +} + +type EmittedEvents = MatrixEventEvents | ThreadEvent.Update; + +type EventHandlerMap = { + [MatrixEventEvents.Decrypted]: (event: MatrixEvent, err?: Error) => void; + [MatrixEventEvents.BeforeRedaction]: (event: MatrixEvent, redactionEvent: MatrixEvent) => void; + [MatrixEventEvents.VisibilityChange]: (event: MatrixEvent, visible: boolean) => void; + [MatrixEventEvents.LocalEventIdReplaced]: (event: MatrixEvent) => void; + [MatrixEventEvents.Status]: (event: MatrixEvent, status: EventStatus) => void; + [MatrixEventEvents.Replaced]: (event: MatrixEvent) => void; + [MatrixEventEvents.RelationsCreated]: (relationType: string, eventType: string) => void; +} & Pick; + +export class MatrixEvent extends TypedEventEmitter { private pushActions: IActionsObject = null; private _replacingEvent: MatrixEvent = null; private _localRedactionEvent: MatrixEvent = null; @@ -292,7 +309,7 @@ export class MatrixEvent extends EventEmitter { */ public verificationRequest: VerificationRequest = null; - private readonly reEmitter: ReEmitter; + private readonly reEmitter: TypedReEmitter; /** * Construct a Matrix Event object @@ -343,7 +360,7 @@ export class MatrixEvent extends EventEmitter { this.txnId = event.txn_id || null; this.localTimestamp = Date.now() - (this.getAge() ?? 0); - this.reEmitter = new ReEmitter(this); + this.reEmitter = new TypedReEmitter(this); } /** @@ -871,7 +888,7 @@ export class MatrixEvent extends EventEmitter { this.setPushActions(null); if (options.emit !== false) { - this.emit("Event.decrypted", this, err); + this.emit(MatrixEventEvents.Decrypted, this, err); } return; @@ -1030,7 +1047,7 @@ export class MatrixEvent extends EventEmitter { public markLocallyRedacted(redactionEvent: MatrixEvent): void { if (this._localRedactionEvent) return; - this.emit("Event.beforeRedaction", this, redactionEvent); + this.emit(MatrixEventEvents.BeforeRedaction, this, redactionEvent); this._localRedactionEvent = redactionEvent; if (!this.event.unsigned) { this.event.unsigned = {}; @@ -1068,7 +1085,7 @@ export class MatrixEvent extends EventEmitter { }); } if (change) { - this.emit("Event.visibilityChange", this, visible); + this.emit(MatrixEventEvents.VisibilityChange, this, visible); } } } @@ -1100,7 +1117,7 @@ export class MatrixEvent extends EventEmitter { this._localRedactionEvent = null; - this.emit("Event.beforeRedaction", this, redactionEvent); + this.emit(MatrixEventEvents.BeforeRedaction, this, redactionEvent); this._replacingEvent = null; // we attempt to replicate what we would see from the server if @@ -1263,7 +1280,7 @@ export class MatrixEvent extends EventEmitter { this.setStatus(null); if (this.getId() !== oldId) { // emit the event if it changed - this.emit("Event.localEventIdReplaced", this); + this.emit(MatrixEventEvents.LocalEventIdReplaced, this); } this.localTimestamp = Date.now() - this.getAge(); @@ -1286,12 +1303,12 @@ export class MatrixEvent extends EventEmitter { */ public setStatus(status: EventStatus): void { this.status = status; - this.emit("Event.status", this, status); + this.emit(MatrixEventEvents.Status, this, status); } public replaceLocalEventId(eventId: string): void { this.event.event_id = eventId; - this.emit("Event.localEventIdReplaced", this); + this.emit(MatrixEventEvents.LocalEventIdReplaced, this); } /** @@ -1340,7 +1357,7 @@ export class MatrixEvent extends EventEmitter { } if (this._replacingEvent !== newEvent) { this._replacingEvent = newEvent; - this.emit("Event.replaced", this); + this.emit(MatrixEventEvents.Replaced, this); this.invalidateExtensibleEvent(); } } diff --git a/src/models/relations.ts b/src/models/relations.ts index 29adaab6685..dbf9f284a02 100644 --- a/src/models/relations.ts +++ b/src/models/relations.ts @@ -16,7 +16,7 @@ limitations under the License. import { EventEmitter } from 'events'; -import { EventStatus, MatrixEvent, IAggregatedRelation } from './event'; +import { EventStatus, IAggregatedRelation, MatrixEvent, MatrixEventEvents } from './event'; import { Room } from './room'; import { logger } from '../logger'; import { RelationType } from "../@types/event"; @@ -84,7 +84,7 @@ export class Relations extends EventEmitter { // If the event is in the process of being sent, listen for cancellation // so we can remove the event from the collection. if (event.isSending()) { - event.on("Event.status", this.onEventStatus); + event.on(MatrixEventEvents.Status, this.onEventStatus); } this.relations.add(event); @@ -97,7 +97,7 @@ export class Relations extends EventEmitter { this.targetEvent.makeReplaced(lastReplacement); } - event.on("Event.beforeRedaction", this.onBeforeRedaction); + event.on(MatrixEventEvents.BeforeRedaction, this.onBeforeRedaction); this.emit("Relations.add", event); @@ -150,14 +150,14 @@ export class Relations extends EventEmitter { private onEventStatus = (event: MatrixEvent, status: EventStatus) => { if (!event.isSending()) { // Sending is done, so we don't need to listen anymore - event.removeListener("Event.status", this.onEventStatus); + event.removeListener(MatrixEventEvents.Status, this.onEventStatus); return; } if (status !== EventStatus.CANCELLED) { return; } // Event was cancelled, remove from the collection - event.removeListener("Event.status", this.onEventStatus); + event.removeListener(MatrixEventEvents.Status, this.onEventStatus); this.removeEvent(event); }; @@ -255,7 +255,7 @@ export class Relations extends EventEmitter { this.targetEvent.makeReplaced(lastReplacement); } - redactedEvent.removeListener("Event.beforeRedaction", this.onBeforeRedaction); + redactedEvent.removeListener(MatrixEventEvents.BeforeRedaction, this.onBeforeRedaction); this.emit("Relations.redaction", redactedEvent); }; @@ -375,6 +375,6 @@ export class Relations extends EventEmitter { return; } this.creationEmitted = true; - this.targetEvent.emit("Event.relationsCreated", this.relationType, this.eventType); + this.targetEvent.emit(MatrixEventEvents.RelationsCreated, this.relationType, this.eventType); } } diff --git a/src/webrtc/callEventHandler.ts b/src/webrtc/callEventHandler.ts index 7b7ca0f7149..eb69ff98c33 100644 --- a/src/webrtc/callEventHandler.ts +++ b/src/webrtc/callEventHandler.ts @@ -14,9 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { MatrixEvent } from '../models/event'; +import { MatrixEvent, MatrixEventEvents } from '../models/event'; import { logger } from '../logger'; -import { createNewMatrixCall, MatrixCall, CallErrorCode, CallState, CallDirection } from './call'; +import { CallDirection, CallErrorCode, CallState, createNewMatrixCall, MatrixCall } from './call'; import { EventType } from '../@types/event'; import { MatrixClient } from '../client'; import { MCallAnswer, MCallHangupReject } from "./callEventTypes"; @@ -101,7 +101,7 @@ export class CallEventHandler { if (event.isBeingDecrypted() || event.isDecryptionFailure()) { // add an event listener for once the event is decrypted. - event.once("Event.decrypted", async () => { + event.once(MatrixEventEvents.Decrypted, async () => { if (!this.eventIsACall(event)) return; if (this.callEventBuffer.includes(event)) { From 466b688ab4c43c8277974a8df35f5d4aaa266ccd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 11:49:58 +0000 Subject: [PATCH 10/30] Type the Relations EventEmitter --- src/models/relations.ts | 23 +++++++++++++++++------ src/models/typed-event-emitter.ts | 2 +- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/models/relations.ts b/src/models/relations.ts index dbf9f284a02..671851b750d 100644 --- a/src/models/relations.ts +++ b/src/models/relations.ts @@ -14,12 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { EventEmitter } from 'events'; - import { EventStatus, IAggregatedRelation, MatrixEvent, MatrixEventEvents } from './event'; import { Room } from './room'; import { logger } from '../logger'; import { RelationType } from "../@types/event"; +import { TypedEventEmitter } from "./typed-event-emitter"; + +export enum RelationsEvents { + Add = "Relations.add", + Remove = "Relations.remove", + Redaction = "Relations.redaction", +} + +export type EventHandlerMap = { + [RelationsEvents.Add]: (event: MatrixEvent) => void; + [RelationsEvents.Remove]: (event: MatrixEvent) => void; + [RelationsEvents.Redaction]: (event: MatrixEvent) => void; +}; /** * A container for relation events that supports easy access to common ways of @@ -29,7 +40,7 @@ import { RelationType } from "../@types/event"; * The typical way to get one of these containers is via * EventTimelineSet#getRelationsForEvent. */ -export class Relations extends EventEmitter { +export class Relations extends TypedEventEmitter { private relationEventIds = new Set(); private relations = new Set(); private annotationsByKey: Record> = {}; @@ -99,7 +110,7 @@ export class Relations extends EventEmitter { event.on(MatrixEventEvents.BeforeRedaction, this.onBeforeRedaction); - this.emit("Relations.add", event); + this.emit(RelationsEvents.Add, event); this.maybeEmitCreated(); } @@ -138,7 +149,7 @@ export class Relations extends EventEmitter { this.targetEvent.makeReplaced(lastReplacement); } - this.emit("Relations.remove", event); + this.emit(RelationsEvents.Remove, event); } /** @@ -257,7 +268,7 @@ export class Relations extends EventEmitter { redactedEvent.removeListener(MatrixEventEvents.BeforeRedaction, this.onBeforeRedaction); - this.emit("Relations.redaction", redactedEvent); + this.emit(RelationsEvents.Redaction, redactedEvent); }; /** diff --git a/src/models/typed-event-emitter.ts b/src/models/typed-event-emitter.ts index 6aed7014ea6..0afabbe5064 100644 --- a/src/models/typed-event-emitter.ts +++ b/src/models/typed-event-emitter.ts @@ -27,7 +27,7 @@ export type ListenerMap = { [eventName in E]: AnyListener }; type EventEmitterEventListener = (eventName: string, listener: AnyListener) => void; type EventEmitterErrorListener = (error: Error) => void; -type Listener< +export type Listener< E extends string, A extends ListenerMap, T extends E | EventEmitterEvents, From 3f5decd7ce0734748bb08adb645bbfa408d4670c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 11:55:19 +0000 Subject: [PATCH 11/30] Type the VerificationRequest EventEmitter --- .../request/VerificationRequest.ts | 27 ++++++++++++------- src/models/related-relations.ts | 7 ++--- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/crypto/verification/request/VerificationRequest.ts b/src/crypto/verification/request/VerificationRequest.ts index b6c0d9ef4bb..498d2dcaa2d 100644 --- a/src/crypto/verification/request/VerificationRequest.ts +++ b/src/crypto/verification/request/VerificationRequest.ts @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { EventEmitter } from 'events'; - import { logger } from '../../../logger'; import { errorFactory, @@ -29,6 +27,7 @@ import { MatrixClient } from "../../../client"; import { MatrixEvent } from "../../../models/event"; import { VerificationBase } from "../Base"; import { VerificationMethod } from "../../index"; +import { TypedEventEmitter } from "../../../models/typed-event-emitter"; // How long after the event's timestamp that the request times out const TIMEOUT_FROM_EVENT_TS = 10 * 60 * 1000; // 10 minutes @@ -76,13 +75,23 @@ interface ITransition { event?: MatrixEvent; } +export enum VerificationRequestEvents { + Change = "change", +} + +type EventHandlerMap = { + [VerificationRequestEvents.Change]: () => void; +}; + /** * State machine for verification requests. * Things that differ based on what channel is used to * send and receive verification events are put in `InRoomChannel` or `ToDeviceChannel`. * @event "change" whenever the state of the request object has changed. */ -export class VerificationRequest extends EventEmitter { +export class VerificationRequest< + C extends IVerificationChannel = IVerificationChannel, +> extends TypedEventEmitter { private eventsByUs = new Map(); private eventsByThem = new Map(); private _observeOnly = false; @@ -453,7 +462,7 @@ export class VerificationRequest { if (!this.observeOnly && this._phase !== PHASE_CANCELLED) { this._declining = true; - this.emit("change"); + this.emit(VerificationRequestEvents.Change); if (this._verifier) { return this._verifier.cancel(errorFactory(code, reason)()); } else { @@ -471,7 +480,7 @@ export class VerificationRequest [...c, ...p.getRelations()], []); } - public on(ev: string, fn: (...params) => void) { + public on(ev: T, fn: Listener) { this.relations.forEach(r => r.on(ev, fn)); } - public off(ev: string, fn: (...params) => void) { + public off(ev: T, fn: Listener) { this.relations.forEach(r => r.off(ev, fn)); } } From 42b308a3c9d0eea16843effc1be321053c5408bb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 11:59:44 +0000 Subject: [PATCH 12/30] Type the DeviceList EventEmitter --- src/crypto/DeviceList.ts | 23 +++++++++++++++++------ src/crypto/index.ts | 4 ++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/crypto/DeviceList.ts b/src/crypto/DeviceList.ts index 6e951263cab..a30da79f797 100644 --- a/src/crypto/DeviceList.ts +++ b/src/crypto/DeviceList.ts @@ -20,8 +20,6 @@ limitations under the License. * Manages the list of other users' devices */ -import { EventEmitter } from 'events'; - import { logger } from '../logger'; import { DeviceInfo, IDevice } from './deviceinfo'; import { CrossSigningInfo, ICrossSigningInfo } from './CrossSigning'; @@ -31,6 +29,7 @@ import { chunkPromises, defer, IDeferred, sleep } from '../utils'; import { IDownloadKeyResult, MatrixClient } from "../client"; import { OlmDevice } from "./OlmDevice"; import { CryptoStore } from "./store/base"; +import { TypedEventEmitter } from "../models/typed-event-emitter"; /* State transition diagram for DeviceList.deviceTrackingStatus * @@ -62,10 +61,22 @@ export enum TrackingStatus { export type DeviceInfoMap = Record>; +export enum DeviceListEvents { + WillUpdateDevices = "crypto.willUpdateDevices", + DevicesUpdated = "crypto.devicesUpdated", + UserCrossSigningUpdated = "userCrossSigningUpdated", +} + +type EventHandlerMap = { + [DeviceListEvents.WillUpdateDevices]: (users: string[], initialFetch: boolean) => void; + [DeviceListEvents.DevicesUpdated]: (users: string[], initialFetch: boolean) => void; + [DeviceListEvents.UserCrossSigningUpdated]: (userId: string) => void; +}; + /** * @alias module:crypto/DeviceList */ -export class DeviceList extends EventEmitter { +export class DeviceList extends TypedEventEmitter { private devices: { [userId: string]: { [deviceId: string]: IDevice } } = {}; public crossSigningInfo: { [userId: string]: ICrossSigningInfo } = {}; @@ -634,7 +645,7 @@ export class DeviceList extends EventEmitter { }); const finished = (success: boolean): void => { - this.emit("crypto.willUpdateDevices", users, !this.hasFetched); + this.emit(DeviceListEvents.WillUpdateDevices, users, !this.hasFetched); users.forEach((u) => { this.dirty = true; @@ -659,7 +670,7 @@ export class DeviceList extends EventEmitter { } }); this.saveIfDirty(); - this.emit("crypto.devicesUpdated", users, !this.hasFetched); + this.emit(DeviceListEvents.DevicesUpdated, users, !this.hasFetched); this.hasFetched = true; }; @@ -867,7 +878,7 @@ class DeviceListUpdateSerialiser { // NB. Unlike most events in the js-sdk, this one is internal to the // js-sdk and is not re-emitted - this.deviceList.emit('userCrossSigningUpdated', userId); + this.deviceList.emit(DeviceListEvents.UserCrossSigningUpdated, userId); } } } diff --git a/src/crypto/index.ts b/src/crypto/index.ts index c1482c5e6e9..0cacbac9c80 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -28,7 +28,7 @@ import { ReEmitter } from '../ReEmitter'; import { logger } from '../logger'; import { IExportedDevice, OlmDevice } from "./OlmDevice"; import * as olmlib from "./olmlib"; -import { DeviceInfoMap, DeviceList } from "./DeviceList"; +import { DeviceInfoMap, DeviceList, DeviceListEvents } from "./DeviceList"; import { DeviceInfo, IDevice } from "./deviceinfo"; import * as algorithms from "./algorithms"; import { createCryptoStoreCacheCallbacks, CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from './CrossSigning'; @@ -364,7 +364,7 @@ export class Crypto extends EventEmitter { // XXX: This isn't removed at any point, but then none of the event listeners // this class sets seem to be removed at any point... :/ - this.deviceList.on('userCrossSigningUpdated', this.onDeviceListUserCrossSigningUpdated); + this.deviceList.on(DeviceListEvents.UserCrossSigningUpdated, this.onDeviceListUserCrossSigningUpdated); this.reEmitter.reEmit(this.deviceList, ["crypto.devicesUpdated", "crypto.willUpdateDevices"]); this.supportedAlgorithms = Object.keys(algorithms.DECRYPTION_CLASSES); From 91bdf63284fe443441fdb3174cc8acb3f06dd099 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 12:07:40 +0000 Subject: [PATCH 13/30] TS improvements and fix default verification methods bug --- src/crypto/index.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 0cacbac9c80..193f66a787e 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -46,7 +46,7 @@ import { IEncryptedEventInfo, IImportRoomKeysOpts, IRecoveryKey, - ISecretStorageKeyInfo + ISecretStorageKeyInfo, } from "./api"; import { OutgoingRoomKeyRequestManager } from './OutgoingRoomKeyRequestManager'; import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store'; @@ -67,7 +67,7 @@ import { Room } from "../models/room"; import { RoomMember } from "../models/room-member"; import { EventStatus, IClearEvent, IEvent, MatrixEvent, MatrixEventEvents } from "../models/event"; import { ICrossSigningKey, IKeysUploadResponse, ISignedKey, MatrixClient, SessionStore } from "../client"; -import type { DecryptionAlgorithm, EncryptionAlgorithm } from "./algorithms/base"; +import type { DecryptionAlgorithm, EncryptionAlgorithm } from "./algorithms"; import type { IRoomEncryption, RoomList } from "./RoomList"; import { IKeyBackupInfo } from "./keybackup"; import { ISyncStateData } from "../sync"; @@ -208,7 +208,7 @@ export class Crypto extends EventEmitter { public readonly secretStorage: SecretStorage; private readonly reEmitter: ReEmitter; - private readonly verificationMethods: any; // TODO types + private readonly verificationMethods: Map; public readonly supportedAlgorithms: string[]; private readonly outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager; private readonly toDeviceVerificationRequests: ToDeviceRequests; @@ -301,7 +301,7 @@ export class Crypto extends EventEmitter { private readonly clientStore: IStore, public readonly cryptoStore: CryptoStore, private readonly roomList: RoomList, - verificationMethods: any[], // TODO types + verificationMethods: Array, ) { super(); this.reEmitter = new ReEmitter(this); @@ -316,17 +316,17 @@ export class Crypto extends EventEmitter { defaultVerificationMethods[method], ); } - } else if (method.NAME) { + } else if (method["NAME"]) { this.verificationMethods.set( - method.NAME, - method, + method["NAME"], + method as typeof VerificationBase, ); } else { logger.warn(`Excluding unknown verification method ${method}`); } } } else { - this.verificationMethods = defaultVerificationMethods; + this.verificationMethods = new Map(Object.entries(defaultVerificationMethods)); } this.backupManager = new BackupManager(baseApis, async () => { From 6705a5e7500471963c77008ca6e00ab620b7bff8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 12:21:16 +0000 Subject: [PATCH 14/30] Type the Crypto EventEmitter --- spec/unit/crypto/crypto-utils.js | 2 +- src/crypto/DeviceList.ts | 2 +- src/crypto/backup.ts | 15 +++--- src/crypto/index.ts | 86 ++++++++++++++++++++------------ 4 files changed, 64 insertions(+), 41 deletions(-) diff --git a/spec/unit/crypto/crypto-utils.js b/spec/unit/crypto/crypto-utils.js index b54b1a18ebe..ecc6fc4b0ae 100644 --- a/spec/unit/crypto/crypto-utils.js +++ b/spec/unit/crypto/crypto-utils.js @@ -26,7 +26,7 @@ export async function resetCrossSigningKeys(client, { crypto.crossSigningInfo.keys = oldKeys; throw e; } - crypto.baseApis.emit("crossSigning.keysChanged", {}); + crypto.emit("crossSigning.keysChanged", {}); await crypto.afterCrossSigningLocalKeyChange(); } diff --git a/src/crypto/DeviceList.ts b/src/crypto/DeviceList.ts index a30da79f797..92aa33f87c8 100644 --- a/src/crypto/DeviceList.ts +++ b/src/crypto/DeviceList.ts @@ -67,7 +67,7 @@ export enum DeviceListEvents { UserCrossSigningUpdated = "userCrossSigningUpdated", } -type EventHandlerMap = { +export type EventHandlerMap = { [DeviceListEvents.WillUpdateDevices]: (users: string[], initialFetch: boolean) => void; [DeviceListEvents.DevicesUpdated]: (users: string[], initialFetch: boolean) => void; [DeviceListEvents.UserCrossSigningUpdated]: (userId: string) => void; diff --git a/src/crypto/backup.ts b/src/crypto/backup.ts index 3577fade720..7ea67f37ece 100644 --- a/src/crypto/backup.ts +++ b/src/crypto/backup.ts @@ -26,14 +26,13 @@ import { MEGOLM_ALGORITHM, verifySignature } from "./olmlib"; import { DeviceInfo } from "./deviceinfo"; import { DeviceTrustLevel } from './CrossSigning'; import { keyFromPassphrase } from './key_passphrase'; -import { sleep } from "../utils"; +import { getCrypto, sleep } from "../utils"; import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store'; import { encodeRecoveryKey } from './recoverykey'; -import { encryptAES, decryptAES, calculateKeyCheck } from './aes'; -import { getCrypto } from '../utils'; -import { ICurve25519AuthData, IAes256AuthData, IKeyBackupInfo, IKeyBackupSession } from "./keybackup"; +import { calculateKeyCheck, decryptAES, encryptAES } from './aes'; +import { IAes256AuthData, ICurve25519AuthData, IKeyBackupInfo, IKeyBackupSession } from "./keybackup"; import { UnstableValue } from "../NamespacedValue"; -import { IMegolmSessionData } from "./index"; +import { CryptoEvents, IMegolmSessionData } from "./index"; const KEY_BACKUP_KEYS_PER_REQUEST = 200; @@ -458,7 +457,7 @@ export class BackupManager { await this.checkKeyBackup(); // Backup version has changed or this backup version // has been deleted - this.baseApis.crypto.emit("crypto.keyBackupFailed", err.data.errcode); + this.baseApis.crypto.emit(CryptoEvents.KeyBackupFailed, err.data.errcode); throw err; } } @@ -487,7 +486,7 @@ export class BackupManager { } let remaining = await this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup(); - this.baseApis.crypto.emit("crypto.keyBackupSessionsRemaining", remaining); + this.baseApis.crypto.emit(CryptoEvents.KeyBackupSessionsRemaining, remaining); const rooms: IKeyBackup["rooms"] = {}; for (const session of sessions) { @@ -524,7 +523,7 @@ export class BackupManager { await this.baseApis.crypto.cryptoStore.unmarkSessionsNeedingBackup(sessions); remaining = await this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup(); - this.baseApis.crypto.emit("crypto.keyBackupSessionsRemaining", remaining); + this.baseApis.crypto.emit(CryptoEvents.KeyBackupSessionsRemaining, remaining); return sessions.length; } diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 193f66a787e..543288f1f27 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -24,12 +24,13 @@ limitations under the License. import anotherjson from "another-json"; import { EventEmitter } from 'events'; -import { ReEmitter } from '../ReEmitter'; +import { TypedReEmitter } from '../ReEmitter'; import { logger } from '../logger'; import { IExportedDevice, OlmDevice } from "./OlmDevice"; import * as olmlib from "./olmlib"; -import { DeviceInfoMap, DeviceList, DeviceListEvents } from "./DeviceList"; +import { DeviceInfoMap, DeviceList, DeviceListEvents, EventHandlerMap as DeviceListHandlerMap } from "./DeviceList"; import { DeviceInfo, IDevice } from "./deviceinfo"; +import type { DecryptionAlgorithm, EncryptionAlgorithm } from "./algorithms"; import * as algorithms from "./algorithms"; import { createCryptoStoreCacheCallbacks, CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from './CrossSigning'; import { EncryptionSetupBuilder } from "./EncryptionSetup"; @@ -64,15 +65,17 @@ import { DehydrationManager, IDeviceKeys, IOneTimeKey } from './dehydration'; import { BackupManager } from "./backup"; import { IStore } from "../store"; import { Room } from "../models/room"; -import { RoomMember } from "../models/room-member"; +import { RoomMember, RoomMemberEvents } from "../models/room-member"; import { EventStatus, IClearEvent, IEvent, MatrixEvent, MatrixEventEvents } from "../models/event"; import { ICrossSigningKey, IKeysUploadResponse, ISignedKey, MatrixClient, SessionStore } from "../client"; -import type { DecryptionAlgorithm, EncryptionAlgorithm } from "./algorithms"; import type { IRoomEncryption, RoomList } from "./RoomList"; import { IKeyBackupInfo } from "./keybackup"; import { ISyncStateData } from "../sync"; import { CryptoStore } from "./store/base"; import { IVerificationChannel } from "./verification/request/Channel"; +import { TypedEventEmitter } from "../models/typed-event-emitter"; +import { VerificationBase } from "./verification/Base"; +import { EventTimelineSetEvents } from "../models/event-timeline-set"; const DeviceVerification = DeviceInfo.DeviceVerification; @@ -192,7 +195,29 @@ export interface IRequestsMap { setRequestByChannel(channel: IVerificationChannel, request: VerificationRequest): void; } -export class Crypto extends EventEmitter { +export enum CryptoEvents { + DeviceVerificationChanged = "deviceVerificationChanged", + UserTrustStatusChanged = "userTrustStatusChanged", + RoomKeyRequest = "crypto.roomKeyRequest", + RoomKeyRequestCancellation = "crypto.roomKeyRequestCancellation", + KeyBackupFailed = "crypto.keyBackupFailed", + KeyBackupSessionsRemaining = "crypto.keyBackupSessionsRemaining", + KeysChanged = "crossSigning.keysChanged", +} + +type EmittedEvents = CryptoEvents | DeviceListEvents.DevicesUpdated | DeviceListEvents.WillUpdateDevices; + +type EventHandlerMap = { + [CryptoEvents.DeviceVerificationChanged]: (userId: string, deviceId: string, device: DeviceInfo) => void; + [CryptoEvents.UserTrustStatusChanged]: (userId: string, trustLevel: UserTrustLevel) => void; + [CryptoEvents.RoomKeyRequest]: (request: IncomingRoomKeyRequest) => void; + [CryptoEvents.RoomKeyRequestCancellation]: (request: IncomingRoomKeyRequestCancellation) => void; + [CryptoEvents.KeyBackupFailed]: (errcode: string) => void; + [CryptoEvents.KeyBackupSessionsRemaining]: (remaining: number) => void; + [CryptoEvents.KeysChanged]: (data: {}) => void; +} & Pick; + +export class Crypto extends TypedEventEmitter { /** * @return {string} The version of Olm. */ @@ -207,7 +232,7 @@ export class Crypto extends EventEmitter { public readonly dehydrationManager: DehydrationManager; public readonly secretStorage: SecretStorage; - private readonly reEmitter: ReEmitter; + private readonly reEmitter: TypedReEmitter; private readonly verificationMethods: Map; public readonly supportedAlgorithms: string[]; private readonly outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager; @@ -304,7 +329,7 @@ export class Crypto extends EventEmitter { verificationMethods: Array, ) { super(); - this.reEmitter = new ReEmitter(this); + this.reEmitter = new TypedReEmitter(this); if (verificationMethods) { this.verificationMethods = new Map(); @@ -365,7 +390,7 @@ export class Crypto extends EventEmitter { // XXX: This isn't removed at any point, but then none of the event listeners // this class sets seem to be removed at any point... :/ this.deviceList.on(DeviceListEvents.UserCrossSigningUpdated, this.onDeviceListUserCrossSigningUpdated); - this.reEmitter.reEmit(this.deviceList, ["crypto.devicesUpdated", "crypto.willUpdateDevices"]); + this.reEmitter.reEmit(this.deviceList, [DeviceListEvents.DevicesUpdated, DeviceListEvents.WillUpdateDevices]); this.supportedAlgorithms = Object.keys(algorithms.DECRYPTION_CLASSES); @@ -493,7 +518,7 @@ export class Crypto extends EventEmitter { deviceTrust.isCrossSigningVerified() ) { const deviceObj = this.deviceList.getStoredDevice(userId, deviceId); - this.emit("deviceVerificationChanged", userId, deviceId, deviceObj); + this.emit(CryptoEvents.DeviceVerificationChanged, userId, deviceId, deviceObj); } } } @@ -1397,11 +1422,10 @@ export class Crypto extends EventEmitter { // that reset the keys this.storeTrustedSelfKeys(null); // emit cross-signing has been disabled - this.emit("crossSigning.keysChanged", {}); + this.emit(CryptoEvents.KeysChanged, {}); // as the trust for our own user has changed, // also emit an event for this - this.emit("userTrustStatusChanged", - this.userId, this.checkUserTrust(userId)); + this.emit(CryptoEvents.UserTrustStatusChanged, this.userId, this.checkUserTrust(userId)); } } else { await this.checkDeviceVerifications(userId); @@ -1416,7 +1440,7 @@ export class Crypto extends EventEmitter { this.deviceList.setRawStoredCrossSigningForUser(userId, crossSigning.toStorage()); } - this.emit("userTrustStatusChanged", userId, this.checkUserTrust(userId)); + this.emit(CryptoEvents.UserTrustStatusChanged, userId, this.checkUserTrust(userId)); } }; @@ -1591,10 +1615,10 @@ export class Crypto extends EventEmitter { upload({ shouldEmit: true }); } - this.emit("userTrustStatusChanged", userId, this.checkUserTrust(userId)); + this.emit(CryptoEvents.UserTrustStatusChanged, userId, this.checkUserTrust(userId)); if (masterChanged) { - this.baseApis.emit("crossSigning.keysChanged", {}); + this.emit(CryptoEvents.KeysChanged, {}); await this.afterCrossSigningLocalKeyChange(); } @@ -1680,19 +1704,13 @@ export class Crypto extends EventEmitter { * * @param {external:EventEmitter} eventEmitter event source where we can register * for event notifications + * @todo use a typed EventEmitter here */ public registerEventHandlers(eventEmitter: EventEmitter): void { - eventEmitter.on("RoomMember.membership", (event: MatrixEvent, member: RoomMember, oldMembership?: string) => { - try { - this.onRoomMembership(event, member, oldMembership); - } catch (e) { - logger.error("Error handling membership change:", e); - } - }); - + eventEmitter.on(RoomMemberEvents.Membership, this.onMembership); eventEmitter.on("toDeviceEvent", this.onToDeviceEvent); - eventEmitter.on("Room.timeline", this.onTimelineEvent); - eventEmitter.on("Event.decrypted", this.onTimelineEvent); + eventEmitter.on(EventTimelineSetEvents.RoomTimeline, this.onTimelineEvent); + eventEmitter.on(MatrixEventEvents.Decrypted, this.onTimelineEvent); } /** Start background processes related to crypto */ @@ -2076,9 +2094,7 @@ export class Crypto extends EventEmitter { if (!this.crossSigningInfo.getId() && userId === this.crossSigningInfo.userId) { this.storeTrustedSelfKeys(xsk.keys); // This will cause our own user trust to change, so emit the event - this.emit( - "userTrustStatusChanged", this.userId, this.checkUserTrust(userId), - ); + this.emit(CryptoEvents.UserTrustStatusChanged, this.userId, this.checkUserTrust(userId)); } // Now sign the master key with our user signing key (unless it's ourself) @@ -2199,7 +2215,7 @@ export class Crypto extends EventEmitter { } const deviceObj = DeviceInfo.fromStorage(dev, deviceId); - this.emit("deviceVerificationChanged", userId, deviceId, deviceObj); + this.emit(CryptoEvents.DeviceVerificationChanged, userId, deviceId, deviceObj); return deviceObj; } @@ -3051,6 +3067,14 @@ export class Crypto extends EventEmitter { }); } + private onMembership = (event: MatrixEvent, member: RoomMember, oldMembership?: string) => { + try { + this.onRoomMembership(event, member, oldMembership); + } catch (e) { + logger.error("Error handling membership change:", e); + } + }; + private onToDeviceEvent = (event: MatrixEvent): void => { try { logger.log(`received to_device ${event.getType()} from: ` + @@ -3561,7 +3585,7 @@ export class Crypto extends EventEmitter { return; } - this.emit("crypto.roomKeyRequest", req); + this.emit(CryptoEvents.RoomKeyRequest, req); } /** @@ -3580,7 +3604,7 @@ export class Crypto extends EventEmitter { // we should probably only notify the app of cancellations we told it // about, but we don't currently have a record of that, so we just pass // everything through. - this.emit("crypto.roomKeyRequestCancellation", cancellation); + this.emit(CryptoEvents.RoomKeyRequestCancellation, cancellation); } /** From 625548c95abdd0b54472676badb4572a1506d3e0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 12:28:49 +0000 Subject: [PATCH 15/30] Type the CallFeed EventEmitter --- src/webrtc/callFeed.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/webrtc/callFeed.ts b/src/webrtc/callFeed.ts index 0c23f3832ce..8f61afaa5d0 100644 --- a/src/webrtc/callFeed.ts +++ b/src/webrtc/callFeed.ts @@ -14,11 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -import EventEmitter from "events"; - import { SDPStreamMetadataPurpose } from "./callEventTypes"; import { MatrixClient } from "../client"; import { RoomMember } from "../models/room-member"; +import { TypedEventEmitter } from "../models/typed-event-emitter"; const POLLING_INTERVAL = 200; // ms export const SPEAKING_THRESHOLD = -60; // dB @@ -47,7 +46,14 @@ export enum CallFeedEvent { Speaking = "speaking", } -export class CallFeed extends EventEmitter { +type EventHandlerMap = { + [CallFeedEvent.NewStream]: (stream: MediaStream) => void; + [CallFeedEvent.MuteStateChanged]: (audioMuted: boolean, videoMuted: boolean) => void; + [CallFeedEvent.VolumeChanged]: (volume: number) => void; + [CallFeedEvent.Speaking]: (speaking: boolean) => void; +}; + +export class CallFeed extends TypedEventEmitter { public stream: MediaStream; public userId: string; public purpose: SDPStreamMetadataPurpose; From 51c0c2dade31104bbbd9f3b780a014704389f7dd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 12:30:34 +0000 Subject: [PATCH 16/30] Standardise --- spec/integ/matrix-client-retrying.spec.ts | 4 +- spec/unit/relations.spec.ts | 6 +- src/client.ts | 12 ++-- src/crypto/DeviceList.ts | 16 ++--- src/crypto/backup.ts | 8 +-- src/crypto/index.ts | 68 +++++++++---------- .../request/VerificationRequest.ts | 18 ++--- src/models/event-timeline-set.ts | 18 ++--- src/models/event.ts | 34 +++++----- src/models/related-relations.ts | 6 +- src/models/relations.ts | 30 ++++---- src/models/room-member.ts | 20 +++--- src/models/room-state.ts | 20 +++--- src/models/room.ts | 62 ++++++++--------- src/models/thread.ts | 16 ++--- src/models/user.ts | 30 ++++---- src/store/memory.ts | 6 +- src/sync.ts | 10 +-- src/webrtc/callEventHandler.ts | 4 +- 19 files changed, 194 insertions(+), 194 deletions(-) diff --git a/spec/integ/matrix-client-retrying.spec.ts b/spec/integ/matrix-client-retrying.spec.ts index 9979e571a30..6f74e4188b8 100644 --- a/spec/integ/matrix-client-retrying.spec.ts +++ b/spec/integ/matrix-client-retrying.spec.ts @@ -1,4 +1,4 @@ -import { EventStatus, RoomEvents } from "../../src/matrix"; +import { EventStatus, RoomEvent } from "../../src/matrix"; import { MatrixScheduler } from "../../src/scheduler"; import { Room } from "../../src/models/room"; import { TestClient } from "../TestClient"; @@ -95,7 +95,7 @@ describe("MatrixClient retrying", function() { // wait for the localecho of ev1 to be updated const p3 = new Promise((resolve, reject) => { - room.on(RoomEvents.LocalEchoUpdated, (ev0) => { + room.on(RoomEvent.LocalEchoUpdated, (ev0) => { if (ev0 === ev1) { resolve(); } diff --git a/spec/unit/relations.spec.ts b/spec/unit/relations.spec.ts index 20e51b63a36..1b479ebbef8 100644 --- a/spec/unit/relations.spec.ts +++ b/spec/unit/relations.spec.ts @@ -15,7 +15,7 @@ limitations under the License. */ import { EventTimelineSet } from "../../src/models/event-timeline-set"; -import { MatrixEvent, MatrixEventEvents } from "../../src/models/event"; +import { MatrixEvent, MatrixEventEvent } from "../../src/models/event"; import { Room } from "../../src/models/room"; import { Relations } from "../../src/models/relations"; @@ -103,7 +103,7 @@ describe("Relations", function() { // Add the target event first, then the relation event { const relationsCreated = new Promise(resolve => { - targetEvent.once(MatrixEventEvents.RelationsCreated, resolve); + targetEvent.once(MatrixEventEvent.RelationsCreated, resolve); }); const timelineSet = new EventTimelineSet(room, { @@ -118,7 +118,7 @@ describe("Relations", function() { // Add the relation event first, then the target event { const relationsCreated = new Promise(resolve => { - targetEvent.once(MatrixEventEvents.RelationsCreated, resolve); + targetEvent.once(MatrixEventEvent.RelationsCreated, resolve); }); const timelineSet = new EventTimelineSet(room, { diff --git a/src/client.ts b/src/client.ts index 18fa865aa0d..c560b68e82d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -23,7 +23,7 @@ import { EventEmitter } from "events"; import { EmoteEvent, IPartialEvent, MessageEvent, NoticeEvent } from "matrix-events-sdk"; import { ISyncStateData, SyncApi, SyncState } from "./sync"; -import { EventStatus, IContent, IDecryptOptions, IEvent, MatrixEvent, MatrixEventEvents } from "./models/event"; +import { EventStatus, IContent, IDecryptOptions, IEvent, MatrixEvent, MatrixEventEvent } from "./models/event"; import { StubStore } from "./store/stub"; import { createNewMatrixCall, MatrixCall } from "./webrtc/call"; import { Filter, IFilterDefinition } from "./filter"; @@ -68,7 +68,7 @@ import { import { DeviceInfo, IDevice } from "./crypto/deviceinfo"; import { decodeRecoveryKey } from './crypto/recoverykey'; import { keyFromAuthData } from './crypto/key_passphrase'; -import { User, UserEvents } from "./models/user"; +import { User, UserEvent } from "./models/user"; import { getHttpUriForMxc } from "./content-repo"; import { SearchResult } from "./models/search-result"; import { @@ -3660,7 +3660,7 @@ export class MatrixClient extends EventEmitter { const targetId = localEvent.getAssociatedId(); if (targetId && targetId.startsWith("~")) { const target = room.getPendingEvents().find(e => e.getId() === targetId); - target.once(MatrixEventEvents.LocalEventIdReplaced, () => { + target.once(MatrixEventEvent.LocalEventIdReplaced, () => { localEvent.updateAssociatedId(target.getId()); }); } @@ -4911,7 +4911,7 @@ export class MatrixClient extends EventEmitter { const user = this.getUser(this.getUserId()); if (user) { user.displayName = name; - user.emit(UserEvents.DisplayName, user.events.presence, user); + user.emit(UserEvent.DisplayName, user.events.presence, user); } return prom; } @@ -4928,7 +4928,7 @@ export class MatrixClient extends EventEmitter { const user = this.getUser(this.getUserId()); if (user) { user.avatarUrl = url; - user.emit(UserEvents.AvatarUrl, user.events.presence, user); + user.emit(UserEvent.AvatarUrl, user.events.presence, user); } return prom; } @@ -6510,7 +6510,7 @@ export class MatrixClient extends EventEmitter { const allEvents = originalEvent ? events.concat(originalEvent) : events; await Promise.all(allEvents.map(e => { if (e.isEncrypted()) { - return new Promise(resolve => e.once(MatrixEventEvents.Decrypted, resolve)); + return new Promise(resolve => e.once(MatrixEventEvent.Decrypted, resolve)); } })); events = events.filter(e => e.getType() === eventType); diff --git a/src/crypto/DeviceList.ts b/src/crypto/DeviceList.ts index 92aa33f87c8..a0fd77a8406 100644 --- a/src/crypto/DeviceList.ts +++ b/src/crypto/DeviceList.ts @@ -61,22 +61,22 @@ export enum TrackingStatus { export type DeviceInfoMap = Record>; -export enum DeviceListEvents { +export enum DeviceListEvent { WillUpdateDevices = "crypto.willUpdateDevices", DevicesUpdated = "crypto.devicesUpdated", UserCrossSigningUpdated = "userCrossSigningUpdated", } export type EventHandlerMap = { - [DeviceListEvents.WillUpdateDevices]: (users: string[], initialFetch: boolean) => void; - [DeviceListEvents.DevicesUpdated]: (users: string[], initialFetch: boolean) => void; - [DeviceListEvents.UserCrossSigningUpdated]: (userId: string) => void; + [DeviceListEvent.WillUpdateDevices]: (users: string[], initialFetch: boolean) => void; + [DeviceListEvent.DevicesUpdated]: (users: string[], initialFetch: boolean) => void; + [DeviceListEvent.UserCrossSigningUpdated]: (userId: string) => void; }; /** * @alias module:crypto/DeviceList */ -export class DeviceList extends TypedEventEmitter { +export class DeviceList extends TypedEventEmitter { private devices: { [userId: string]: { [deviceId: string]: IDevice } } = {}; public crossSigningInfo: { [userId: string]: ICrossSigningInfo } = {}; @@ -645,7 +645,7 @@ export class DeviceList extends TypedEventEmitter { - this.emit(DeviceListEvents.WillUpdateDevices, users, !this.hasFetched); + this.emit(DeviceListEvent.WillUpdateDevices, users, !this.hasFetched); users.forEach((u) => { this.dirty = true; @@ -670,7 +670,7 @@ export class DeviceList extends TypedEventEmitter void; - [CryptoEvents.UserTrustStatusChanged]: (userId: string, trustLevel: UserTrustLevel) => void; - [CryptoEvents.RoomKeyRequest]: (request: IncomingRoomKeyRequest) => void; - [CryptoEvents.RoomKeyRequestCancellation]: (request: IncomingRoomKeyRequestCancellation) => void; - [CryptoEvents.KeyBackupFailed]: (errcode: string) => void; - [CryptoEvents.KeyBackupSessionsRemaining]: (remaining: number) => void; - [CryptoEvents.KeysChanged]: (data: {}) => void; -} & Pick; + [CryptoEvent.DeviceVerificationChanged]: (userId: string, deviceId: string, device: DeviceInfo) => void; + [CryptoEvent.UserTrustStatusChanged]: (userId: string, trustLevel: UserTrustLevel) => void; + [CryptoEvent.RoomKeyRequest]: (request: IncomingRoomKeyRequest) => void; + [CryptoEvent.RoomKeyRequestCancellation]: (request: IncomingRoomKeyRequestCancellation) => void; + [CryptoEvent.KeyBackupFailed]: (errcode: string) => void; + [CryptoEvent.KeyBackupSessionsRemaining]: (remaining: number) => void; + [CryptoEvent.KeysChanged]: (data: {}) => void; +} & Pick; export class Crypto extends TypedEventEmitter { /** @@ -389,8 +389,8 @@ export class Crypto extends TypedEventEmitter { // XXX: This isn't removed at any point, but then none of the event listeners // this class sets seem to be removed at any point... :/ - this.deviceList.on(DeviceListEvents.UserCrossSigningUpdated, this.onDeviceListUserCrossSigningUpdated); - this.reEmitter.reEmit(this.deviceList, [DeviceListEvents.DevicesUpdated, DeviceListEvents.WillUpdateDevices]); + this.deviceList.on(DeviceListEvent.UserCrossSigningUpdated, this.onDeviceListUserCrossSigningUpdated); + this.reEmitter.reEmit(this.deviceList, [DeviceListEvent.DevicesUpdated, DeviceListEvent.WillUpdateDevices]); this.supportedAlgorithms = Object.keys(algorithms.DECRYPTION_CLASSES); @@ -518,7 +518,7 @@ export class Crypto extends TypedEventEmitter { deviceTrust.isCrossSigningVerified() ) { const deviceObj = this.deviceList.getStoredDevice(userId, deviceId); - this.emit(CryptoEvents.DeviceVerificationChanged, userId, deviceId, deviceObj); + this.emit(CryptoEvent.DeviceVerificationChanged, userId, deviceId, deviceObj); } } } @@ -1422,10 +1422,10 @@ export class Crypto extends TypedEventEmitter { // that reset the keys this.storeTrustedSelfKeys(null); // emit cross-signing has been disabled - this.emit(CryptoEvents.KeysChanged, {}); + this.emit(CryptoEvent.KeysChanged, {}); // as the trust for our own user has changed, // also emit an event for this - this.emit(CryptoEvents.UserTrustStatusChanged, this.userId, this.checkUserTrust(userId)); + this.emit(CryptoEvent.UserTrustStatusChanged, this.userId, this.checkUserTrust(userId)); } } else { await this.checkDeviceVerifications(userId); @@ -1440,7 +1440,7 @@ export class Crypto extends TypedEventEmitter { this.deviceList.setRawStoredCrossSigningForUser(userId, crossSigning.toStorage()); } - this.emit(CryptoEvents.UserTrustStatusChanged, userId, this.checkUserTrust(userId)); + this.emit(CryptoEvent.UserTrustStatusChanged, userId, this.checkUserTrust(userId)); } }; @@ -1615,10 +1615,10 @@ export class Crypto extends TypedEventEmitter { upload({ shouldEmit: true }); } - this.emit(CryptoEvents.UserTrustStatusChanged, userId, this.checkUserTrust(userId)); + this.emit(CryptoEvent.UserTrustStatusChanged, userId, this.checkUserTrust(userId)); if (masterChanged) { - this.emit(CryptoEvents.KeysChanged, {}); + this.emit(CryptoEvent.KeysChanged, {}); await this.afterCrossSigningLocalKeyChange(); } @@ -1707,10 +1707,10 @@ export class Crypto extends TypedEventEmitter { * @todo use a typed EventEmitter here */ public registerEventHandlers(eventEmitter: EventEmitter): void { - eventEmitter.on(RoomMemberEvents.Membership, this.onMembership); + eventEmitter.on(RoomMemberEvent.Membership, this.onMembership); eventEmitter.on("toDeviceEvent", this.onToDeviceEvent); - eventEmitter.on(EventTimelineSetEvents.RoomTimeline, this.onTimelineEvent); - eventEmitter.on(MatrixEventEvents.Decrypted, this.onTimelineEvent); + eventEmitter.on(EventTimelineSetEvent.RoomTimeline, this.onTimelineEvent); + eventEmitter.on(MatrixEventEvent.Decrypted, this.onTimelineEvent); } /** Start background processes related to crypto */ @@ -2094,7 +2094,7 @@ export class Crypto extends TypedEventEmitter { if (!this.crossSigningInfo.getId() && userId === this.crossSigningInfo.userId) { this.storeTrustedSelfKeys(xsk.keys); // This will cause our own user trust to change, so emit the event - this.emit(CryptoEvents.UserTrustStatusChanged, this.userId, this.checkUserTrust(userId)); + this.emit(CryptoEvent.UserTrustStatusChanged, this.userId, this.checkUserTrust(userId)); } // Now sign the master key with our user signing key (unless it's ourself) @@ -2215,7 +2215,7 @@ export class Crypto extends TypedEventEmitter { } const deviceObj = DeviceInfo.fromStorage(dev, deviceId); - this.emit(CryptoEvents.DeviceVerificationChanged, userId, deviceId, deviceObj); + this.emit(CryptoEvent.DeviceVerificationChanged, userId, deviceId, deviceObj); return deviceObj; } @@ -3100,7 +3100,7 @@ export class Crypto extends TypedEventEmitter { event.attemptDecryption(this); } // once the event has been decrypted, try again - event.once(MatrixEventEvents.Decrypted, (ev) => { + event.once(MatrixEventEvent.Decrypted, (ev) => { this.onToDeviceEvent(ev); }); } @@ -3249,15 +3249,15 @@ export class Crypto extends TypedEventEmitter { reject(new Error("Event status set to CANCELLED.")); } }; - event.once(MatrixEventEvents.LocalEventIdReplaced, eventIdListener); - event.on(MatrixEventEvents.Status, statusListener); + event.once(MatrixEventEvent.LocalEventIdReplaced, eventIdListener); + event.on(MatrixEventEvent.Status, statusListener); }); } catch (err) { logger.error("error while waiting for the verification event to be sent: " + err.message); return; } finally { - event.removeListener(MatrixEventEvents.LocalEventIdReplaced, eventIdListener); - event.removeListener(MatrixEventEvents.Status, statusListener); + event.removeListener(MatrixEventEvent.LocalEventIdReplaced, eventIdListener); + event.removeListener(MatrixEventEvent.Status, statusListener); } } let request = requestsMap.getRequest(event); @@ -3585,7 +3585,7 @@ export class Crypto extends TypedEventEmitter { return; } - this.emit(CryptoEvents.RoomKeyRequest, req); + this.emit(CryptoEvent.RoomKeyRequest, req); } /** @@ -3604,7 +3604,7 @@ export class Crypto extends TypedEventEmitter { // we should probably only notify the app of cancellations we told it // about, but we don't currently have a record of that, so we just pass // everything through. - this.emit(CryptoEvents.RoomKeyRequestCancellation, cancellation); + this.emit(CryptoEvent.RoomKeyRequestCancellation, cancellation); } /** diff --git a/src/crypto/verification/request/VerificationRequest.ts b/src/crypto/verification/request/VerificationRequest.ts index 498d2dcaa2d..cf56912d701 100644 --- a/src/crypto/verification/request/VerificationRequest.ts +++ b/src/crypto/verification/request/VerificationRequest.ts @@ -75,12 +75,12 @@ interface ITransition { event?: MatrixEvent; } -export enum VerificationRequestEvents { +export enum VerificationRequestEvent { Change = "change", } type EventHandlerMap = { - [VerificationRequestEvents.Change]: () => void; + [VerificationRequestEvent.Change]: () => void; }; /** @@ -91,7 +91,7 @@ type EventHandlerMap = { */ export class VerificationRequest< C extends IVerificationChannel = IVerificationChannel, -> extends TypedEventEmitter { +> extends TypedEventEmitter { private eventsByUs = new Map(); private eventsByThem = new Map(); private _observeOnly = false; @@ -462,7 +462,7 @@ export class VerificationRequest< public async cancel({ reason = "User declined", code = "m.user" } = {}): Promise { if (!this.observeOnly && this._phase !== PHASE_CANCELLED) { this._declining = true; - this.emit(VerificationRequestEvents.Change); + this.emit(VerificationRequestEvent.Change); if (this._verifier) { return this._verifier.cancel(errorFactory(code, reason)()); } else { @@ -480,7 +480,7 @@ export class VerificationRequest< if (!this.observeOnly && this.phase === PHASE_REQUESTED && !this.initiatedByMe) { const methods = [...this.verificationMethods.keys()]; this._accepting = true; - this.emit(VerificationRequestEvents.Change); + this.emit(VerificationRequestEvent.Change); await this.channel.send(READY_TYPE, { methods }); } } @@ -504,12 +504,12 @@ export class VerificationRequest< handled = true; } if (handled) { - this.off(VerificationRequestEvents.Change, check); + this.off(VerificationRequestEvent.Change, check); } return handled; }; if (!check()) { - this.on(VerificationRequestEvents.Change, check); + this.on(VerificationRequestEvent.Change, check); } }); } @@ -517,7 +517,7 @@ export class VerificationRequest< private setPhase(phase: Phase, notify = true): void { this._phase = phase; if (notify) { - this.emit(VerificationRequestEvents.Change); + this.emit(VerificationRequestEvent.Change); } } @@ -777,7 +777,7 @@ export class VerificationRequest< // set phase as last thing as this emits the "change" event this.setPhase(phase); } else if (this._observeOnly !== wasObserveOnly) { - this.emit(VerificationRequestEvents.Change); + this.emit(VerificationRequestEvent.Change); } } finally { // log events we processed so we can see from rageshakes what events were added to a request diff --git a/src/models/event-timeline-set.ts b/src/models/event-timeline-set.ts index b8ae0a063d4..2082730179f 100644 --- a/src/models/event-timeline-set.ts +++ b/src/models/event-timeline-set.ts @@ -19,7 +19,7 @@ limitations under the License. */ import { EventTimeline } from "./event-timeline"; -import { EventStatus, MatrixEvent, MatrixEventEvents } from "./event"; +import { EventStatus, MatrixEvent, MatrixEventEvent } from "./event"; import { logger } from '../logger'; import { Relations } from './relations'; import { Room } from "./room"; @@ -56,19 +56,19 @@ export interface IRoomTimelineData { liveEvent?: boolean; } -export enum EventTimelineSetEvents { +export enum EventTimelineSetEvent { RoomTimeline = "Room.timeline", RoomTimelineReset = "Room.timelineReset", } export type EventHandlerMap = { - [EventTimelineSetEvents.RoomTimeline]: + [EventTimelineSetEvent.RoomTimeline]: (event: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed: boolean, data: IRoomTimelineData) => void; - [EventTimelineSetEvents.RoomTimelineReset]: + [EventTimelineSetEvent.RoomTimelineReset]: (room: Room, eventTimelineSet: EventTimelineSet, resetAllTimelines: boolean) => void; }; -export class EventTimelineSet extends TypedEventEmitter { +export class EventTimelineSet extends TypedEventEmitter { private readonly timelineSupport: boolean; private unstableClientRelationAggregation: boolean; private displayPendingEvents: boolean; @@ -258,7 +258,7 @@ export class EventTimelineSet extends TypedEventEmitter { + event.once(MatrixEventEvent.Decrypted, () => { this.aggregateRelations(event); }); return; diff --git a/src/models/event.ts b/src/models/event.ts index 1402f214933..1ec7402a25b 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -204,7 +204,7 @@ export interface IMessageVisibilityHidden { // A singleton implementing `IMessageVisibilityVisible`. const MESSAGE_VISIBLE: IMessageVisibilityVisible = Object.freeze({ visible: true }); -export enum MatrixEventEvents { +export enum MatrixEventEvent { Decrypted = "Event.decrypted", BeforeRedaction = "Event.beforeRedaction", VisibilityChange = "Event.visibilityChange", @@ -214,16 +214,16 @@ export enum MatrixEventEvents { RelationsCreated = "Event.relationsCreated", } -type EmittedEvents = MatrixEventEvents | ThreadEvent.Update; +type EmittedEvents = MatrixEventEvent | ThreadEvent.Update; type EventHandlerMap = { - [MatrixEventEvents.Decrypted]: (event: MatrixEvent, err?: Error) => void; - [MatrixEventEvents.BeforeRedaction]: (event: MatrixEvent, redactionEvent: MatrixEvent) => void; - [MatrixEventEvents.VisibilityChange]: (event: MatrixEvent, visible: boolean) => void; - [MatrixEventEvents.LocalEventIdReplaced]: (event: MatrixEvent) => void; - [MatrixEventEvents.Status]: (event: MatrixEvent, status: EventStatus) => void; - [MatrixEventEvents.Replaced]: (event: MatrixEvent) => void; - [MatrixEventEvents.RelationsCreated]: (relationType: string, eventType: string) => void; + [MatrixEventEvent.Decrypted]: (event: MatrixEvent, err?: Error) => void; + [MatrixEventEvent.BeforeRedaction]: (event: MatrixEvent, redactionEvent: MatrixEvent) => void; + [MatrixEventEvent.VisibilityChange]: (event: MatrixEvent, visible: boolean) => void; + [MatrixEventEvent.LocalEventIdReplaced]: (event: MatrixEvent) => void; + [MatrixEventEvent.Status]: (event: MatrixEvent, status: EventStatus) => void; + [MatrixEventEvent.Replaced]: (event: MatrixEvent) => void; + [MatrixEventEvent.RelationsCreated]: (relationType: string, eventType: string) => void; } & Pick; export class MatrixEvent extends TypedEventEmitter { @@ -888,7 +888,7 @@ export class MatrixEvent extends TypedEventEmitter [...c, ...p.getRelations()], []); } - public on(ev: T, fn: Listener) { + public on(ev: T, fn: Listener) { this.relations.forEach(r => r.on(ev, fn)); } - public off(ev: T, fn: Listener) { + public off(ev: T, fn: Listener) { this.relations.forEach(r => r.off(ev, fn)); } } diff --git a/src/models/relations.ts b/src/models/relations.ts index 671851b750d..1bd70929700 100644 --- a/src/models/relations.ts +++ b/src/models/relations.ts @@ -14,22 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { EventStatus, IAggregatedRelation, MatrixEvent, MatrixEventEvents } from './event'; +import { EventStatus, IAggregatedRelation, MatrixEvent, MatrixEventEvent } from './event'; import { Room } from './room'; import { logger } from '../logger'; import { RelationType } from "../@types/event"; import { TypedEventEmitter } from "./typed-event-emitter"; -export enum RelationsEvents { +export enum RelationsEvent { Add = "Relations.add", Remove = "Relations.remove", Redaction = "Relations.redaction", } export type EventHandlerMap = { - [RelationsEvents.Add]: (event: MatrixEvent) => void; - [RelationsEvents.Remove]: (event: MatrixEvent) => void; - [RelationsEvents.Redaction]: (event: MatrixEvent) => void; + [RelationsEvent.Add]: (event: MatrixEvent) => void; + [RelationsEvent.Remove]: (event: MatrixEvent) => void; + [RelationsEvent.Redaction]: (event: MatrixEvent) => void; }; /** @@ -40,7 +40,7 @@ export type EventHandlerMap = { * The typical way to get one of these containers is via * EventTimelineSet#getRelationsForEvent. */ -export class Relations extends TypedEventEmitter { +export class Relations extends TypedEventEmitter { private relationEventIds = new Set(); private relations = new Set(); private annotationsByKey: Record> = {}; @@ -95,7 +95,7 @@ export class Relations extends TypedEventEmitter { if (!event.isSending()) { // Sending is done, so we don't need to listen anymore - event.removeListener(MatrixEventEvents.Status, this.onEventStatus); + event.removeListener(MatrixEventEvent.Status, this.onEventStatus); return; } if (status !== EventStatus.CANCELLED) { return; } // Event was cancelled, remove from the collection - event.removeListener(MatrixEventEvents.Status, this.onEventStatus); + event.removeListener(MatrixEventEvent.Status, this.onEventStatus); this.removeEvent(event); }; @@ -266,9 +266,9 @@ export class Relations extends TypedEventEmitter void; - [RoomMemberEvents.Name]: (event: MatrixEvent, member: RoomMember, oldName: string | null) => void; - [RoomMemberEvents.PowerLevel]: (event: MatrixEvent, member: RoomMember) => void; - [RoomMemberEvents.Typing]: (event: MatrixEvent, member: RoomMember) => void; + [RoomMemberEvent.Membership]: (event: MatrixEvent, member: RoomMember, oldMembership: string | null) => void; + [RoomMemberEvent.Name]: (event: MatrixEvent, member: RoomMember, oldName: string | null) => void; + [RoomMemberEvent.PowerLevel]: (event: MatrixEvent, member: RoomMember) => void; + [RoomMemberEvent.Typing]: (event: MatrixEvent, member: RoomMember) => void; }; -export class RoomMember extends TypedEventEmitter { +export class RoomMember extends TypedEventEmitter { private _isOutOfBand = false; private _modified: number; public _requestedProfileInfo: boolean; // used by sync.ts @@ -164,11 +164,11 @@ export class RoomMember extends TypedEventEmitter void; - [RoomStateEvents.Members]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void; - [RoomStateEvents.NewMember]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void; + [RoomStateEvent.Events]: (event: MatrixEvent, state: RoomState, lastStateEvent: MatrixEvent | null) => void; + [RoomStateEvent.Members]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void; + [RoomStateEvent.NewMember]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void; }; -export class RoomState extends TypedEventEmitter { +export class RoomState extends TypedEventEmitter { private sentinels: Record = {}; // userId: RoomMember // stores fuzzy matches to a list of userIDs (applies utils.removeHiddenChars to keys) private displayNameToUserIds: Record = {}; @@ -318,7 +318,7 @@ export class RoomState extends TypedEventEmitter void; - [RoomEvents.Tags]: (event: MatrixEvent, room: Room) => void; - [RoomEvents.AccountData]: (event: MatrixEvent, room: Room, lastEvent?: MatrixEvent) => void; - [RoomEvents.Receipt]: (event: MatrixEvent, room: Room) => void; - [RoomEvents.Name]: (room: Room) => void; - [RoomEvents.Redaction]: (event: MatrixEvent, room: Room) => void; - [RoomEvents.RedactionCancelled]: (event: MatrixEvent, room: Room) => void; - [RoomEvents.LocalEchoUpdated]: ( + [RoomEvent.MyMembership]: (room: Room, membership: string, prevMembership?: string) => void; + [RoomEvent.Tags]: (event: MatrixEvent, room: Room) => void; + [RoomEvent.AccountData]: (event: MatrixEvent, room: Room, lastEvent?: MatrixEvent) => void; + [RoomEvent.Receipt]: (event: MatrixEvent, room: Room) => void; + [RoomEvent.Name]: (room: Room) => void; + [RoomEvent.Redaction]: (event: MatrixEvent, room: Room) => void; + [RoomEvent.RedactionCancelled]: (event: MatrixEvent, room: Room) => void; + [RoomEvent.LocalEchoUpdated]: ( event: MatrixEvent, room: Room, oldEventId?: string, @@ -176,7 +176,7 @@ type EventHandlerMap = { [ThreadEvent.New]: (thread: Thread) => void; } & Pick< ThreadHandlerMap, - EventTimelineSetEvents.RoomTimeline | EventTimelineSetEvents.RoomTimelineReset | ThreadEvent.Update + EventTimelineSetEvent.RoomTimeline | EventTimelineSetEvent.RoomTimelineReset | ThreadEvent.Update >; export class Room extends TypedEventEmitter { @@ -333,8 +333,8 @@ export class Room extends TypedEventEmitter { // the subsequent ones are the filtered ones in no particular order. this.timelineSets = [new EventTimelineSet(this, opts)]; this.reEmitter.reEmit(this.getUnfilteredTimelineSet(), [ - EventTimelineSetEvents.RoomTimeline, - EventTimelineSetEvents.RoomTimelineReset, + EventTimelineSetEvent.RoomTimeline, + EventTimelineSetEvent.RoomTimelineReset, ]); this.fixUpLegacyTimelineFields(); @@ -749,7 +749,7 @@ export class Room extends TypedEventEmitter { if (membership === "leave") { this.cleanupAfterLeaving(); } - this.emit(RoomEvents.MyMembership, this, membership, prevMembership); + this.emit(RoomEvent.MyMembership, this, membership, prevMembership); } } @@ -1323,8 +1323,8 @@ export class Room extends TypedEventEmitter { const opts = Object.assign({ filter: filter }, this.opts); const timelineSet = new EventTimelineSet(this, opts); this.reEmitter.reEmit(timelineSet, [ - EventTimelineSetEvents.RoomTimeline, - EventTimelineSetEvents.RoomTimelineReset, + EventTimelineSetEvent.RoomTimeline, + EventTimelineSetEvent.RoomTimelineReset, ]); this.filteredTimelineSets[filter.filterId] = timelineSet; this.timelineSets.push(timelineSet); @@ -1453,8 +1453,8 @@ export class Room extends TypedEventEmitter { this.threads.set(thread.id, thread); this.reEmitter.reEmit(thread, [ ThreadEvent.Update, - EventTimelineSetEvents.RoomTimeline, - EventTimelineSetEvents.RoomTimelineReset, + EventTimelineSetEvent.RoomTimeline, + EventTimelineSetEvent.RoomTimelineReset, ]); if (!this.lastThread || this.lastThread.rootEvent.localTimestamp < rootEvent.localTimestamp) { @@ -1496,7 +1496,7 @@ export class Room extends TypedEventEmitter { } } - this.emit(RoomEvents.Redaction, event, this); + this.emit(RoomEvent.Redaction, event, this); // TODO: we stash user displaynames (among other things) in // RoomMember objects which are then attached to other events @@ -1618,7 +1618,7 @@ export class Room extends TypedEventEmitter { } if (redactedEvent) { redactedEvent.markLocallyRedacted(event); - this.emit(RoomEvents.Redaction, event, this); + this.emit(RoomEvent.Redaction, event, this); } } } else { @@ -1636,7 +1636,7 @@ export class Room extends TypedEventEmitter { } } - this.emit(RoomEvents.LocalEchoUpdated, event, this, null, null); + this.emit(RoomEvent.LocalEchoUpdated, event, this, null, null); } /** @@ -1764,7 +1764,7 @@ export class Room extends TypedEventEmitter { } } - this.emit(RoomEvents.LocalEchoUpdated, localEvent, this, oldEventId, oldStatus); + this.emit(RoomEvent.LocalEchoUpdated, localEvent, this, oldEventId, oldStatus); } /** @@ -1848,7 +1848,7 @@ export class Room extends TypedEventEmitter { } this.savePendingEvents(); - this.emit(RoomEvents.LocalEchoUpdated, event, this, oldEventId, oldStatus); + this.emit(RoomEvent.LocalEchoUpdated, event, this, oldEventId, oldStatus); } private revertRedactionLocalEcho(redactionEvent: MatrixEvent): void { @@ -1861,7 +1861,7 @@ export class Room extends TypedEventEmitter { if (redactedEvent) { redactedEvent.unmarkLocallyRedacted(); // re-render after undoing redaction - this.emit(RoomEvents.RedactionCancelled, redactionEvent, this); + this.emit(RoomEvent.RedactionCancelled, redactionEvent, this); // reapply relation now redaction failed if (redactedEvent.isRelation()) { this.aggregateNonLiveRelation(redactedEvent); @@ -2001,7 +2001,7 @@ export class Room extends TypedEventEmitter { }); if (oldName !== this.name) { - this.emit(RoomEvents.Name, this); + this.emit(RoomEvent.Name, this); } } @@ -2094,7 +2094,7 @@ export class Room extends TypedEventEmitter { this.addReceiptsToStructure(event, synthetic); // send events after we've regenerated the structure & cache, otherwise things that // listened for the event would read stale data. - this.emit(RoomEvents.Receipt, event, this); + this.emit(RoomEvent.Receipt, event, this); } /** @@ -2228,7 +2228,7 @@ export class Room extends TypedEventEmitter { // XXX: we could do a deep-comparison to see if the tags have really // changed - but do we want to bother? - this.emit(RoomEvents.Tags, event, this); + this.emit(RoomEvent.Tags, event, this); } /** @@ -2243,7 +2243,7 @@ export class Room extends TypedEventEmitter { } const lastEvent = this.accountData[event.getType()]; this.accountData[event.getType()] = event; - this.emit(RoomEvents.AccountData, event, this, lastEvent); + this.emit(RoomEvent.AccountData, event, this, lastEvent); } } diff --git a/src/models/thread.ts b/src/models/thread.ts index e0a82b32fda..c6d3362cd1b 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { EventTimelineSetEvents, MatrixClient, RoomEvents } from "../matrix"; +import { EventTimelineSetEvent, MatrixClient, RoomEvent } from "../matrix"; import { TypedReEmitter } from "../ReEmitter"; import { RelationType } from "../@types/event"; import { IRelationsRequestOpts } from "../@types/requests"; @@ -33,14 +33,14 @@ export enum ThreadEvent { } type EmittedEvents = Exclude - | EventTimelineSetEvents.RoomTimeline - | EventTimelineSetEvents.RoomTimelineReset; + | EventTimelineSetEvent.RoomTimeline + | EventTimelineSetEvent.RoomTimelineReset; export type EventHandlerMap = { [ThreadEvent.Update]: (thread: Thread) => void; [ThreadEvent.NewReply]: (thread: Thread, event: MatrixEvent) => void; [ThreadEvent.ViewThread]: () => void; -} & Pick; +} & Pick; interface IThreadOpts { initialEvents?: MatrixEvent[]; @@ -87,8 +87,8 @@ export class Thread extends TypedEventEmitter { this.reEmitter = new TypedReEmitter(this); this.reEmitter.reEmit(this.timelineSet, [ - EventTimelineSetEvents.RoomTimeline, - EventTimelineSetEvents.RoomTimelineReset, + EventTimelineSetEvent.RoomTimeline, + EventTimelineSetEvent.RoomTimelineReset, ]); // If we weren't able to find the root event, it's probably missing @@ -103,8 +103,8 @@ export class Thread extends TypedEventEmitter { opts?.initialEvents?.forEach(event => this.addEvent(event)); - this.room.on(RoomEvents.LocalEchoUpdated, this.onEcho); - this.room.on(EventTimelineSetEvents.RoomTimeline, this.onEcho); + this.room.on(RoomEvent.LocalEchoUpdated, this.onEcho); + this.room.on(EventTimelineSetEvent.RoomTimeline, this.onEcho); } public get hasServerSideSupport(): boolean { diff --git a/src/models/user.ts b/src/models/user.ts index 934e252cd28..7ceff037c7d 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -21,7 +21,7 @@ limitations under the License. import { MatrixEvent } from "./event"; import { TypedEventEmitter } from "./typed-event-emitter"; -export enum UserEvents { +export enum UserEvent { DisplayName = "User.displayName", AvatarUrl = "User.avatarUrl", Presence = "User.presence", @@ -32,15 +32,15 @@ export enum UserEvents { } type EventHandlerMap = { - [UserEvents.DisplayName]: (event: MatrixEvent | undefined, user: User) => void; - [UserEvents.AvatarUrl]: (event: MatrixEvent | undefined, user: User) => void; - [UserEvents.Presence]: (event: MatrixEvent | undefined, user: User) => void; - [UserEvents.CurrentlyActive]: (event: MatrixEvent | undefined, user: User) => void; - [UserEvents.LastPresenceTs]: (event: MatrixEvent | undefined, user: User) => void; - [UserEvents._UnstableStatusMessage]: (user: User) => void; + [UserEvent.DisplayName]: (event: MatrixEvent | undefined, user: User) => void; + [UserEvent.AvatarUrl]: (event: MatrixEvent | undefined, user: User) => void; + [UserEvent.Presence]: (event: MatrixEvent | undefined, user: User) => void; + [UserEvent.CurrentlyActive]: (event: MatrixEvent | undefined, user: User) => void; + [UserEvent.LastPresenceTs]: (event: MatrixEvent | undefined, user: User) => void; + [UserEvent._UnstableStatusMessage]: (user: User) => void; }; -export class User extends TypedEventEmitter { +export class User extends TypedEventEmitter { private modified: number; // XXX these should be read-only @@ -111,25 +111,25 @@ export class User extends TypedEventEmitter { const firstFire = this.events.presence === null; this.events.presence = event; - const eventsToFire: UserEvents[] = []; + const eventsToFire: UserEvent[] = []; if (event.getContent().presence !== this.presence || firstFire) { - eventsToFire.push(UserEvents.Presence); + eventsToFire.push(UserEvent.Presence); } if (event.getContent().avatar_url && event.getContent().avatar_url !== this.avatarUrl) { - eventsToFire.push(UserEvents.AvatarUrl); + eventsToFire.push(UserEvent.AvatarUrl); } if (event.getContent().displayname && event.getContent().displayname !== this.displayName) { - eventsToFire.push(UserEvents.DisplayName); + eventsToFire.push(UserEvent.DisplayName); } if (event.getContent().currently_active !== undefined && event.getContent().currently_active !== this.currentlyActive) { - eventsToFire.push(UserEvents.CurrentlyActive); + eventsToFire.push(UserEvent.CurrentlyActive); } this.presence = event.getContent().presence; - eventsToFire.push(UserEvents.LastPresenceTs); + eventsToFire.push(UserEvent.LastPresenceTs); if (event.getContent().status_msg) { this.presenceStatusMsg = event.getContent().status_msg; @@ -230,7 +230,7 @@ export class User extends TypedEventEmitter { if (!event.getContent()) this.unstable_statusMessage = ""; else this.unstable_statusMessage = event.getContent()["status"]; this.updateModifiedTime(); - this.emit(UserEvents._UnstableStatusMessage, this); + this.emit(UserEvent._UnstableStatusMessage, this); } } diff --git a/src/store/memory.ts b/src/store/memory.ts index c3ae49bbde7..b29d3d3647a 100644 --- a/src/store/memory.ts +++ b/src/store/memory.ts @@ -24,7 +24,7 @@ import { Group } from "../models/group"; import { Room } from "../models/room"; import { User } from "../models/user"; import { IEvent, MatrixEvent } from "../models/event"; -import { RoomState, RoomStateEvents } from "../models/room-state"; +import { RoomState, RoomStateEvent } from "../models/room-state"; import { RoomMember } from "../models/room-member"; import { Filter } from "../filter"; import { ISavedSync, IStore } from "./index"; @@ -126,7 +126,7 @@ export class MemoryStore implements IStore { this.rooms[room.roomId] = room; // add listeners for room member changes so we can keep the room member // map up-to-date. - room.currentState.on(RoomStateEvents.Members, this.onRoomMember); + room.currentState.on(RoomStateEvent.Members, this.onRoomMember); // add existing members room.currentState.getMembers().forEach((m) => { this.onRoomMember(null, room.currentState, m); @@ -185,7 +185,7 @@ export class MemoryStore implements IStore { */ public removeRoom(roomId: string): void { if (this.rooms[roomId]) { - this.rooms[roomId].currentState.removeListener(RoomStateEvents.Members, this.onRoomMember); + this.rooms[roomId].currentState.removeListener(RoomStateEvent.Members, this.onRoomMember); } delete this.rooms[roomId]; } diff --git a/src/sync.ts b/src/sync.ts index 3a7e1d55160..aae06a67410 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -53,7 +53,7 @@ import { MatrixError, Method } from "./http-api"; import { ISavedSync } from "./store"; import { EventType } from "./@types/event"; import { IPushRules } from "./@types/PushRules"; -import { RoomStateEvents } from "./models/room-state"; +import { RoomStateEvent } from "./models/room-state"; const DEBUG = true; @@ -232,7 +232,7 @@ export class SyncApi { client.reEmitter.reEmit(room.currentState, [ "RoomState.events", "RoomState.members", "RoomState.newMember", ]); - room.currentState.on(RoomStateEvents.NewMember, function(event, state, member) { + room.currentState.on(RoomStateEvent.NewMember, function(event, state, member) { member.user = client.getUser(member.userId); client.reEmitter.reEmit( member, @@ -252,9 +252,9 @@ export class SyncApi { */ private deregisterStateListeners(room: Room): void { // could do with a better way of achieving this. - room.currentState.removeAllListeners(RoomStateEvents.Events); - room.currentState.removeAllListeners(RoomStateEvents.Members); - room.currentState.removeAllListeners(RoomStateEvents.NewMember); + room.currentState.removeAllListeners(RoomStateEvent.Events); + room.currentState.removeAllListeners(RoomStateEvent.Members); + room.currentState.removeAllListeners(RoomStateEvent.NewMember); } /** diff --git a/src/webrtc/callEventHandler.ts b/src/webrtc/callEventHandler.ts index eb69ff98c33..58d9652ac31 100644 --- a/src/webrtc/callEventHandler.ts +++ b/src/webrtc/callEventHandler.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { MatrixEvent, MatrixEventEvents } from '../models/event'; +import { MatrixEvent, MatrixEventEvent } from '../models/event'; import { logger } from '../logger'; import { CallDirection, CallErrorCode, CallState, createNewMatrixCall, MatrixCall } from './call'; import { EventType } from '../@types/event'; @@ -101,7 +101,7 @@ export class CallEventHandler { if (event.isBeingDecrypted() || event.isDecryptionFailure()) { // add an event listener for once the event is decrypted. - event.once(MatrixEventEvents.Decrypted, async () => { + event.once(MatrixEventEvent.Decrypted, async () => { if (!this.eventIsACall(event)) return; if (this.callEventBuffer.includes(event)) { From d1b8d70f75f41d16b0b821d751ea1b178c48b28d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 12:38:14 +0000 Subject: [PATCH 17/30] Fix CrossSigning wrongly extending EventEmitted --- src/crypto/CrossSigning.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/crypto/CrossSigning.ts b/src/crypto/CrossSigning.ts index 077d705b846..21dd0ee1623 100644 --- a/src/crypto/CrossSigning.ts +++ b/src/crypto/CrossSigning.ts @@ -19,7 +19,6 @@ limitations under the License. * @module crypto/CrossSigning */ -import { EventEmitter } from 'events'; import { PkSigning } from "@matrix-org/olm"; import { decodeBase64, encodeBase64, pkSign, pkVerify } from './olmlib'; @@ -55,7 +54,7 @@ export interface ICrossSigningInfo { crossSigningVerifiedBefore: boolean; } -export class CrossSigningInfo extends EventEmitter { +export class CrossSigningInfo { public keys: Record = {}; public firstUse = true; // This tracks whether we've ever verified this user with any identity. @@ -79,9 +78,7 @@ export class CrossSigningInfo extends EventEmitter { public readonly userId: string, private callbacks: ICryptoCallbacks = {}, private cacheCallbacks: ICacheCallbacks = {}, - ) { - super(); - } + ) {} public static fromStorage(obj: ICrossSigningInfo, userId: string): CrossSigningInfo { const res = new CrossSigningInfo(userId); From 9685fbb7db1548b882a45999710d5e4d7383e705 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 12:50:34 +0000 Subject: [PATCH 18/30] Type the IAccountDataClient & AccountDataClientAdapter EventEmitters --- src/client.ts | 10 ++++++++++ src/crypto/EncryptionSetup.ts | 21 ++++++++++++++++----- src/crypto/SecretStorage.ts | 18 +++++++++--------- src/models/event-timeline-set.ts | 4 ++-- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/client.ts b/src/client.ts index c560b68e82d..f54d04f87b1 100644 --- a/src/client.ts +++ b/src/client.ts @@ -747,6 +747,16 @@ interface ITimestampToEventResponse { // Probably not the most graceful solution but does a good enough job for now const EVENT_ID_PREFIX = "$"; +export enum ClientEvent { + AccountData = "accountData", +} + +type EmittedEvents = ClientEvent; + +export type ClientEventHandlerMap = { + [ClientEvent.AccountData]: (event: MatrixEvent, lastEvent?: MatrixEvent) => void; +}; + /** * Represents a Matrix Client. Only directly construct this if you want to use * custom modules. Normally, {@link createClient} should be used diff --git a/src/crypto/EncryptionSetup.ts b/src/crypto/EncryptionSetup.ts index 27bcf7d780d..61ba34eaf99 100644 --- a/src/crypto/EncryptionSetup.ts +++ b/src/crypto/EncryptionSetup.ts @@ -14,17 +14,25 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { EventEmitter } from "events"; - import { logger } from "../logger"; import { MatrixEvent } from "../models/event"; import { createCryptoStoreCacheCallbacks, ICacheCallbacks } from "./CrossSigning"; import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store'; import { Method, PREFIX_UNSTABLE } from "../http-api"; import { Crypto, IBootstrapCrossSigningOpts } from "./index"; -import { CrossSigningKeys, ICrossSigningKey, ICryptoCallbacks, ISignedKey, KeySignatures } from "../matrix"; +import { + ClientEvent, + CrossSigningKeys, + ClientEventHandlerMap, + ICrossSigningKey, + ICryptoCallbacks, + ISignedKey, + KeySignatures, +} from "../matrix"; import { ISecretStorageKeyInfo } from "./api"; import { IKeyBackupInfo } from "./keybackup"; +import { TypedEventEmitter } from "../models/typed-event-emitter"; +import { IAccountDataClient } from "./SecretStorage"; interface ICrossSigningKeys { authUpload: IBootstrapCrossSigningOpts["authUploadDeviceSigningKeys"]; @@ -256,7 +264,10 @@ export class EncryptionSetupOperation { * Catches account data set by SecretStorage during bootstrapping by * implementing the methods related to account data in MatrixClient */ -class AccountDataClientAdapter extends EventEmitter { +class AccountDataClientAdapter + extends TypedEventEmitter + implements IAccountDataClient { + // public readonly values = new Map(); /** @@ -303,7 +314,7 @@ class AccountDataClientAdapter extends EventEmitter { // and it seems to rely on this. return Promise.resolve().then(() => { const event = new MatrixEvent({ type, content }); - this.emit("accountData", event, lastEvent); + this.emit(ClientEvent.AccountData, event, lastEvent); return {}; }); } diff --git a/src/crypto/SecretStorage.ts b/src/crypto/SecretStorage.ts index f3cdb8683f3..b0c7891d0d6 100644 --- a/src/crypto/SecretStorage.ts +++ b/src/crypto/SecretStorage.ts @@ -14,15 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { EventEmitter } from 'stream'; - import { logger } from '../logger'; import * as olmlib from './olmlib'; +import { encodeBase64 } from './olmlib'; import { randomString } from '../randomstring'; -import { encryptAES, decryptAES, IEncryptedPayload, calculateKeyCheck } from './aes'; -import { encodeBase64 } from "./olmlib"; -import { ICryptoCallbacks, MatrixClient, MatrixEvent } from '../matrix'; +import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from './aes'; +import { ClientEvent, ICryptoCallbacks, MatrixEvent } from '../matrix'; +import { ClientEventHandlerMap, MatrixClient } from "../client"; import { IAddSecretStorageKeyOpts, ISecretStorageKeyInfo } from './api'; +import { TypedEventEmitter } from '../models/typed-event-emitter'; export const SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha2"; @@ -36,7 +36,7 @@ export interface ISecretRequest { cancel: (reason: string) => void; } -export interface IAccountDataClient extends EventEmitter { +export interface IAccountDataClient extends TypedEventEmitter { // Subset of MatrixClient (which also uses any for the event content) getAccountDataFromServer: (eventType: string) => Promise; getAccountData: (eventType: string) => MatrixEvent; @@ -98,17 +98,17 @@ export class SecretStorage { ev.getType() === 'm.secret_storage.default_key' && ev.getContent().key === keyId ) { - this.accountDataAdapter.removeListener('accountData', listener); + this.accountDataAdapter.removeListener(ClientEvent.AccountData, listener); resolve(); } }; - this.accountDataAdapter.on('accountData', listener); + this.accountDataAdapter.on(ClientEvent.AccountData, listener); this.accountDataAdapter.setAccountData( 'm.secret_storage.default_key', { key: keyId }, ).catch(e => { - this.accountDataAdapter.removeListener('accountData', listener); + this.accountDataAdapter.removeListener(ClientEvent.AccountData, listener); reject(e); }); }); diff --git a/src/models/event-timeline-set.ts b/src/models/event-timeline-set.ts index 2082730179f..11d57226f6f 100644 --- a/src/models/event-timeline-set.ts +++ b/src/models/event-timeline-set.ts @@ -61,14 +61,14 @@ export enum EventTimelineSetEvent { RoomTimelineReset = "Room.timelineReset", } -export type EventHandlerMap = { +export type EventTimelineSetHandlerMap = { [EventTimelineSetEvent.RoomTimeline]: (event: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed: boolean, data: IRoomTimelineData) => void; [EventTimelineSetEvent.RoomTimelineReset]: (room: Room, eventTimelineSet: EventTimelineSet, resetAllTimelines: boolean) => void; }; -export class EventTimelineSet extends TypedEventEmitter { +export class EventTimelineSet extends TypedEventEmitter { private readonly timelineSupport: boolean; private unstableClientRelationAggregation: boolean; private displayPendingEvents: boolean; From 87ccd56d1e2156cd678e07aabd5dddc87f5fd2cd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 12:52:32 +0000 Subject: [PATCH 19/30] Re-type the LocalStorageErrorsEventsEmitter --- src/store/local-storage-events-emitter.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/store/local-storage-events-emitter.ts b/src/store/local-storage-events-emitter.ts index 2320cc1ef84..24524c63438 100644 --- a/src/store/local-storage-events-emitter.ts +++ b/src/store/local-storage-events-emitter.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { EventEmitter } from "events"; +import { TypedEventEmitter } from "../models/typed-event-emitter"; export enum LocalStorageErrors { Global = 'Global', @@ -25,6 +25,15 @@ export enum LocalStorageErrors { QuotaExceededError = 'QuotaExceededError' } +type EventHandlerMap = { + [LocalStorageErrors.Global]: (error: Error) => void; + [LocalStorageErrors.SetItemError]: (error: Error) => void; + [LocalStorageErrors.GetItemError]: (error: Error) => void; + [LocalStorageErrors.RemoveItemError]: (error: Error) => void; + [LocalStorageErrors.ClearError]: (error: Error) => void; + [LocalStorageErrors.QuotaExceededError]: (error: Error) => void; +}; + /** * Used in element-web as a temporary hack to handle all the localStorage errors on the highest level possible * As of 15.11.2021 (DD/MM/YYYY) we're not properly handling local storage exceptions anywhere. @@ -33,5 +42,5 @@ export enum LocalStorageErrors { * maybe you should check out your disk, as it's probably dying and your session may die with it. * See: https://github.com/vector-im/element-web/issues/18423 */ -class LocalStorageErrorsEventsEmitter extends EventEmitter {} +class LocalStorageErrorsEventsEmitter extends TypedEventEmitter {} export const localStorageErrorsEventsEmitter = new LocalStorageErrorsEventsEmitter(); From 2712bfe8dec62a44aabeacd5a36be3c412093e4b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 13:23:27 +0000 Subject: [PATCH 20/30] Type more EventEmitters --- src/client.ts | 2 +- src/crypto/index.ts | 8 ++++--- src/crypto/verification/Base.ts | 18 ++++++++++---- src/crypto/verification/IllegalMethod.ts | 4 ++-- src/crypto/verification/QRCode.ts | 24 +++++++++++++------ src/crypto/verification/SAS.ts | 21 ++++++++++++---- .../request/VerificationRequest.ts | 11 +++++---- src/models/thread.ts | 2 +- src/models/typed-event-emitter.ts | 6 +++-- 9 files changed, 68 insertions(+), 28 deletions(-) diff --git a/src/client.ts b/src/client.ts index f54d04f87b1..5ce9ee02c3e 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1830,7 +1830,7 @@ export class MatrixClient extends EventEmitter { * @returns {Verification} a verification object * @deprecated Use `requestVerification` instead. */ - public beginKeyVerification(method: string, userId: string, deviceId: string): Verification { + public beginKeyVerification(method: string, userId: string, deviceId: string): Verification { if (!this.crypto) { throw new Error("End-to-end encryption disabled"); } diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 7ce6628a5f5..5d818b9b458 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -35,6 +35,7 @@ import * as algorithms from "./algorithms"; import { createCryptoStoreCacheCallbacks, CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from './CrossSigning'; import { EncryptionSetupBuilder } from "./EncryptionSetup"; import { + IAccountDataClient, ISecretRequest, SECRET_STORAGE_ALGORITHM_V1_AES, SecretStorage, @@ -338,7 +339,7 @@ export class Crypto extends TypedEventEmitter { if (defaultVerificationMethods[method]) { this.verificationMethods.set( method, - defaultVerificationMethods[method], + defaultVerificationMethods[method], ); } } else if (method["NAME"]) { @@ -351,7 +352,8 @@ export class Crypto extends TypedEventEmitter { } } } else { - this.verificationMethods = new Map(Object.entries(defaultVerificationMethods)); + this.verificationMethods = + new Map(Object.entries(defaultVerificationMethods)) as Map; } this.backupManager = new BackupManager(baseApis, async () => { @@ -406,7 +408,7 @@ export class Crypto extends TypedEventEmitter { this.crossSigningInfo = new CrossSigningInfo(userId, cryptoCallbacks, cacheCallbacks); // Yes, we pass the client twice here: see SecretStorage - this.secretStorage = new SecretStorage(baseApis, cryptoCallbacks, baseApis); + this.secretStorage = new SecretStorage(baseApis as IAccountDataClient, cryptoCallbacks, baseApis); this.dehydrationManager = new DehydrationManager(this); // Assuming no app-supplied callback, default to getting from SSSS. diff --git a/src/crypto/verification/Base.ts b/src/crypto/verification/Base.ts index a47c0960716..68e9c96fc0a 100644 --- a/src/crypto/verification/Base.ts +++ b/src/crypto/verification/Base.ts @@ -20,8 +20,6 @@ limitations under the License. * @module crypto/verification/Base */ -import { EventEmitter } from 'events'; - import { MatrixEvent } from '../../models/event'; import { logger } from '../../logger'; import { DeviceInfo } from '../deviceinfo'; @@ -30,6 +28,7 @@ import { KeysDuringVerification, requestKeysDuringVerification } from "../CrossS import { IVerificationChannel } from "./request/Channel"; import { MatrixClient } from "../../client"; import { VerificationRequest } from "./request/VerificationRequest"; +import { ListenerMap, TypedEventEmitter } from "../../models/typed-event-emitter"; const timeoutException = new Error("Verification timed out"); @@ -41,7 +40,18 @@ export class SwitchStartEventError extends Error { export type KeyVerifier = (keyId: string, device: DeviceInfo, keyInfo: string) => void; -export class VerificationBase extends EventEmitter { +export enum VerificationEvent { + Cancel = "cancel", +} + +export type VerificationEventHandlerMap = { + [VerificationEvent.Cancel]: (e: Error | MatrixEvent) => void; +}; + +export class VerificationBase< + Events extends string, + Arguments extends ListenerMap, +> extends TypedEventEmitter { private cancelled = false; private _done = false; private promise: Promise = null; @@ -261,7 +271,7 @@ export class VerificationBase extends EventEmitter { } // Also emit a 'cancel' event that the app can listen for to detect cancellation // before calling verify() - this.emit('cancel', e); + this.emit(VerificationEvent.Cancel, e); } } diff --git a/src/crypto/verification/IllegalMethod.ts b/src/crypto/verification/IllegalMethod.ts index b752d7404d3..f01364a212f 100644 --- a/src/crypto/verification/IllegalMethod.ts +++ b/src/crypto/verification/IllegalMethod.ts @@ -20,7 +20,7 @@ limitations under the License. * @module crypto/verification/IllegalMethod */ -import { VerificationBase as Base } from "./Base"; +import { VerificationBase as Base, VerificationEvent, VerificationEventHandlerMap } from "./Base"; import { IVerificationChannel } from "./request/Channel"; import { MatrixClient } from "../../client"; import { MatrixEvent } from "../../models/event"; @@ -30,7 +30,7 @@ import { VerificationRequest } from "./request/VerificationRequest"; * @class crypto/verification/IllegalMethod/IllegalMethod * @extends {module:crypto/verification/Base} */ -export class IllegalMethod extends Base { +export class IllegalMethod extends Base { public static factory( channel: IVerificationChannel, baseApis: MatrixClient, diff --git a/src/crypto/verification/QRCode.ts b/src/crypto/verification/QRCode.ts index 5b4c45ddaea..3c16c4955c9 100644 --- a/src/crypto/verification/QRCode.ts +++ b/src/crypto/verification/QRCode.ts @@ -19,7 +19,7 @@ limitations under the License. * @module crypto/verification/QRCode */ -import { VerificationBase as Base } from "./Base"; +import { VerificationBase as Base, VerificationEventHandlerMap } from "./Base"; import { newKeyMismatchError, newUserCancelledError } from './Error'; import { decodeBase64, encodeUnpaddedBase64 } from "../olmlib"; import { logger } from '../../logger'; @@ -31,15 +31,25 @@ import { MatrixEvent } from "../../models/event"; export const SHOW_QR_CODE_METHOD = "m.qr_code.show.v1"; export const SCAN_QR_CODE_METHOD = "m.qr_code.scan.v1"; +interface IReciprocateQr { + confirm(): void; + cancel(): void; +} + +export enum QrCodeEvent { + ShowReciprocateQr = "show_reciprocate_qr", +} + +type EventHandlerMap = { + [QrCodeEvent.ShowReciprocateQr]: (qr: IReciprocateQr) => void; +} & VerificationEventHandlerMap; + /** * @class crypto/verification/QRCode/ReciprocateQRCode * @extends {module:crypto/verification/Base} */ -export class ReciprocateQRCode extends Base { - public reciprocateQREvent: { - confirm(): void; - cancel(): void; - }; +export class ReciprocateQRCode extends Base { + public reciprocateQREvent: IReciprocateQr; public static factory( channel: IVerificationChannel, @@ -76,7 +86,7 @@ export class ReciprocateQRCode extends Base { confirm: resolve, cancel: () => reject(newUserCancelledError()), }; - this.emit("show_reciprocate_qr", this.reciprocateQREvent); + this.emit(QrCodeEvent.ShowReciprocateQr, this.reciprocateQREvent); }); // 3. determine key to sign / mark as trusted diff --git a/src/crypto/verification/SAS.ts b/src/crypto/verification/SAS.ts index 5582ff4f462..4dcd5521541 100644 --- a/src/crypto/verification/SAS.ts +++ b/src/crypto/verification/SAS.ts @@ -22,7 +22,12 @@ limitations under the License. import anotherjson from 'another-json'; import { Utility, SAS as OlmSAS } from "@matrix-org/olm"; -import { VerificationBase as Base, SwitchStartEventError } from "./Base"; +import { + VerificationBase as Base, + SwitchStartEventError, + VerificationEvent, + VerificationEventHandlerMap +} from "./Base"; import { errorFactory, newInvalidMessageError, @@ -232,11 +237,19 @@ function intersection(anArray: T[], aSet: Set): T[] { return anArray instanceof Array ? anArray.filter(x => aSet.has(x)) : []; } +export enum SasEvent { + ShowSas = "show_sas", +} + +type EventHandlerMap = { + [SasEvent.ShowSas]: (sas: ISasEvent) => void; +} & VerificationEventHandlerMap; + /** * @alias module:crypto/verification/SAS * @extends {module:crypto/verification/Base} */ -export class SAS extends Base { +export class SAS extends Base { private waitingForAccept: boolean; public ourSASPubKey: string; public theirSASPubKey: string; @@ -371,7 +384,7 @@ export class SAS extends Base { cancel: () => reject(newUserCancelledError()), mismatch: () => reject(newMismatchedSASError()), }; - this.emit("show_sas", this.sasEvent); + this.emit(SasEvent.ShowSas, this.sasEvent); }); [e] = await Promise.all([ @@ -447,7 +460,7 @@ export class SAS extends Base { cancel: () => reject(newUserCancelledError()), mismatch: () => reject(newMismatchedSASError()), }; - this.emit("show_sas", this.sasEvent); + this.emit(SasEvent.ShowSas, this.sasEvent); }); [e] = await Promise.all([ diff --git a/src/crypto/verification/request/VerificationRequest.ts b/src/crypto/verification/request/VerificationRequest.ts index cf56912d701..71611558f79 100644 --- a/src/crypto/verification/request/VerificationRequest.ts +++ b/src/crypto/verification/request/VerificationRequest.ts @@ -113,7 +113,7 @@ export class VerificationRequest< private commonMethods: VerificationMethod[] = []; private _phase: Phase; private _cancellingUserId: string; - private _verifier: VerificationBase; + private _verifier: VerificationBase; constructor( public readonly channel: C, @@ -245,7 +245,7 @@ export class VerificationRequest< } /** The verifier to do the actual verification, once the method has been established. Only defined when the `phase` is PHASE_STARTED. */ - public get verifier(): VerificationBase { + public get verifier(): VerificationBase { return this._verifier; } @@ -419,7 +419,10 @@ export class VerificationRequest< * @param {string?} targetDevice.deviceId the id of the device to direct this request to * @returns {VerifierBase} the verifier of the given method */ - public beginKeyVerification(method: VerificationMethod, targetDevice: ITargetDevice = null): VerificationBase { + public beginKeyVerification( + method: VerificationMethod, + targetDevice: ITargetDevice = null, + ): VerificationBase { // need to allow also when unsent in case of to_device if (!this.observeOnly && !this._verifier) { const validStartPhase = @@ -889,7 +892,7 @@ export class VerificationRequest< method: VerificationMethod, startEvent: MatrixEvent = null, targetDevice: ITargetDevice = null, - ): VerificationBase { + ): VerificationBase { if (!targetDevice) { targetDevice = this.targetDevice; } diff --git a/src/models/thread.ts b/src/models/thread.ts index c6d3362cd1b..c130ad3f43d 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -20,7 +20,7 @@ import { RelationType } from "../@types/event"; import { IRelationsRequestOpts } from "../@types/requests"; import { IThreadBundledRelationship, MatrixEvent } from "./event"; import { Direction, EventTimeline } from "./event-timeline"; -import { EventTimelineSet, EventHandlerMap as EventTimelineSetHandlerMap } from './event-timeline-set'; +import { EventTimelineSet, EventTimelineSetHandlerMap } from './event-timeline-set'; import { Room } from './room'; import { TypedEventEmitter } from "./typed-event-emitter"; import { RoomState } from "./room-state"; diff --git a/src/models/typed-event-emitter.ts b/src/models/typed-event-emitter.ts index 0afabbe5064..730ee58f94a 100644 --- a/src/models/typed-event-emitter.ts +++ b/src/models/typed-event-emitter.ts @@ -45,6 +45,7 @@ export type Listener< export abstract class TypedEventEmitter< Events extends string, Arguments extends ListenerMap, + SuperclassArguments extends ListenerMap = Arguments, > extends EventEmitter { public addListener( event: T, @@ -53,8 +54,9 @@ export abstract class TypedEventEmitter< return super.addListener(event, listener); } - public emit(event: T, ...args: Parameters): boolean { - // @ts-expect-error TS2488 + public emit(event: T, ...args: Parameters): boolean; + public emit(event: T, ...args: Parameters): boolean; + public emit(event: T, ...args: any[]): boolean { return super.emit(event, ...args); } From 147837cd2ff9be38b7467e4eee271d81c6c5046f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 21:50:18 +0000 Subject: [PATCH 21/30] Type the MatrixClient EventEmitter --- src/client.ts | 163 +++++++++++++++++++++++++------ src/crypto/backup.ts | 6 +- src/crypto/index.ts | 52 ++++++---- src/event-mapper.ts | 9 +- src/models/event-timeline-set.ts | 20 ++-- src/models/event.ts | 8 +- src/models/room-member.ts | 4 +- src/models/room-state.ts | 4 +- src/models/room.ts | 31 +++--- src/models/thread.ts | 14 +-- src/models/user.ts | 4 +- src/sync.ts | 105 +++++++++++--------- src/webrtc/callEventHandler.ts | 24 +++-- 13 files changed, 292 insertions(+), 152 deletions(-) diff --git a/src/client.ts b/src/client.ts index 5ce9ee02c3e..a4cbcd9401f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -19,15 +19,22 @@ limitations under the License. * @module client */ -import { EventEmitter } from "events"; import { EmoteEvent, IPartialEvent, MessageEvent, NoticeEvent } from "matrix-events-sdk"; import { ISyncStateData, SyncApi, SyncState } from "./sync"; -import { EventStatus, IContent, IDecryptOptions, IEvent, MatrixEvent, MatrixEventEvent } from "./models/event"; +import { + EventStatus, + IContent, + IDecryptOptions, + IEvent, + MatrixEvent, + MatrixEventEvent, + MatrixEventHandlerMap, +} from "./models/event"; import { StubStore } from "./store/stub"; import { createNewMatrixCall, MatrixCall } from "./webrtc/call"; import { Filter, IFilterDefinition } from "./filter"; -import { CallEventHandler } from './webrtc/callEventHandler'; +import { CallEvent, CallEventHandler, CallEventHandlerMap } from './webrtc/callEventHandler'; import * as utils from './utils'; import { sleep } from './utils'; import { Group } from "./models/group"; @@ -37,7 +44,7 @@ import { AutoDiscovery, AutoDiscoveryAction } from "./autodiscovery"; import * as olmlib from "./crypto/olmlib"; import { decodeBase64, encodeBase64 } from "./crypto/olmlib"; import { IExportedDevice as IOlmDevice } from "./crypto/OlmDevice"; -import { ReEmitter } from './ReEmitter'; +import { TypedReEmitter } from './ReEmitter'; import { IRoomEncryption, RoomList } from './crypto/RoomList'; import { logger } from './logger'; import { SERVICE_TYPES } from './service-types'; @@ -58,6 +65,8 @@ import { } from "./http-api"; import { Crypto, + CryptoEvent, + CryptoEventHandlerMap, fixBackupKey, IBootstrapCrossSigningOpts, ICheckOwnCrossSigningTrustOpts, @@ -68,7 +77,7 @@ import { import { DeviceInfo, IDevice } from "./crypto/deviceinfo"; import { decodeRecoveryKey } from './crypto/recoverykey'; import { keyFromAuthData } from './crypto/key_passphrase'; -import { User, UserEvent } from "./models/user"; +import { User, UserEvent, UserEventHandlerMap } from "./models/user"; import { getHttpUriForMxc } from "./content-repo"; import { SearchResult } from "./models/search-result"; import { @@ -88,7 +97,20 @@ import { } from "./crypto/keybackup"; import { IIdentityServerProvider } from "./@types/IIdentityServerProvider"; import { MatrixScheduler } from "./scheduler"; -import { IAuthData, ICryptoCallbacks, IMinimalEvent, IRoomEvent, IStateEvent, NotificationCountType } from "./matrix"; +import { + IAuthData, + ICryptoCallbacks, + IMinimalEvent, + IRoomEvent, + IStateEvent, + NotificationCountType, + RoomEvent, + RoomEventHandlerMap, + RoomMemberEvent, + RoomMemberEventHandlerMap, + RoomStateEvent, + RoomStateEventHandlerMap, +} from "./matrix"; import { CrossSigningKey, IAddSecretStorageKeyOpts, @@ -155,6 +177,8 @@ import { IThreepid } from "./@types/threepids"; import { CryptoStore } from "./crypto/store/base"; import { MediaHandler } from "./webrtc/mediaHandler"; import { IRefreshTokenResponse } from "./@types/auth"; +import { TypedEventEmitter } from "./models/typed-event-emitter"; +import { DeviceListEvent } from "./crypto/DeviceList"; export type Store = IStore; export type SessionStore = WebStorageSessionStore; @@ -453,7 +477,7 @@ export interface ISignedKey { } export type KeySignatures = Record>; -interface IUploadKeySignaturesResponse { +export interface IUploadKeySignaturesResponse { failures: Record void; + [ClientEvent.Event]: (event: MatrixEvent) => void; + [ClientEvent.ToDeviceEvent]: (event: MatrixEvent) => void; [ClientEvent.AccountData]: (event: MatrixEvent, lastEvent?: MatrixEvent) => void; -}; + [ClientEvent.Room]: (room: Room) => void; + [ClientEvent.DeleteRoom]: (roomId: string) => void; + [ClientEvent.SyncUnexpectedError]: (error: Error) => void; + [ClientEvent.ClientWellKnown]: (data: IClientWellKnown) => void; + [ClientEvent.Group]: (group: Group) => void; + [ClientEvent.GroupProfile]: (group: Group) => void; + [ClientEvent.GroupMyMembership]: (group: Group) => void; +} & RoomEventHandlerMap + & RoomStateEventHandlerMap + & CryptoEventHandlerMap + & MatrixEventHandlerMap + & RoomMemberEventHandlerMap + & UserEventHandlerMap + & CallEventHandlerMap; /** * Represents a Matrix Client. Only directly construct this if you want to use * custom modules. Normally, {@link createClient} should be used * as it specifies 'sensible' defaults for these modules. */ -export class MatrixClient extends EventEmitter { +export class MatrixClient extends TypedEventEmitter { public static readonly RESTORE_BACKUP_ERROR_BAD_KEY = 'RESTORE_BACKUP_ERROR_BAD_KEY'; - public reEmitter = new ReEmitter(this); + public reEmitter = new TypedReEmitter(this); public olmVersion: [number, number, number] = null; // populated after initCrypto public usingExternalCrypto = false; public store: Store; @@ -907,7 +1011,7 @@ export class MatrixClient extends EventEmitter { // Start listening for calls after the initial sync is done // We do not need to backfill the call event buffer // with encrypted events that might never get decrypted - this.on("sync", this.startCallEventHandler); + this.on(ClientEvent.Sync, this.startCallEventHandler); } this.timelineSupport = Boolean(opts.timelineSupport); @@ -932,7 +1036,7 @@ export class MatrixClient extends EventEmitter { // actions for themselves, so we have to kinda help them out when they are encrypted. // We do this so that push rules are correctly executed on events in their decrypted // state, such as highlights when the user's name is mentioned. - this.on("Event.decrypted", (event) => { + this.on(MatrixEventEvent.Decrypted, (event) => { const oldActions = event.getPushActions(); const actions = this.getPushActionsForEvent(event, true); @@ -967,7 +1071,7 @@ export class MatrixClient extends EventEmitter { // Like above, we have to listen for read receipts from ourselves in order to // correctly handle notification counts on encrypted rooms. // This fixes https://github.com/vector-im/element-web/issues/9421 - this.on("Room.receipt", (event, room) => { + this.on(RoomEvent.Receipt, (event, room) => { if (room && this.isRoomEncrypted(room.roomId)) { // Figure out if we've read something or if it's just informational const content = event.getContent(); @@ -1002,7 +1106,7 @@ export class MatrixClient extends EventEmitter { // Note: we don't need to handle 'total' notifications because the counts // will come from the server. - room.setUnreadNotificationCount("highlight", highlightCount); + room.setUnreadNotificationCount(NotificationCountType.Highlight, highlightCount); } }); } @@ -1567,16 +1671,16 @@ export class MatrixClient extends EventEmitter { ); this.reEmitter.reEmit(crypto, [ - "crypto.keyBackupFailed", - "crypto.keyBackupSessionsRemaining", - "crypto.roomKeyRequest", - "crypto.roomKeyRequestCancellation", + CryptoEvent.KeyBackupFailed, + CryptoEvent.KeyBackupSessionsRemaining, + CryptoEvent.RoomKeyRequest, + CryptoEvent.RoomKeyRequestCancellation, "crypto.warning", - "crypto.devicesUpdated", - "crypto.willUpdateDevices", - "deviceVerificationChanged", - "userTrustStatusChanged", - "crossSigning.keysChanged", + DeviceListEvent.DevicesUpdated, + DeviceListEvent.WillUpdateDevices, + CryptoEvent.DeviceVerificationChanged, + CryptoEvent.UserTrustStatusChanged, + CryptoEvent.KeysChanged, ]); logger.log("Crypto: initialising crypto object..."); @@ -1588,9 +1692,8 @@ export class MatrixClient extends EventEmitter { this.olmVersion = Crypto.getOlmVersion(); - // if crypto initialisation was successful, tell it to attach its event - // handlers. - crypto.registerEventHandlers(this); + // if crypto initialisation was successful, tell it to attach its event handlers. + crypto.registerEventHandlers(this as Parameters[0]); this.crypto = crypto; } @@ -4768,7 +4871,7 @@ export class MatrixClient extends EventEmitter { } return promise.then((response) => { this.store.removeRoom(roomId); - this.emit("deleteRoom", roomId); + this.emit(ClientEvent.DeleteRoom, roomId); return response; }); } @@ -6108,7 +6211,7 @@ export class MatrixClient extends EventEmitter { private startCallEventHandler = (): void => { if (this.isInitialSyncComplete()) { this.callEventHandler.start(); - this.off("sync", this.startCallEventHandler); + this.off(ClientEvent.Sync, this.startCallEventHandler); } }; @@ -6256,7 +6359,7 @@ export class MatrixClient extends EventEmitter { // it absorbs errors and returns `{}`. this.clientWellKnownPromise = AutoDiscovery.getRawClientConfig(this.getDomain()); this.clientWellKnown = await this.clientWellKnownPromise; - this.emit("WellKnown.client", this.clientWellKnown); + this.emit(ClientEvent.ClientWellKnown, this.clientWellKnown); } public getClientWellKnown(): IClientWellKnown { diff --git a/src/crypto/backup.ts b/src/crypto/backup.ts index d0954e3e3a0..f3a6824d140 100644 --- a/src/crypto/backup.ts +++ b/src/crypto/backup.ts @@ -154,7 +154,7 @@ export class BackupManager { this.algorithm = await BackupManager.makeAlgorithm(info, this.getKey); - this.baseApis.emit('crypto.keyBackupStatus', true); + this.baseApis.emit(CryptoEvent.KeyBackupStatus, true); // There may be keys left over from a partially completed backup, so // schedule a send to check. @@ -172,7 +172,7 @@ export class BackupManager { this.backupInfo = undefined; - this.baseApis.emit('crypto.keyBackupStatus', false); + this.baseApis.emit(CryptoEvent.KeyBackupStatus, false); } public getKeyBackupEnabled(): boolean | null { @@ -579,7 +579,7 @@ export class BackupManager { ); const remaining = await this.baseApis.crypto.cryptoStore.countSessionsNeedingBackup(); - this.baseApis.emit("crypto.keyBackupSessionsRemaining", remaining); + this.baseApis.emit(CryptoEvent.KeyBackupSessionsRemaining, remaining); return remaining; } diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 5d818b9b458..faa01291b0b 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -22,7 +22,6 @@ limitations under the License. */ import anotherjson from "another-json"; -import { EventEmitter } from 'events'; import { TypedReEmitter } from '../ReEmitter'; import { logger } from '../logger'; @@ -65,10 +64,18 @@ import { calculateKeyCheck, decryptAES, encryptAES } from './aes'; import { DehydrationManager, IDeviceKeys, IOneTimeKey } from './dehydration'; import { BackupManager } from "./backup"; import { IStore } from "../store"; -import { Room } from "../models/room"; +import { Room, RoomEvent } from "../models/room"; import { RoomMember, RoomMemberEvent } from "../models/room-member"; import { EventStatus, IClearEvent, IEvent, MatrixEvent, MatrixEventEvent } from "../models/event"; -import { ICrossSigningKey, IKeysUploadResponse, ISignedKey, MatrixClient, SessionStore } from "../client"; +import { + ClientEvent, + ICrossSigningKey, + IKeysUploadResponse, + ISignedKey, + IUploadKeySignaturesResponse, + MatrixClient, + SessionStore, +} from "../client"; import type { IRoomEncryption, RoomList } from "./RoomList"; import { IKeyBackupInfo } from "./keybackup"; import { ISyncStateData } from "../sync"; @@ -76,7 +83,6 @@ import { CryptoStore } from "./store/base"; import { IVerificationChannel } from "./verification/request/Channel"; import { TypedEventEmitter } from "../models/typed-event-emitter"; import { VerificationBase } from "./verification/Base"; -import { EventTimelineSetEvent } from "../models/event-timeline-set"; const DeviceVerification = DeviceInfo.DeviceVerification; @@ -201,24 +207,34 @@ export enum CryptoEvent { UserTrustStatusChanged = "userTrustStatusChanged", RoomKeyRequest = "crypto.roomKeyRequest", RoomKeyRequestCancellation = "crypto.roomKeyRequestCancellation", + KeyBackupStatus = "crypto.keyBackupStatus", KeyBackupFailed = "crypto.keyBackupFailed", KeyBackupSessionsRemaining = "crypto.keyBackupSessionsRemaining", + KeySignatureUploadFailure = "crypto.keySignatureUploadFailure", + VerificationRequest = "crypto.verification.request", KeysChanged = "crossSigning.keysChanged", } type EmittedEvents = CryptoEvent | DeviceListEvent.DevicesUpdated | DeviceListEvent.WillUpdateDevices; -type EventHandlerMap = { +export type CryptoEventHandlerMap = { [CryptoEvent.DeviceVerificationChanged]: (userId: string, deviceId: string, device: DeviceInfo) => void; [CryptoEvent.UserTrustStatusChanged]: (userId: string, trustLevel: UserTrustLevel) => void; [CryptoEvent.RoomKeyRequest]: (request: IncomingRoomKeyRequest) => void; [CryptoEvent.RoomKeyRequestCancellation]: (request: IncomingRoomKeyRequestCancellation) => void; + [CryptoEvent.KeyBackupStatus]: (enabled: boolean) => void; [CryptoEvent.KeyBackupFailed]: (errcode: string) => void; [CryptoEvent.KeyBackupSessionsRemaining]: (remaining: number) => void; + [CryptoEvent.KeySignatureUploadFailure]: ( + failures: IUploadKeySignaturesResponse["failures"], + source: "checkOwnCrossSigningTrust" | "afterCrossSigningLocalKeyChange" | "setDeviceVerification", + upload: (opts: { shouldEmit: boolean }) => Promise + ) => void; + [CryptoEvent.VerificationRequest]: (request: VerificationRequest) => void; [CryptoEvent.KeysChanged]: (data: {}) => void; -} & Pick; +} & DeviceListHandlerMap; -export class Crypto extends TypedEventEmitter { +export class Crypto extends TypedEventEmitter { /** * @return {string} The version of Olm. */ @@ -233,7 +249,7 @@ export class Crypto extends TypedEventEmitter { public readonly dehydrationManager: DehydrationManager; public readonly secretStorage: SecretStorage; - private readonly reEmitter: TypedReEmitter; + private readonly reEmitter: TypedReEmitter; private readonly verificationMethods: Map; public readonly supportedAlgorithms: string[]; private readonly outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager; @@ -1198,7 +1214,7 @@ export class Crypto extends TypedEventEmitter { if (Object.keys(failures || []).length > 0) { if (shouldEmit) { this.baseApis.emit( - "crypto.keySignatureUploadFailure", + CryptoEvent.KeySignatureUploadFailure, failures, "afterCrossSigningLocalKeyChange", upload, // continuation @@ -1599,7 +1615,7 @@ export class Crypto extends TypedEventEmitter { if (Object.keys(failures || []).length > 0) { if (shouldEmit) { this.baseApis.emit( - "crypto.keySignatureUploadFailure", + CryptoEvent.KeySignatureUploadFailure, failures, "checkOwnCrossSigningTrust", upload, @@ -1706,12 +1722,14 @@ export class Crypto extends TypedEventEmitter { * * @param {external:EventEmitter} eventEmitter event source where we can register * for event notifications - * @todo use a typed EventEmitter here */ - public registerEventHandlers(eventEmitter: EventEmitter): void { + public registerEventHandlers(eventEmitter: TypedEventEmitter< + RoomMemberEvent.Membership | ClientEvent.ToDeviceEvent | RoomEvent.Timeline | MatrixEventEvent.Decrypted, + any + >): void { eventEmitter.on(RoomMemberEvent.Membership, this.onMembership); - eventEmitter.on("toDeviceEvent", this.onToDeviceEvent); - eventEmitter.on(EventTimelineSetEvent.RoomTimeline, this.onTimelineEvent); + eventEmitter.on(ClientEvent.ToDeviceEvent, this.onToDeviceEvent); + eventEmitter.on(RoomEvent.Timeline, this.onTimelineEvent); eventEmitter.on(MatrixEventEvent.Decrypted, this.onTimelineEvent); } @@ -2118,7 +2136,7 @@ export class Crypto extends TypedEventEmitter { if (Object.keys(failures || []).length > 0) { if (shouldEmit) { this.baseApis.emit( - "crypto.keySignatureUploadFailure", + CryptoEvent.KeySignatureUploadFailure, failures, "setDeviceVerification", upload, @@ -2202,7 +2220,7 @@ export class Crypto extends TypedEventEmitter { if (Object.keys(failures || []).length > 0) { if (shouldEmit) { this.baseApis.emit( - "crypto.keySignatureUploadFailure", + CryptoEvent.KeySignatureUploadFailure, failures, "setDeviceVerification", upload, // continuation @@ -3286,7 +3304,7 @@ export class Crypto extends TypedEventEmitter { !request.invalid && // check it has enough events to pass the UNSENT stage !request.observeOnly; if (shouldEmit) { - this.baseApis.emit("crypto.verification.request", request); + this.baseApis.emit(CryptoEvent.VerificationRequest, request); } } diff --git a/src/event-mapper.ts b/src/event-mapper.ts index 9b938486021..e6942cbd47b 100644 --- a/src/event-mapper.ts +++ b/src/event-mapper.ts @@ -15,7 +15,7 @@ limitations under the License. */ import { MatrixClient } from "./client"; -import { IEvent, MatrixEvent } from "./models/event"; +import { IEvent, MatrixEvent, MatrixEventEvent } from "./models/event"; export type EventMapper = (obj: Partial) => MatrixEvent; @@ -33,7 +33,7 @@ export function eventMapperFor(client: MatrixClient, options: MapperOpts): Event if (event.isEncrypted()) { if (!preventReEmit) { client.reEmitter.reEmit(event, [ - "Event.decrypted", + MatrixEventEvent.Decrypted, ]); } if (decrypt) { @@ -41,7 +41,10 @@ export function eventMapperFor(client: MatrixClient, options: MapperOpts): Event } } if (!preventReEmit) { - client.reEmitter.reEmit(event, ["Event.replaced", "Event.visibilityChange"]); + client.reEmitter.reEmit(event, [ + MatrixEventEvent.Replaced, + MatrixEventEvent.VisibilityChange, + ]); } return event; } diff --git a/src/models/event-timeline-set.ts b/src/models/event-timeline-set.ts index 11d57226f6f..1fda0d977a4 100644 --- a/src/models/event-timeline-set.ts +++ b/src/models/event-timeline-set.ts @@ -22,7 +22,7 @@ import { EventTimeline } from "./event-timeline"; import { EventStatus, MatrixEvent, MatrixEventEvent } from "./event"; import { logger } from '../logger'; import { Relations } from './relations'; -import { Room } from "./room"; +import { Room, RoomEvent } from "./room"; import { Filter } from "../filter"; import { EventType, RelationType } from "../@types/event"; import { RoomState } from "./room-state"; @@ -56,19 +56,15 @@ export interface IRoomTimelineData { liveEvent?: boolean; } -export enum EventTimelineSetEvent { - RoomTimeline = "Room.timeline", - RoomTimelineReset = "Room.timelineReset", -} +type EmittedEvents = RoomEvent.Timeline | RoomEvent.TimelineReset; export type EventTimelineSetHandlerMap = { - [EventTimelineSetEvent.RoomTimeline]: + [RoomEvent.Timeline]: (event: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed: boolean, data: IRoomTimelineData) => void; - [EventTimelineSetEvent.RoomTimelineReset]: - (room: Room, eventTimelineSet: EventTimelineSet, resetAllTimelines: boolean) => void; + [RoomEvent.TimelineReset]: (room: Room, eventTimelineSet: EventTimelineSet, resetAllTimelines: boolean) => void; }; -export class EventTimelineSet extends TypedEventEmitter { +export class EventTimelineSet extends TypedEventEmitter { private readonly timelineSupport: boolean; private unstableClientRelationAggregation: boolean; private displayPendingEvents: boolean; @@ -258,7 +254,7 @@ export class EventTimelineSet extends TypedEventEmitter void; [MatrixEventEvent.BeforeRedaction]: (event: MatrixEvent, redactionEvent: MatrixEvent) => void; [MatrixEventEvent.VisibilityChange]: (event: MatrixEvent, visible: boolean) => void; @@ -224,9 +224,9 @@ type EventHandlerMap = { [MatrixEventEvent.Status]: (event: MatrixEvent, status: EventStatus) => void; [MatrixEventEvent.Replaced]: (event: MatrixEvent) => void; [MatrixEventEvent.RelationsCreated]: (relationType: string, eventType: string) => void; -} & Pick; +} & ThreadEventHandlerMap; -export class MatrixEvent extends TypedEventEmitter { +export class MatrixEvent extends TypedEventEmitter { private pushActions: IActionsObject = null; private _replacingEvent: MatrixEvent = null; private _localRedactionEvent: MatrixEvent = null; @@ -309,7 +309,7 @@ export class MatrixEvent extends TypedEventEmitter; + private readonly reEmitter: TypedReEmitter; /** * Construct a Matrix Event object diff --git a/src/models/room-member.ts b/src/models/room-member.ts index cc2cccf2fa6..a3d350583a0 100644 --- a/src/models/room-member.ts +++ b/src/models/room-member.ts @@ -34,14 +34,14 @@ export enum RoomMemberEvent { Typing = "RoomMember.typing", } -type EventHandlerMap = { +export type RoomMemberEventHandlerMap = { [RoomMemberEvent.Membership]: (event: MatrixEvent, member: RoomMember, oldMembership: string | null) => void; [RoomMemberEvent.Name]: (event: MatrixEvent, member: RoomMember, oldName: string | null) => void; [RoomMemberEvent.PowerLevel]: (event: MatrixEvent, member: RoomMember) => void; [RoomMemberEvent.Typing]: (event: MatrixEvent, member: RoomMember) => void; }; -export class RoomMember extends TypedEventEmitter { +export class RoomMember extends TypedEventEmitter { private _isOutOfBand = false; private _modified: number; public _requestedProfileInfo: boolean; // used by sync.ts diff --git a/src/models/room-state.ts b/src/models/room-state.ts index 36e8c291585..59bf1ae0115 100644 --- a/src/models/room-state.ts +++ b/src/models/room-state.ts @@ -40,13 +40,13 @@ export enum RoomStateEvent { NewMember = "RoomState.newMember", } -type EventHandlerMap = { +export type RoomStateEventHandlerMap = { [RoomStateEvent.Events]: (event: MatrixEvent, state: RoomState, lastStateEvent: MatrixEvent | null) => void; [RoomStateEvent.Members]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void; [RoomStateEvent.NewMember]: (event: MatrixEvent, state: RoomState, member: RoomMember) => void; }; -export class RoomState extends TypedEventEmitter { +export class RoomState extends TypedEventEmitter { private sentinels: Record = {}; // userId: RoomMember // stores fuzzy matches to a list of userIDs (applies utils.removeHiddenChars to keys) private displayNameToUserIds: Record = {}; diff --git a/src/models/room.ts b/src/models/room.ts index 29f082bffe1..a38430608d6 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -18,7 +18,7 @@ limitations under the License. * @module models/room */ -import { EventTimelineSet, DuplicateStrategy, EventTimelineSetEvent } from "./event-timeline-set"; +import { EventTimelineSet, DuplicateStrategy } from "./event-timeline-set"; import { Direction, EventTimeline } from "./event-timeline"; import { getHttpUriForMxc } from "../content-repo"; import * as utils from "../utils"; @@ -151,15 +151,17 @@ export enum RoomEvent { Redaction = "Room.redaction", RedactionCancelled = "Room.redactionCancelled", LocalEchoUpdated = "Room.localEchoUpdated", + Timeline = "Room.timeline", + TimelineReset = "Room.timelineReset", } type EmittedEvents = RoomEvent | ThreadEvent.New | ThreadEvent.Update - | EventTimelineSetEvent.RoomTimeline - | EventTimelineSetEvent.RoomTimelineReset; + | RoomEvent.Timeline + | RoomEvent.TimelineReset; -type EventHandlerMap = { +export type RoomEventHandlerMap = { [RoomEvent.MyMembership]: (room: Room, membership: string, prevMembership?: string) => void; [RoomEvent.Tags]: (event: MatrixEvent, room: Room) => void; [RoomEvent.AccountData]: (event: MatrixEvent, room: Room, lastEvent?: MatrixEvent) => void; @@ -174,13 +176,10 @@ type EventHandlerMap = { oldStatus?: EventStatus, ) => void; [ThreadEvent.New]: (thread: Thread) => void; -} & Pick< - ThreadHandlerMap, - EventTimelineSetEvent.RoomTimeline | EventTimelineSetEvent.RoomTimelineReset | ThreadEvent.Update ->; +} & ThreadHandlerMap; -export class Room extends TypedEventEmitter { - private readonly reEmitter: TypedReEmitter; +export class Room extends TypedEventEmitter { + private readonly reEmitter: TypedReEmitter; private txnToEvent: Record = {}; // Pending in-flight requests { string: MatrixEvent } // receipts should clobber based on receipt_type and user_id pairs hence // the form of this structure. This is sub-optimal for the exposed APIs @@ -333,8 +332,8 @@ export class Room extends TypedEventEmitter { // the subsequent ones are the filtered ones in no particular order. this.timelineSets = [new EventTimelineSet(this, opts)]; this.reEmitter.reEmit(this.getUnfilteredTimelineSet(), [ - EventTimelineSetEvent.RoomTimeline, - EventTimelineSetEvent.RoomTimelineReset, + RoomEvent.Timeline, + RoomEvent.TimelineReset, ]); this.fixUpLegacyTimelineFields(); @@ -1323,8 +1322,8 @@ export class Room extends TypedEventEmitter { const opts = Object.assign({ filter: filter }, this.opts); const timelineSet = new EventTimelineSet(this, opts); this.reEmitter.reEmit(timelineSet, [ - EventTimelineSetEvent.RoomTimeline, - EventTimelineSetEvent.RoomTimelineReset, + RoomEvent.Timeline, + RoomEvent.TimelineReset, ]); this.filteredTimelineSets[filter.filterId] = timelineSet; this.timelineSets.push(timelineSet); @@ -1453,8 +1452,8 @@ export class Room extends TypedEventEmitter { this.threads.set(thread.id, thread); this.reEmitter.reEmit(thread, [ ThreadEvent.Update, - EventTimelineSetEvent.RoomTimeline, - EventTimelineSetEvent.RoomTimelineReset, + RoomEvent.Timeline, + RoomEvent.TimelineReset, ]); if (!this.lastThread || this.lastThread.rootEvent.localTimestamp < rootEvent.localTimestamp) { diff --git a/src/models/thread.ts b/src/models/thread.ts index c130ad3f43d..379e8d96063 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { EventTimelineSetEvent, MatrixClient, RoomEvent } from "../matrix"; +import { MatrixClient, RoomEvent } from "../matrix"; import { TypedReEmitter } from "../ReEmitter"; import { RelationType } from "../@types/event"; import { IRelationsRequestOpts } from "../@types/requests"; @@ -33,14 +33,14 @@ export enum ThreadEvent { } type EmittedEvents = Exclude - | EventTimelineSetEvent.RoomTimeline - | EventTimelineSetEvent.RoomTimelineReset; + | RoomEvent.Timeline + | RoomEvent.TimelineReset; export type EventHandlerMap = { [ThreadEvent.Update]: (thread: Thread) => void; [ThreadEvent.NewReply]: (thread: Thread, event: MatrixEvent) => void; [ThreadEvent.ViewThread]: () => void; -} & Pick; +} & EventTimelineSetHandlerMap; interface IThreadOpts { initialEvents?: MatrixEvent[]; @@ -87,8 +87,8 @@ export class Thread extends TypedEventEmitter { this.reEmitter = new TypedReEmitter(this); this.reEmitter.reEmit(this.timelineSet, [ - EventTimelineSetEvent.RoomTimeline, - EventTimelineSetEvent.RoomTimelineReset, + RoomEvent.Timeline, + RoomEvent.TimelineReset, ]); // If we weren't able to find the root event, it's probably missing @@ -104,7 +104,7 @@ export class Thread extends TypedEventEmitter { opts?.initialEvents?.forEach(event => this.addEvent(event)); this.room.on(RoomEvent.LocalEchoUpdated, this.onEcho); - this.room.on(EventTimelineSetEvent.RoomTimeline, this.onEcho); + this.room.on(RoomEvent.Timeline, this.onEcho); } public get hasServerSideSupport(): boolean { diff --git a/src/models/user.ts b/src/models/user.ts index 7ceff037c7d..2e4b81875e4 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -31,7 +31,7 @@ export enum UserEvent { _UnstableStatusMessage = "User.unstable_statusMessage", } -type EventHandlerMap = { +export type UserEventHandlerMap = { [UserEvent.DisplayName]: (event: MatrixEvent | undefined, user: User) => void; [UserEvent.AvatarUrl]: (event: MatrixEvent | undefined, user: User) => void; [UserEvent.Presence]: (event: MatrixEvent | undefined, user: User) => void; @@ -40,7 +40,7 @@ type EventHandlerMap = { [UserEvent._UnstableStatusMessage]: (user: User) => void; }; -export class User extends TypedEventEmitter { +export class User extends TypedEventEmitter { private modified: number; // XXX these should be read-only diff --git a/src/sync.ts b/src/sync.ts index aae06a67410..090c3b8d13d 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -23,8 +23,8 @@ limitations under the License. * for HTTP and WS at some point. */ -import { User } from "./models/user"; -import { NotificationCountType, Room } from "./models/room"; +import { User, UserEvent } from "./models/user"; +import { NotificationCountType, Room, RoomEvent } from "./models/room"; import { Group } from "./models/group"; import * as utils from "./utils"; import { IDeferred } from "./utils"; @@ -33,7 +33,7 @@ import { EventTimeline } from "./models/event-timeline"; import { PushProcessor } from "./pushprocessor"; import { logger } from './logger'; import { InvalidStoreError } from './errors'; -import { IStoredClientOpts, MatrixClient, PendingEventOrdering } from "./client"; +import { ClientEvent, IStoredClientOpts, MatrixClient, PendingEventOrdering } from "./client"; import { Category, IEphemeral, @@ -54,6 +54,7 @@ import { ISavedSync } from "./store"; import { EventType } from "./@types/event"; import { IPushRules } from "./@types/PushRules"; import { RoomStateEvent } from "./models/room-state"; +import { RoomMemberEvent } from "./models/room-member"; const DEBUG = true; @@ -172,8 +173,10 @@ export class SyncApi { } if (client.getNotifTimelineSet()) { - client.reEmitter.reEmit(client.getNotifTimelineSet(), - ["Room.timeline", "Room.timelineReset"]); + client.reEmitter.reEmit(client.getNotifTimelineSet(), [ + RoomEvent.Timeline, + RoomEvent.TimelineReset, + ]); } } @@ -193,14 +196,17 @@ export class SyncApi { timelineSupport, unstableClientRelationAggregation, }); - client.reEmitter.reEmit(room, ["Room.name", "Room.timeline", - "Room.redaction", - "Room.redactionCancelled", - "Room.receipt", "Room.tags", - "Room.timelineReset", - "Room.localEchoUpdated", - "Room.accountData", - "Room.myMembership", + client.reEmitter.reEmit(room, [ + RoomEvent.Name, + RoomEvent.Redaction, + RoomEvent.RedactionCancelled, + RoomEvent.Receipt, + RoomEvent.Tags, + RoomEvent.LocalEchoUpdated, + RoomEvent.AccountData, + RoomEvent.MyMembership, + RoomEvent.Timeline, + RoomEvent.TimelineReset, "Room.replaceEvent", "Room.visibilityChange", ]); @@ -215,7 +221,10 @@ export class SyncApi { public createGroup(groupId: string): Group { const client = this.client; const group = new Group(groupId); - client.reEmitter.reEmit(group, ["Group.profile", "Group.myMembership"]); + client.reEmitter.reEmit(group, [ + ClientEvent.GroupProfile, + ClientEvent.GroupMyMembership, + ]); client.store.storeGroup(group); return group; } @@ -230,19 +239,18 @@ export class SyncApi { // to the client now. We need to add a listener for RoomState.members in // order to hook them correctly. (TODO: find a better way?) client.reEmitter.reEmit(room.currentState, [ - "RoomState.events", "RoomState.members", "RoomState.newMember", + RoomStateEvent.Events, + RoomStateEvent.Members, + RoomStateEvent.NewMember, ]); room.currentState.on(RoomStateEvent.NewMember, function(event, state, member) { member.user = client.getUser(member.userId); - client.reEmitter.reEmit( - member, - [ - "RoomMember.name", - "RoomMember.typing", - "RoomMember.powerLevel", - "RoomMember.membership", - ], - ); + client.reEmitter.reEmit(member, [ + RoomMemberEvent.Name, + RoomMemberEvent.Typing, + RoomMemberEvent.PowerLevel, + RoomMemberEvent.Membership, + ]); }); } @@ -317,7 +325,7 @@ export class SyncApi { room.recalculate(); client.store.storeRoom(room); - client.emit("Room", room); + client.emit(ClientEvent.Room, room); this.processEventsForNotifs(room, events); }); @@ -365,7 +373,7 @@ export class SyncApi { user.setPresenceEvent(presenceEvent); client.store.storeUser(user); } - client.emit("event", presenceEvent); + client.emit(ClientEvent.Event, presenceEvent); }); } @@ -391,7 +399,7 @@ export class SyncApi { response.messages.start); client.store.storeRoom(this._peekRoom); - client.emit("Room", this._peekRoom); + client.emit(ClientEvent.Room, this._peekRoom); this.peekPoll(this._peekRoom); return this._peekRoom; @@ -448,7 +456,7 @@ export class SyncApi { user.setPresenceEvent(presenceEvent); this.client.store.storeUser(user); } - this.client.emit("event", presenceEvent); + this.client.emit(ClientEvent.Event, presenceEvent); }); // strip out events which aren't for the given room_id (e.g presence) @@ -843,7 +851,7 @@ export class SyncApi { logger.error("Caught /sync error", e.stack || e); // Emit the exception for client handling - this.client.emit("sync.unexpectedError", e); + this.client.emit(ClientEvent.SyncUnexpectedError, e); } // update this as it may have changed @@ -1076,7 +1084,7 @@ export class SyncApi { user.setPresenceEvent(presenceEvent); client.store.storeUser(user); } - client.emit("event", presenceEvent); + client.emit(ClientEvent.Event, presenceEvent); }); } @@ -1099,7 +1107,7 @@ export class SyncApi { client.pushRules = PushProcessor.rewriteDefaultRules(rules); } const prevEvent = prevEventsMap[accountDataEvent.getId()]; - client.emit("accountData", accountDataEvent, prevEvent); + client.emit(ClientEvent.AccountData, accountDataEvent, prevEvent); return accountDataEvent; }, ); @@ -1152,7 +1160,7 @@ export class SyncApi { } } - client.emit("toDeviceEvent", toDeviceEvent); + client.emit(ClientEvent.ToDeviceEvent, toDeviceEvent); }, ); } else { @@ -1204,10 +1212,10 @@ export class SyncApi { if (inviteObj.isBrandNewRoom) { room.recalculate(); client.store.storeRoom(room); - client.emit("Room", room); + client.emit(ClientEvent.Room, room); } stateEvents.forEach(function(e) { - client.emit("event", e); + client.emit(ClientEvent.Event, e); }); room.updateMyMembership("invite"); }); @@ -1328,13 +1336,13 @@ export class SyncApi { room.recalculate(); if (joinObj.isBrandNewRoom) { client.store.storeRoom(room); - client.emit("Room", room); + client.emit(ClientEvent.Room, room); } this.processEventsForNotifs(room, events); const processRoomEvent = async (e) => { - client.emit("event", e); + client.emit(ClientEvent.Event, e); if (e.isState() && e.getType() == "m.room.encryption" && this.opts.crypto) { await this.opts.crypto.onCryptoEvent(e); } @@ -1354,10 +1362,10 @@ export class SyncApi { await utils.promiseMapSeries(timelineEvents, processRoomEvent); await utils.promiseMapSeries(threadedEvents, processRoomEvent); ephemeralEvents.forEach(function(e) { - client.emit("event", e); + client.emit(ClientEvent.Event, e); }); accountDataEvents.forEach(function(e) { - client.emit("event", e); + client.emit(ClientEvent.Event, e); }); room.updateMyMembership("join"); @@ -1384,22 +1392,22 @@ export class SyncApi { room.recalculate(); if (leaveObj.isBrandNewRoom) { client.store.storeRoom(room); - client.emit("Room", room); + client.emit(ClientEvent.Room, room); } this.processEventsForNotifs(room, events); stateEvents.forEach(function(e) { - client.emit("event", e); + client.emit(ClientEvent.Event, e); }); timelineEvents.forEach(function(e) { - client.emit("event", e); + client.emit(ClientEvent.Event, e); }); threadedEvents.forEach(function(e) { - client.emit("event", e); + client.emit(ClientEvent.Event, e); }); accountDataEvents.forEach(function(e) { - client.emit("event", e); + client.emit(ClientEvent.Event, e); }); room.updateMyMembership("leave"); @@ -1554,7 +1562,7 @@ export class SyncApi { group.setMyMembership(sectionName); if (isBrandNew) { // Now we've filled in all the fields, emit the Group event - this.client.emit("Group", group); + this.client.emit(ClientEvent.Group, group); } } } @@ -1781,7 +1789,7 @@ export class SyncApi { const old = this.syncState; this.syncState = newState; this.syncStateData = data; - this.client.emit("sync", this.syncState, old, data); + this.client.emit(ClientEvent.Sync, this.syncState, old, data); } /** @@ -1799,8 +1807,11 @@ export class SyncApi { function createNewUser(client: MatrixClient, userId: string): User { const user = new User(userId); client.reEmitter.reEmit(user, [ - "User.avatarUrl", "User.displayName", "User.presence", - "User.currentlyActive", "User.lastPresenceTs", + UserEvent.AvatarUrl, + UserEvent.DisplayName, + UserEvent.Presence, + UserEvent.CurrentlyActive, + UserEvent.LastPresenceTs, ]); return user; } diff --git a/src/webrtc/callEventHandler.ts b/src/webrtc/callEventHandler.ts index 58d9652ac31..340cf806899 100644 --- a/src/webrtc/callEventHandler.ts +++ b/src/webrtc/callEventHandler.ts @@ -18,13 +18,23 @@ import { MatrixEvent, MatrixEventEvent } from '../models/event'; import { logger } from '../logger'; import { CallDirection, CallErrorCode, CallState, createNewMatrixCall, MatrixCall } from './call'; import { EventType } from '../@types/event'; -import { MatrixClient } from '../client'; +import { ClientEvent, MatrixClient } from '../client'; import { MCallAnswer, MCallHangupReject } from "./callEventTypes"; +import { SyncState } from "../sync"; +import { RoomEvent } from "../models/room"; // Don't ring unless we'd be ringing for at least 3 seconds: the user needs some // time to press the 'accept' button const RING_GRACE_PERIOD = 3000; +export enum CallEvent { + Incoming = "Call.incoming", +} + +export type CallEventHandlerMap = { + [CallEvent.Incoming]: (call: MatrixCall) => void; +}; + export class CallEventHandler { client: MatrixClient; calls: Map; @@ -47,17 +57,17 @@ export class CallEventHandler { } public start() { - this.client.on("sync", this.evaluateEventBuffer); - this.client.on("Room.timeline", this.onRoomTimeline); + this.client.on(ClientEvent.Sync, this.evaluateEventBuffer); + this.client.on(RoomEvent.Timeline, this.onRoomTimeline); } public stop() { - this.client.removeListener("sync", this.evaluateEventBuffer); - this.client.removeListener("Room.timeline", this.onRoomTimeline); + this.client.removeListener(ClientEvent.Sync, this.evaluateEventBuffer); + this.client.removeListener(RoomEvent.Timeline, this.onRoomTimeline); } private evaluateEventBuffer = async () => { - if (this.client.getSyncState() === "SYNCING") { + if (this.client.getSyncState() === SyncState.Syncing) { await Promise.all(this.callEventBuffer.map(event => { this.client.decryptEventIfNeeded(event); })); @@ -221,7 +231,7 @@ export class CallEventHandler { call.hangup(CallErrorCode.Replaced, true); } } else { - this.client.emit("Call.incoming", call); + this.client.emit(CallEvent.Incoming, call); } return; } else if (type === EventType.CallCandidates) { From 2cd0ce7a8f5585256e22b960bebb9859b901629b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 22:08:26 +0000 Subject: [PATCH 22/30] Tidy --- src/client.ts | 22 +++++++++------------- src/crypto/DeviceList.ts | 21 ++++++--------------- src/crypto/index.ts | 22 ++++++++++++++-------- src/webrtc/callEventHandler.ts | 8 ++++---- 4 files changed, 33 insertions(+), 40 deletions(-) diff --git a/src/client.ts b/src/client.ts index a4cbcd9401f..68fb94e89d6 100644 --- a/src/client.ts +++ b/src/client.ts @@ -34,7 +34,7 @@ import { import { StubStore } from "./store/stub"; import { createNewMatrixCall, MatrixCall } from "./webrtc/call"; import { Filter, IFilterDefinition } from "./filter"; -import { CallEvent, CallEventHandler, CallEventHandlerMap } from './webrtc/callEventHandler'; +import { CallEventHandlerEvent, CallEventHandler, CallEventHandlerEventHandlerMap } from './webrtc/callEventHandler'; import * as utils from './utils'; import { sleep } from './utils'; import { Group } from "./models/group"; @@ -178,7 +178,6 @@ import { CryptoStore } from "./crypto/store/base"; import { MediaHandler } from "./webrtc/mediaHandler"; import { IRefreshTokenResponse } from "./@types/auth"; import { TypedEventEmitter } from "./models/typed-event-emitter"; -import { DeviceListEvent } from "./crypto/DeviceList"; export type Store = IStore; export type SessionStore = WebStorageSessionStore; @@ -812,16 +811,13 @@ type CryptoEvents = CryptoEvent.KeySignatureUploadFailure | CryptoEvent.DeviceVerificationChanged | CryptoEvent.UserTrustStatusChanged | CryptoEvent.KeysChanged - | DeviceListEvent.DevicesUpdated - | DeviceListEvent.WillUpdateDevices; + | CryptoEvent.Warning + | CryptoEvent.DevicesUpdated + | CryptoEvent.WillUpdateDevices; type MatrixEventEvents = MatrixEventEvent.Decrypted | MatrixEventEvent.Replaced | MatrixEventEvent.VisibilityChange; type RoomMemberEvents = RoomMemberEvent.Name - | RoomMemberEvent.Typing - | RoomMemberEvent.PowerLevel - | RoomMemberEvent.Membership - | RoomMemberEvent.Name | RoomMemberEvent.Typing | RoomMemberEvent.PowerLevel | RoomMemberEvent.Membership; @@ -839,7 +835,7 @@ type EmittedEvents = ClientEvent | MatrixEventEvents | RoomMemberEvents | UserEvents - | CallEvent.Incoming; + | CallEventHandlerEvent.Incoming; export type ClientEventHandlerMap = { [ClientEvent.Sync]: (state: SyncState, lastState?: SyncState, data?: ISyncStateData) => void; @@ -859,7 +855,7 @@ export type ClientEventHandlerMap = { & MatrixEventHandlerMap & RoomMemberEventHandlerMap & UserEventHandlerMap - & CallEventHandlerMap; + & CallEventHandlerEventHandlerMap; /** * Represents a Matrix Client. Only directly construct this if you want to use @@ -1675,9 +1671,9 @@ export class MatrixClient extends TypedEventEmitter>; -export enum DeviceListEvent { - WillUpdateDevices = "crypto.willUpdateDevices", - DevicesUpdated = "crypto.devicesUpdated", - UserCrossSigningUpdated = "userCrossSigningUpdated", -} - -export type EventHandlerMap = { - [DeviceListEvent.WillUpdateDevices]: (users: string[], initialFetch: boolean) => void; - [DeviceListEvent.DevicesUpdated]: (users: string[], initialFetch: boolean) => void; - [DeviceListEvent.UserCrossSigningUpdated]: (userId: string) => void; -}; +type EmittedEvents = CryptoEvent.WillUpdateDevices | CryptoEvent.DevicesUpdated | CryptoEvent.UserCrossSigningUpdated; /** * @alias module:crypto/DeviceList */ -export class DeviceList extends TypedEventEmitter { +export class DeviceList extends TypedEventEmitter { private devices: { [userId: string]: { [deviceId: string]: IDevice } } = {}; public crossSigningInfo: { [userId: string]: ICrossSigningInfo } = {}; @@ -645,7 +636,7 @@ export class DeviceList extends TypedEventEmitter { - this.emit(DeviceListEvent.WillUpdateDevices, users, !this.hasFetched); + this.emit(CryptoEvent.WillUpdateDevices, users, !this.hasFetched); users.forEach((u) => { this.dirty = true; @@ -670,7 +661,7 @@ export class DeviceList extends TypedEventEmitter void; [CryptoEvent.UserTrustStatusChanged]: (userId: string, trustLevel: UserTrustLevel) => void; @@ -231,10 +233,14 @@ export type CryptoEventHandlerMap = { upload: (opts: { shouldEmit: boolean }) => Promise ) => void; [CryptoEvent.VerificationRequest]: (request: VerificationRequest) => void; + [CryptoEvent.Warning]: (type: string) => void; [CryptoEvent.KeysChanged]: (data: {}) => void; -} & DeviceListHandlerMap; + [CryptoEvent.WillUpdateDevices]: (users: string[], initialFetch: boolean) => void; + [CryptoEvent.DevicesUpdated]: (users: string[], initialFetch: boolean) => void; + [CryptoEvent.UserCrossSigningUpdated]: (userId: string) => void; +}; -export class Crypto extends TypedEventEmitter { +export class Crypto extends TypedEventEmitter { /** * @return {string} The version of Olm. */ @@ -249,7 +255,7 @@ export class Crypto extends TypedEventEmitter; + private readonly reEmitter: TypedReEmitter; private readonly verificationMethods: Map; public readonly supportedAlgorithms: string[]; private readonly outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager; @@ -407,8 +413,8 @@ export class Crypto extends TypedEventEmitter void; +export type CallEventHandlerEventHandlerMap = { + [CallEventHandlerEvent.Incoming]: (call: MatrixCall) => void; }; export class CallEventHandler { @@ -231,7 +231,7 @@ export class CallEventHandler { call.hangup(CallErrorCode.Replaced, true); } } else { - this.client.emit(CallEvent.Incoming, call); + this.client.emit(CallEventHandlerEvent.Incoming, call); } return; } else if (type === EventType.CallCandidates) { From c1e3f9b54619800f9438b45368a1a96d3d88989e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 22:08:34 +0000 Subject: [PATCH 23/30] Remove entirely unused re-emits --- src/sync.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sync.ts b/src/sync.ts index 090c3b8d13d..043dfc62c04 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -207,8 +207,6 @@ export class SyncApi { RoomEvent.MyMembership, RoomEvent.Timeline, RoomEvent.TimelineReset, - "Room.replaceEvent", - "Room.visibilityChange", ]); this.registerStateListeners(room); return room; From c85ea3a3809b8ac8f52e3c5a8ee18d83e56a8acc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 23:00:25 +0000 Subject: [PATCH 24/30] Wrap up remaining types/enums --- src/client.ts | 15 ++++++++++----- src/http-api.ts | 25 +++++++++++++++++-------- src/webrtc/call.ts | 4 ++-- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/client.ts b/src/client.ts index 68fb94e89d6..01c049a98d6 100644 --- a/src/client.ts +++ b/src/client.ts @@ -32,7 +32,7 @@ import { MatrixEventHandlerMap, } from "./models/event"; import { StubStore } from "./store/stub"; -import { createNewMatrixCall, MatrixCall } from "./webrtc/call"; +import { CallEvent, CallEventHandlerMap, createNewMatrixCall, MatrixCall } from "./webrtc/call"; import { Filter, IFilterDefinition } from "./filter"; import { CallEventHandlerEvent, CallEventHandler, CallEventHandlerEventHandlerMap } from './webrtc/callEventHandler'; import * as utils from './utils'; @@ -49,7 +49,7 @@ import { IRoomEncryption, RoomList } from './crypto/RoomList'; import { logger } from './logger'; import { SERVICE_TYPES } from './service-types'; import { - FileType, + FileType, HttpApiEvent, HttpApiEventHandlerMap, IHttpOpts, IUpload, MatrixError, @@ -835,7 +835,10 @@ type EmittedEvents = ClientEvent | MatrixEventEvents | RoomMemberEvents | UserEvents - | CallEventHandlerEvent.Incoming; + | CallEvent // re-emitted by call.ts using Object.values + | CallEventHandlerEvent.Incoming + | HttpApiEvent.SessionLoggedOut + | HttpApiEvent.NoConsent; export type ClientEventHandlerMap = { [ClientEvent.Sync]: (state: SyncState, lastState?: SyncState, data?: ISyncStateData) => void; @@ -855,7 +858,9 @@ export type ClientEventHandlerMap = { & MatrixEventHandlerMap & RoomMemberEventHandlerMap & UserEventHandlerMap - & CallEventHandlerEventHandlerMap; + & CallEventHandlerEventHandlerMap + & CallEventHandlerMap + & HttpApiEventHandlerMap; /** * Represents a Matrix Client. Only directly construct this if you want to use @@ -946,7 +951,7 @@ export class MatrixClient extends TypedEventEmitter[0], { baseUrl: opts.baseUrl, idBaseUrl: opts.idBaseUrl, accessToken: opts.accessToken, diff --git a/src/http-api.ts b/src/http-api.ts index fd016c731e8..2879ea68159 100644 --- a/src/http-api.ts +++ b/src/http-api.ts @@ -21,7 +21,6 @@ limitations under the License. */ import { parse as parseContentType, ParsedMediaType } from "content-type"; -import EventEmitter from "events"; import type { IncomingHttpHeaders, IncomingMessage } from "http"; import type { Request as _Request, CoreOptions } from "request"; @@ -35,6 +34,7 @@ import { IDeferred } from "./utils"; import { Callback } from "./client"; import * as utils from "./utils"; import { logger } from './logger'; +import { TypedEventEmitter } from "./models/typed-event-emitter"; /* TODO: @@ -164,6 +164,16 @@ export enum Method { export type FileType = Document | XMLHttpRequestBodyInit; +export enum HttpApiEvent { + SessionLoggedOut = "Session.logged_out", + NoConsent = "no_consent", +} + +export type HttpApiEventHandlerMap = { + [HttpApiEvent.SessionLoggedOut]: (err: MatrixError) => void; + [HttpApiEvent.NoConsent]: (message: string, consentUri: string) => void; +}; + /** * Construct a MatrixHttpApi. * @constructor @@ -192,7 +202,10 @@ export type FileType = Document | XMLHttpRequestBodyInit; export class MatrixHttpApi { private uploads: IUpload[] = []; - constructor(private eventEmitter: EventEmitter, public readonly opts: IHttpOpts) { + constructor( + private eventEmitter: TypedEventEmitter, + public readonly opts: IHttpOpts, + ) { utils.checkObjectHasKeys(opts, ["baseUrl", "request", "prefix"]); opts.onlyData = !!opts.onlyData; opts.useAuthorizationHeader = !!opts.useAuthorizationHeader; @@ -603,13 +616,9 @@ export class MatrixHttpApi { requestPromise.catch((err: MatrixError) => { if (err.errcode == 'M_UNKNOWN_TOKEN' && !requestOpts?.inhibitLogoutEmit) { - this.eventEmitter.emit("Session.logged_out", err); + this.eventEmitter.emit(HttpApiEvent.SessionLoggedOut, err); } else if (err.errcode == 'M_CONSENT_NOT_GIVEN') { - this.eventEmitter.emit( - "no_consent", - err.message, - err.data.consent_uri, - ); + this.eventEmitter.emit(HttpApiEvent.NoConsent, err.message, err.data.consent_uri); } }); diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 9f43c9baefa..9a74e826a40 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -240,7 +240,7 @@ function genCallID(): string { return Date.now().toString() + randomString(16); } -type EventHandlerMap = { +export type CallEventHandlerMap = { [CallEvent.DataChannel]: (channel: RTCDataChannel) => void; [CallEvent.FeedsChanged]: (feeds: CallFeed[]) => void; [CallEvent.Replaced]: (newCall: MatrixCall) => void; @@ -266,7 +266,7 @@ type EventHandlerMap = { * @param {Array} opts.turnServers Optional. A list of TURN servers. * @param {MatrixClient} opts.client The Matrix Client instance to send events to. */ -export class MatrixCall extends TypedEventEmitter { +export class MatrixCall extends TypedEventEmitter { public roomId: string; public callId: string; public state = CallState.Fledgling; From 78d5b34d1ff7b92536792d80b23434a5293e3268 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 23:04:19 +0000 Subject: [PATCH 25/30] delint --- src/crypto/verification/SAS.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/crypto/verification/SAS.ts b/src/crypto/verification/SAS.ts index 4dcd5521541..a3599d5dc68 100644 --- a/src/crypto/verification/SAS.ts +++ b/src/crypto/verification/SAS.ts @@ -22,12 +22,7 @@ limitations under the License. import anotherjson from 'another-json'; import { Utility, SAS as OlmSAS } from "@matrix-org/olm"; -import { - VerificationBase as Base, - SwitchStartEventError, - VerificationEvent, - VerificationEventHandlerMap -} from "./Base"; +import { VerificationBase as Base, SwitchStartEventError, VerificationEventHandlerMap } from "./Base"; import { errorFactory, newInvalidMessageError, From f895fc71c6427e89fb00645c20849201b36dac3d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 16 Feb 2022 23:15:15 +0000 Subject: [PATCH 26/30] Fix tests --- src/crypto/index.ts | 2 +- src/models/event-status.ts | 40 ++++++++++++++++++++++++++++++++++++++ src/models/event.ts | 26 ++----------------------- src/models/room.ts | 3 ++- 4 files changed, 45 insertions(+), 26 deletions(-) create mode 100644 src/models/event-status.ts diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 6e0fe7d4e61..14771da4312 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -51,6 +51,7 @@ import { } from "./api"; import { OutgoingRoomKeyRequestManager } from './OutgoingRoomKeyRequestManager'; import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store'; +import { VerificationBase } from "./verification/Base"; import { ReciprocateQRCode, SCAN_QR_CODE_METHOD, SHOW_QR_CODE_METHOD } from './verification/QRCode'; import { SAS as SASVerification } from './verification/SAS'; import { keyFromPassphrase } from './key_passphrase'; @@ -82,7 +83,6 @@ import { ISyncStateData } from "../sync"; import { CryptoStore } from "./store/base"; import { IVerificationChannel } from "./verification/request/Channel"; import { TypedEventEmitter } from "../models/typed-event-emitter"; -import { VerificationBase } from "./verification/Base"; const DeviceVerification = DeviceInfo.DeviceVerification; diff --git a/src/models/event-status.ts b/src/models/event-status.ts new file mode 100644 index 00000000000..faca97186c9 --- /dev/null +++ b/src/models/event-status.ts @@ -0,0 +1,40 @@ +/* +Copyright 2015 - 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/** + * Enum for event statuses. + * @readonly + * @enum {string} + */ +export enum EventStatus { + /** The event was not sent and will no longer be retried. */ + NOT_SENT = "not_sent", + + /** The message is being encrypted */ + ENCRYPTING = "encrypting", + + /** The event is in the process of being sent. */ + SENDING = "sending", + + /** The event is in a queue waiting to be sent. */ + QUEUED = "queued", + + /** The event has been sent to the server, but we have not yet received the echo. */ + SENT = "sent", + + /** The event was cancelled before it was successfully sent. */ + CANCELLED = "cancelled", +} diff --git a/src/models/event.ts b/src/models/event.ts index 5c1f09a733b..47def019b47 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -33,31 +33,9 @@ import { IActionsObject } from '../pushprocessor'; import { TypedReEmitter } from '../ReEmitter'; import { MatrixError } from "../http-api"; import { TypedEventEmitter } from "./typed-event-emitter"; +import { EventStatus } from "./event-status"; -/** - * Enum for event statuses. - * @readonly - * @enum {string} - */ -export enum EventStatus { - /** The event was not sent and will no longer be retried. */ - NOT_SENT = "not_sent", - - /** The message is being encrypted */ - ENCRYPTING = "encrypting", - - /** The event is in the process of being sent. */ - SENDING = "sending", - - /** The event is in a queue waiting to be sent. */ - QUEUED = "queued", - - /** The event has been sent to the server, but we have not yet received the echo. */ - SENT = "sent", - - /** The event was cancelled before it was successfully sent. */ - CANCELLED = "cancelled", -} +export { EventStatus } from "./event-status"; const interns: Record = {}; function intern(str: string): string { diff --git a/src/models/room.ts b/src/models/room.ts index a38430608d6..716f995fde4 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -23,7 +23,8 @@ import { Direction, EventTimeline } from "./event-timeline"; import { getHttpUriForMxc } from "../content-repo"; import * as utils from "../utils"; import { normalize } from "../utils"; -import { EventStatus, IEvent, MatrixEvent } from "./event"; +import { IEvent, MatrixEvent } from "./event"; +import { EventStatus } from "./event-status"; import { RoomMember } from "./room-member"; import { IRoomSummary, RoomSummary } from "./room-summary"; import { logger } from '../logger'; From 1f3f6821d5ffe3e8b865d44ab79e5cb24f3c97ab Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Feb 2022 08:09:13 +0000 Subject: [PATCH 27/30] Fix tests --- spec/unit/crypto/verification/secret_request.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/crypto/verification/secret_request.spec.js b/spec/unit/crypto/verification/secret_request.spec.js index 4b768311a3d..398edc10a60 100644 --- a/spec/unit/crypto/verification/secret_request.spec.js +++ b/spec/unit/crypto/verification/secret_request.spec.js @@ -14,10 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { VerificationBase } from '../../../../src/crypto/verification/Base'; import { CrossSigningInfo } from '../../../../src/crypto/CrossSigning'; import { encodeBase64 } from "../../../../src/crypto/olmlib"; import { setupWebcrypto, teardownWebcrypto } from './util'; +import { VerificationBase } from '../../../../src/crypto/verification/Base'; jest.useFakeTimers(); From 72e580312cc0ac0864948a4be9f167471f8837fc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Feb 2022 18:20:49 +0000 Subject: [PATCH 28/30] Use ESLint to prevent usage of EventEmitter directly --- .eslintrc.js | 3 +++ spec/unit/ReEmitter.spec.ts | 1 + spec/unit/crypto.spec.js | 1 + src/ReEmitter.ts | 1 + src/models/group.js | 1 + src/models/typed-event-emitter.ts | 3 ++- 6 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 1735739e326..6fc5b99a671 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -31,6 +31,9 @@ module.exports = { "no-async-promise-executor": "off", // We use a `logger` intermediary module "no-console": "error", + + // restrict EventEmitters to force callers to use TypedEventEmitter + "no-restricted-imports": ["error", "events"], }, overrides: [{ files: [ diff --git a/spec/unit/ReEmitter.spec.ts b/spec/unit/ReEmitter.spec.ts index 3570b06fea1..4ce28429d12 100644 --- a/spec/unit/ReEmitter.spec.ts +++ b/spec/unit/ReEmitter.spec.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// eslint-disable-next-line no-restricted-imports import { EventEmitter } from "events"; import { ReEmitter } from "../../src/ReEmitter"; diff --git a/spec/unit/crypto.spec.js b/spec/unit/crypto.spec.js index 3245a28c0ad..450a99af43e 100644 --- a/spec/unit/crypto.spec.js +++ b/spec/unit/crypto.spec.js @@ -1,4 +1,5 @@ import '../olm-loader'; +// eslint-disable-next-line no-restricted-imports import { EventEmitter } from "events"; import { Crypto } from "../../src/crypto"; diff --git a/src/ReEmitter.ts b/src/ReEmitter.ts index 330eb730347..5a352b8f077 100644 --- a/src/ReEmitter.ts +++ b/src/ReEmitter.ts @@ -16,6 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// eslint-disable-next-line no-restricted-imports import { EventEmitter } from "events"; import { ListenerMap, TypedEventEmitter } from "./models/typed-event-emitter"; diff --git a/src/models/group.js b/src/models/group.js index 44fae31661e..29f0fb3846c 100644 --- a/src/models/group.js +++ b/src/models/group.js @@ -20,6 +20,7 @@ limitations under the License. * @deprecated groups/communities never made it to the spec and support for them is being discontinued. */ +// eslint-disable-next-line no-restricted-imports import { EventEmitter } from "events"; import * as utils from "../utils"; diff --git a/src/models/typed-event-emitter.ts b/src/models/typed-event-emitter.ts index 730ee58f94a..691ec5ec350 100644 --- a/src/models/typed-event-emitter.ts +++ b/src/models/typed-event-emitter.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// eslint-disable-next-line no-restricted-imports import { EventEmitter } from "events"; export enum EventEmitterEvents { @@ -42,7 +43,7 @@ export type Listener< * to properly type this, so that our events are not stringly-based and prone * to silly typos. */ -export abstract class TypedEventEmitter< +export class TypedEventEmitter< Events extends string, Arguments extends ListenerMap, SuperclassArguments extends ListenerMap = Arguments, From d9844b3318881c795a8a9b14e51ff19d0b58826a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Feb 2022 18:21:06 +0000 Subject: [PATCH 29/30] Type the IDB EventEmitter without an enum --- src/store/indexeddb.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/store/indexeddb.ts b/src/store/indexeddb.ts index 51fa88d5f53..018f5abd197 100644 --- a/src/store/indexeddb.ts +++ b/src/store/indexeddb.ts @@ -16,8 +16,6 @@ limitations under the License. /* eslint-disable @babel/no-invalid-this */ -import { EventEmitter } from 'events'; - import { MemoryStore, IOpts as IBaseOpts } from "./memory"; import { LocalIndexedDBStoreBackend } from "./indexeddb-local-backend"; import { RemoteIndexedDBStoreBackend } from "./indexeddb-remote-backend"; @@ -27,6 +25,7 @@ import { logger } from '../logger'; import { ISavedSync } from "./index"; import { IIndexedDBBackend } from "./indexeddb-backend"; import { ISyncResponse } from "../sync-accumulator"; +import { TypedEventEmitter } from "../models/typed-event-emitter"; /** * This is an internal module. See {@link IndexedDBStore} for the public class. @@ -46,6 +45,10 @@ interface IOpts extends IBaseOpts { workerFactory?: () => Worker; } +type EventHandlerMap = { + "degraded": (e: Error) => void; +}; + export class IndexedDBStore extends MemoryStore { static exists(indexedDB: IDBFactory, dbName: string): Promise { return LocalIndexedDBStoreBackend.exists(indexedDB, dbName); @@ -59,7 +62,7 @@ export class IndexedDBStore extends MemoryStore { // the database, such that we can derive the set if users that have been // modified since we last saved. private userModifiedMap: Record = {}; // user_id : timestamp - private emitter = new EventEmitter(); + private emitter = new TypedEventEmitter(); /** * Construct a new Indexed Database store, which extends MemoryStore. From eaeb4584b37f922ca99ce38247c47fc60dab5ced Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 22 Feb 2022 11:23:56 +0000 Subject: [PATCH 30/30] Fix types --- src/models/thread.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/models/thread.ts b/src/models/thread.ts index 379e8d96063..4c63bdf4a13 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -55,7 +55,7 @@ export class Thread extends TypedEventEmitter { /** * A reference to all the events ID at the bottom of the threads */ - public readonly timelineSet; + public readonly timelineSet: EventTimelineSet; private _currentUserParticipated = false; @@ -148,10 +148,11 @@ export class Thread extends TypedEventEmitter { * the tail/root references if needed * Will fire "Thread.update" * @param event The event to add + * @param {boolean} toStartOfTimeline whether the event is being added + * to the start (and not the end) of the timeline. */ public async addEvent(event: MatrixEvent, toStartOfTimeline = false): Promise { - // Add all incoming events to the thread's timeline set when there's - // no server support + // Add all incoming events to the thread's timeline set when there's no server support if (!this.hasServerSideSupport) { // all the relevant membership info to hydrate events with a sender // is held in the main room timeline