mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-07 07:55:16 +02:00
feat: add coturn changes
This commit is contained in:
parent
7e438ad049
commit
fde2940e53
11 changed files with 421 additions and 63 deletions
|
|
@ -32,3 +32,12 @@ ENABLE_TRACING=false
|
|||
# LANGFUSE_SECRET_KEY="sk-lf-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
# LANGFUSE_PUBLIC_KEY="pk-lf-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
# LANGFUSE_HOST="https://cloud.langfuse.com"
|
||||
|
||||
# TURN Server Configuration (for WebRTC NAT traversal)
|
||||
# Required for reliable WebRTC connections behind firewalls/NAT
|
||||
# Uses time-limited credentials (TURN REST API) for security
|
||||
TURN_HOST=localhost
|
||||
TURN_SECRET=dograh-turn-secret-change-in-production
|
||||
# TURN_PORT=3478 # Default: 3478
|
||||
# TURN_TLS_PORT=5349 # Default: 5349
|
||||
# TURN_CREDENTIAL_TTL=86400 # Default: 24 hours in seconds
|
||||
|
|
|
|||
|
|
@ -102,3 +102,10 @@ DEFAULT_CAMPAIGN_RETRY_CONFIG = {
|
|||
"retry_on_no_answer": True,
|
||||
"retry_on_voicemail": False,
|
||||
}
|
||||
|
||||
|
||||
TURN_SECRET = os.getenv("TURN_SECRET")
|
||||
TURN_HOST = os.getenv("TURN_HOST", "localhost")
|
||||
TURN_PORT = int(os.getenv("TURN_PORT", "3478"))
|
||||
TURN_TLS_PORT = int(os.getenv("TURN_TLS_PORT", "5349"))
|
||||
TURN_CREDENTIAL_TTL = int(os.getenv("TURN_CREDENTIAL_TTL", "86400"))
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from api.routes.service_keys import router as service_keys_router
|
|||
from api.routes.superuser import router as superuser_router
|
||||
from api.routes.telephony import router as telephony_router
|
||||
from api.routes.tool import router as tool_router
|
||||
from api.routes.turn_credentials import router as turn_credentials_router
|
||||
from api.routes.user import router as user_router
|
||||
from api.routes.webrtc_signaling import router as webrtc_signaling_router
|
||||
from api.routes.workflow import router as workflow_router
|
||||
|
|
@ -43,6 +44,7 @@ router.include_router(looptalk_router)
|
|||
router.include_router(organization_usage_router)
|
||||
router.include_router(reports_router)
|
||||
router.include_router(webrtc_signaling_router)
|
||||
router.include_router(turn_credentials_router)
|
||||
router.include_router(public_embed_router)
|
||||
router.include_router(public_agent_router)
|
||||
router.include_router(public_download_router)
|
||||
|
|
|
|||
143
api/routes/turn_credentials.py
Normal file
143
api/routes/turn_credentials.py
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
"""TURN credentials endpoint for time-limited WebRTC authentication.
|
||||
|
||||
This module implements the TURN REST API credential generation as specified in
|
||||
draft-uberti-behave-turn-rest-00. It generates ephemeral credentials that are
|
||||
valid for a configurable TTL and are cryptographically bound to the user.
|
||||
|
||||
The credential format:
|
||||
- Username: {expiration_timestamp}:{user_id}
|
||||
- Password: base64(hmac-sha1(shared_secret, username))
|
||||
|
||||
References:
|
||||
- https://datatracker.ietf.org/doc/html/draft-uberti-behave-turn-rest-00
|
||||
- https://github.com/coturn/coturn/wiki/turnserver#turn-rest-api
|
||||
"""
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import time
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel
|
||||
|
||||
from api.constants import (
|
||||
TURN_CREDENTIAL_TTL,
|
||||
TURN_HOST,
|
||||
TURN_PORT,
|
||||
TURN_SECRET,
|
||||
TURN_TLS_PORT,
|
||||
)
|
||||
from api.db.models import UserModel
|
||||
from api.services.auth.depends import get_user
|
||||
|
||||
router = APIRouter(prefix="/turn", tags=["turn"])
|
||||
|
||||
|
||||
class TurnCredentialsResponse(BaseModel):
|
||||
"""Response model for TURN credentials."""
|
||||
|
||||
username: str
|
||||
password: str
|
||||
ttl: int
|
||||
uris: List[str]
|
||||
|
||||
|
||||
class TurnConfigResponse(BaseModel):
|
||||
"""Response model for TURN configuration status."""
|
||||
|
||||
enabled: bool
|
||||
host: Optional[str] = None
|
||||
|
||||
|
||||
def generate_turn_credentials(user_id: str, ttl: int = TURN_CREDENTIAL_TTL) -> dict:
|
||||
"""Generate time-limited TURN credentials using HMAC-SHA1.
|
||||
|
||||
Args:
|
||||
user_id: Unique identifier for the user (for auditing)
|
||||
ttl: Time-to-live in seconds for the credentials
|
||||
|
||||
Returns:
|
||||
Dictionary with username, password, ttl, and TURN URIs
|
||||
|
||||
Raises:
|
||||
ValueError: If TURN_SECRET is not configured
|
||||
"""
|
||||
if not TURN_SECRET:
|
||||
raise ValueError("TURN_SECRET is not configured")
|
||||
|
||||
# Calculate expiration timestamp
|
||||
expiration = int(time.time()) + ttl
|
||||
|
||||
# Username format: {expiration}:{user_id}
|
||||
# This allows the TURN server to:
|
||||
# 1. Verify the credential hasn't expired
|
||||
# 2. Track usage per user for auditing
|
||||
username = f"{expiration}:{user_id}"
|
||||
|
||||
# Password: base64(hmac-sha1(secret, username))
|
||||
# This is the standard TURN REST API algorithm
|
||||
password = base64.b64encode(
|
||||
hmac.new(
|
||||
TURN_SECRET.encode("utf-8"),
|
||||
username.encode("utf-8"),
|
||||
hashlib.sha1,
|
||||
).digest()
|
||||
).decode("utf-8")
|
||||
|
||||
# Build TURN URIs
|
||||
uris = [
|
||||
f"turn:{TURN_HOST}:{TURN_PORT}", # TURN over UDP
|
||||
f"turn:{TURN_HOST}:{TURN_PORT}?transport=tcp", # TURN over TCP
|
||||
]
|
||||
|
||||
# Add TLS URIs if TLS port is configured
|
||||
if TURN_TLS_PORT:
|
||||
uris.extend(
|
||||
[
|
||||
f"turns:{TURN_HOST}:{TURN_TLS_PORT}", # TURN over TLS
|
||||
f"turns:{TURN_HOST}:{TURN_TLS_PORT}?transport=tcp", # TURN over TLS+TCP
|
||||
]
|
||||
)
|
||||
|
||||
return {
|
||||
"username": username,
|
||||
"password": password,
|
||||
"ttl": ttl,
|
||||
"uris": uris,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/credentials", response_model=TurnCredentialsResponse)
|
||||
async def get_turn_credentials(
|
||||
user: UserModel = Depends(get_user),
|
||||
) -> TurnCredentialsResponse:
|
||||
"""Get time-limited TURN credentials for WebRTC connections.
|
||||
|
||||
This endpoint generates ephemeral TURN credentials that are:
|
||||
- Valid for the configured TTL (default: 24 hours)
|
||||
- Cryptographically bound to the user via HMAC
|
||||
- Compatible with coturn's use-auth-secret mode
|
||||
|
||||
Returns:
|
||||
TurnCredentialsResponse with username, password, ttl, and TURN URIs
|
||||
"""
|
||||
if not TURN_SECRET:
|
||||
logger.warning("TURN credentials requested but TURN_SECRET not configured")
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail="TURN server not configured",
|
||||
)
|
||||
|
||||
try:
|
||||
credentials = generate_turn_credentials(str(user.id))
|
||||
logger.debug(f"Generated TURN credentials for user {user.id}")
|
||||
return TurnCredentialsResponse(**credentials)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to generate TURN credentials: {e}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Failed to generate TURN credentials",
|
||||
)
|
||||
|
|
@ -7,12 +7,17 @@ Uses the SmallWebRTC API contract:
|
|||
- SmallWebRTCConnection for peer connection management
|
||||
- candidate_from_sdp() for parsing ICE candidates
|
||||
- add_ice_candidate() for trickling support
|
||||
|
||||
TURN Authentication:
|
||||
- Uses time-limited credentials (TURN REST API) when TURN_SECRET is configured
|
||||
- Credentials are generated per-connection using HMAC-SHA1
|
||||
- Falls back to static credentials if TURN_SECRET is not set (legacy mode)
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from datetime import UTC, datetime
|
||||
from typing import Dict, List
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from aiortc import RTCIceServer
|
||||
from aiortc.sdp import candidate_from_sdp
|
||||
|
|
@ -22,6 +27,12 @@ from starlette.websockets import WebSocketState
|
|||
|
||||
from api.db import db_client
|
||||
from api.db.models import UserModel
|
||||
from api.routes.turn_credentials import (
|
||||
TURN_HOST,
|
||||
TURN_PORT,
|
||||
TURN_SECRET,
|
||||
generate_turn_credentials,
|
||||
)
|
||||
from api.services.auth.depends import get_user_ws
|
||||
from api.services.pipecat.run_pipeline import run_pipeline_smallwebrtc
|
||||
from api.services.pipecat.ws_sender_registry import (
|
||||
|
|
@ -35,35 +46,62 @@ from pipecat.utils.context import set_current_run_id
|
|||
router = APIRouter(prefix="/ws")
|
||||
|
||||
|
||||
def get_ice_servers() -> List[RTCIceServer]:
|
||||
"""Build ICE servers configuration including TURN if configured."""
|
||||
def get_ice_servers(user_id: Optional[str] = None) -> List[RTCIceServer]:
|
||||
"""Build ICE servers configuration including TURN if configured.
|
||||
|
||||
Args:
|
||||
user_id: Optional user ID for generating time-limited TURN credentials.
|
||||
If provided and TURN_SECRET is configured, uses TURN REST API.
|
||||
|
||||
Returns:
|
||||
List of RTCIceServer configurations for WebRTC peer connection.
|
||||
"""
|
||||
servers: List[RTCIceServer] = [RTCIceServer(urls="stun:stun.l.google.com:19302")]
|
||||
|
||||
# Add TURN server if configured
|
||||
turn_host = os.getenv("TURN_HOST")
|
||||
# Check if TURN is configured
|
||||
if not TURN_HOST:
|
||||
return servers
|
||||
|
||||
# Use time-limited credentials if TURN_SECRET is configured (recommended)
|
||||
if TURN_SECRET and user_id:
|
||||
try:
|
||||
credentials = generate_turn_credentials(user_id)
|
||||
servers.append(
|
||||
RTCIceServer(
|
||||
urls=credentials["uris"],
|
||||
username=credentials["username"],
|
||||
credential=credentials["password"],
|
||||
)
|
||||
)
|
||||
logger.info(
|
||||
f"TURN server configured with time-limited credentials, TTL: {credentials['ttl']}s"
|
||||
)
|
||||
return servers
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to generate TURN credentials: {e}")
|
||||
|
||||
# Fallback to static credentials (legacy mode - not recommended for production)
|
||||
turn_username = os.getenv("TURN_USERNAME")
|
||||
turn_password = os.getenv("TURN_PASSWORD")
|
||||
|
||||
if turn_host and turn_username and turn_password:
|
||||
if turn_username and turn_password:
|
||||
servers.append(
|
||||
RTCIceServer(
|
||||
urls=[
|
||||
f"turn:{turn_host}:3478",
|
||||
f"turn:{turn_host}:3478?transport=tcp",
|
||||
f"turn:{TURN_HOST}:{TURN_PORT}",
|
||||
f"turn:{TURN_HOST}:{TURN_PORT}?transport=tcp",
|
||||
],
|
||||
username=turn_username,
|
||||
credential=turn_password,
|
||||
)
|
||||
)
|
||||
logger.info(f"TURN server configured: {turn_host}:3478")
|
||||
logger.warning(
|
||||
f"TURN server configured with static credentials (consider using TURN_SECRET for time-limited auth)"
|
||||
)
|
||||
|
||||
return servers
|
||||
|
||||
|
||||
# ICE servers configuration
|
||||
ice_servers = get_ice_servers()
|
||||
|
||||
|
||||
class SignalingManager:
|
||||
"""Manages WebSocket connections and WebRTC peer connections."""
|
||||
|
||||
|
|
@ -178,8 +216,10 @@ class SignalingManager:
|
|||
)
|
||||
else:
|
||||
# Create new connection using correct SmallWebRTC API
|
||||
# Generate ICE servers with time-limited TURN credentials for this user
|
||||
user_ice_servers = get_ice_servers(user_id=str(user.id))
|
||||
pc = SmallWebRTCConnection(
|
||||
ice_servers=ice_servers, connection_timeout_secs=60
|
||||
ice_servers=user_ice_servers, connection_timeout_secs=60
|
||||
)
|
||||
# Set the pc_id before initialization so it's available in get_answer()
|
||||
pc._pc_id = pc_id
|
||||
|
|
|
|||
105
config/coturn/turnserver.conf
Normal file
105
config/coturn/turnserver.conf
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# Coturn TURN Server Configuration
|
||||
# For use with time-limited credentials (TURN REST API)
|
||||
|
||||
# =============================================================================
|
||||
# Listener Configuration
|
||||
# =============================================================================
|
||||
|
||||
# TURN listener port for UDP and TCP
|
||||
listening-port=3478
|
||||
|
||||
# TURN listener port for TLS and DTLS
|
||||
tls-listening-port=5349
|
||||
|
||||
# Relay port range for media
|
||||
# These ports are used for actual media relay between peers
|
||||
min-port=49152
|
||||
max-port=49200
|
||||
|
||||
# =============================================================================
|
||||
# Authentication Configuration (TURN REST API)
|
||||
# =============================================================================
|
||||
|
||||
# Enable TURN REST API authentication (time-limited credentials)
|
||||
# This uses HMAC-SHA1 based authentication with a shared secret
|
||||
# Username format: {expiration_timestamp}:{user_id}
|
||||
# Password: base64(hmac-sha1(secret, username))
|
||||
use-auth-secret
|
||||
|
||||
# Shared secret for TURN REST API authentication
|
||||
# IMPORTANT: Change this value in production!
|
||||
# This must match the TURN_SECRET environment variable in the backend API
|
||||
static-auth-secret=dograh-turn-secret-change-in-production
|
||||
|
||||
# Realm for the TURN server (required for long-term credential mechanism)
|
||||
# This is sent to clients in 401 challenges and used in credential validation
|
||||
realm=dograh.local
|
||||
|
||||
# =============================================================================
|
||||
# Security Settings
|
||||
# =============================================================================
|
||||
|
||||
# Use fingerprint in TURN messages for additional security
|
||||
fingerprint
|
||||
|
||||
# Disable multicast peers (security best practice)
|
||||
no-multicast-peers
|
||||
|
||||
# Disable CLI interface (not needed in containerized deployment)
|
||||
no-cli
|
||||
|
||||
# =============================================================================
|
||||
# Logging Configuration
|
||||
# =============================================================================
|
||||
|
||||
# Log to stdout for Docker compatibility
|
||||
log-file=stdout
|
||||
|
||||
# Enable verbose logging (comment out in production for performance)
|
||||
verbose
|
||||
|
||||
# Use ISO-8601 timestamps in logs
|
||||
new-log-timestamp
|
||||
|
||||
# =============================================================================
|
||||
# Performance & Limits
|
||||
# =============================================================================
|
||||
|
||||
# Total allocation quota (0 = unlimited)
|
||||
# total-quota=0
|
||||
|
||||
# Per-user allocation quota (0 = unlimited)
|
||||
# user-quota=0
|
||||
|
||||
# Max bandwidth per session in bytes per second (0 = unlimited)
|
||||
# max-bps=0
|
||||
|
||||
# =============================================================================
|
||||
# Network Configuration
|
||||
# =============================================================================
|
||||
|
||||
# For Docker with host networking, external-ip is auto-detected
|
||||
# For cloud deployments (AWS, GCP), set external IP explicitly:
|
||||
# external-ip=<PUBLIC_IP>/<PRIVATE_IP>
|
||||
|
||||
# Uncomment to restrict to specific listening IPs:
|
||||
# listening-ip=0.0.0.0
|
||||
|
||||
# =============================================================================
|
||||
# STUN Configuration
|
||||
# =============================================================================
|
||||
|
||||
# Allow STUN binding requests (enabled by default)
|
||||
# no-stun
|
||||
|
||||
# =============================================================================
|
||||
# TLS Configuration (optional - for TURNS)
|
||||
# =============================================================================
|
||||
|
||||
# Uncomment and set paths for TLS support:
|
||||
# cert=/etc/coturn/certs/turn_server_cert.pem
|
||||
# pkey=/etc/coturn/certs/turn_server_pkey.pem
|
||||
|
||||
# Disable TLS/DTLS if not using certificates
|
||||
no-tls
|
||||
no-dtls
|
||||
|
|
@ -58,6 +58,17 @@ services:
|
|||
networks:
|
||||
- app-network
|
||||
|
||||
coturn:
|
||||
image: coturn/coturn:4.8.0
|
||||
container_name: coturn
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
volumes:
|
||||
- ./config/coturn/turnserver.conf:/etc/coturn/turnserver.conf:ro
|
||||
command:
|
||||
- "-c"
|
||||
- "/etc/coturn/turnserver.conf"
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
|
|
|
|||
|
|
@ -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