import { Router } from 'express'; import { mediaProvider } from '../../media/service'; import { simpleStreamingEnabled } from '../../media/config'; import { requireDeviceAuth } from '../../middleware/device-auth'; import { writeAuditLog } from '../../services/audit'; import { streamParamSchema } from './schemas'; import { ensureStreamDeviceAuth, getOwnedStreamSession, isStreamParticipant } from './shared'; const router = Router(); router.get('/:streamSessionId/publish-credentials', requireDeviceAuth, async (req, res) => { const parsedParams = streamParamSchema.safeParse(req.params); if (!parsedParams.success) { res.status(400).json({ message: 'Invalid streamSessionId', errors: parsedParams.error.flatten() }); return; } const deviceAuth = ensureStreamDeviceAuth(req, res); if (!deviceAuth) return; if (simpleStreamingEnabled) { res.status(409).json({ message: 'SIMPLE_STREAMING does not use publish credentials' }); return; } const session = await getOwnedStreamSession(parsedParams.data.streamSessionId, deviceAuth.userId); if (!session) { res.status(404).json({ message: 'Stream session not found' }); return; } if (session.cameraDeviceId !== deviceAuth.deviceId) { res.status(403).json({ message: 'Only camera device can request publish credentials' }); return; } if (!session.mediaSessionId || session.status !== 'streaming') { res.status(409).json({ message: 'Stream session is not ready for publish credentials' }); return; } const credentials = await mediaProvider.issuePublishCredentials({ mediaSessionId: session.mediaSessionId, cameraDeviceId: session.cameraDeviceId, ownerUserId: session.ownerUserId, }); res.json(credentials); await writeAuditLog({ ownerUserId: session.ownerUserId, actorDeviceId: session.cameraDeviceId, action: 'stream.publish_credentials_issued', targetType: 'stream_session', targetId: session.id, metadata: { mediaProvider: credentials.provider }, ipAddress: req.ip, }); }); router.get('/:streamSessionId/subscribe-credentials', requireDeviceAuth, async (req, res) => { const parsedParams = streamParamSchema.safeParse(req.params); if (!parsedParams.success) { res.status(400).json({ message: 'Invalid streamSessionId', errors: parsedParams.error.flatten() }); return; } const deviceAuth = ensureStreamDeviceAuth(req, res); if (!deviceAuth) return; if (simpleStreamingEnabled) { res.status(409).json({ message: 'SIMPLE_STREAMING does not use subscribe credentials' }); return; } const session = await getOwnedStreamSession(parsedParams.data.streamSessionId, deviceAuth.userId); if (!session) { res.status(404).json({ message: 'Stream session not found' }); return; } if (!isStreamParticipant(session, deviceAuth.deviceId)) { res.status(403).json({ message: 'Device cannot request subscribe credentials for this stream' }); return; } if (!session.mediaSessionId || session.status !== 'streaming') { res.status(409).json({ message: 'Stream is not active yet' }); return; } const credentials = await mediaProvider.issueSubscribeCredentials({ mediaSessionId: session.mediaSessionId, viewerDeviceId: deviceAuth.deviceId, ownerUserId: session.ownerUserId, }); res.json(credentials); await writeAuditLog({ ownerUserId: session.ownerUserId, actorDeviceId: deviceAuth.deviceId, action: 'stream.subscribe_credentials_issued', targetType: 'stream_session', targetId: session.id, metadata: { mediaProvider: credentials.provider }, ipAddress: req.ip, }); }); router.get('/:streamSessionId/playback-token', requireDeviceAuth, async (req, res) => { const parsedParams = streamParamSchema.safeParse(req.params); if (!parsedParams.success) { res.status(400).json({ message: 'Invalid streamSessionId', errors: parsedParams.error.flatten() }); return; } const deviceAuth = ensureStreamDeviceAuth(req, res); if (!deviceAuth) return; if (simpleStreamingEnabled) { res.status(409).json({ message: 'SIMPLE_STREAMING does not issue playback tokens' }); return; } const session = await getOwnedStreamSession(parsedParams.data.streamSessionId, deviceAuth.userId); if (!session) { res.status(404).json({ message: 'Stream session not found' }); return; } if (!isStreamParticipant(session, deviceAuth.deviceId)) { res.status(403).json({ message: 'Device cannot request playback token for this stream' }); return; } if (!session.streamKey || !session.mediaSessionId || session.status !== 'streaming') { res.status(409).json({ message: 'Stream is not active yet' }); return; } const credentials = await mediaProvider.issueSubscribeCredentials({ mediaSessionId: session.mediaSessionId, viewerDeviceId: deviceAuth.deviceId, ownerUserId: deviceAuth.userId, }); res.json({ streamSessionId: session.id, streamKey: session.streamKey, status: session.status, playbackToken: credentials.subscribeToken, subscribeUrl: credentials.subscribeUrl, mediaProvider: credentials.provider, expiresInSeconds: credentials.expiresInSeconds, }); }); export default router;