From 57d4c0ff51e1cb1df5ce774ddfd1a6cfc3d8614e Mon Sep 17 00:00:00 2001 From: "kushal.saha" Date: Thu, 6 Aug 2026 07:20:11 +0000 Subject: [PATCH] chore: return metered service from server --- .env.example | 4 + app/Http/Controllers/IceServerController.php | 71 ++++++++++ config/services.php | 5 + resources/js/app.js | 11 +- resources/js/metered.js | 54 +++++++ .../views/interview/candidate_room.blade.php | 133 ++++++++++++------ resources/views/interview/index.blade.php | 42 +++--- routes/web.php | 8 +- 8 files changed, 256 insertions(+), 72 deletions(-) create mode 100644 app/Http/Controllers/IceServerController.php create mode 100644 resources/js/metered.js diff --git a/.env.example b/.env.example index cbf2c0a..7ada4ff 100644 --- a/.env.example +++ b/.env.example @@ -69,3 +69,7 @@ MICROSOFT_CLIENT_ID= MICROSOFT_CLIENT_SECRET= MICROSOFT_REDIRECT_URI="${APP_URL}/auth/microsoft/callback" MICROSOFT_TENANT_ID=common + +# Metered TURN Server Credentials +METERED_URL= +METERED_KEY= diff --git a/app/Http/Controllers/IceServerController.php b/app/Http/Controllers/IceServerController.php new file mode 100644 index 0000000..56c0b00 --- /dev/null +++ b/app/Http/Controllers/IceServerController.php @@ -0,0 +1,71 @@ +has('candidate_interview_id')) { + return response()->json(['error' => 'Unauthenticated access.'], 401); + } + + try { + $baseUrl = config( + "services.metered.url", + env('METERD_URL', 'https://single-login-system.metered.live/api/v1/turn/credentials') + ); + $apiKey = config( + "services.metered.key", + env('METERED_KEY', 'ffc9146ac5c2f75d83d7c679d8553a21c8fa') + ); + + $url = $baseUrl; + if ($apiKey && !str_contains($url, "apiKey=")) { + $separator = str_contains($url, "?") ? "&" : "?"; + $url .= $separator . "apiKey=" . urlencode($apiKey); + } + + $response = Http::timeout(5)->get($url); + + if ($response->successful()) { + return response()->json($response->json()); + } + + Log::error( + "Metered API error response", + [ + "status" => $response->status(), + "body" => $response->body(), + ], + ); + + return response()->json( + [ + "error" => "Failed to fetch ICE servers", + "status" => $response->status(), + ], + $response->status() >= 400 && $response->status() < 600 + ? $response->status() + : 500, + ); + } catch (\Throwable $e) { + Log::error( + "ICE servers fetch exception: " . $e->getMessage(), + ); + + return response()->json( + [ + "error" => "An error occurred while fetching ICE servers.", + "message" => $e->getMessage(), + ], + 500, + ); + } + } +} + diff --git a/config/services.php b/config/services.php index 8a2bd9c..7f7062c 100644 --- a/config/services.php +++ b/config/services.php @@ -42,4 +42,9 @@ 'tenant' => env('MICROSOFT_TENANT_ID', env('MICROSOFT_GRAPH_TENANT_ID', 'common')), ], + 'metered' => [ + 'url' => env('METERED_URL'), + 'key' => env('METERED_KEY'), + ], + ]; diff --git a/resources/js/app.js b/resources/js/app.js index 8337712..5c60800 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1 +1,10 @@ -// +import { getMeteredIceServers, initMeteredIceServers, globalIceServers } from './metered.js'; + +window.globalIceServers = globalIceServers; +window.getMeteredIceServers = getMeteredIceServers; +window.initMeteredIceServers = initMeteredIceServers; + +// Pre-fetch TURN credentials immediately on app boot +if (typeof window !== 'undefined') { + window.getMeteredIceServers(); +} diff --git a/resources/js/metered.js b/resources/js/metered.js new file mode 100644 index 0000000..fd3b02a --- /dev/null +++ b/resources/js/metered.js @@ -0,0 +1,54 @@ +/** + * Metered TURN Server Integration + */ + +let globalIceServers = [ + { urls: 'stun:stun.l.google.com:19302' } +]; +let meteredIceFetchPromise = null; + +/** + * Fetch ICE servers from /ice-servers endpoint and merge with default STUN server. + * Handles network errors gracefully with fallback. + * + * @returns {Promise} Resolves with the array of ICE server objects. + */ +export function getMeteredIceServers() { + if (meteredIceFetchPromise) return meteredIceFetchPromise; + + meteredIceFetchPromise = fetch('/ice-servers', { + headers: { + 'Accept': 'application/json', + 'X-Requested-With': 'XMLHttpRequest' + } + }) + .then(response => { + if (!response.ok) { + throw new Error(`Failed to fetch ICE servers: HTTP status ${response.status}`); + } + return response.json(); + }) + .then(meteredIce => { + if (Array.isArray(meteredIce) && meteredIce.length > 0) { + globalIceServers = [ + { urls: 'stun:stun.l.google.com:19302' }, + ...meteredIce.slice(0, 3) + ]; + } + window.globalIceServers = globalIceServers; + return globalIceServers; + }) + .catch(e => { + console.warn('Metered TURN fetch warning:', e); + window.globalIceServers = globalIceServers; + return globalIceServers; + }); + + return meteredIceFetchPromise; +} + +export function initMeteredIceServers() { + return getMeteredIceServers(); +} + +export { globalIceServers }; diff --git a/resources/views/interview/candidate_room.blade.php b/resources/views/interview/candidate_room.blade.php index 060828d..8ed5696 100644 --- a/resources/views/interview/candidate_room.blade.php +++ b/resources/views/interview/candidate_room.blade.php @@ -10,13 +10,16 @@ - - + + + + +