101 lines
2.9 KiB
JavaScript
101 lines
2.9 KiB
JavaScript
|
|
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;
|