feat(webrtc): integrate Cloudflare TURN/STUN servers
- Add backend endpoint to fetch ICE server credentials from Cloudflare - Implement dynamic ICE server configuration in frontend - Add fallback to public STUN servers when Cloudflare unavailable - Create comprehensive test suite for WebRTC API endpoint - Update environment configuration with Cloudflare TURN credentials Backend changes: - New route: GET /api/webrtc/ice-servers (authenticated) - Fetches temporary credentials from Cloudflare API with 24h TTL - Returns formatted ICE servers for RTCPeerConnection - Graceful fallback to Google STUN servers on errors Frontend changes: - Remove hardcoded ICE servers from useWebRTC hook - Fetch ICE servers dynamically from backend on mount - Store servers in ref for peer connection initialization - Add webrtcAPI service for backend communication Tests: - 9 comprehensive tests covering all scenarios - 100% coverage for webrtc.js route - Tests authentication, success, and all fallback scenarios
This commit is contained in:
100
backend/src/routes/webrtc.js
Normal file
100
backend/src/routes/webrtc.js
Normal file
@@ -0,0 +1,100 @@
|
||||
const express = require('express');
|
||||
const { authenticate } = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* GET /api/webrtc/ice-servers
|
||||
* Get ICE servers configuration (STUN/TURN) for WebRTC
|
||||
* Requires authentication
|
||||
*/
|
||||
router.get('/ice-servers', authenticate, async (req, res) => {
|
||||
try {
|
||||
const turnTokenId = process.env.CLOUDFLARE_TURN_TOKEN_ID;
|
||||
const turnApiToken = process.env.CLOUDFLARE_TURN_API_TOKEN;
|
||||
|
||||
if (!turnTokenId || !turnApiToken) {
|
||||
console.error('Cloudflare TURN credentials not configured');
|
||||
// Fallback to public STUN servers if TURN not configured
|
||||
return res.json({
|
||||
success: true,
|
||||
iceServers: [
|
||||
{ urls: 'stun:stun.l.google.com:19302' },
|
||||
{ urls: 'stun:stun1.l.google.com:19302' },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// Request TURN credentials from Cloudflare
|
||||
const cloudflareUrl = `https://rtc.live.cloudflare.com/v1/turn/keys/${turnTokenId}/credentials/generate`;
|
||||
|
||||
const response = await fetch(cloudflareUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${turnApiToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
ttl: 86400, // 24 hours
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('Cloudflare TURN API error:', response.status, errorText);
|
||||
|
||||
// Fallback to public STUN servers
|
||||
return res.json({
|
||||
success: true,
|
||||
iceServers: [
|
||||
{ urls: 'stun:stun.l.google.com:19302' },
|
||||
{ urls: 'stun:stun1.l.google.com:19302' },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Cloudflare returns: { iceServers: { urls: [...], username: "...", credential: "..." } }
|
||||
// We need to return it in the format expected by RTCPeerConnection
|
||||
const cloudflareIceServers = data.iceServers;
|
||||
|
||||
if (!cloudflareIceServers || !cloudflareIceServers.urls) {
|
||||
console.error('Invalid response from Cloudflare TURN API:', data);
|
||||
// Fallback to public STUN servers
|
||||
return res.json({
|
||||
success: true,
|
||||
iceServers: [
|
||||
{ urls: 'stun:stun.l.google.com:19302' },
|
||||
{ urls: 'stun:stun1.l.google.com:19302' },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// Return in RTCPeerConnection format
|
||||
// Cloudflare provides all URLs (STUN + TURN) in one object with credentials
|
||||
res.json({
|
||||
success: true,
|
||||
iceServers: [
|
||||
{
|
||||
urls: cloudflareIceServers.urls,
|
||||
username: cloudflareIceServers.username,
|
||||
credential: cloudflareIceServers.credential,
|
||||
}
|
||||
],
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching TURN credentials:', error);
|
||||
|
||||
// Fallback to public STUN servers on error
|
||||
res.json({
|
||||
success: true,
|
||||
iceServers: [
|
||||
{ urls: 'stun:stun.l.google.com:19302' },
|
||||
{ urls: 'stun:stun1.l.google.com:19302' },
|
||||
],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user