Files

61 lines
2.4 KiB
TypeScript

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, accessVersion?: string): string {
const secret = requiredEnv("LIVEKIT_API_SECRET");
return `anouma-${createHmac("sha256", secret).update(JSON.stringify([eventSlug, accessVersion])).digest("hex").slice(0, 32)}`;
}
export async function createLiveKitToken(args: {
eventSlug: string;
participantId: string;
name: string;
role: MeetingRole;
clientId?: string;
accessVersion?: string;
expiresAt: number;
}): Promise<string> {
const room = getLiveKitRoomName(args.eventSlug, args.accessVersion);
const remainingSeconds = Math.floor((args.expiresAt - Date.now()) / 1000);
if (remainingSeconds <= 0) throw new Error("Meeting access has expired");
const token = new AccessToken(requiredEnv("LIVEKIT_API_KEY"), requiredEnv("LIVEKIT_API_SECRET"), {
identity: args.participantId,
name: args.name,
// Self-hosted LiveKit cannot revoke already issued JWTs. Keep the
// connection credential short-lived; the app re-authorizes reconnects.
ttl: Math.min(60, remainingSeconds),
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"));
}