feat: one-click join + live pod presence

Join UX: click a member's name to join directly, or type a name and "Join"
(adds to roster + joins) — removes the redundant dropdown step. "Add" still
adds to the roster without joining.

Presence: new GET /api/presence (LiveKit RoomServiceClient) reports who's
connected per pod. The pod list polls it and shows a "N in room" badge plus a
green dot on members currently connected, so people can see active pods and jump in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kartikeya
2026-06-27 16:46:47 -07:00
parent 8271188cf1
commit 0f3b55a4f9
5 changed files with 146 additions and 49 deletions
+39
View File
@@ -0,0 +1,39 @@
import { RoomServiceClient } from 'livekit-server-sdk';
import { env } from '../env.js';
function isConfigured(): boolean {
return !!env.LIVEKIT_URL && !env.LIVEKIT_URL.includes('REPLACE_ME');
}
let client: RoomServiceClient | null = null;
function svc(): RoomServiceClient {
if (!client) {
const httpUrl = env.LIVEKIT_URL.replace(/^wss:/, 'https:').replace(/^ws:/, 'http:');
client = new RoomServiceClient(httpUrl, env.LIVEKIT_API_KEY, env.LIVEKIT_API_SECRET);
}
return client;
}
/**
* Who is currently connected in each pod's LiveKit room, as display names,
* keyed by pod id (= room name). Empty rooms are omitted. Returns {} if
* LiveKit isn't configured or the API call fails (presence is best-effort).
*/
export async function getPresence(): Promise<Record<string, string[]>> {
if (!isConfigured()) return {};
const out: Record<string, string[]> = {};
const rooms = await svc().listRooms();
await Promise.all(
rooms
.filter((r) => r.numParticipants > 0)
.map(async (r) => {
try {
const ps = await svc().listParticipants(r.name);
out[r.name] = ps.map((p) => p.name || p.identity);
} catch {
out[r.name] = [];
}
}),
);
return out;
}