Skip to content

Commit b265d79

Browse files
authored
Re-emit room state events on rooms (#2607)
* Re-emit room state events on rooms This also fixes some potential memory leaks and abuse of removeAllListeners in sync.ts. * Remove some stray whitespace * Deduplicate some code to appease SonarCloud * Name helper function more explicitly
1 parent eb79f62 commit b265d79

File tree

5 files changed

+140
-145
lines changed

5 files changed

+140
-145
lines changed

.eslintrc.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ module.exports = {
5757
// We're okay with assertion errors when we ask for them
5858
"@typescript-eslint/no-non-null-assertion": "off",
5959

60+
// The non-TypeScript rule produces false positives
61+
"func-call-spacing": "off",
62+
"@typescript-eslint/func-call-spacing": ["error"],
63+
6064
"quotes": "off",
6165
// We use a `logger` intermediary module
6266
"no-console": "error",

src/ReEmitter.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,16 @@ import { ListenerMap, TypedEventEmitter } from "./models/typed-event-emitter";
2424
export class ReEmitter {
2525
constructor(private readonly target: EventEmitter) {}
2626

27+
// Map from emitter to event name to re-emitter
28+
private reEmitters = new Map<EventEmitter, Map<string, (...args: any[]) => void>>();
29+
2730
public reEmit(source: EventEmitter, eventNames: string[]): void {
31+
let reEmittersByEvent = this.reEmitters.get(source);
32+
if (!reEmittersByEvent) {
33+
reEmittersByEvent = new Map();
34+
this.reEmitters.set(source, reEmittersByEvent);
35+
}
36+
2837
for (const eventName of eventNames) {
2938
// We include the source as the last argument for event handlers which may need it,
3039
// such as read receipt listeners on the client class which won't have the context
@@ -44,7 +53,20 @@ export class ReEmitter {
4453
this.target.emit(eventName, ...args, source);
4554
};
4655
source.on(eventName, forSource);
56+
reEmittersByEvent.set(eventName, forSource);
57+
}
58+
}
59+
60+
public stopReEmitting(source: EventEmitter, eventNames: string[]): void {
61+
const reEmittersByEvent = this.reEmitters.get(source);
62+
if (!reEmittersByEvent) return; // We were never re-emitting these events in the first place
63+
64+
for (const eventName of eventNames) {
65+
source.off(eventName, reEmittersByEvent.get(eventName));
66+
reEmittersByEvent.delete(eventName);
4767
}
68+
69+
if (reEmittersByEvent.size === 0) this.reEmitters.delete(source);
4870
}
4971
}
5072

@@ -62,4 +84,11 @@ export class TypedReEmitter<
6284
): void {
6385
super.reEmit(source, eventNames);
6486
}
87+
88+
public stopReEmitting<ReEmittedEvents extends string, T extends Events & ReEmittedEvents>(
89+
source: TypedEventEmitter<ReEmittedEvents, any>,
90+
eventNames: T[],
91+
): void {
92+
super.stopReEmitting(source, eventNames);
93+
}
6594
}

src/models/room.ts

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
2+
Copyright 2015 - 2022 The Matrix.org Foundation C.I.C.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -37,7 +37,8 @@ import {
3737
import { IRoomVersionsCapability, MatrixClient, PendingEventOrdering, RoomVersionStability } from "../client";
3838
import { GuestAccess, HistoryVisibility, JoinRule, ResizeMethod } from "../@types/partials";
3939
import { Filter, IFilterDefinition } from "../filter";
40-
import { RoomState } from "./room-state";
40+
import { RoomState, RoomStateEvent, RoomStateEventHandlerMap } from "./room-state";
41+
import { BeaconEvent, BeaconEventHandlerMap } from "./beacon";
4142
import {
4243
Thread,
4344
ThreadEvent,
@@ -172,16 +173,19 @@ export enum RoomEvent {
172173
}
173174

174175
type EmittedEvents = RoomEvent
176+
| RoomStateEvent.Events
177+
| RoomStateEvent.Members
178+
| RoomStateEvent.NewMember
179+
| RoomStateEvent.Update
180+
| RoomStateEvent.Marker
175181
| ThreadEvent.New
176182
| ThreadEvent.Update
177183
| ThreadEvent.NewReply
178-
| RoomEvent.Timeline
179-
| RoomEvent.TimelineReset
180-
| RoomEvent.TimelineRefresh
181-
| RoomEvent.HistoryImportedWithinTimeline
182-
| RoomEvent.OldStateUpdated
183-
| RoomEvent.CurrentStateUpdated
184-
| MatrixEventEvent.BeforeRedaction;
184+
| MatrixEventEvent.BeforeRedaction
185+
| BeaconEvent.New
186+
| BeaconEvent.Update
187+
| BeaconEvent.Destroy
188+
| BeaconEvent.LivenessChange;
185189

186190
export type RoomEventHandlerMap = {
187191
[RoomEvent.MyMembership]: (room: Room, membership: string, prevMembership?: string) => void;
@@ -205,7 +209,21 @@ export type RoomEventHandlerMap = {
205209
) => void;
206210
[RoomEvent.TimelineRefresh]: (room: Room, eventTimelineSet: EventTimelineSet) => void;
207211
[ThreadEvent.New]: (thread: Thread, toStartOfTimeline: boolean) => void;
208-
} & ThreadHandlerMap & MatrixEventHandlerMap;
212+
} & ThreadHandlerMap
213+
& MatrixEventHandlerMap
214+
& Pick<
215+
RoomStateEventHandlerMap,
216+
RoomStateEvent.Events
217+
| RoomStateEvent.Members
218+
| RoomStateEvent.NewMember
219+
| RoomStateEvent.Update
220+
| RoomStateEvent.Marker
221+
| BeaconEvent.New
222+
>
223+
& Pick<
224+
BeaconEventHandlerMap,
225+
BeaconEvent.Update | BeaconEvent.Destroy | BeaconEvent.LivenessChange
226+
>;
209227

210228
export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap> {
211229
public readonly reEmitter: TypedReEmitter<EmittedEvents, RoomEventHandlerMap>;
@@ -1068,6 +1086,32 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
10681086

10691087
if (previousCurrentState !== this.currentState) {
10701088
this.emit(RoomEvent.CurrentStateUpdated, this, previousCurrentState, this.currentState);
1089+
1090+
// Re-emit various events on the current room state
1091+
// TODO: If currentState really only exists for backwards
1092+
// compatibility, shouldn't we be doing this some other way?
1093+
this.reEmitter.stopReEmitting(previousCurrentState, [
1094+
RoomStateEvent.Events,
1095+
RoomStateEvent.Members,
1096+
RoomStateEvent.NewMember,
1097+
RoomStateEvent.Update,
1098+
RoomStateEvent.Marker,
1099+
BeaconEvent.New,
1100+
BeaconEvent.Update,
1101+
BeaconEvent.Destroy,
1102+
BeaconEvent.LivenessChange,
1103+
]);
1104+
this.reEmitter.reEmit(this.currentState, [
1105+
RoomStateEvent.Events,
1106+
RoomStateEvent.Members,
1107+
RoomStateEvent.NewMember,
1108+
RoomStateEvent.Update,
1109+
RoomStateEvent.Marker,
1110+
BeaconEvent.New,
1111+
BeaconEvent.Update,
1112+
BeaconEvent.Destroy,
1113+
BeaconEvent.LivenessChange,
1114+
]);
10711115
}
10721116
}
10731117

src/sliding-sync-sdk.ts

Lines changed: 2 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,11 @@ import { logger } from './logger';
1919
import * as utils from "./utils";
2020
import { EventTimeline } from "./models/event-timeline";
2121
import { ClientEvent, IStoredClientOpts, MatrixClient, PendingEventOrdering } from "./client";
22-
import { ISyncStateData, SyncState } from "./sync";
22+
import { ISyncStateData, SyncState, _createAndReEmitRoom } from "./sync";
2323
import { MatrixEvent } from "./models/event";
2424
import { Crypto } from "./crypto";
2525
import { IMinimalEvent, IRoomEvent, IStateEvent, IStrippedState } from "./sync-accumulator";
2626
import { MatrixError } from "./http-api";
27-
import { RoomStateEvent } from "./models/room-state";
28-
import { RoomMemberEvent } from "./models/room-member";
2927
import {
3028
Extension,
3129
ExtensionState,
@@ -290,7 +288,7 @@ export class SlidingSyncSdk {
290288
logger.debug("initial flag not set but no stored room exists for room ", roomId, roomData);
291289
return;
292290
}
293-
room = createRoom(this.client, roomId, this.opts);
291+
room = _createAndReEmitRoom(this.client, roomId, this.opts);
294292
}
295293
this.processRoomData(this.client, room, roomData);
296294
}
@@ -536,7 +534,6 @@ export class SlidingSyncSdk {
536534
}
537535
538536
if (limited) {
539-
deregisterStateListeners(room);
540537
room.resetLiveTimeline(
541538
roomData.prev_batch,
542539
null, // TODO this.opts.canResetEntireTimeline(room.roomId) ? null : syncEventData.oldSyncToken,
@@ -546,7 +543,6 @@ export class SlidingSyncSdk {
546543
// reason to stop incrementally tracking notifications and
547544
// reset the timeline.
548545
this.client.resetNotifTimelineSet();
549-
registerStateListeners(this.client, room);
550546
}
551547
} */
552548

@@ -816,58 +812,6 @@ function ensureNameEvent(client: MatrixClient, roomId: string, roomData: MSC3575
816812
// Helper functions which set up JS SDK structs are below and are identical to the sync v2 counterparts,
817813
// just outside the class.
818814

819-
function createRoom(client: MatrixClient, roomId: string, opts: Partial<IStoredClientOpts>): Room { // XXX cargoculted from sync.ts
820-
const { timelineSupport } = client;
821-
const room = new Room(roomId, client, client.getUserId(), {
822-
lazyLoadMembers: opts.lazyLoadMembers,
823-
pendingEventOrdering: opts.pendingEventOrdering,
824-
timelineSupport,
825-
});
826-
client.reEmitter.reEmit(room, [
827-
RoomEvent.Name,
828-
RoomEvent.Redaction,
829-
RoomEvent.RedactionCancelled,
830-
RoomEvent.Receipt,
831-
RoomEvent.Tags,
832-
RoomEvent.LocalEchoUpdated,
833-
RoomEvent.AccountData,
834-
RoomEvent.MyMembership,
835-
RoomEvent.Timeline,
836-
RoomEvent.TimelineReset,
837-
]);
838-
registerStateListeners(client, room);
839-
return room;
840-
}
841-
842-
function registerStateListeners(client: MatrixClient, room: Room): void { // XXX cargoculted from sync.ts
843-
// we need to also re-emit room state and room member events, so hook it up
844-
// to the client now. We need to add a listener for RoomState.members in
845-
// order to hook them correctly.
846-
client.reEmitter.reEmit(room.currentState, [
847-
RoomStateEvent.Events,
848-
RoomStateEvent.Members,
849-
RoomStateEvent.NewMember,
850-
RoomStateEvent.Update,
851-
]);
852-
room.currentState.on(RoomStateEvent.NewMember, function(event, state, member) {
853-
member.user = client.getUser(member.userId);
854-
client.reEmitter.reEmit(member, [
855-
RoomMemberEvent.Name,
856-
RoomMemberEvent.Typing,
857-
RoomMemberEvent.PowerLevel,
858-
RoomMemberEvent.Membership,
859-
]);
860-
});
861-
}
862-
863-
/*
864-
function deregisterStateListeners(room: Room): void { // XXX cargoculted from sync.ts
865-
// could do with a better way of achieving this.
866-
room.currentState.removeAllListeners(RoomStateEvent.Events);
867-
room.currentState.removeAllListeners(RoomStateEvent.Members);
868-
room.currentState.removeAllListeners(RoomStateEvent.NewMember);
869-
} */
870-
871815
function mapEvents(client: MatrixClient, roomId: string, events: object[], decrypt = true): MatrixEvent[] {
872816
const mapper = client.getEventMapper({ decrypt });
873817
return (events as Array<IStrippedState | IRoomEvent | IStateEvent | IMinimalEvent>).map(function(e) {

0 commit comments

Comments
 (0)