mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-10 08:05:22 +02:00
feat: add coturn configurations (#143)
* feat: add coturn changes * add turn credentials and config * fix: fix setup_remote script and docker compose
This commit is contained in:
parent
7e438ad049
commit
bf972fcfec
13 changed files with 470 additions and 86 deletions
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
Route to provide TURN server configuration at runtime.
|
||||
This allows OSS users to configure TURN servers via docker-compose.yaml
|
||||
environment variables, since NEXT_PUBLIC_* keys are injected at build time.
|
||||
*/
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function GET() {
|
||||
const host = process.env.TURN_HOST || '';
|
||||
const username = process.env.TURN_USERNAME || '';
|
||||
const password = process.env.TURN_PASSWORD || '';
|
||||
|
||||
// Only return enabled: true if all required fields are set
|
||||
const enabled = !!(host && username && password);
|
||||
|
||||
return NextResponse.json({
|
||||
enabled,
|
||||
host,
|
||||
username,
|
||||
password,
|
||||
});
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import { client } from "@/client/client.gen";
|
||||
import { validateUserConfigurationsApiV1UserConfigurationsUserValidateGet, validateWorkflowApiV1WorkflowWorkflowIdValidatePost } from "@/client/sdk.gen";
|
||||
import { getTurnCredentialsApiV1TurnCredentialsGet, validateUserConfigurationsApiV1UserConfigurationsUserValidateGet, validateWorkflowApiV1WorkflowWorkflowIdValidatePost } from "@/client/sdk.gen";
|
||||
import { TurnCredentialsResponse } from "@/client/types.gen";
|
||||
import { WorkflowValidationError } from "@/components/flow/types";
|
||||
import logger from '@/lib/logger';
|
||||
|
||||
|
|
@ -57,13 +58,9 @@ export const useWebSocketRTC = ({ workflowId, workflowRunId, accessToken, initia
|
|||
const useAudio = true;
|
||||
const audioCodec = 'default';
|
||||
|
||||
// TURN server configuration fetched at runtime from /api/config/turn
|
||||
const turnConfigRef = useRef<{
|
||||
enabled: boolean;
|
||||
host: string;
|
||||
username: string;
|
||||
password: string;
|
||||
} | null>(null);
|
||||
// TURN server credentials fetched at runtime from backend API
|
||||
// Uses time-limited credentials (TURN REST API) for security
|
||||
const turnCredentialsRef = useRef<TurnCredentialsResponse | null>(null);
|
||||
|
||||
const audioRef = useRef<HTMLAudioElement>(null);
|
||||
const pcRef = useRef<RTCPeerConnection | null>(null);
|
||||
|
|
@ -100,19 +97,16 @@ export const useWebSocketRTC = ({ workflowId, workflowRunId, accessToken, initia
|
|||
iceServers.push({ urls: ['stun:stun.l.google.com:19302'] });
|
||||
}
|
||||
|
||||
// Add TURN server if configured (fetched from /api/config/turn)
|
||||
const turnConfig = turnConfigRef.current;
|
||||
if (turnConfig?.enabled) {
|
||||
// Add TURN server if credentials are available (time-limited credentials from backend)
|
||||
const turnCredentials = turnCredentialsRef.current;
|
||||
if (turnCredentials?.uris && turnCredentials.uris.length > 0) {
|
||||
iceServers.push({
|
||||
urls: [
|
||||
`turn:${turnConfig.host}:3478`, // TURN over UDP
|
||||
`turn:${turnConfig.host}:3478?transport=tcp`, // TURN over TCP
|
||||
],
|
||||
username: turnConfig.username,
|
||||
credential: turnConfig.password
|
||||
urls: turnCredentials.uris,
|
||||
username: turnCredentials.username,
|
||||
credential: turnCredentials.password
|
||||
});
|
||||
|
||||
logger.info(`TURN server configured: ${turnConfig.host}:3478`);
|
||||
logger.info(`TURN server configured with ${turnCredentials.uris.length} URIs, TTL: ${turnCredentials.ttl}s`);
|
||||
}
|
||||
|
||||
const config: RTCConfiguration = {
|
||||
|
|
@ -467,17 +461,24 @@ export const useWebSocketRTC = ({ workflowId, workflowRunId, accessToken, initia
|
|||
setConnectionStatus('connecting');
|
||||
|
||||
try {
|
||||
// Fetch TURN configuration at runtime
|
||||
// Fetch time-limited TURN credentials from backend API
|
||||
try {
|
||||
const turnResponse = await fetch('/api/config/turn');
|
||||
if (turnResponse.ok) {
|
||||
turnConfigRef.current = await turnResponse.json();
|
||||
if (turnConfigRef.current?.enabled) {
|
||||
logger.info('TURN server enabled via runtime config');
|
||||
}
|
||||
const turnResponse = await getTurnCredentialsApiV1TurnCredentialsGet({
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
if (turnResponse.data) {
|
||||
turnCredentialsRef.current = turnResponse.data;
|
||||
logger.info(`TURN credentials obtained, TTL: ${turnCredentialsRef.current.ttl}s`);
|
||||
} else if (turnResponse.response.status === 503) {
|
||||
// TURN not configured on server - this is OK, we'll use STUN only
|
||||
logger.info('TURN server not configured, using STUN only');
|
||||
} else {
|
||||
logger.warn(`Failed to fetch TURN credentials: ${turnResponse.response.status}`);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('Failed to fetch TURN config, continuing without TURN:', e);
|
||||
logger.warn('Failed to fetch TURN credentials, continuing without TURN:', e);
|
||||
}
|
||||
|
||||
// Validate API keys
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -879,6 +879,16 @@ export type TriggerCallResponse = {
|
|||
workflow_run_name: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Response model for TURN credentials.
|
||||
*/
|
||||
export type TurnCredentialsResponse = {
|
||||
username: string;
|
||||
password: string;
|
||||
ttl: number;
|
||||
uris: Array<string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Request schema for Twilio configuration.
|
||||
*/
|
||||
|
|
@ -4201,6 +4211,39 @@ export type GetDailyRunsDetailApiV1OrganizationsReportsDailyRunsGetResponses = {
|
|||
|
||||
export type GetDailyRunsDetailApiV1OrganizationsReportsDailyRunsGetResponse = GetDailyRunsDetailApiV1OrganizationsReportsDailyRunsGetResponses[keyof GetDailyRunsDetailApiV1OrganizationsReportsDailyRunsGetResponses];
|
||||
|
||||
export type GetTurnCredentialsApiV1TurnCredentialsGetData = {
|
||||
body?: never;
|
||||
headers?: {
|
||||
authorization?: string | null;
|
||||
'X-API-Key'?: string | null;
|
||||
};
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/api/v1/turn/credentials';
|
||||
};
|
||||
|
||||
export type GetTurnCredentialsApiV1TurnCredentialsGetErrors = {
|
||||
/**
|
||||
* Not found
|
||||
*/
|
||||
404: unknown;
|
||||
/**
|
||||
* Validation Error
|
||||
*/
|
||||
422: HttpValidationError;
|
||||
};
|
||||
|
||||
export type GetTurnCredentialsApiV1TurnCredentialsGetError = GetTurnCredentialsApiV1TurnCredentialsGetErrors[keyof GetTurnCredentialsApiV1TurnCredentialsGetErrors];
|
||||
|
||||
export type GetTurnCredentialsApiV1TurnCredentialsGetResponses = {
|
||||
/**
|
||||
* Successful Response
|
||||
*/
|
||||
200: TurnCredentialsResponse;
|
||||
};
|
||||
|
||||
export type GetTurnCredentialsApiV1TurnCredentialsGetResponse = GetTurnCredentialsApiV1TurnCredentialsGetResponses[keyof GetTurnCredentialsApiV1TurnCredentialsGetResponses];
|
||||
|
||||
export type OptionsInitApiV1PublicEmbedInitOptionsData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue