diff --git a/packages/core/src/events/eventBase.ts b/packages/core/src/events/eventBase.ts index 145ffd6b4..b0e30719e 100644 --- a/packages/core/src/events/eventBase.ts +++ b/packages/core/src/events/eventBase.ts @@ -1,8 +1,9 @@ import { Pdu, PduForType } from '@hs/room'; +import type { EventID } from '@hs/room'; export type EventBase = { - auth_events: string[]; - prev_events: string[]; + auth_events: EventID[]; + prev_events: EventID[]; type: | 'm.room.member' | 'm.room.create' @@ -32,7 +33,7 @@ export type EventBase = { }; export type RedactedEvent = EventBase & { - redacts: string; + redacts: EventID; type: 'm.room.redaction'; }; @@ -51,8 +52,8 @@ export const createEventBase = ( props: { roomId: string; sender: string; - auth_events?: string[]; - prev_events?: string[]; + auth_events?: EventID[]; + prev_events?: EventID[]; depth: number; content: Events[T]['content']; state_key?: string; @@ -92,8 +93,8 @@ const _createEventBase = < }: { roomId: string; sender: string; - auth_events?: string[]; - prev_events?: string[]; + auth_events?: EventID[]; + prev_events?: EventID[]; depth: number; type: string; content?: TContent; diff --git a/packages/core/src/events/m.reaction.ts b/packages/core/src/events/m.reaction.ts index dc5d685fc..9ce1c18fe 100644 --- a/packages/core/src/events/m.reaction.ts +++ b/packages/core/src/events/m.reaction.ts @@ -1,3 +1,4 @@ +import type { EventID } from '@hs/room'; import { type EventBase, createEventBase } from './eventBase'; import { createEventWithId } from './utils/createSignedEvent'; @@ -7,7 +8,7 @@ declare module './eventBase' { content: { 'm.relates_to': { rel_type: 'm.annotation'; - event_id: string; + event_id: EventID; key: string; }; }; @@ -19,9 +20,9 @@ declare module './eventBase' { } export type ReactionAuthEvents = { - 'm.room.create': string | undefined; - 'm.room.power_levels': string | undefined; - 'm.room.member': string | undefined; + 'm.room.create': EventID | undefined; + 'm.room.power_levels': EventID | undefined; + 'm.room.member': EventID | undefined; }; export const isReactionEvent = (event: EventBase): event is ReactionEvent => { @@ -33,7 +34,7 @@ export interface ReactionEvent extends EventBase { content: { 'm.relates_to': { rel_type: 'm.annotation'; - event_id: string; + event_id: EventID; key: string; }; }; @@ -62,12 +63,12 @@ export const reactionEvent = ({ roomId: string; sender: string; auth_events: ReactionAuthEvents; - prev_events: string[]; + prev_events: EventID[]; depth: number; content: { 'm.relates_to': { rel_type: 'm.annotation'; - event_id: string; + event_id: EventID; key: string; }; }; diff --git a/packages/core/src/events/m.room.guest_access.ts b/packages/core/src/events/m.room.guest_access.ts index e2b13097e..572d7f871 100644 --- a/packages/core/src/events/m.room.guest_access.ts +++ b/packages/core/src/events/m.room.guest_access.ts @@ -1,3 +1,4 @@ +import { EventID } from '@hs/room'; import { type EventBase, createEventBase } from './eventBase'; import { createEventWithId } from './utils/createSignedEvent'; @@ -24,8 +25,8 @@ export const roomGuestAccessEvent = ({ }: { roomId: string; sender: string; - auth_events: string[]; - prev_events: string[]; + auth_events: EventID[]; + prev_events: EventID[]; depth: number; ts?: number; }) => { diff --git a/packages/core/src/events/m.room.history_visibility.ts b/packages/core/src/events/m.room.history_visibility.ts index 4fee81257..78a0b7411 100644 --- a/packages/core/src/events/m.room.history_visibility.ts +++ b/packages/core/src/events/m.room.history_visibility.ts @@ -1,3 +1,4 @@ +import { EventID } from '@hs/room'; import { type EventBase, createEventBase } from './eventBase'; import { createEventWithId } from './utils/createSignedEvent'; @@ -26,8 +27,8 @@ export const roomHistoryVisibilityEvent = ({ }: { roomId: string; sender: string; - auth_events: string[]; - prev_events: string[]; + auth_events: EventID[]; + prev_events: EventID[]; depth: number; ts?: number; }) => { diff --git a/packages/core/src/events/m.room.join_rules.ts b/packages/core/src/events/m.room.join_rules.ts index d15071a43..b209c152a 100644 --- a/packages/core/src/events/m.room.join_rules.ts +++ b/packages/core/src/events/m.room.join_rules.ts @@ -1,3 +1,4 @@ +import { EventID } from '@hs/room'; import { type EventBase, createEventBase } from './eventBase'; import { createEventWithId } from './utils/createSignedEvent'; @@ -33,8 +34,8 @@ export const roomJoinRulesEvent = ({ }: { roomId: string; sender: string; - auth_events: string[]; - prev_events: string[]; + auth_events: EventID[]; + prev_events: EventID[]; depth: number; ts?: number; }) => { diff --git a/packages/core/src/events/m.room.member.ts b/packages/core/src/events/m.room.member.ts index fdaee2bac..92c5b6103 100644 --- a/packages/core/src/events/m.room.member.ts +++ b/packages/core/src/events/m.room.member.ts @@ -1,3 +1,4 @@ +import { EventID } from '@hs/room'; import { createEventBase } from './eventBase'; import type { Membership, RoomMemberEvent } from './isRoomMemberEvent'; import { createEventWithId } from './utils/createSignedEvent'; @@ -18,12 +19,12 @@ declare module './eventBase' { } export type AuthEvents = { - 'm.room.create': string; - 'm.room.power_levels'?: string; - 'm.room.join_rules'?: string; - 'm.room.history_visibility'?: string; + 'm.room.create': EventID; + 'm.room.power_levels'?: EventID; + 'm.room.join_rules'?: EventID; + 'm.room.history_visibility'?: EventID; } & { - [K in `m.room.member:${string}`]?: string; + [K in `m.room.member:${string}`]?: EventID; }; const isTruthy = ( @@ -50,7 +51,7 @@ export const roomMemberEvent = ({ sender: string; state_key: string; auth_events: AuthEvents; - prev_events: string[]; + prev_events: EventID[]; depth: number; unsigned?: RoomMemberEvent['unsigned']; content?: Record; diff --git a/packages/core/src/events/m.room.message.ts b/packages/core/src/events/m.room.message.ts index 343a716d6..af4380163 100644 --- a/packages/core/src/events/m.room.message.ts +++ b/packages/core/src/events/m.room.message.ts @@ -1,3 +1,4 @@ +import type { EventID } from '@hs/room'; import { type EventBase, createEventBase } from './eventBase'; import { createEventWithId } from './utils/createSignedEvent'; @@ -74,14 +75,14 @@ declare module './eventBase' { export type MessageRelation = { rel_type: RelationType; - event_id: string; + event_id: EventID; } & (RelationTypeReplace | Record); export type RelationType = 'm.replace' | 'm.annotation'; export type RelationTypeReplace = { rel_type: 'm.replace'; - event_id: string; + event_id: EventID; 'm.new_content'?: { body: string; msgtype: MessageType; @@ -91,9 +92,9 @@ export type RelationTypeReplace = { }; export type MessageAuthEvents = { - 'm.room.create': string; - 'm.room.power_levels': string; - 'm.room.member': string; + 'm.room.create': EventID; + 'm.room.power_levels': EventID; + 'm.room.member': EventID; }; export const isRoomMessageEvent = ( @@ -137,7 +138,7 @@ export const roomMessageEvent = ({ roomId: string; sender: string; auth_events: MessageAuthEvents; - prev_events: string[]; + prev_events: EventID[]; depth: number; unsigned?: RoomMessageEvent['unsigned']; content: ( diff --git a/packages/core/src/events/m.room.name.ts b/packages/core/src/events/m.room.name.ts index 34706a265..1bb91f636 100644 --- a/packages/core/src/events/m.room.name.ts +++ b/packages/core/src/events/m.room.name.ts @@ -1,3 +1,4 @@ +import { EventID } from '@hs/room'; import { type EventBase, createEventBase } from './eventBase'; import { createEventWithId } from './utils/createSignedEvent'; @@ -16,9 +17,9 @@ declare module './eventBase' { } export type RoomNameAuthEvents = { - 'm.room.create': string; - 'm.room.power_levels': string; - 'm.room.member': string; + 'm.room.create': EventID; + 'm.room.power_levels': EventID; + 'm.room.member': EventID; }; export const isRoomNameEvent = (event: EventBase): event is RoomNameEvent => { @@ -52,7 +53,7 @@ export const roomNameEvent = ({ roomId: string; sender: string; auth_events: RoomNameAuthEvents; - prev_events: string[]; + prev_events: EventID[]; depth: number; content: { name: string; diff --git a/packages/core/src/events/m.room.power_levels.ts b/packages/core/src/events/m.room.power_levels.ts index 2d4767127..b4a6e2934 100644 --- a/packages/core/src/events/m.room.power_levels.ts +++ b/packages/core/src/events/m.room.power_levels.ts @@ -1,3 +1,4 @@ +import { EventID } from '@hs/room'; import { type EventBase, createEventBase } from './eventBase'; import { createEventWithId } from './utils/createSignedEvent'; @@ -53,11 +54,11 @@ export const roomPowerLevelsEvent = ({ roomId: string; members: [sender: string, ...member: string[]]; auth_events: { - 'm.room.create': string; - 'm.room.power_levels': string; - 'm.room.member': string; // TODO: Based on the tests, this is optional, based on the code, this is required check this + 'm.room.create': EventID; + 'm.room.power_levels': EventID; + 'm.room.member': EventID; // TODO: Based on the tests, this is optional, based on the code, this is required check this }; - prev_events: string[]; + prev_events: EventID[]; depth: number; content?: RoomPowerLevelsEvent['content']; ts?: number; @@ -70,7 +71,7 @@ export const roomPowerLevelsEvent = ({ auth_events['m.room.create'], auth_events['m.room.power_levels'], auth_events['m.room.member'], - ].filter(Boolean) as string[], + ].filter(Boolean) as EventID[], prev_events, depth, ts, diff --git a/packages/core/src/events/m.room.redaction.ts b/packages/core/src/events/m.room.redaction.ts index 6f164d0a1..c0dfbacd3 100644 --- a/packages/core/src/events/m.room.redaction.ts +++ b/packages/core/src/events/m.room.redaction.ts @@ -1,3 +1,4 @@ +import { EventID } from '@hs/room'; import { type EventBase, createEventBase } from './eventBase'; import { createEventWithId } from './utils/createSignedEvent'; @@ -8,9 +9,9 @@ declare module './eventBase' { } export type RedactionAuthEvents = { - 'm.room.create': string | undefined; - 'm.room.power_levels': string | undefined; - 'm.room.member'?: string | undefined; + 'm.room.create': EventID | undefined; + 'm.room.power_levels': EventID | undefined; + 'm.room.member'?: EventID | undefined; }; export const isRedactionEvent = (event: EventBase): event is RedactionEvent => { @@ -24,7 +25,7 @@ export interface RedactionEvent extends EventBase { unsigned: { age_ts: number; }; - redacts: string; + redacts: EventID; } const isTruthy = ( @@ -47,10 +48,10 @@ export const redactionEvent = ({ roomId: string; sender: string; auth_events: RedactionAuthEvents; - prev_events: string[]; + prev_events: EventID[]; depth: number; content: { - redacts: string; + redacts: EventID; reason?: string; }; origin?: string; diff --git a/packages/core/src/events/m.room.tombstone.ts b/packages/core/src/events/m.room.tombstone.ts index 85e1c26a0..a793b852a 100644 --- a/packages/core/src/events/m.room.tombstone.ts +++ b/packages/core/src/events/m.room.tombstone.ts @@ -1,3 +1,4 @@ +import { EventID } from '@hs/room'; import { type EventBase, createEventBase } from './eventBase'; import { createEventWithId } from './utils/createSignedEvent'; @@ -20,9 +21,9 @@ declare module './eventBase' { } export type TombstoneAuthEvents = { - 'm.room.create': string; - 'm.room.power_levels': string; - 'm.room.member': string; + 'm.room.create': EventID | undefined; + 'm.room.power_levels': EventID | undefined; + 'm.room.member': EventID | undefined; }; type RoomTombstoneEventProps = { @@ -31,7 +32,7 @@ type RoomTombstoneEventProps = { body: string; replacementRoom?: string; auth_events: TombstoneAuthEvents; - prev_events: string[]; + prev_events: EventID[]; depth: number; ts?: number; origin?: string; diff --git a/packages/core/src/models/event.model.ts b/packages/core/src/models/event.model.ts index 9cbdec893..34b2b6885 100644 --- a/packages/core/src/models/event.model.ts +++ b/packages/core/src/models/event.model.ts @@ -1,10 +1,10 @@ -import { Pdu, PduForType } from '@hs/room'; +import { EventID, Pdu, PduForType } from '@hs/room'; import type { EventBase as CoreEventBase } from '../events/eventBase'; // TODO: use room package interface PersistentEventBase { - _id: string; + _id: EventID; event: E; origin: string; diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 72e89e6b3..661aed1bb 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,11 +1,12 @@ import { Pdu } from '@hs/room'; +import type { EventID } from '@hs/room'; export enum EncryptionValidAlgorithm { ed25519 = 'ed25519', } export type SignedEvent = T & { - event_id: string; + event_id: EventID; hashes: { sha256: string; }; diff --git a/packages/core/src/utils/generateId.ts b/packages/core/src/utils/generateId.ts index 85f27204a..fdc6c32d1 100644 --- a/packages/core/src/utils/generateId.ts +++ b/packages/core/src/utils/generateId.ts @@ -1,14 +1,15 @@ import crypto from 'node:crypto'; +import { EventID } from '@hs/room'; import { toUnpaddedBase64 } from './binaryData'; import { pruneEventDict } from './pruneEventDict'; import { encodeCanonicalJson } from './signJson'; -export function generateId(content: T): string { +export function generateId(content: T): EventID { // remove the fields that are not part of the hash const { unsigned, signatures, ...toHash } = pruneEventDict(content as any); return `\$${toUnpaddedBase64( crypto.createHash('sha256').update(encodeCanonicalJson(toHash)).digest(), { urlSafe: true }, - )}`; + )}` as EventID; } diff --git a/packages/federation-sdk/src/index.ts b/packages/federation-sdk/src/index.ts index 92ba55baf..357ddc06a 100644 --- a/packages/federation-sdk/src/index.ts +++ b/packages/federation-sdk/src/index.ts @@ -1,4 +1,5 @@ import type { Membership } from '@hs/core'; +import type { EventID } from '@hs/room'; import { container } from 'tsyringe'; import { ConfigService } from './services/config.service'; import { EduService } from './services/edu.service'; @@ -106,7 +107,7 @@ export type HomeserverEventSignatures = { origin?: string; }; 'homeserver.matrix.message': { - event_id: string; + event_id: EventID; room_id: string; sender: string; origin_server_ts: number; @@ -115,9 +116,9 @@ export type HomeserverEventSignatures = { msgtype: string; 'm.relates_to'?: { rel_type: 'm.replace' | 'm.annotation' | 'm.thread'; - event_id: string; + event_id: EventID; 'm.in_reply_to'?: { - event_id: string; + event_id: EventID; room_id: string; sender: string; origin_server_ts: number; @@ -132,7 +133,7 @@ export type HomeserverEventSignatures = { }; }; 'homeserver.matrix.accept-invite': { - event_id: string; + event_id: EventID; room_id: string; sender: string; origin_server_ts: number; @@ -143,30 +144,30 @@ export type HomeserverEventSignatures = { }; }; 'homeserver.matrix.reaction': { - event_id: string; + event_id: EventID; room_id: string; sender: string; origin_server_ts: number; content: { 'm.relates_to': { rel_type: 'm.annotation'; - event_id: string; + event_id: EventID; key: string; }; }; }; 'homeserver.matrix.redaction': { - event_id: string; + event_id: EventID; room_id: string; sender: string; origin_server_ts: number; - redacts: string; + redacts: EventID; content: { reason?: string; }; }; 'homeserver.matrix.membership': { - event_id: string; + event_id: EventID; room_id: string; sender: string; state_key: string; diff --git a/packages/federation-sdk/src/repositories/event-staging.repository.ts b/packages/federation-sdk/src/repositories/event-staging.repository.ts index 81013820e..8662c6d1d 100644 --- a/packages/federation-sdk/src/repositories/event-staging.repository.ts +++ b/packages/federation-sdk/src/repositories/event-staging.repository.ts @@ -1,6 +1,6 @@ import { generateId } from '@hs/core'; import type { EventStagingStore } from '@hs/core'; -import { Pdu } from '@hs/room'; +import { type EventID, Pdu } from '@hs/room'; import type { Collection, DeleteResult, UpdateResult } from 'mongodb'; import { inject, singleton } from 'tsyringe'; @@ -14,7 +14,7 @@ export class EventStagingRepository { } async create( - eventId: string, + eventId: EventID, origin: string, event: Pdu, ): Promise { @@ -40,7 +40,7 @@ export class EventStagingRepository { ); } - removeByEventId(eventId: string): Promise { + removeByEventId(eventId: EventID): Promise { return this.collection.deleteOne({ _id: eventId }); } diff --git a/packages/federation-sdk/src/repositories/event.repository.ts b/packages/federation-sdk/src/repositories/event.repository.ts index 3c6bb00d6..f30cf04c6 100644 --- a/packages/federation-sdk/src/repositories/event.repository.ts +++ b/packages/federation-sdk/src/repositories/event.repository.ts @@ -1,6 +1,6 @@ import { generateId } from '@hs/core'; import type { EventBase, EventStore } from '@hs/core'; -import { Pdu, PduForType, PduType } from '@hs/room'; +import { type EventID, Pdu, PduForType, PduType } from '@hs/room'; import type { Collection, Filter, @@ -19,7 +19,7 @@ export class EventRepository { private readonly collection: Collection, ) {} - async findById(eventId: string): Promise { + async findById(eventId: EventID): Promise { return this.collection.findOne({ _id: eventId }); } @@ -115,13 +115,13 @@ export class EventRepository { async create( origin: string, event: Pdu, - eventId: string, + eventId: EventID, stateId = '', ): Promise { return this.persistEvent(origin, event, eventId, stateId); } - async redactEvent(eventId: string, redactedEvent: Pdu): Promise { + async redactEvent(eventId: EventID, redactedEvent: Pdu): Promise { await this.collection.updateOne( { _id: eventId }, { $set: { event: redactedEvent } }, // Purposefully replacing the entire event @@ -194,7 +194,7 @@ export class EventRepository { }); } - async updateStateId(eventId: string, stateId: string): Promise { + async updateStateId(eventId: EventID, stateId: string): Promise { await this.collection.updateOne({ _id: eventId }, { $set: { stateId } }); } @@ -202,14 +202,18 @@ export class EventRepository { // more on the respective adr async findPrevEvents(roomId: string) { return this.collection - .find({ nextEventId: '', 'event.room_id': roomId, _id: { $ne: '' } }) + .find({ + nextEventId: '', + 'event.room_id': roomId, + _id: { $ne: '' as EventID }, + }) .toArray(); } private async persistEvent( origin: string, event: Pdu, - eventId: string, + eventId: EventID, stateId: string, ) { try { @@ -262,11 +266,11 @@ export class EventRepository { } findByIds( - eventIds: string[], + eventIds: EventID[], ): FindCursor>>> { - return this.collection.find({ _id: { $in: eventIds } }) as FindCursor< - WithId>> - >; + return this.collection.find({ + _id: { $in: eventIds }, + }) as FindCursor>>>; } findByRoomIdAndTypes( @@ -280,7 +284,7 @@ export class EventRepository { } async setMissingDependencies( - eventId: string, + eventId: EventID, missingDependencies: EventStore['missing_dependencies'], ): Promise { await this.collection.updateOne( @@ -308,7 +312,7 @@ export class EventRepository { findByRoomIdExcludingEventIds( roomId: string, - eventIdsToExclude: string[], + eventIdsToExclude: EventID[], limit: number, ): FindCursor { return this.collection.find( @@ -347,7 +351,7 @@ export class EventRepository { findEventsByIdsWithDepth( roomId: string, - eventIds: string[], + eventIds: EventID[], ): FindCursor>> { return this.collection.find( { diff --git a/packages/federation-sdk/src/repositories/room.repository.ts b/packages/federation-sdk/src/repositories/room.repository.ts index c8c1ffa66..743d6206e 100644 --- a/packages/federation-sdk/src/repositories/room.repository.ts +++ b/packages/federation-sdk/src/repositories/room.repository.ts @@ -1,4 +1,5 @@ import type { EventBase } from '@hs/core'; +import type { EventID } from '@hs/room'; import { Collection } from 'mongodb'; import { inject, singleton } from 'tsyringe'; @@ -11,7 +12,7 @@ export type Room = { alias?: string; canonical_alias?: string; deleted?: boolean; - tombstone_event_id?: string; + tombstone_event_id?: EventID; }; }; diff --git a/packages/federation-sdk/src/repositories/state.repository.ts b/packages/federation-sdk/src/repositories/state.repository.ts index a976374c4..f8d1c7246 100644 --- a/packages/federation-sdk/src/repositories/state.repository.ts +++ b/packages/federation-sdk/src/repositories/state.repository.ts @@ -8,14 +8,14 @@ import { } from 'mongodb'; import { inject, singleton } from 'tsyringe'; -import type { StateMapKey } from '@hs/room'; +import type { EventID, StateMapKey } from '@hs/room'; import type { PersistentEventBase } from '@hs/room'; export type StateStore = { _id: ObjectId; delta: { identifier: StateMapKey; - eventId: string; + eventId: EventID; }; createdAt: Date; diff --git a/packages/federation-sdk/src/services/event-fetcher.service.ts b/packages/federation-sdk/src/services/event-fetcher.service.ts index ac0b207f1..fd85698ef 100644 --- a/packages/federation-sdk/src/services/event-fetcher.service.ts +++ b/packages/federation-sdk/src/services/event-fetcher.service.ts @@ -1,7 +1,7 @@ import { isFederationEventWithPDUs } from '@hs/core'; import { createLogger } from '@hs/core'; import { generateId } from '@hs/core'; -import { Pdu } from '@hs/room'; +import { EventID, Pdu } from '@hs/room'; import { singleton } from 'tsyringe'; import { EventRepository } from '../repositories/event.repository'; import { ConfigService } from './config.service'; @@ -25,7 +25,7 @@ export class EventFetcherService { ) {} public async fetchEventsByIds( - eventIds: string[], + eventIds: EventID[], roomId: string, originServer: string, ): Promise { diff --git a/packages/federation-sdk/src/services/event.service.ts b/packages/federation-sdk/src/services/event.service.ts index 403f2d225..170a8f2c1 100644 --- a/packages/federation-sdk/src/services/event.service.ts +++ b/packages/federation-sdk/src/services/event.service.ts @@ -18,6 +18,7 @@ import { pruneEventDict } from '@hs/core'; import { checkSignAndHashes } from '@hs/core'; import { createLogger } from '@hs/core'; import { + type EventID, type Pdu, type PduForType, type PduType, @@ -66,7 +67,7 @@ export class EventService { } async getEventById>>( - eventId: string, + eventId: EventID, type?: T, ): Promise

{ if (type) { @@ -77,13 +78,13 @@ export class EventService { } async checkIfEventsExists( - eventIds: string[], - ): Promise<{ missing: string[]; found: string[] }> { + eventIds: EventID[], + ): Promise<{ missing: EventID[]; found: EventID[] }> { const eventsCursor = this.eventRepository.findByIds(eventIds); const events = await eventsCursor.toArray(); return eventIds.reduce( - (acc: { missing: string[]; found: string[] }, id) => { + (acc: { missing: EventID[]; found: EventID[] }, id) => { const event = events.find((event) => event._id === id); if (event) { @@ -470,8 +471,8 @@ export class EventService { async getMissingEvents( roomId: string, - earliestEventsId: string[], - latestEventsId: string[], + earliestEventsId: EventID[], + latestEventsId: EventID[], limit: number, minDepth: number, ): Promise<{ events: Pdu[] }> { @@ -497,8 +498,8 @@ export class EventService { } async getEventsByIds( - eventIds: string[], - ): Promise<{ _id: string; event: Pdu }[]> { + eventIds: EventID[], + ): Promise<{ _id: EventID; event: Pdu }[]> { if (!eventIds || eventIds.length === 0) { return []; } @@ -618,7 +619,7 @@ export class EventService { } async checkUserPermission( - powerLevelsEventId: string, + powerLevelsEventId: EventID, userId: string, actionType: PduType, ): Promise { @@ -683,7 +684,7 @@ export class EventService { async getStateIds( roomId: string, - eventId: string, + eventId: EventID, ): Promise<{ pdu_ids: string[]; auth_chain_ids: string[] }> { try { // Ensure the event exists and belongs to the requested room @@ -736,7 +737,7 @@ export class EventService { async getState( roomId: string, - eventId: string, + eventId: EventID, ): Promise<{ pdus: Record[]; auth_chain: Record[]; @@ -754,7 +755,7 @@ export class EventService { state = await this.stateService.findStateAtEvent(eventId); const pdus: Record[] = []; - const authChainIds = new Set(); + const authChainIds = new Set(); // Get room version for the store const roomVersion = await this.stateService.getRoomVersion(roomId); diff --git a/packages/federation-sdk/src/services/message.service.ts b/packages/federation-sdk/src/services/message.service.ts index 9adba39c8..d26c9a2ee 100644 --- a/packages/federation-sdk/src/services/message.service.ts +++ b/packages/federation-sdk/src/services/message.service.ts @@ -14,6 +14,7 @@ import { import { createLogger } from '@hs/core'; import { signEvent } from '@hs/core'; import { + type EventID, type PersistentEventBase, PersistentEventFactory, type RoomVersion, @@ -335,7 +336,7 @@ export class MessageService { async unsetReaction( roomId: string, - eventIdReactedTo: string, + eventIdReactedTo: EventID, _emoji: string, senderUserId: string, ): Promise { @@ -413,7 +414,7 @@ export class MessageService { async redactMessage( roomId: string, - eventIdToRedact: string, + eventIdToRedact: EventID, senderUserId: string, ): Promise { const isTombstoned = await this.roomService.isRoomTombstoned(roomId); diff --git a/packages/federation-sdk/src/services/missing-event.service.ts b/packages/federation-sdk/src/services/missing-event.service.ts index 5323d8842..bfe66f055 100644 --- a/packages/federation-sdk/src/services/missing-event.service.ts +++ b/packages/federation-sdk/src/services/missing-event.service.ts @@ -1,12 +1,13 @@ import { createLogger } from '@hs/core'; +import { EventID } from '@hs/room'; import { singleton } from 'tsyringe'; import { EventFetcherService } from './event-fetcher.service'; import { EventService } from './event.service'; import { StateService } from './state.service'; type MissingEventType = { - eventId: string; + eventId: EventID; roomId: string; origin: string; }; diff --git a/packages/federation-sdk/src/services/profiles.service.ts b/packages/federation-sdk/src/services/profiles.service.ts index 7315d4935..e715308e3 100644 --- a/packages/federation-sdk/src/services/profiles.service.ts +++ b/packages/federation-sdk/src/services/profiles.service.ts @@ -2,7 +2,13 @@ import { createLogger } from '@hs/core'; import { ConfigService } from './config.service'; import { EventService } from './event.service'; -import { Pdu, PduForType, PersistentEventFactory, RoomVersion } from '@hs/room'; +import { + EventID, + Pdu, + PduForType, + PersistentEventFactory, + RoomVersion, +} from '@hs/room'; import { singleton } from 'tsyringe'; import { EventRepository } from '../repositories/event.repository'; import { StateService } from './state.service'; @@ -102,8 +108,8 @@ export class ProfilesService { async getMissingEvents( roomId: string, - earliestEvents: string[], - latestEvents: string[], + earliestEvents: EventID[], + latestEvents: EventID[], limit = 10, minDepth = 0, ): Promise<{ events: Pdu[] }> { diff --git a/packages/federation-sdk/src/services/room.service.ts b/packages/federation-sdk/src/services/room.service.ts index 19c4afa28..206ddc6aa 100644 --- a/packages/federation-sdk/src/services/room.service.ts +++ b/packages/federation-sdk/src/services/room.service.ts @@ -21,6 +21,7 @@ import { type SigningKey } from '@hs/core'; import { logger } from '@hs/core'; import { + type EventID, PduCreateEventContent, PduForType, PduJoinRuleEventContent, @@ -1036,18 +1037,22 @@ export class RoomService { const depth = currentDepth + 1; const authEventsMap: TombstoneAuthEvents = { - 'm.room.create': - authEvents.find((event) => event.event.type === 'm.room.create')?._id || - '', - 'm.room.power_levels': - authEvents.find((event) => event.event.type === 'm.room.power_levels') - ?._id || '', - 'm.room.member': - authEvents.find((event) => event.event.type === 'm.room.member')?._id || - '', + 'm.room.create': authEvents.find( + (event) => event.event.type === 'm.room.create', + )?._id, + 'm.room.power_levels': authEvents.find( + (event) => event.event.type === 'm.room.power_levels', + )?._id, + 'm.room.member': authEvents.find( + (event) => event.event.type === 'm.room.member', + )?._id, }; const prevEvents = latestEvent ? [latestEvent._id] : []; + const authEventsArray = Object.values(authEventsMap).filter( + (event) => event !== undefined, + ) as EventID[]; + const event = await this.stateService.buildEvent<'m.room.tombstone'>( { room_id: roomId, @@ -1056,7 +1061,7 @@ export class RoomService { body: reason, replacement_room: replacementRoomId, }, - auth_events: [...Object.values(authEventsMap)], + auth_events: authEventsArray, prev_events: prevEvents, depth, origin_server_ts: Date.now(), diff --git a/packages/federation-sdk/src/services/send-join.service.ts b/packages/federation-sdk/src/services/send-join.service.ts index dfe34c208..b3ddd6b93 100644 --- a/packages/federation-sdk/src/services/send-join.service.ts +++ b/packages/federation-sdk/src/services/send-join.service.ts @@ -1,5 +1,6 @@ import { type RoomMemberEvent, isRoomMemberEvent } from '@hs/core'; import { + type EventID, type PduMembershipEventContent, PersistentEventFactory, getAuthChain, @@ -20,7 +21,7 @@ export class SendJoinService { private readonly configService: ConfigService, ) {} - async sendJoin(roomId: string, eventId: string, event: RoomMemberEvent) { + async sendJoin(roomId: string, eventId: EventID, event: RoomMemberEvent) { const stateService = this.stateService; const roomVersion = await stateService.getRoomVersion(roomId); diff --git a/packages/federation-sdk/src/services/staging-area.service.ts b/packages/federation-sdk/src/services/staging-area.service.ts index f104f5c28..e0ed48d35 100644 --- a/packages/federation-sdk/src/services/staging-area.service.ts +++ b/packages/federation-sdk/src/services/staging-area.service.ts @@ -3,6 +3,7 @@ import { singleton } from 'tsyringe'; import { createLogger, isRedactedEvent } from '@hs/core'; import { Pdu, PduPowerLevelsEventContent } from '@hs/room'; +import type { EventID } from '@hs/room'; import { EventAuthorizationService } from './event-authorization.service'; import { EventEmitterService } from './event-emitter.service'; import { EventService } from './event.service'; @@ -149,7 +150,7 @@ export class StagingAreaService { msgtype: event.event.content?.msgtype as string, 'm.relates_to': event.event.content?.['m.relates_to'] as { rel_type: 'm.replace' | 'm.annotation' | 'm.thread'; - event_id: string; + event_id: EventID; }, 'm.new_content': event.event.content?.['m.new_content'] as { body: string; @@ -169,7 +170,7 @@ export class StagingAreaService { content: event.event.content as { 'm.relates_to': { rel_type: 'm.annotation'; - event_id: string; + event_id: EventID; key: string; }; }, diff --git a/packages/federation-sdk/src/services/state.service.ts b/packages/federation-sdk/src/services/state.service.ts index 66522bd0b..57542203b 100644 --- a/packages/federation-sdk/src/services/state.service.ts +++ b/packages/federation-sdk/src/services/state.service.ts @@ -1,5 +1,6 @@ import { signEvent } from '@hs/core'; import { + type EventID, type PduContent, type PduType, RoomState, @@ -115,7 +116,7 @@ export class StateService { * This is the state prior to the event */ async findStateAtEvent( - eventId: string, + eventId: EventID, include: 'always' | 'event' = 'event', ): Promise { this.logger.debug({ eventId }, 'finding state before event'); @@ -252,8 +253,8 @@ export class StateService { private async buildFullRoomStateFromEvents( stateMappings: StateStore[], - ): Promise> { - const state = new Map(); + ): Promise> { + const state = new Map(); // first reconstruct the final state for await (const stateMapping of stateMappings) { if (!stateMapping.delta) { @@ -267,7 +268,7 @@ export class StateService { } private async buildFullRoomStateStoredEvents( - events: Map, + events: Map, roomVersion: RoomVersion, ): Promise> { const finalState = new Map(); @@ -344,7 +345,7 @@ export class StateService { return new RoomState(state); } - async getFullRoomStateBeforeEvent2(eventId: string): Promise { + async getFullRoomStateBeforeEvent2(eventId: EventID): Promise { const state = await this.findStateAtEvent(eventId); return new RoomState(state); } @@ -372,7 +373,9 @@ export class StateService { const cache = new Map(); return { - getEvents: async (eventIds: string[]): Promise => { + getEvents: async ( + eventIds: EventID[], + ): Promise => { const events = []; const toFind = []; @@ -772,7 +775,7 @@ export class StateService { // auth events referenced in the message const store = this._getStore(roomVersion); const authEventsReferencedInMessage = await store.getEvents( - event.event.auth_events as string[], + event.event.auth_events, ); const authEventsReferencedMap = new Map(); for (const authEvent of authEventsReferencedInMessage) { diff --git a/packages/federation-sdk/src/utils/event-schemas.ts b/packages/federation-sdk/src/utils/event-schemas.ts index cdda166bf..7baf974a6 100644 --- a/packages/federation-sdk/src/utils/event-schemas.ts +++ b/packages/federation-sdk/src/utils/event-schemas.ts @@ -1,3 +1,4 @@ +import { eventIdSchema } from '@hs/room'; import { z } from 'zod'; const baseEventSchema = z.object({ @@ -6,7 +7,7 @@ const baseEventSchema = z.object({ sender: z.string(), room_id: z.string(), origin_server_ts: z.number().int().positive(), - event_id: z.string().optional(), + event_id: eventIdSchema.optional(), state_key: z.string().optional(), depth: z.number().int().nonnegative().optional(), prev_events: z @@ -62,7 +63,7 @@ const reactionEventSchema = baseEventSchema.extend({ .object({ 'm.relates_to': z.object({ rel_type: z.literal('m.annotation'), - event_id: z.string(), + event_id: eventIdSchema, key: z.string(), }), }) @@ -99,7 +100,7 @@ const joinRulesEventSchema = baseEventSchema.extend({ const redactionEventSchema = baseEventSchema.extend({ type: z.literal('m.room.redaction'), - redacts: z.string(), + redacts: eventIdSchema, content: z .object({ reason: z.string().optional(), diff --git a/packages/homeserver/src/controllers/federation/profiles.controller.ts b/packages/homeserver/src/controllers/federation/profiles.controller.ts index a9b80f591..41f343875 100644 --- a/packages/homeserver/src/controllers/federation/profiles.controller.ts +++ b/packages/homeserver/src/controllers/federation/profiles.controller.ts @@ -1,5 +1,5 @@ import { ProfilesService } from '@hs/federation-sdk'; -import { type RoomVersion } from '@hs/room'; +import { EventID, type RoomVersion } from '@hs/room'; import { Elysia } from 'elysia'; import { container } from 'tsyringe'; import { @@ -103,8 +103,8 @@ export const profilesPlugin = (app: Elysia) => { async ({ params, body }) => profilesService.getMissingEvents( params.roomId, - body.earliest_events, - body.latest_events, + body.earliest_events as EventID[], + body.latest_events as EventID[], body.limit, body.min_depth, ), diff --git a/packages/homeserver/src/controllers/federation/send-join.controller.ts b/packages/homeserver/src/controllers/federation/send-join.controller.ts index b15c92524..2a8a3660e 100644 --- a/packages/homeserver/src/controllers/federation/send-join.controller.ts +++ b/packages/homeserver/src/controllers/federation/send-join.controller.ts @@ -1,4 +1,5 @@ import { SendJoinService } from '@hs/federation-sdk'; +import type { EventID } from '@hs/room'; import { Elysia, t } from 'elysia'; import { container } from 'tsyringe'; import { @@ -19,7 +20,7 @@ export const sendJoinPlugin = (app: Elysia) => { }) => { const { roomId, eventId } = params; - return sendJoinService.sendJoin(roomId, eventId, body as any); + return sendJoinService.sendJoin(roomId, eventId as EventID, body as any); }, { params: t.Object({ diff --git a/packages/homeserver/src/controllers/federation/state.controller.ts b/packages/homeserver/src/controllers/federation/state.controller.ts index 1277b4a2b..8305f1a97 100644 --- a/packages/homeserver/src/controllers/federation/state.controller.ts +++ b/packages/homeserver/src/controllers/federation/state.controller.ts @@ -1,4 +1,5 @@ import { EventService } from '@hs/federation-sdk'; +import { EventID } from '@hs/room'; import { Elysia } from 'elysia'; import { container } from 'tsyringe'; import { @@ -18,7 +19,7 @@ export const statePlugin = (app: Elysia) => { .get( '/_matrix/federation/v1/state_ids/:roomId', ({ params, query }) => - eventService.getStateIds(params.roomId, query.event_id), + eventService.getStateIds(params.roomId, query.event_id as EventID), { params: GetStateIdsParamsDto, query: GetStateIdsQueryDto, @@ -36,7 +37,7 @@ export const statePlugin = (app: Elysia) => { .get( '/_matrix/federation/v1/state/:roomId', ({ params, query }) => - eventService.getState(params.roomId, query.event_id!), + eventService.getState(params.roomId, query.event_id as EventID), { params: GetStateParamsDto, query: GetStateQueryDto, diff --git a/packages/homeserver/src/controllers/federation/transactions.controller.ts b/packages/homeserver/src/controllers/federation/transactions.controller.ts index 9435f4e06..64b476997 100644 --- a/packages/homeserver/src/controllers/federation/transactions.controller.ts +++ b/packages/homeserver/src/controllers/federation/transactions.controller.ts @@ -1,4 +1,5 @@ import { ConfigService, EventService } from '@hs/federation-sdk'; +import { EventID } from '@hs/room'; import { Elysia } from 'elysia'; import { container } from 'tsyringe'; import { @@ -43,7 +44,9 @@ export const transactionsPlugin = (app: Elysia) => { .get( '/_matrix/federation/v1/event/:eventId', async ({ params, set }) => { - const eventData = await eventService.getEventById(params.eventId); + const eventData = await eventService.getEventById( + params.eventId as EventID, + ); if (!eventData) { set.status = 404; return { diff --git a/packages/homeserver/src/controllers/internal/message.controller.ts b/packages/homeserver/src/controllers/internal/message.controller.ts index 88ca9e485..632f65d7a 100644 --- a/packages/homeserver/src/controllers/internal/message.controller.ts +++ b/packages/homeserver/src/controllers/internal/message.controller.ts @@ -1,4 +1,5 @@ import { MessageService } from '@hs/federation-sdk'; +import { EventID } from '@hs/room'; import { Elysia } from 'elysia'; import { container } from 'tsyringe'; import { type ErrorResponse, ErrorResponseDto } from '../../dtos'; @@ -115,7 +116,7 @@ export const internalMessagePlugin = (app: Elysia) => { try { const eventId = await messageService.unsetReaction( roomId, - params.messageId, + params.messageId as EventID, emoji, senderUserId, ); @@ -154,7 +155,7 @@ export const internalMessagePlugin = (app: Elysia) => { const { roomId, senderUserId } = body; const eventId = await messageService.redactMessage( roomId, - params.messageId, + params.messageId as EventID, senderUserId, ); diff --git a/packages/homeserver/src/controllers/internal/room.controller.ts b/packages/homeserver/src/controllers/internal/room.controller.ts index 44a478884..5dd321495 100644 --- a/packages/homeserver/src/controllers/internal/room.controller.ts +++ b/packages/homeserver/src/controllers/internal/room.controller.ts @@ -1,7 +1,11 @@ import { RoomService } from '@hs/federation-sdk'; import { StateService } from '@hs/federation-sdk'; import { InviteService } from '@hs/federation-sdk'; -import { type PduCreateEventContent, PersistentEventFactory } from '@hs/room'; +import { + EventID, + type PduCreateEventContent, + PersistentEventFactory, +} from '@hs/room'; import { Elysia, t } from 'elysia'; import { container } from 'tsyringe'; import { @@ -185,7 +189,7 @@ export const internalRoomPlugin = (app: Elysia) => { async ({ params, query }) => { const eventId = query.event_id; if (eventId) { - const room = await stateService.findStateAtEvent(eventId); + const room = await stateService.findStateAtEvent(eventId as EventID); const state: Record = {}; for (const [key, value] of room.entries()) { state[key] = value.event; diff --git a/packages/room/src/manager/event-wrapper.ts b/packages/room/src/manager/event-wrapper.ts index 016f3eb4b..edb7d82b6 100644 --- a/packages/room/src/manager/event-wrapper.ts +++ b/packages/room/src/manager/event-wrapper.ts @@ -4,7 +4,7 @@ import { type EventStore, getStateMapKey, } from '../state_resolution/definitions/definitions'; -import type { PduForType, StateMapKey } from '../types/_common'; +import type { EventID, PduForType, StateMapKey } from '../types/_common'; import { Pdu, PduContent, @@ -116,7 +116,7 @@ export abstract class PersistentEventBase< } // v1 should have this already, others, generates it - abstract get eventId(): string; + abstract get eventId(): EventID; getContent>(): T { return this.rawEvent.content as T; diff --git a/packages/room/src/manager/v3.ts b/packages/room/src/manager/v3.ts index 384b7043c..a718bfef8 100644 --- a/packages/room/src/manager/v3.ts +++ b/packages/room/src/manager/v3.ts @@ -1,4 +1,5 @@ import { toUnpaddedBase64 } from '@hs/crypto'; +import type { EventID } from '../types/_common'; import {} from '../types/v3-11'; import { type EventStore, @@ -9,7 +10,7 @@ import type { RoomVersion3To11 } from './type'; // v3 is where it changes first export class PersistentEventV3 extends PersistentEventBase { - private _eventId?: string; + private _eventId?: EventID; async getAuthorizationEvents(store: EventStore) { return store.getEvents(this.rawEvent.auth_events); @@ -18,8 +19,7 @@ export class PersistentEventV3 extends PersistentEventBase { async getPreviousEvents(store: EventStore) { return store.getEvents(this.rawEvent.prev_events); } - get eventId(): string { - // ok to cache since object should be freezed at the time of calculating reference hash + get eventId(): EventID { if (this._eventId) { return this._eventId; } @@ -28,7 +28,8 @@ export class PersistentEventV3 extends PersistentEventBase { const referenceHash = this.getReferenceHash(); // The event ID is the reference hash of the event encoded using Unpadded Base64, prefixed with $. A resulting event ID using this approach should look similar to $CD66HAED5npg6074c6pDtLKalHjVfYb2q4Q3LZgrW6o. - this._eventId = `\$${toUnpaddedBase64(referenceHash, { urlSafe: true })}`; + this._eventId = + `\$${toUnpaddedBase64(referenceHash, { urlSafe: true })}` as EventID; return this._eventId; } diff --git a/packages/room/src/state_resolution/definitions/algorithm/v2.spec.ts b/packages/room/src/state_resolution/definitions/algorithm/v2.spec.ts index 39752e0eb..e3dc77dfc 100644 --- a/packages/room/src/state_resolution/definitions/algorithm/v2.spec.ts +++ b/packages/room/src/state_resolution/definitions/algorithm/v2.spec.ts @@ -1,4 +1,4 @@ -import { type StateMapKey } from '../../../types/_common'; +import { type EventID, type StateMapKey } from '../../../types/_common'; import {} from '../../../types/v3-11'; import { type EventStore, @@ -54,7 +54,7 @@ class FakeEvent { content: Record; room_id: string; event_dict: any; - _event_id: string; + _event_id: EventID; constructor( id: string, sender: string, diff --git a/packages/room/src/state_resolution/definitions/algorithm/v2.ts b/packages/room/src/state_resolution/definitions/algorithm/v2.ts index cba94af16..29bacd63b 100644 --- a/packages/room/src/state_resolution/definitions/algorithm/v2.ts +++ b/packages/room/src/state_resolution/definitions/algorithm/v2.ts @@ -104,7 +104,7 @@ export async function resolveStateV2Plus( async getEvents(eventIds) { const resultEvents = [] as PersistentEventBase[]; - const eventIdsToFind = [] as string[]; + const eventIdsToFind = [] as EventID[]; for (const eventId of eventIds) { const event = eventIdToEventMap.get(eventId); diff --git a/packages/room/src/state_resolution/definitions/definitions.ts b/packages/room/src/state_resolution/definitions/definitions.ts index b7afba879..eac7dda6e 100644 --- a/packages/room/src/state_resolution/definitions/definitions.ts +++ b/packages/room/src/state_resolution/definitions/definitions.ts @@ -81,7 +81,7 @@ export function partitionState( // Auth chain export interface EventStore { - getEvents(eventIds: string[]): Promise; + getEvents(eventIds: EventID[]): Promise; } /* diff --git a/packages/room/src/types/_common.ts b/packages/room/src/types/_common.ts index 07cc24f33..bcbf1d61b 100644 --- a/packages/room/src/types/_common.ts +++ b/packages/room/src/types/_common.ts @@ -1,9 +1,12 @@ +import z from 'zod'; import type { Pdu, PduType } from './v3-11'; -export type EventID = string; - export type StateKey = string; +export const eventIdSchema = z.string().brand('EventID'); + +export type EventID = z.infer; + export type StateMapKey = `${PduType}:${StateKey}`; export type State = Map; diff --git a/packages/room/src/types/v3-11.ts b/packages/room/src/types/v3-11.ts index 1ae327084..8cc078539 100644 --- a/packages/room/src/types/v3-11.ts +++ b/packages/room/src/types/v3-11.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { PduForType } from './_common'; +import { PduForType, eventIdSchema } from './_common'; // Copied from: https://github.com/element-hq/synapse/blob/2277df2a1eb685f85040ef98fa21d41aa4cdd389/synapse/api/constants.py#L103-L141 @@ -197,7 +197,7 @@ export type PduRoomTopicEventContent = z.infer< export const PduRoomRedactionContentSchema = z.object({ reason: z.string().optional(), - redacts: z.string().describe('event id'), + redacts: eventIdSchema.describe('event id'), }); export type PduRoomRedactionContent = z.infer< @@ -531,7 +531,7 @@ export type PduMessageReactionEventContent = z.infer< // SPEC: https://spec.matrix.org/v1.12/rooms/v1/#event-format export const PduNoContentTimelineEventSchema = { auth_events: z - .array(z.string()) + .array(eventIdSchema) .describe( 'A list of event IDs that are required in the room state before this event can be applied. The server will not send this event if it is not satisfied.', ), @@ -549,7 +549,7 @@ export const PduNoContentTimelineEventSchema = { 'The timestamp of the event. This is a number that is the number of milliseconds since the Unix epoch.', ), prev_events: z - .array(z.string()) + .array(eventIdSchema) .describe( 'A list of event IDs that are required in the room state before this event can be applied. The server will not send this event if it is not satisfied.', ),