diff --git a/packages/federation-sdk/src/index.ts b/packages/federation-sdk/src/index.ts index 41e625ba..9a439f70 100644 --- a/packages/federation-sdk/src/index.ts +++ b/packages/federation-sdk/src/index.ts @@ -114,6 +114,39 @@ export interface HomeserverServices { emitter: EventEmitterService; } +type RelatesTo = + | { + rel_type: 'm.replace'; + event_id: EventID; + } + | { + rel_type: 'm.annotation'; + event_id: EventID; + key: string; + } + | { + rel_type: 'm.thread'; + event_id: EventID; + 'm.in_reply_to'?: { + event_id: EventID; + room_id: string; + sender: string; + origin_server_ts: number; + }; + is_falling_back?: boolean; + } + | { + // SPEC: Though rich replies form a relationship to another event, they do not use rel_type to create this relationship. + // Instead, a subkey named m.in_reply_to is used to describe the reply’s relationship, + + // rich {"body":"quote","m.mentions":{},"m.relates_to":{"is_falling_back":false,"m.in_reply_to":{"event_id":"$0vkvf2Ha_FdWe3zVaoDw3X15VCyZIZRYrHQXuoZDURQ"}},"msgtype":"m.text"} + + 'm.in_reply_to': { + event_id: EventID; + }; + is_falling_back?: boolean; + }; + export type HomeserverEventSignatures = { 'homeserver.ping': { message: string; @@ -130,6 +163,22 @@ export type HomeserverEventSignatures = { last_active_ago?: number; origin?: string; }; + 'homeserver.matrix.encrypted': { + event_id: EventID; + event: PduForType<'m.room.encrypted'>; + + room_id: string; + sender: string; + origin_server_ts: number; + content: { + algorithm: 'm.megolm.v1.aes-sha2'; + ciphertext: string; + 'm.relates_to'?: RelatesTo; + device_id?: string; + sender_key?: string; + session_id?: string; + }; + }; 'homeserver.matrix.message': { event_id: EventID; event: PduForType<'m.room.message'>; @@ -141,38 +190,7 @@ export type HomeserverEventSignatures = { body: string; msgtype: MessageType; url?: string; - 'm.relates_to'?: - | { - rel_type: 'm.replace'; - event_id: EventID; - } - | { - rel_type: 'm.annotation'; - event_id: EventID; - key: string; - } - | { - rel_type: 'm.thread'; - event_id: EventID; - 'm.in_reply_to'?: { - event_id: EventID; - room_id: string; - sender: string; - origin_server_ts: number; - }; - is_falling_back?: boolean; - } - | { - // SPEC: Though rich replies form a relationship to another event, they do not use rel_type to create this relationship. - // Instead, a subkey named m.in_reply_to is used to describe the reply’s relationship, - - // rich {"body":"quote","m.mentions":{},"m.relates_to":{"is_falling_back":false,"m.in_reply_to":{"event_id":"$0vkvf2Ha_FdWe3zVaoDw3X15VCyZIZRYrHQXuoZDURQ"}},"msgtype":"m.text"} - - 'm.in_reply_to': { - event_id: EventID; - }; - is_falling_back?: boolean; - }; + 'm.relates_to'?: RelatesTo; 'm.new_content'?: { body: string; msgtype: MessageType; diff --git a/packages/federation-sdk/src/repositories/event.repository.ts b/packages/federation-sdk/src/repositories/event.repository.ts index 4010332e..0cadea01 100644 --- a/packages/federation-sdk/src/repositories/event.repository.ts +++ b/packages/federation-sdk/src/repositories/event.repository.ts @@ -75,6 +75,7 @@ export class EventRepository { case 'm.reaction': case 'm.room.name': case 'm.room.message': + case 'm.room.encrypted': case 'm.room.member': case 'm.room.power_levels': case 'm.room.topic': diff --git a/packages/federation-sdk/src/services/staging-area.service.ts b/packages/federation-sdk/src/services/staging-area.service.ts index 0854f5f0..594b1a02 100644 --- a/packages/federation-sdk/src/services/staging-area.service.ts +++ b/packages/federation-sdk/src/services/staging-area.service.ts @@ -234,6 +234,32 @@ export class StagingAreaService { }, }); break; + case event.event.type === 'm.room.encrypted': + this.eventEmitterService.emit('homeserver.matrix.encrypted', { + event_id: eventId, + event: event.event, + room_id: roomId, + sender: event.event.sender, + origin_server_ts: event.event.origin_server_ts, + content: { + ...event.event.content, + 'm.relates_to': event.event.content?.['m.relates_to'] as + | { + rel_type: 'm.replace'; + event_id: EventID; + } + | { + rel_type: 'm.annotation'; + event_id: EventID; + key: string; + } + | { + rel_type: 'm.thread'; + event_id: EventID; + }, + }, + }); + break; case event.event.type === 'm.reaction': { this.eventEmitterService.emit('homeserver.matrix.reaction', { event_id: eventId, diff --git a/packages/room/src/types/v3-11.ts b/packages/room/src/types/v3-11.ts index 8b031950..1e2b8321 100644 --- a/packages/room/src/types/v3-11.ts +++ b/packages/room/src/types/v3-11.ts @@ -366,21 +366,8 @@ export type PduRoomNameEventContent = z.infer< typeof PduRoomNameEventContentSchema >; -// Base message content schema -const BaseMessageContentSchema = z.object({ - body: z.string().describe('The body of the message.'), - msgtype: z - .enum([ - 'm.text', - 'm.image', - 'm.file', - 'm.audio', - 'm.video', - 'm.emote', - 'm.notice', - 'm.location', - ]) - .describe('The type of the message.'), +// Base timeline content schema +const BaseTimelineContentSchema = z.object({ // Optional fields for message edits and relations aka threads 'm.relates_to': z .object({ @@ -406,6 +393,24 @@ const BaseMessageContentSchema = z.object({ }) .optional() .describe('Relation information for edits, replies, reactions, etc.'), +}); + +// Base message content schema +const BaseMessageContentSchema = BaseTimelineContentSchema.extend({ + body: z.string().describe('The body of the message.'), + msgtype: z + .enum([ + 'm.text', + 'm.image', + 'm.file', + 'm.audio', + 'm.video', + 'm.emote', + 'm.notice', + 'm.location', + ]) + .describe('The type of the message.'), + // Optional fields for message edits and relations aka threads format: z .enum(['org.matrix.custom.html']) .describe('The format of the message content.') @@ -512,6 +517,26 @@ export const PduMessageEventContentSchema = z.union([ }), ]); +const EncryptedContentSchema = BaseTimelineContentSchema.extend({ + algorithm: z + .enum(['m.megolm.v1.aes-sha2']) + .describe('The algorithm used to encrypt the content.'), + ciphertext: z.string().describe('The encrypted content.'), + // Optional fields for message edits and relations aka threads + device_id: z + .string() + .describe('The formatted body of the message.') + .optional(), + sender_key: z + .string() + .describe('The formatted body of the message.') + .optional(), + session_id: z + .string() + .describe('The formatted body of the message.') + .optional(), +}); + export type PduMessageEventContent = z.infer< typeof PduMessageEventContentSchema >; @@ -675,6 +700,12 @@ const EventPduTypeRoomTombstone = z.object({ content: PduRoomTombstoneEventContentSchema, }); +const EventPduTypeRoomEncrypted = z.object({ + ...PduNoContentTimelineEventSchema, + type: z.literal('m.room.encrypted'), + content: EncryptedContentSchema, +}); + const EventPduTypeRoomMessage = z.object({ ...PduNoContentTimelineEventSchema, type: z.literal('m.room.message'), @@ -723,6 +754,8 @@ export const PduStateEventSchema = z.discriminatedUnion('type', [ export const PduTimelineSchema = z.discriminatedUnion('type', [ EventPduTypeRoomMessage, + EventPduTypeRoomEncrypted, + EventPduTypeRoomReaction, EventPduTypeRoomRedaction, @@ -740,6 +773,7 @@ export type PduContent = PduForType['content']; export function isTimelineEventType(type: PduType) { return ( type === 'm.room.message' || + type === 'm.room.encrypted' || type === 'm.reaction' || type === 'm.room.redaction' );