From 8a9fc9572cbb6206dc3d43ccbcdff061aa6373d5 Mon Sep 17 00:00:00 2001 From: gnuxie Date: Fri, 31 Jan 2025 18:58:11 +0000 Subject: [PATCH 1/3] Introduce generic item batching. We want to also provide a safe batcher. --- src/StateTracking/EventBatch.ts | 66 +++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/src/StateTracking/EventBatch.ts b/src/StateTracking/EventBatch.ts index ede82cd..ac74bb8 100644 --- a/src/StateTracking/EventBatch.ts +++ b/src/StateTracking/EventBatch.ts @@ -1,12 +1,16 @@ -// Copyright 2022 - 2023 Gnuxie +// Copyright 2022 - 2025 Gnuxie // Copyright 2019 - 2021 The Matrix.org Foundation C.I.C. // -// SPDX-License-Identifier: AFL-3.0 AND Apache-2.0 +// SPDX-License-Identifier: Apache-2.0 // // SPDX-FileAttributionText: // This modified file incorporates work from mjolnir // https://github.com/matrix-org/mjolnir // +// SPDX-FileAttributionText: +// This modified file incorporates work from matrix-protection-suite +// https://github.com/Gnuxie/matrix-protection-suite +// import { StringEventID } from '@the-draupnir-project/matrix-basic-types'; import { Logger } from '../Logging/Logger'; @@ -17,23 +21,24 @@ function logBatchCompleteCallbackError(e: unknown): void { log.error('Caught an exception from the callback for an event batch', e); } -type EventWithID = { event_id: StringEventID }; - -export interface EventBatch { - addEvent({ event_id }: E): void; +export interface Batch { + add(key: Key, data: Value): void; isFinished(): boolean; - batchCompleteCallback: (events: E[]) => Promise; + batchCompleteCallback: (entries: [Key, Value][]) => Promise; } -export class ConstantPeriodEventBatch - implements EventBatch +export class ConstantPeriodItemBatch + implements Batch { private readonly waitPeriodMS: number; - private events = new Map(); + private items = new Map(); private isBatchComplete = false; private isWaiting = false; constructor( - public readonly batchCompleteCallback: EventBatch['batchCompleteCallback'], + public readonly batchCompleteCallback: Batch< + Key, + Value + >['batchCompleteCallback'], { waitPeriodMS = 200 } ) { this.waitPeriodMS = waitPeriodMS; @@ -43,16 +48,16 @@ export class ConstantPeriodEventBatch return this.isBatchComplete; } - public addEvent(event: E): void { + public add(key: Key, item: Value): void { if (this.isFinished()) { throw new TypeError( 'Something tried adding an event to a completed EventBatch' ); } - if (this.events.has(event.event_id)) { + if (this.items.has(key)) { return; } - this.events.set(event.event_id, event); + this.items.set(key, item); if (!this.isWaiting) { // spawn off the timer to call the callback. this.startCallbackTimer(); @@ -69,8 +74,39 @@ export class ConstantPeriodEventBatch private completeBatch(): void { this.isBatchComplete = true; - this.batchCompleteCallback([...this.events.values()]).catch( + this.batchCompleteCallback([...this.items.entries()]).catch( logBatchCompleteCallbackError ); } } + +type EventWithID = { event_id: StringEventID }; + +export interface EventBatch { + addEvent({ event_id }: E): void; + isFinished(): boolean; + batchCompleteCallback: (events: E[]) => Promise; +} + +export class ConstantPeriodEventBatch + implements EventBatch +{ + private readonly batch: ConstantPeriodItemBatch; + constructor( + public readonly batchCompleteCallback: EventBatch['batchCompleteCallback'], + { waitPeriodMS = 200 } + ) { + this.batch = new ConstantPeriodItemBatch( + (entries) => this.batchCompleteCallback(entries.map((entry) => entry[1])), + { waitPeriodMS } + ); + } + + isFinished(): boolean { + return this.batch.isFinished(); + } + + addEvent(event: E): void { + this.batch.add(event.event_id, event); + } +} From 537986681357619aee7e0b5ecb5b5d2db385ad4b Mon Sep 17 00:00:00 2001 From: gnuxie Date: Fri, 31 Jan 2025 19:26:12 +0000 Subject: [PATCH 2/3] Add StandardBatcher for convenance. --- src/StateTracking/EventBatch.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/StateTracking/EventBatch.ts b/src/StateTracking/EventBatch.ts index ac74bb8..01931e2 100644 --- a/src/StateTracking/EventBatch.ts +++ b/src/StateTracking/EventBatch.ts @@ -21,6 +21,28 @@ function logBatchCompleteCallbackError(e: unknown): void { log.error('Caught an exception from the callback for an event batch', e); } +export interface Batcher { + add(key: Key, value: Value): void; +} + +export class StandardBatcher + implements Batcher +{ + private currentBatch: Batch; + public constructor( + private readonly batchFactoryMethod: () => Batch + ) { + this.currentBatch = batchFactoryMethod(); + } + + public add(key: Key, value: Value): void { + if (this.currentBatch.isFinished()) { + this.currentBatch = this.batchFactoryMethod(); + } + this.currentBatch.add(key, value); + } +} + export interface Batch { add(key: Key, data: Value): void; isFinished(): boolean; From 1b1fc296a981fcdc85641054fded9f63f02bc0d6 Mon Sep 17 00:00:00 2001 From: gnuxie Date: Fri, 31 Jan 2025 19:26:48 +0000 Subject: [PATCH 3/3] handleExternalInvite -> handleExternalMembership. Protections may need access to other events, such as leaving rooms. --- src/Protection/ProtectedRoomsSet.ts | 8 ++++---- src/Protection/Protection.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Protection/ProtectedRoomsSet.ts b/src/Protection/ProtectedRoomsSet.ts index 829ca37..823268a 100644 --- a/src/Protection/ProtectedRoomsSet.ts +++ b/src/Protection/ProtectedRoomsSet.ts @@ -69,7 +69,7 @@ export interface ProtectedRoomsSet { readonly allProtectedRooms: MatrixRoomID[]; handleTimelineEvent(roomID: StringRoomID, event: RoomEvent): void; handleEventReport(report: EventReport): void; - handleExternalInvite(roomID: StringRoomID, event: MembershipEvent): void; + handleExternalMembership(roomID: StringRoomID, event: MembershipEvent): void; isProtectedRoom(roomID: StringRoomID): boolean; unregisterListeners(): void; } @@ -168,15 +168,15 @@ export class StandardProtectedRoomsSet implements ProtectedRoomsSet { void Task(protection.handleEventReport(report)); } } - public handleExternalInvite( + public handleExternalMembership( roomID: StringRoomID, event: MembershipEvent ): void { for (const protection of this.protections.allProtections) { - if (protection.handleExternalInvite === undefined) { + if (protection.handleExternalMembership === undefined) { continue; } - protection.handleExternalInvite(roomID, event); + protection.handleExternalMembership(roomID, event); } } diff --git a/src/Protection/Protection.ts b/src/Protection/Protection.ts index 4e5c20b..477f0e3 100644 --- a/src/Protection/Protection.ts +++ b/src/Protection/Protection.ts @@ -157,11 +157,11 @@ export interface Protection { handlePermissionRequirementsMet?(room: MatrixRoomID): void; /** - * Handle an invitation to a room that is external to the protected rooms set. + * Handle an invitation, room join, kick, leave etc to a room that is external to the protected rooms set. * @param roomID The room the invitation is for. * @param event The invitation event itself. */ - handleExternalInvite?(roomID: StringRoomID, event: MembershipEvent): void; + handleExternalMembership?(roomID: StringRoomID, event: MembershipEvent): void; /** * This can be used to determine who are new to the overall protected rooms