Files
anouma/lib/meeting/rooms.ts
T
maro 45261a0461 Initial commit: ANOUMA website with Payload CMS, WebRTC meetings, and booking system
- Next.js 16 App Router site with the ANOUMA design system
- Payload CMS (PostgreSQL) for offers, events, posts and page content
- WebRTC video-call system with custom signaling server
- SMTP email reminders and booking-request notifications
- Customer accounts, calendar-based availability, and booking workflow
2026-08-25 16:40:51 +02:00

77 lines
2.4 KiB
TypeScript

import type { WebSocket } from "ws";
import type { MeetingRole } from "./token";
export type Participant = {
ws: WebSocket;
participantId: string;
name: string;
role: MeetingRole;
};
type Room = {
participants: Map<string, Participant>;
kickedParticipantIds: Set<string>;
};
// In-memory only — this server process is the single source of truth for
// signaling (no media is ever stored or relayed through it). If this app is
// ever scaled to multiple instances, this state needs to move to a shared
// store (e.g. Redis pub/sub) alongside a real SFU.
const rooms = new Map<string, Room>();
function getOrCreateRoom(eventSlug: string): Room {
let room = rooms.get(eventSlug);
if (!room) {
room = { participants: new Map(), kickedParticipantIds: new Set() };
rooms.set(eventSlug, room);
}
return room;
}
export function isKicked(eventSlug: string, participantId: string): boolean {
return rooms.get(eventSlug)?.kickedParticipantIds.has(participantId) ?? false;
}
export function joinRoom(eventSlug: string, participant: Participant): Participant[] {
const room = getOrCreateRoom(eventSlug);
const existing = [...room.participants.values()];
room.participants.set(participant.participantId, participant);
return existing;
}
export function leaveRoom(eventSlug: string, participantId: string) {
const room = rooms.get(eventSlug);
if (!room) return;
room.participants.delete(participantId);
if (room.participants.size === 0) rooms.delete(eventSlug);
}
export function kickFromRoom(eventSlug: string, participantId: string) {
const room = rooms.get(eventSlug);
if (!room) return;
room.kickedParticipantIds.add(participantId);
room.participants.delete(participantId);
}
export function getParticipant(eventSlug: string, participantId: string): Participant | undefined {
return rooms.get(eventSlug)?.participants.get(participantId);
}
export function getRoomParticipants(eventSlug: string): Participant[] {
return [...(rooms.get(eventSlug)?.participants.values() ?? [])];
}
export function broadcast(
eventSlug: string,
message: unknown,
opts: { exclude?: string } = {},
) {
const room = rooms.get(eventSlug);
if (!room) return;
const data = JSON.stringify(message);
for (const p of room.participants.values()) {
if (opts.exclude && p.participantId === opts.exclude) continue;
if (p.ws.readyState === p.ws.OPEN) p.ws.send(data);
}
}