/** * Room Management for Socket.io * * Manages room subscriptions for targeted event delivery. */ import type { RoomType, TransparencyEventType } from './types'; import { EventCategory, getEventCategory } from './events'; /** * Parse a room type to extract its components */ export function parseRoom(room: RoomType): { type: string; id?: string } { if (room.includes(':')) { const [type, id] = room.split(':'); return { type, id }; } return { type: room }; } /** * Create a room name for a specific entity */ export function createRoom(type: 'plant' | 'farm' | 'user', id: string): RoomType { return `${type}:${id}` as RoomType; } /** * Get the default rooms for a user based on their role */ export function getDefaultRooms(userId?: string): RoomType[] { const rooms: RoomType[] = ['global']; if (userId) { rooms.push(`user:${userId}` as RoomType); } return rooms; } /** * Get category-based room for an event type */ export function getCategoryRoom(type: TransparencyEventType): RoomType { const category = getEventCategory(type); switch (category) { case EventCategory.PLANT: return 'plants'; case EventCategory.TRANSPORT: return 'transport'; case EventCategory.FARM: return 'farms'; case EventCategory.DEMAND: return 'demand'; case EventCategory.SYSTEM: case EventCategory.AGENT: case EventCategory.BLOCKCHAIN: case EventCategory.AUDIT: return 'system'; default: return 'global'; } } /** * Determine which rooms should receive an event */ export function getEventRooms( type: TransparencyEventType, data: Record ): RoomType[] { const rooms: RoomType[] = ['global']; // Add category room const categoryRoom = getCategoryRoom(type); if (categoryRoom !== 'global') { rooms.push(categoryRoom); } // Add entity-specific rooms if (data.plantId && typeof data.plantId === 'string') { rooms.push(`plant:${data.plantId}` as RoomType); } if (data.farmId && typeof data.farmId === 'string') { rooms.push(`farm:${data.farmId}` as RoomType); } if (data.userId && typeof data.userId === 'string') { rooms.push(`user:${data.userId}` as RoomType); } return rooms; } /** * Check if a room is valid */ export function isValidRoom(room: string): room is RoomType { const validPrefixes = ['global', 'plants', 'transport', 'farms', 'demand', 'system']; const validEntityPrefixes = ['plant:', 'farm:', 'user:']; if (validPrefixes.includes(room)) { return true; } return validEntityPrefixes.some((prefix) => room.startsWith(prefix)); } /** * Room subscription limits per connection */ export const ROOM_LIMITS = { maxRooms: 50, maxEntityRooms: 20, maxGlobalRooms: 10, }; /** * Check if a connection can join another room */ export function canJoinRoom(currentRooms: RoomType[], newRoom: RoomType): boolean { if (currentRooms.length >= ROOM_LIMITS.maxRooms) { return false; } const parsed = parseRoom(newRoom); const entityRooms = currentRooms.filter((r) => r.includes(':')).length; const globalRooms = currentRooms.filter((r) => !r.includes(':')).length; if (parsed.id && entityRooms >= ROOM_LIMITS.maxEntityRooms) { return false; } if (!parsed.id && globalRooms >= ROOM_LIMITS.maxGlobalRooms) { return false; } return true; }