// Realtime event taxonomy (spec section 3). Every live feature is an event on // the same receive socket; these names are the contract between backend // publishers and client handlers. Payload shapes are added alongside features. // // Bucket A — durable, must be correct → through the backend (persist, publish) // Bucket B — ephemeral, throwaway → published, not persisted // Bucket C — connection-derived → Centrifugo built-in presence // // EventType is the single source of truth for every event `type` value — never // write the raw string literal anywhere; reference EventType.* instead. import type { Message } from '../types/message'; import type { Conversation } from '../types/conversation'; export const EventType = { // Bucket A MessageNew: 'message.new', MessageEdit: 'message.edit', MessageDelete: 'message.delete', ReactionAdd: 'reaction.add', ReactionRemove: 'reaction.remove', ReadReceipt: 'read.receipt', LastSeen: 'last_seen', ConversationNew: 'conversation.new', ConversationMembership: 'conversation.membership', ProfileUpdate: 'profile.update', // Bucket B TypingStart: 'typing.start', TypingStop: 'typing.stop', RecordingAudio: 'recording.audio', RecordingVideo: 'recording.video', // Bucket C Presence: 'presence', } as const; export type EventType = (typeof EventType)[keyof typeof EventType]; // --- Realtime event payloads (published by the backend, handled by clients) --- // Each `type` field is derived from EventType so a typo is a compile error. export interface MessageNewEvent { type: typeof EventType.MessageNew; message: Message; } export interface MessageEditEvent { type: typeof EventType.MessageEdit; message: Message; } export interface MessageDeleteEvent { type: typeof EventType.MessageDelete; conversationId: string; messageId: string; } // Published to a user's personal channel when a new conversation involving them // is created (a DM someone started, or a group they were added to) so it appears // in their list immediately. export interface ConversationNewEvent { type: typeof EventType.ConversationNew; conversation: Conversation; } export type ConversationMembershipAction = 'added' | 'removed'; export interface ConversationMembershipEvent { type: typeof EventType.ConversationMembership; action: ConversationMembershipAction; conversationId: string; userId: string; } export interface ReactionEvent { type: typeof EventType.ReactionAdd | typeof EventType.ReactionRemove; conversationId: string; messageId: string; emoji: string; userId: string; } export interface ReadReceiptEvent { type: typeof EventType.ReadReceipt; conversationId: string; userId: string; seq: number; } export interface TypingEvent { type: typeof EventType.TypingStart | typeof EventType.TypingStop; conversationId: string; userId: string; } export interface ProfileUpdateEvent { type: typeof EventType.ProfileUpdate; userId: string; displayName: string; avatarUrl: string | null; }