import { createHmac } from "node:crypto"; import { AccessToken, RoomServiceClient, TrackSource } from "livekit-server-sdk"; import type { MeetingRole } from "./token"; function requiredEnv(name: "LIVEKIT_URL" | "LIVEKIT_API_KEY" | "LIVEKIT_API_SECRET"): string { const value = process.env[name]; if (!value) throw new Error(`${name} is not set`); return value; } export function getLiveKitClientUrl(): string { return requiredEnv("LIVEKIT_URL").replace(/^http:/, "ws:").replace(/^https:/, "wss:").replace(/\/$/, ""); } function getLiveKitApiUrl(): string { return requiredEnv("LIVEKIT_URL").replace(/^ws:/, "http:").replace(/^wss:/, "https:").replace(/\/$/, ""); } /** Stable opaque room name; the public event slug is never sent to LiveKit logs. */ export function getLiveKitRoomName(eventSlug: string): string { const secret = requiredEnv("LIVEKIT_API_SECRET"); return `anouma-${createHmac("sha256", secret).update(eventSlug).digest("hex").slice(0, 32)}`; } export async function createLiveKitToken(args: { eventSlug: string; participantId: string; name: string; role: MeetingRole; clientId?: string; }): Promise { const room = getLiveKitRoomName(args.eventSlug); const token = new AccessToken(requiredEnv("LIVEKIT_API_KEY"), requiredEnv("LIVEKIT_API_SECRET"), { identity: args.participantId, name: args.name, ttl: "2h", metadata: JSON.stringify({ role: args.role, clientId: args.clientId }), }); token.addGrant({ roomJoin: true, room, canPublish: true, canSubscribe: true, canPublishData: true, canPublishSources: args.role === "host" ? [TrackSource.CAMERA, TrackSource.MICROPHONE, TrackSource.SCREEN_SHARE, TrackSource.SCREEN_SHARE_AUDIO] : [TrackSource.CAMERA, TrackSource.MICROPHONE], }); return token.toJwt(); } export function getLiveKitRoomService(): RoomServiceClient { return new RoomServiceClient(getLiveKitApiUrl(), requiredEnv("LIVEKIT_API_KEY"), requiredEnv("LIVEKIT_API_SECRET")); }