diff --git a/packages/federation-sdk/src/services/state.service.ts b/packages/federation-sdk/src/services/state.service.ts index c268edc9d..ca45182c8 100644 --- a/packages/federation-sdk/src/services/state.service.ts +++ b/packages/federation-sdk/src/services/state.service.ts @@ -91,7 +91,7 @@ export class StateService { throw new Error('Create event not found for room version'); } - return createEvent.event.content?.room_version; + return createEvent.event.content.room_version; } private logState(label: string, state: State) { @@ -438,7 +438,7 @@ export class StateService { } private async addPrevEvents(event: PersistentEventBase) { - const roomVersion = await this.getRoomVersion(event.roomId); + const roomVersion = event.version; if (!roomVersion) { throw new Error('Room version not found while filling prev events'); } @@ -478,13 +478,7 @@ export class StateService { event: PersistentEventBase, state: State, ): Promise { - const roomVersion = event.isCreateEvent() - ? event.getContent().room_version - : await this.getRoomVersion(event.roomId); - - if (!roomVersion) { - throw new Error('Room version not found'); - } + const roomVersion = event.version; // always check for conflicts at the prev_event state @@ -594,9 +588,7 @@ export class StateService { return; } - const roomVersion = event.isCreateEvent() - ? event.getContent().room_version - : await this.getRoomVersion(event.roomId); + const roomVersion = event.version; if (!roomVersion) { throw new Error('Room version not found'); @@ -748,12 +740,7 @@ export class StateService { throw new Error('State events are not persisted with this method'); } - const roomVersion = await this.getRoomVersion(event.roomId); - if (!roomVersion) { - throw new Error( - 'Room version not found when trying to persist a timeline event', - ); - } + const roomVersion = event.version; const { state: room, stateId } = await this.getFullRoomStateAndStateId( event.roomId, diff --git a/packages/room/src/authorizartion-rules/rules.ts b/packages/room/src/authorizartion-rules/rules.ts index ddd82cd6f..67f6b07df 100644 --- a/packages/room/src/authorizartion-rules/rules.ts +++ b/packages/room/src/authorizartion-rules/rules.ts @@ -1,13 +1,9 @@ import assert from 'node:assert'; -import { - type PduCreateEventContent, - type PduMembershipEventContent, - type PduPowerLevelsEventContent, - type PduType, -} from '../types/v3-11'; +import { type PduType } from '../types/v3-11'; import type { PersistentEventBase } from '../manager/event-wrapper'; import { PowerLevelEvent } from '../manager/power-level-event-wrapper'; +import { RoomVersion } from '../manager/type'; import { type EventStore, getStateByMapKey, @@ -173,7 +169,9 @@ async function isMembershipChangeAllowed( const content = membershipEventToCheck.getContent(); - const previousEvents = await membershipEventToCheck.getPreviousEvents(store); + const previousEvents = await store.getEvents( + membershipEventToCheck.getPreviousEventIds(), + ); switch (content.membership) { case 'join': { @@ -353,7 +351,7 @@ async function isMembershipChangeAllowed( export function validatePowerLevelEvent( powerLevelEvent: PowerLevelEvent, - roomCreateEvent: PersistentEventBase, + roomCreateEvent: PersistentEventBase, authEventMap: Map, ) { // If the users property in content is not an object with keys that are valid user IDs with values that are integers (or a string that is an integer), reject. diff --git a/packages/room/src/manager/event-wrapper.ts b/packages/room/src/manager/event-wrapper.ts index acfefb27e..904fc070c 100644 --- a/packages/room/src/manager/event-wrapper.ts +++ b/packages/room/src/manager/event-wrapper.ts @@ -41,7 +41,7 @@ export const REDACT_ALLOW_ALL_KEYS: unique symbol = Symbol.for('all'); // convinient wrapper to manage schema differences when working with same algorithms across different versions export abstract class PersistentEventBase< - T extends RoomVersion = '11', + Version extends RoomVersion = RoomVersion, Type extends PduType = PduType, > { private _rejectedReason?: string; @@ -50,7 +50,10 @@ export abstract class PersistentEventBase< protected rawEvent: PduWithHashesAndSignaturesOptional; - constructor(event: PduWithHashesAndSignaturesOptional) { + constructor( + event: PduWithHashesAndSignaturesOptional, + public readonly version: Version, + ) { this.rawEvent = JSON.parse(JSON.stringify(event)); if (this.rawEvent.signatures) { this.signatures = this.rawEvent.signatures; @@ -141,14 +144,13 @@ export abstract class PersistentEventBase< throw new Error('Event is not a power level event'); } - // room version dependent - abstract getAuthorizationEvents( - store: EventStore, - ): Promise[]>; + getAuthEventIds() { + return this.rawEvent.auth_events; + } - abstract getPreviousEvents( - store: EventStore, - ): Promise[]>; + getPreviousEventIds() { + return this.rawEvent.prev_events; + } isState() { // spec wise this is the right way to check if an event is a state event @@ -162,49 +164,55 @@ export abstract class PersistentEventBase< return !this.isState(); } - isTopicEvent(): this is PersistentEventBase { + isTopicEvent(): this is PersistentEventBase { return this.isState() && this.type === 'm.room.topic'; } - isPowerLevelEvent(): this is PersistentEventBase { + isPowerLevelEvent(): this is PersistentEventBase< + Version, + 'm.room.power_levels' + > { return this.isState() && this.type === 'm.room.power_levels'; } - isNameEvent(): this is PersistentEventBase { + isNameEvent(): this is PersistentEventBase { return this.isState() && this.type === 'm.room.name'; } - isJoinRuleEvent(): this is PersistentEventBase { + isJoinRuleEvent(): this is PersistentEventBase { return this.isState() && this.type === 'm.room.join_rules'; } - isMembershipEvent(): this is PersistentEventBase { + isMembershipEvent(): this is PersistentEventBase { return this.isState() && this.type === 'm.room.member'; } - isCreateEvent(): this is PersistentEventBase { + isCreateEvent(): this is PersistentEventBase { return this.isState() && this.type === 'm.room.create'; } - isServerAclEvent(): this is PersistentEventBase { + isServerAclEvent(): this is PersistentEventBase< + Version, + 'm.room.server_acl' + > { return this.isState() && this.type === 'm.room.server_acl'; } isHistoryVisibilityEvent(): this is PersistentEventBase< - T, + Version, 'm.room.history_visibility' > { return this.isState() && this.type === 'm.room.history_visibility'; } isCanonicalAliasEvent(): this is PersistentEventBase< - T, + Version, 'm.room.canonical_alias' > { return this.isState() && this.type === 'm.room.canonical_alias'; } - isAliasEvent(): this is PersistentEventBase { + isAliasEvent(): this is PersistentEventBase { return this.isState() && this.type === 'm.room.aliases'; } @@ -412,7 +420,7 @@ export abstract class PersistentEventBase< return this._rejectedReason; } - addPrevEvents(events: PersistentEventBase[]) { + addPrevEvents(events: PersistentEventBase[]) { this.rawEvent.prev_events.push(...events.map((e) => e.eventId)); if (this.rawEvent.depth <= events[events.length - 1].depth) { this.rawEvent.depth = events[events.length - 1].depth + 1; @@ -420,7 +428,7 @@ export abstract class PersistentEventBase< return this; } - authedBy(event: PersistentEventBase) { + authedBy(event: PersistentEventBase) { this.rawEvent.auth_events.push(event.eventId); return this; } diff --git a/packages/room/src/manager/factory.ts b/packages/room/src/manager/factory.ts index 212708475..a8a2f1d0c 100644 --- a/packages/room/src/manager/factory.ts +++ b/packages/room/src/manager/factory.ts @@ -1,11 +1,4 @@ -import { - Pdu, - type PduCreateEventContent, - type PduJoinRuleEventContent, - type PduMembershipEventContent, - PduPowerLevelsEventContent, - PduType, -} from '../types/v3-11'; +import { Pdu, type PduCreateEventContent, PduType } from '../types/v3-11'; import { PersistentEventV3 } from './v3'; @@ -59,7 +52,7 @@ export class PersistentEventFactory { static createFromRawEvent( event: PduWithHashesAndSignaturesOptional, - roomVersion: RoomVersion, + roomVersion: string, ): PersistentEventBase { if (!PersistentEventFactory.isSupportedRoomVersion(roomVersion)) { throw new Error(`Room version ${roomVersion} is not supported`); @@ -69,32 +62,17 @@ export class PersistentEventFactory { case '3': case '4': case '5': - return new PersistentEventV3(event) as PersistentEventBase< - RoomVersion, - Type - >; + return new PersistentEventV3(event, roomVersion); case '6': case '7': - return new PersistentEventV6(event) as PersistentEventBase< - RoomVersion, - Type - >; + return new PersistentEventV6(event, roomVersion); case '8': - return new PersistentEventV8(event) as PersistentEventBase< - RoomVersion, - Type - >; + return new PersistentEventV8(event, roomVersion); case '9': case '10': - return new PersistentEventV9(event) as PersistentEventBase< - RoomVersion, - Type - >; + return new PersistentEventV9(event, roomVersion); case '11': - return new PersistentEventV11(event) as PersistentEventBase< - RoomVersion, - Type - >; + return new PersistentEventV11(event, roomVersion); default: throw new Error(`Unknown room version: ${roomVersion}`); } diff --git a/packages/room/src/manager/power-level-event-wrapper.ts b/packages/room/src/manager/power-level-event-wrapper.ts index 997d722e3..a0d96c4ba 100644 --- a/packages/room/src/manager/power-level-event-wrapper.ts +++ b/packages/room/src/manager/power-level-event-wrapper.ts @@ -68,7 +68,10 @@ class PowerLevelEvent< return this._content.redact ?? 50; } - getPowerLevelForUser(userId: string, createEvent?: PersistentEventBase) { + getPowerLevelForUser( + userId: string, + createEvent?: PersistentEventBase, + ) { if (!this._content) { if (createEvent?.sender === userId) { return 100; diff --git a/packages/room/src/manager/room-state.ts b/packages/room/src/manager/room-state.ts index fc0e3ab63..bda3d0c32 100644 --- a/packages/room/src/manager/room-state.ts +++ b/packages/room/src/manager/room-state.ts @@ -151,6 +151,6 @@ export class RoomState { throw new Error('Room create event not found'); } - return createEvent.getContent().room_version; + return createEvent.getContent().room_version as RoomVersion; } } diff --git a/packages/room/src/manager/v11.ts b/packages/room/src/manager/v11.ts index 3b56856f1..c351de2bb 100644 --- a/packages/room/src/manager/v11.ts +++ b/packages/room/src/manager/v11.ts @@ -1,8 +1,10 @@ -import {} from '../types/v3-11'; +import { type PduType } from '../types/v3-11'; import { REDACT_ALLOW_ALL_KEYS } from './event-wrapper'; import { PersistentEventV9 } from './v9'; -export class PersistentEventV11 extends PersistentEventV9 { +export class PersistentEventV11< + Type extends PduType = PduType, +> extends PersistentEventV9 { getAllowedKeys(): string[] { return [ 'event_id', diff --git a/packages/room/src/manager/v3.ts b/packages/room/src/manager/v3.ts index 25a16ae56..ffc49c73f 100644 --- a/packages/room/src/manager/v3.ts +++ b/packages/room/src/manager/v3.ts @@ -1,6 +1,6 @@ import { toUnpaddedBase64 } from '@rocket.chat/federation-crypto'; import type { EventID } from '../types/_common'; -import {} from '../types/v3-11'; +import { PduType } from '../types/v3-11'; import { type EventStore, PersistentEventBase, @@ -9,16 +9,11 @@ import { import type { RoomVersion3To11 } from './type'; // v3 is where it changes first -export class PersistentEventV3 extends PersistentEventBase { +export class PersistentEventV3< + Type extends PduType = PduType, +> extends PersistentEventBase { private _eventId?: EventID; - async getAuthorizationEvents(store: EventStore) { - return store.getEvents(this.rawEvent.auth_events); - } - - async getPreviousEvents(store: EventStore) { - return store.getEvents(this.rawEvent.prev_events); - } get eventId(): EventID { if (this._eventId) { return this._eventId; diff --git a/packages/room/src/manager/v6.ts b/packages/room/src/manager/v6.ts index 0e9e465b9..49afe3dd4 100644 --- a/packages/room/src/manager/v6.ts +++ b/packages/room/src/manager/v6.ts @@ -1,7 +1,9 @@ -import {} from '../types/v3-11'; +import { PduType } from '../types/v3-11'; import { PersistentEventV3 } from './v3'; -export class PersistentEventV6 extends PersistentEventV3 { +export class PersistentEventV6< + Type extends PduType = PduType, +> extends PersistentEventV3 { getAllowedContentKeys() { const resp = super.getAllowedContentKeys(); diff --git a/packages/room/src/manager/v8.ts b/packages/room/src/manager/v8.ts index 2c006cfbb..c1d1e414b 100644 --- a/packages/room/src/manager/v8.ts +++ b/packages/room/src/manager/v8.ts @@ -1,7 +1,9 @@ -import {} from '../types/v3-11'; +import { PduType } from '../types/v3-11'; import { PersistentEventV6 } from './v6'; -export class PersistentEventV8 extends PersistentEventV6 { +export class PersistentEventV8< + Type extends PduType = PduType, +> extends PersistentEventV6 { getAllowedContentKeys() { const resp = super.getAllowedContentKeys(); diff --git a/packages/room/src/manager/v9.spec.ts b/packages/room/src/manager/v9.spec.ts index ed49de38a..6c304a7a1 100644 --- a/packages/room/src/manager/v9.spec.ts +++ b/packages/room/src/manager/v9.spec.ts @@ -1,36 +1,42 @@ import { expect, test } from 'bun:test'; +import { EventID } from '../types/_common'; import { PersistentEventV9 } from './v9'; test('event without origin', async () => { - const event = new PersistentEventV9({ - type: 'm.room.member', - auth_events: [ - '$gbm6Tyhskcai9hxHXAh7RCoDlrwl1GFf4pWd1P6ELM4', - '$g5tzeYmxj1ulmzQv07uDZZztxHhiebe5WH7Gg7npwd0', - '$LrAkyyTl5j9Pda7DWo8_epIKa0Q0r6Epw2UHQnSIukI', - '$HFVJ9Ub_2je7bL2LM9uiK1HBYZA0nYDevMT3e-8s_5I', - ], - content: { - avatar_url: 'mxc://matrix.org/MyC00lAvatar', - displayname: '@diego:rc1', - membership: 'invite', + const event = new PersistentEventV9( + { + type: 'm.room.member', + auth_events: [ + '$gbm6Tyhskcai9hxHXAh7RCoDlrwl1GFf4pWd1P6ELM4', + '$g5tzeYmxj1ulmzQv07uDZZztxHhiebe5WH7Gg7npwd0', + '$LrAkyyTl5j9Pda7DWo8_epIKa0Q0r6Epw2UHQnSIukI', + '$HFVJ9Ub_2je7bL2LM9uiK1HBYZA0nYDevMT3e-8s_5I', + ] as EventID[], + content: { + avatar_url: 'mxc://matrix.org/MyC00lAvatar', + displayname: '@diego:rc1', + membership: 'invite', + }, + depth: 8, + hashes: { + sha256: 'AVnMNw6L0jAq69eJUfDRYfmwRVrkh3qKmAStKSvscsI', + }, + origin_server_ts: 1756156853485, + prev_events: ['$HhSZakbJx7fbn5zMxn7QQHCsRFHjEMRa3OIQdCdR2oc' as EventID], + room_id: '!cIgsCPRFcbabBKlTRk:hs2', + sender: '@admin:hs2', + unsigned: { + age: 5, + invite_room_state: [], + }, + signatures: {}, + state_key: '@diego:rc1', }, - depth: 8, - hashes: { - sha256: 'AVnMNw6L0jAq69eJUfDRYfmwRVrkh3qKmAStKSvscsI', - }, - origin_server_ts: 1756156853485, - prev_events: ['$HhSZakbJx7fbn5zMxn7QQHCsRFHjEMRa3OIQdCdR2oc'], - room_id: '!cIgsCPRFcbabBKlTRk:hs2', - sender: '@admin:hs2', - unsigned: { - age: 5, - invite_room_state: [], - }, - signatures: {}, - state_key: '@diego:rc1', - }); + '10', + ); - expect(event.eventId).toBe('$iCA3OWE1EGtPVWIyGudgmifuJcIluQw88FuK_gd0FpM'); + expect(event.eventId).toBe( + '$iCA3OWE1EGtPVWIyGudgmifuJcIluQw88FuK_gd0FpM' as EventID, + ); }); diff --git a/packages/room/src/manager/v9.ts b/packages/room/src/manager/v9.ts index 6c9a1345f..971175e06 100644 --- a/packages/room/src/manager/v9.ts +++ b/packages/room/src/manager/v9.ts @@ -1,7 +1,9 @@ -import {} from '../types/v3-11'; +import { PduType } from '../types/v3-11'; import { PersistentEventV8 } from './v8'; -export class PersistentEventV9 extends PersistentEventV8 { +export class PersistentEventV9< + Type extends PduType = PduType, +> extends PersistentEventV8 { getAllowedContentKeys() { const resp = super.getAllowedContentKeys(); diff --git a/packages/room/src/state_resolution/definitions/definitions.ts b/packages/room/src/state_resolution/definitions/definitions.ts index 48b00636f..9abc14a3b 100644 --- a/packages/room/src/state_resolution/definitions/definitions.ts +++ b/packages/room/src/state_resolution/definitions/definitions.ts @@ -21,8 +21,10 @@ export function getStateByMapKey( type: T; state_key?: string; }, -): PersistentEventBase | undefined { - return map.get(getStateMapKey(filter)) as PersistentEventBase; +) { + return map.get(getStateMapKey(filter)) as + | PersistentEventBase + | undefined; } // https://spec.matrix.org/v1.12/rooms/v2/#definitions @@ -115,7 +117,7 @@ export async function getAuthChain( return eventIdToAuthChainMap.get(eventId)!; } - const authEvents = await event.getAuthorizationEvents(store); + const authEvents = await store.getEvents(event.getAuthEventIds()); if (authEvents.length === 0) { eventIdToAuthChainMap.set(eventId, existingAuthChainPart); return existingAuthChainPart; @@ -346,7 +348,7 @@ export async function reverseTopologicalPowerSort( } // auths are the parents, must be on tiop - for (const authEvent of await event.getAuthorizationEvents(store)) { + for (const authEvent of await store.getEvents(event.getAuthEventIds())) { eventMap.set(authEvent.eventId, authEvent); if ( @@ -426,7 +428,7 @@ export async function mainlineOrdering( events: PersistentEventBase[], // TODO: or take event ids store: EventStore, // Let P = P0 be an m.room.power_levels event - powerLevelEvent?: PersistentEventBase, // of which we will calculate the mainline + powerLevelEvent?: PersistentEventBase, // of which we will calculate the mainline ): Promise { const getMainline = async ( event: PersistentEventBase, @@ -439,7 +441,7 @@ export async function mainlineOrdering( const fn = async ( event: PersistentEventBase, ) => { - const authEvents = await event.getAuthorizationEvents(store); + const authEvents = await store.getEvents(event.getAuthEventIds()); // await new Promise((resolve) => setTimeout(resolve, 3000)); @@ -495,8 +497,9 @@ export async function mainlineOrdering( return mainlineMap.get(_event.eventId) || 0; } - const authEvents: PersistentEventBase[] = - await _event.getAuthorizationEvents(store); + const authEvents: PersistentEventBase[] = await store.getEvents( + _event.getAuthEventIds(), + ); _event = null; @@ -572,7 +575,7 @@ export async function iterativeAuthChecks( for (const event of events) { const authEventStateMap = new Map(); - for (const authEvent of await event.getAuthorizationEvents(store)) { + for (const authEvent of await store.getEvents(event.getAuthEventIds())) { authEventStateMap.set(authEvent.getUniqueStateIdentifier(), authEvent); }