/** * In-memory store for live sharing sessions (camera + location). * Sessions expire after TTL_MS without an update. */ import { closeRouter, getProducer, getTransport } from './mediasoup.js' const TTL_MS = 60_000 // 60 seconds without update = inactive const sessions = new Map() /** * @typedef {{ * id: string * userId: string * label: string * lat: number * lng: number * updatedAt: number * routerId: string | null * producerId: string | null * transportId: string | null * }} LiveSession */ /** * @param {string} userId * @param {string} [label] * @returns {LiveSession} The created live session. */ export function createSession(userId, label = '') { const id = crypto.randomUUID() const now = Date.now() const session = { id, userId, label: (label || 'Live').trim() || 'Live', lat: 0, lng: 0, updatedAt: now, routerId: null, producerId: null, transportId: null, } sessions.set(id, session) return session } /** * @param {string} id * @returns {LiveSession | undefined} The session or undefined. */ export function getLiveSession(id) { return sessions.get(id) } /** * Get an existing active session for a user (for replacing with a new one). * @param {string} userId * @returns {LiveSession | undefined} The first active session for the user, or undefined. */ export function getActiveSessionByUserId(userId) { const now = Date.now() for (const [, s] of sessions) { if (s.userId === userId && now - s.updatedAt <= TTL_MS) { return s } } return undefined } /** * @param {string} id * @param {{ lat?: number, lng?: number, routerId?: string | null, producerId?: string | null, transportId?: string | null }} updates */ export function updateLiveSession(id, updates) { const session = sessions.get(id) if (!session) return const now = Date.now() if (Number.isFinite(updates.lat)) session.lat = updates.lat if (Number.isFinite(updates.lng)) session.lng = updates.lng if (updates.routerId !== undefined) session.routerId = updates.routerId if (updates.producerId !== undefined) session.producerId = updates.producerId if (updates.transportId !== undefined) session.transportId = updates.transportId session.updatedAt = now } /** * @param {string} id */ export function deleteLiveSession(id) { sessions.delete(id) } /** * Clear all sessions (for tests only). */ export function clearSessions() { sessions.clear() } /** * Returns sessions updated within TTL_MS (active only). * Also cleans up expired sessions. * @returns {Promise>} Active sessions with hasStream flag. */ export async function getActiveSessions() { const now = Date.now() const result = [] const expiredIds = [] for (const [id, s] of sessions) { if (now - s.updatedAt <= TTL_MS) { result.push({ id: s.id, userId: s.userId, label: s.label, lat: s.lat, lng: s.lng, updatedAt: s.updatedAt, hasStream: Boolean(s.producerId), }) } else { expiredIds.push(id) } } // Clean up expired sessions and their WebRTC resources for (const id of expiredIds) { const session = sessions.get(id) if (session) { // Clean up producer if it exists if (session.producerId) { const producer = getProducer(session.producerId) if (producer) { producer.close() } } // Clean up transport if it exists if (session.transportId) { const transport = getTransport(session.transportId) if (transport) { transport.close() } } // Clean up router if (session.routerId) { await closeRouter(id).catch((err) => { console.error(`[liveSessions] Error closing router for expired session ${id}:`, err) }) } sessions.delete(id) } } return result }