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
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user