Skip to content

Commit 704bb66

Browse files
authored
Refactor how we listen for matrix events. (#446)
* Refactor Matrix event listener in Mjolnir and ManagedMjolnir. closes #411. Issue #411 says that we have to be careful about room.join, but this was before we figured how to make matrix-appservice-bridge echo events sent by its own intents. * Remove MatrixClientListener since it isn't actually needed. * Protect which config values can be used for ManagedMjolnirs. * Introduce MatrixSendClient so listeners aren't accidentally added to a MatrixClient instead of MatrixEmitter. * doc * Move provisioned mjolnir config to src/config. This just aids maintance so whenever someone goes to change the config of the bot they will see this and update it. * doc for matrix intent listener.
1 parent 262e80a commit 704bb66

18 files changed

+242
-91
lines changed

src/ManagementRoomOutput.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ limitations under the License.
1515
*/
1616

1717
import * as Sentry from "@sentry/node";
18-
import { extractRequestError, LogLevel, LogService, MatrixClient, MessageType, Permalinks, TextualMessageEventContent, UserID } from "matrix-bot-sdk";
18+
import { extractRequestError, LogLevel, LogService, MessageType, Permalinks, TextualMessageEventContent, UserID } from "matrix-bot-sdk";
1919
import { IConfig } from "./config";
20+
import { MatrixSendClient } from "./MatrixEmitter";
2021
import { htmlEscape } from "./utils";
2122

2223
const levelToFn = {
@@ -33,7 +34,7 @@ export default class ManagementRoomOutput {
3334

3435
constructor(
3536
private readonly managementRoomId: string,
36-
private readonly client: MatrixClient,
37+
private readonly client: MatrixSendClient,
3738
private readonly config: IConfig,
3839
) {
3940

src/MatrixEmitter.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
Copyright 2019-2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import EventEmitter from "events";
18+
import { MatrixClient } from "matrix-bot-sdk";
19+
20+
/**
21+
* This is an interface created in order to keep the event listener
22+
* Mjolnir uses for new events generic.
23+
* Used to provide a unified API for messages received from matrix-bot-sdk (using GET /sync)
24+
* when we're in single bot mode and messages received from matrix-appservice-bridge (using pushed /transaction)
25+
* when we're in appservice mode.
26+
*/
27+
export declare interface MatrixEmitter extends EventEmitter {
28+
on(event: 'room.event', listener: (roomId: string, mxEvent: any) => void ): this
29+
emit(event: 'room.event', roomId: string, mxEvent: any): boolean
30+
31+
on(event: 'room.message', listener: (roomId: string, mxEvent: any) => void ): this
32+
emit(event: 'room.message', roomId: string, mxEvent: any): boolean
33+
34+
on(event: 'room.invite', listener: (roomId: string, mxEvent: any) => void ): this
35+
emit(event: 'room.invite', roomId: string, mxEvent: any): boolean
36+
37+
on(event: 'room.join', listener: (roomId: string, mxEvent: any) => void ): this
38+
emit(event: 'room.join', roomId: string, mxEvent: any): boolean
39+
40+
on(event: 'room.leave', listener: (roomId: string, mxEvent: any) => void ): this
41+
emit(event: 'room.leave', roomId: string, mxEvent: any): boolean
42+
43+
on(event: 'room.archived', listener: (roomId: string, mxEvent: any) => void ): this
44+
emit(event: 'room.archived', roomId: string, mxEvent: any): boolean
45+
46+
start(): Promise<void>;
47+
stop(): void;
48+
}
49+
50+
/**
51+
* A `MatrixClient` without the properties of `MatrixEmitter`.
52+
* This is in order to enforce listeners are added to `MatrixEmitter`s
53+
* rather than on the matrix-bot-sdk version of the matrix client.
54+
*/
55+
export type MatrixSendClient = Omit<MatrixClient, keyof MatrixEmitter>;

src/Mjolnir.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {
1818
extractRequestError,
1919
LogLevel,
2020
LogService,
21-
MatrixClient,
2221
MembershipEvent,
2322
Permalinks,
2423
} from "matrix-bot-sdk";
@@ -39,6 +38,7 @@ import ManagementRoomOutput from "./ManagementRoomOutput";
3938
import { ProtectionManager } from "./protections/ProtectionManager";
4039
import { RoomMemberManager } from "./RoomMembers";
4140
import ProtectedRoomsConfig from "./ProtectedRoomsConfig";
41+
import { MatrixEmitter, MatrixSendClient } from "./MatrixEmitter";
4242

4343
export const STATE_NOT_STARTED = "not_started";
4444
export const STATE_CHECKING_PERMISSIONS = "checking_permissions";
@@ -88,15 +88,15 @@ export class Mjolnir {
8888

8989
/**
9090
* Adds a listener to the client that will automatically accept invitations.
91-
* @param {MatrixClient} client
91+
* @param {MatrixSendClient} client
9292
* @param options By default accepts invites from anyone.
9393
* @param {string} options.managementRoom The room to report ignored invitations to if `recordIgnoredInvites` is true.
9494
* @param {boolean} options.recordIgnoredInvites Whether to report invites that will be ignored to the `managementRoom`.
9595
* @param {boolean} options.autojoinOnlyIfManager Whether to only accept an invitation by a user present in the `managementRoom`.
9696
* @param {string} options.acceptInvitesFromSpace A space of users to accept invites from, ignores invites form users not in this space.
9797
*/
98-
private static addJoinOnInviteListener(mjolnir: Mjolnir, client: MatrixClient, options: { [key: string]: any }) {
99-
client.on("room.invite", async (roomId: string, inviteEvent: any) => {
98+
private static addJoinOnInviteListener(mjolnir: Mjolnir, client: MatrixSendClient, options: { [key: string]: any }) {
99+
mjolnir.matrixEmitter.on("room.invite", async (roomId: string, inviteEvent: any) => {
100100
const membershipEvent = new MembershipEvent(inviteEvent);
101101

102102
const reportInvite = async () => {
@@ -130,17 +130,16 @@ export class Mjolnir {
130130
});
131131
if (!spaceUserIds.includes(membershipEvent.sender)) return reportInvite(); // ignore invite
132132
}
133-
134133
return client.joinRoom(roomId);
135134
});
136135
}
137136

138137
/**
139138
* Create a new Mjolnir instance from a client and the options in the configuration file, ready to be started.
140-
* @param {MatrixClient} client The client for Mjolnir to use.
139+
* @param {MatrixSendClient} client The client for Mjolnir to use.
141140
* @returns A new Mjolnir instance that can be started without further setup.
142141
*/
143-
static async setupMjolnirFromConfig(client: MatrixClient, config: IConfig): Promise<Mjolnir> {
142+
static async setupMjolnirFromConfig(client: MatrixSendClient, matrixEmitter: MatrixEmitter, config: IConfig): Promise<Mjolnir> {
144143
const policyLists: PolicyList[] = [];
145144
const joinedRooms = await client.getJoinedRooms();
146145

@@ -152,15 +151,16 @@ export class Mjolnir {
152151
}
153152

154153
const ruleServer = config.web.ruleServer ? new RuleServer() : null;
155-
const mjolnir = new Mjolnir(client, await client.getUserId(), managementRoomId, config, policyLists, ruleServer);
154+
const mjolnir = new Mjolnir(client, await client.getUserId(), matrixEmitter, managementRoomId, config, policyLists, ruleServer);
156155
await mjolnir.managementRoomOutput.logMessage(LogLevel.INFO, "index", "Mjolnir is starting up. Use !mjolnir to query status.");
157156
Mjolnir.addJoinOnInviteListener(mjolnir, client, config);
158157
return mjolnir;
159158
}
160159

161160
constructor(
162-
public readonly client: MatrixClient,
161+
public readonly client: MatrixSendClient,
163162
private readonly clientUserId: string,
163+
public readonly matrixEmitter: MatrixEmitter,
164164
public readonly managementRoomId: string,
165165
public readonly config: IConfig,
166166
private policyLists: PolicyList[],
@@ -171,9 +171,9 @@ export class Mjolnir {
171171

172172
// Setup bot.
173173

174-
client.on("room.event", this.handleEvent.bind(this));
174+
matrixEmitter.on("room.event", this.handleEvent.bind(this));
175175

176-
client.on("room.message", async (roomId, event) => {
176+
matrixEmitter.on("room.message", async (roomId, event) => {
177177
if (roomId !== this.managementRoomId) return;
178178
if (!event['content']) return;
179179

@@ -208,11 +208,11 @@ export class Mjolnir {
208208
}
209209
});
210210

211-
client.on("room.join", (roomId: string, event: any) => {
211+
matrixEmitter.on("room.join", (roomId: string, event: any) => {
212212
LogService.info("Mjolnir", `Joined ${roomId}`);
213213
return this.resyncJoinedRooms();
214214
});
215-
client.on("room.leave", (roomId: string, event: any) => {
215+
matrixEmitter.on("room.leave", (roomId: string, event: any) => {
216216
LogService.info("Mjolnir", `Left ${roomId}`);
217217
return this.resyncJoinedRooms();
218218
});
@@ -234,7 +234,7 @@ export class Mjolnir {
234234
this.reportPoller = new ReportPoller(this, this.reportManager);
235235
}
236236
// Setup join/leave listener
237-
this.roomJoins = new RoomMemberManager(this.client);
237+
this.roomJoins = new RoomMemberManager(this.matrixEmitter);
238238
this.taskQueue = new ThrottlingQueue(this, config.backgroundDelayMS);
239239

240240
this.protectionManager = new ProtectionManager(this);
@@ -303,7 +303,7 @@ export class Mjolnir {
303303
}
304304

305305
// Start the bot.
306-
await this.client.start();
306+
await this.matrixEmitter.start();
307307

308308
this.currentState = STATE_SYNCING;
309309
if (this.config.syncOnStartup) {
@@ -331,7 +331,7 @@ export class Mjolnir {
331331
*/
332332
public stop() {
333333
LogService.info("Mjolnir", "Stopping Mjolnir...");
334-
this.client.stop();
334+
this.matrixEmitter.stop();
335335
this.webapis.stop();
336336
this.reportPoller?.stop();
337337
}

src/ProtectedRoomsConfig.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ limitations under the License.
1515
*/
1616

1717
import AwaitLock from 'await-lock';
18-
import { extractRequestError, LogService, MatrixClient, Permalinks } from "matrix-bot-sdk";
18+
import { extractRequestError, LogService, Permalinks } from "matrix-bot-sdk";
1919
import { IConfig } from "./config";
20+
import { MatrixSendClient } from './MatrixEmitter';
2021
const PROTECTED_ROOMS_EVENT_TYPE = "org.matrix.mjolnir.protected_rooms";
2122

2223
/**
@@ -32,7 +33,7 @@ export default class ProtectedRoomsConfig {
3233
/** This is to prevent clobbering the account data for the protected rooms if several rooms are explicitly protected concurrently. */
3334
private accountDataLock = new AwaitLock();
3435

35-
constructor(private readonly client: MatrixClient) {
36+
constructor(private readonly client: MatrixSendClient) {
3637

3738
}
3839

src/ProtectedRoomsSet.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { LogLevel, LogService, MatrixClient, MatrixGlob, Permalinks, UserID } from "matrix-bot-sdk";
17+
import { LogLevel, LogService, MatrixGlob, Permalinks, UserID } from "matrix-bot-sdk";
1818
import { IConfig } from "./config";
1919
import ErrorCache, { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "./ErrorCache";
2020
import ManagementRoomOutput from "./ManagementRoomOutput";
21+
import { MatrixSendClient } from "./MatrixEmitter";
2122
import AccessControlUnit, { Access } from "./models/AccessControlUnit";
2223
import { RULE_ROOM, RULE_SERVER, RULE_USER } from "./models/ListRule";
2324
import PolicyList, { ListRuleChange } from "./models/PolicyList";
@@ -88,7 +89,7 @@ export class ProtectedRoomsSet {
8889
private readonly accessControlUnit = new AccessControlUnit([]);
8990

9091
constructor(
91-
private readonly client: MatrixClient,
92+
private readonly client: MatrixSendClient,
9293
private readonly clientUserId: string,
9394
private readonly managementRoomId: string,
9495
private readonly managementRoomOutput: ManagementRoomOutput,

src/RoomMembers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MatrixClient } from "matrix-bot-sdk";
1+
import { MatrixEmitter } from "./MatrixEmitter";
22

33
enum Action {
44
Join,
@@ -154,7 +154,7 @@ class RoomMembers {
154154
export class RoomMemberManager {
155155
private perRoom: Map<string /* room id */, RoomMembers> = new Map();
156156
private readonly cbHandleEvent;
157-
constructor(private client: MatrixClient) {
157+
constructor(private client: MatrixEmitter) {
158158
// Listen for join events.
159159
this.cbHandleEvent = this.handleEvent.bind(this);
160160
client.on("room.event", this.cbHandleEvent);

0 commit comments

Comments
 (0)