feat(meetings): guest join by link — external people can join without a Connect account (batch68)
- Server: POST /api/meetings/guest-token issues an unauthenticated LiveKit token for a room that is currently live or a valid scheduled meeting (throwaway guest identity). Mesh signaling already tolerates anonymous peers. - Client: opening /home?meet=CODE while signed out runs a lightweight GUEST mode — name prompt, then straight into the meeting with the full meeting client and the chat/sidebar chrome hidden. Signed-in users who open the link auto-join. - sfuConnect uses the guest token for guests. Guests get a friendly "left the meeting / rejoin" screen (no chat to fall back to). - Meeting "Add people" panel gains a "Copy invite link" (copyMeetingLink) that copies the guest link — foundation for the emailed invites in #4. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -929,6 +929,23 @@ route('POST', '/api/meetings/token', async (req, res) => {
|
||||
json(res, 200, { token, url: LIVEKIT_URL, identity: u.id, name: u.name || u.email });
|
||||
});
|
||||
|
||||
// GUEST (no-login) LiveKit token — lets an external person invited by link join a meeting without a
|
||||
// Connect account. Only minted for a room that is currently LIVE or a valid (not-ended) scheduled
|
||||
// meeting, so a token can't be created for an arbitrary/expired code. Identity is a throwaway guest id.
|
||||
route('POST', '/api/meetings/guest-token', async (req, res) => {
|
||||
if (!LIVEKIT_ENABLED) return json(res, 501, { error: 'sfu not configured' });
|
||||
const { room, name } = await readBody(req);
|
||||
const rm = String(room || '').trim();
|
||||
if (!/^\d{6}$/.test(rm)) return json(res, 400, { error: 'invalid room' });
|
||||
const live = (() => { try { return require('./presence').meetingRooms.has(rm); } catch (_) { return false; } })();
|
||||
const sched = (() => { try { const s = R.scheduledMeetings.byCode(rm); return !!(s && !s.ended_at); } catch (_) { return false; } })();
|
||||
if (!live && !sched) return json(res, 404, { error: 'meeting not found or not active' });
|
||||
const gid = 'guest-' + crypto.randomBytes(8).toString('hex');
|
||||
const gname = String(name || 'Guest').slice(0, 60);
|
||||
const token = livekitToken(gid, gname, rm, JSON.stringify({ guest: true }));
|
||||
json(res, 200, { token, url: LIVEKIT_URL, identity: gid, name: gname });
|
||||
});
|
||||
|
||||
// Decline an incoming 1:1 call: drops the caller, posts a "Call declined" line, clears the call.
|
||||
route('POST', '/api/calls/decline', async (req, res) => {
|
||||
const u = currentUser(req);
|
||||
|
||||
Reference in New Issue
Block a user