From f0b108444c1237a083b3fec4f9409822651ec143 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Fri, 24 May 2024 12:37:40 +0200 Subject: [PATCH] Update application servers to return json responses --- .../openvidu-angular/src/app/app.component.ts | 8 +- application-client/openvidu-js/web/app.js | 6 +- application-server/dotnet/Program.cs | 5 +- application-server/dotnet/README.md | 24 ++++ application-server/go/main.go | 4 +- application-server/java/pom.xml | 2 +- .../io/openvidu/basic/java/Controller.java | 14 +-- application-server/node/index.js | 4 +- application-server/php/composer.lock | 18 +-- application-server/php/index.php | 15 +-- application-server/python/app.py | 14 +-- application-server/ruby/Gemfile | 1 + application-server/ruby/app.rb | 7 +- application-server/rust/README.md | 16 ++- application-server/rust/src/main.rs | 107 +++++++++++------- 15 files changed, 155 insertions(+), 90 deletions(-) create mode 100644 application-server/dotnet/README.md diff --git a/application-client/openvidu-angular/src/app/app.component.ts b/application-client/openvidu-angular/src/app/app.component.ts index b05f9083..5c8e6ab9 100644 --- a/application-client/openvidu-angular/src/app/app.component.ts +++ b/application-client/openvidu-angular/src/app/app.component.ts @@ -68,7 +68,8 @@ export class AppComponent implements OnDestroy { // Publish your camera and microphone await this.room.localParticipant.enableCameraAndMicrophone(); } catch (error: any) { - console.log('There was an error connecting to the room:', error?.message); + console.log('There was an error connecting to the room:', error?.error?.errorMessage || error?.message || error); + await this.leaveRoom(); } } @@ -98,8 +99,9 @@ export class AppComponent implements OnDestroy { * access to the endpoints. */ async getToken(roomName: string, participantName: string): Promise { - return lastValueFrom( - this.httpClient.post(APPLICATION_SERVER_URL + 'token', { roomName, participantName }) + const response = await lastValueFrom( + this.httpClient.post<{ token: string }>(APPLICATION_SERVER_URL + 'token', { roomName, participantName }) ); + return response.token; } } diff --git a/application-client/openvidu-js/web/app.js b/application-client/openvidu-js/web/app.js index b9806ad4..5bd75c34 100644 --- a/application-client/openvidu-js/web/app.js +++ b/application-client/openvidu-js/web/app.js @@ -69,6 +69,7 @@ async function joinRoom() { addTrack(localVideoTrack, userName, true); } catch (error) { console.log("There was an error connecting to the room:", error.message); + await leaveRoom(); } } @@ -170,9 +171,10 @@ async function getToken(roomName, participantName) { }); if (!response.ok) { - throw new Error("Failed to get token"); + const error = await response.json(); + throw new Error(`Failed to get token: ${error.errorMessage}`); } const token = await response.json(); - return token; + return token.token; } diff --git a/application-server/dotnet/Program.cs b/application-server/dotnet/Program.cs index e88c99cc..5471ca8c 100644 --- a/application-server/dotnet/Program.cs +++ b/application-server/dotnet/Program.cs @@ -45,11 +45,11 @@ app.MapPost("/token", async (HttpRequest request) => if (bodyParams.TryGetValue("roomName", out var roomName) && bodyParams.TryGetValue("participantName", out var participantName)) { var token = CreateLiveKitJWT(roomName.ToString(), participantName.ToString()); - return Results.Json(token); + return Results.Json(new { token }); } else { - return Results.BadRequest("\"roomName\" and \"participantName\" are required"); + return Results.BadRequest(new { errorMessage = "roomName and participantName are required" }); } }); @@ -66,7 +66,6 @@ app.MapPost("/webhook", async (HttpRequest request) => try { VerifyWebhookEvent(authHeader.First(), postData); - } catch (Exception e) { diff --git a/application-server/dotnet/README.md b/application-server/dotnet/README.md new file mode 100644 index 00000000..ca4c5a46 --- /dev/null +++ b/application-server/dotnet/README.md @@ -0,0 +1,24 @@ +# Basic PHP + +Basic server application built for .NET with ASP.NET Core. + +For further information, check the [tutorial documentation](https://livekit-tutorials.openvidu.io/tutorials/application-server/dotnet/). + +## Prerequisites + +- [.NET](https://dotnet.microsoft.com/en-us/download) + +## Run + +1. Download repository + +```bash +git clone https://github.com/OpenVidu/openvidu-livekit-tutorials.git +cd openvidu-livekit-tutorials/application-server/dotnet +``` + +2. Run the application + +```bash +dotnet run +``` diff --git a/application-server/go/main.go b/application-server/go/main.go index a020b886..1963ecdf 100644 --- a/application-server/go/main.go +++ b/application-server/go/main.go @@ -36,7 +36,7 @@ func createToken(context *gin.Context) { } if body.RoomName == "" || body.ParticipantName == "" { - context.JSON(http.StatusBadRequest, "roomName and participantName are required") + context.JSON(http.StatusBadRequest, gin.H{"errorMessage": "roomName and participantName are required"}) return } @@ -53,7 +53,7 @@ func createToken(context *gin.Context) { return } - context.JSON(http.StatusOK, token) + context.JSON(http.StatusOK, gin.H{"token": token}) } func receiveWebhook(context *gin.Context) { diff --git a/application-server/java/pom.xml b/application-server/java/pom.xml index 246e8e84..ade97d9d 100644 --- a/application-server/java/pom.xml +++ b/application-server/java/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.5 + 3.2.6 diff --git a/application-server/java/src/main/java/io/openvidu/basic/java/Controller.java b/application-server/java/src/main/java/io/openvidu/basic/java/Controller.java index 0d86a1d6..26455fe3 100644 --- a/application-server/java/src/main/java/io/openvidu/basic/java/Controller.java +++ b/application-server/java/src/main/java/io/openvidu/basic/java/Controller.java @@ -14,7 +14,7 @@ import io.livekit.server.AccessToken; import io.livekit.server.RoomJoin; import io.livekit.server.RoomName; import io.livekit.server.WebhookReceiver; -import livekit.LivekitWebhook; +import livekit.LivekitWebhook.WebhookEvent; @CrossOrigin(origins = "*") @RestController @@ -28,15 +28,15 @@ public class Controller { /** * @param params JSON object with roomName and participantName - * @return The JWT token + * @return JSON object with the JWT token */ - @PostMapping(value = "/token", consumes = "application/json", produces = "application/json") - public ResponseEntity createToken(@RequestBody Map params) { + @PostMapping(value = "/token") + public ResponseEntity> createToken(@RequestBody Map params) { String roomName = params.get("roomName"); String participantName = params.get("participantName"); if (roomName == null || participantName == null) { - return ResponseEntity.badRequest().body("\"roomName and participantName are required\""); + return ResponseEntity.badRequest().body(Map.of("errorMessage", "roomName and participantName are required")); } AccessToken token = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET); @@ -44,14 +44,14 @@ public class Controller { token.setIdentity(participantName); token.addGrants(new RoomJoin(true), new RoomName(roomName)); - return ResponseEntity.ok("\"" + token.toJwt() + "\""); + return ResponseEntity.ok(Map.of("token", token.toJwt())); } @PostMapping(value = "/webhook", consumes = "application/webhook+json") public ResponseEntity receiveWebhook(@RequestHeader("Authorization") String authHeader, @RequestBody String body) { WebhookReceiver webhookReceiver = new WebhookReceiver(LIVEKIT_API_KEY, LIVEKIT_API_SECRET); try { - LivekitWebhook.WebhookEvent event = webhookReceiver.receive(body, authHeader); + WebhookEvent event = webhookReceiver.receive(body, authHeader); System.out.println("LiveKit Webhook: " + event.toString()); } catch (Exception e) { System.err.println("Error validating webhook event: " + e.getMessage()); diff --git a/application-server/node/index.js b/application-server/node/index.js index 20609817..b99dd1e5 100644 --- a/application-server/node/index.js +++ b/application-server/node/index.js @@ -18,7 +18,7 @@ app.post("/token", async (req, res) => { const participantName = req.body.participantName; if (!roomName || !participantName) { - res.status(400).json("roomName and participantName are required"); + res.status(400).json({ errorMessage: "roomName and participantName are required" }); return; } @@ -27,7 +27,7 @@ app.post("/token", async (req, res) => { }); at.addGrant({ roomJoin: true, room: roomName }); const token = await at.toJwt(); - res.json(token); + res.json({ token }); }); const webhookReceiver = new WebhookReceiver( diff --git a/application-server/php/composer.lock b/application-server/php/composer.lock index aaf8d654..40b8c9e0 100644 --- a/application-server/php/composer.lock +++ b/application-server/php/composer.lock @@ -58,26 +58,26 @@ }, { "name": "firebase/php-jwt", - "version": "v6.10.0", + "version": "v6.10.1", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "a49db6f0a5033aef5143295342f1c95521b075ff" + "reference": "500501c2ce893c824c801da135d02661199f60c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/a49db6f0a5033aef5143295342f1c95521b075ff", - "reference": "a49db6f0a5033aef5143295342f1c95521b075ff", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/500501c2ce893c824c801da135d02661199f60c5", + "reference": "500501c2ce893c824c801da135d02661199f60c5", "shasum": "" }, "require": { - "php": "^7.4||^8.0" + "php": "^8.0" }, "require-dev": { - "guzzlehttp/guzzle": "^6.5||^7.4", + "guzzlehttp/guzzle": "^7.4", "phpspec/prophecy-phpunit": "^2.0", "phpunit/phpunit": "^9.5", - "psr/cache": "^1.0||^2.0", + "psr/cache": "^2.0||^3.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0" }, @@ -115,9 +115,9 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.10.0" + "source": "https://github.com/firebase/php-jwt/tree/v6.10.1" }, - "time": "2023-12-01T16:26:39+00:00" + "time": "2024-05-18T18:05:11+00:00" }, { "name": "google/protobuf", diff --git a/application-server/php/index.php b/application-server/php/index.php index 4368721d..5d1eebfc 100644 --- a/application-server/php/index.php +++ b/application-server/php/index.php @@ -11,11 +11,12 @@ Dotenv::createImmutable(__DIR__)->safeLoad(); header("Access-Control-Allow-Origin: *"); header("Access-Control-Allow-Headers: Content-Type, Authorization"); +header("Content-type: application/json"); $LIVEKIT_API_KEY = $_ENV["LIVEKIT_API_KEY"] ?? "devkey"; $LIVEKIT_API_SECRET = $_ENV["LIVEKIT_API_SECRET"] ?? "secret"; -if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER["REQUEST_METHOD"] === "POST" && $_SERVER["PATH_INFO"] === "/token") { +if (isset($_SERVER["REQUEST_METHOD"]) && $_SERVER["REQUEST_METHOD"] === "POST" && $_SERVER["PATH_INFO"] === "/token") { $data = json_decode(file_get_contents("php://input"), true); $roomName = $data["roomName"] ?? null; @@ -23,7 +24,7 @@ if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER["REQUEST_METHOD"] === "POST" & if (!$roomName || !$participantName) { http_response_code(400); - echo json_encode("roomName and participantName are required"); + echo json_encode(["errorMessage" => "roomName and participantName are required"]); exit(); } @@ -37,19 +38,19 @@ if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER["REQUEST_METHOD"] === "POST" & ->setGrant($videoGrant) ->toJwt(); - echo json_encode($token); + echo json_encode(["token" => $token]); exit(); } $webhookReceiver = (new WebhookReceiver($LIVEKIT_API_KEY, $LIVEKIT_API_SECRET)); -if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER["REQUEST_METHOD"] === "POST" && $_SERVER["PATH_INFO"] === "/webhook") { +if (isset($_SERVER["REQUEST_METHOD"]) && $_SERVER["REQUEST_METHOD"] === "POST" && $_SERVER["PATH_INFO"] === "/webhook") { $headers = getallheaders(); - $authHeader = $headers['Authorization']; + $authHeader = $headers["Authorization"]; $body = file_get_contents("php://input"); try { $event = $webhookReceiver->receive($body, $authHeader); - error_log('LiveKit Webhook:'); + error_log("LiveKit Webhook:"); error_log(print_r($event->getEvent(), true)); exit(); } catch (Exception $e) { @@ -60,5 +61,5 @@ if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER["REQUEST_METHOD"] === "POST" & } } -echo json_encode("Unsupported endpoint or method"); +echo json_encode(["errorMessage" => "Unsupported endpoint or method"]); exit(); diff --git a/application-server/python/app.py b/application-server/python/app.py index 6868f8c1..76b7d668 100644 --- a/application-server/python/app.py +++ b/application-server/python/app.py @@ -1,5 +1,5 @@ import os -from flask import Flask, request, jsonify +from flask import Flask, request from flask_cors import CORS from dotenv import load_dotenv from livekit.api import AccessToken, VideoGrants, TokenVerifier, WebhookReceiver @@ -21,14 +21,14 @@ def create_token(): participant_name = request.json.get("participantName") if not room_name or not participant_name: - return jsonify("roomName and participantName are required"), 400 + return {"errorMessage": "roomName and participantName are required"}, 400 token = ( AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET) .with_identity(participant_name) .with_grants(VideoGrants(room_join=True, room=room_name)) ) - return jsonify(token.to_jwt()) + return {"token": token.to_jwt()} token_verifier = TokenVerifier(LIVEKIT_API_KEY, LIVEKIT_API_SECRET) @@ -36,19 +36,19 @@ webhook_receiver = WebhookReceiver(token_verifier) @app.post("/webhook") -async def receive_webhook(): +def receive_webhook(): auth_token = request.headers.get("Authorization") if not auth_token: - return jsonify("Authorization header is required"), 401 + return "Authorization header is required", 401 try: event = webhook_receiver.receive(request.data.decode("utf-8"), auth_token) print("LiveKit Webhook:", event) - return jsonify(), 200 + return "ok" except: print("Authorization header is not valid") - return jsonify("Authorization header is not valid"), 401 + return "Authorization header is not valid", 401 if __name__ == "__main__": diff --git a/application-server/ruby/Gemfile b/application-server/ruby/Gemfile index 0b4be15b..212055f2 100644 --- a/application-server/ruby/Gemfile +++ b/application-server/ruby/Gemfile @@ -2,6 +2,7 @@ source 'https://rubygems.org' gem 'sinatra' gem 'sinatra-cors' +gem 'sinatra-contrib' gem "rackup", "~> 2.1" gem 'puma' gem 'livekit-server-sdk' \ No newline at end of file diff --git a/application-server/ruby/app.rb b/application-server/ruby/app.rb index 8677152f..3bbfc6dd 100644 --- a/application-server/ruby/app.rb +++ b/application-server/ruby/app.rb @@ -1,4 +1,5 @@ require 'sinatra' +require 'sinatra/json' require 'sinatra/cors' require 'livekit' require './env.rb' @@ -22,14 +23,14 @@ post '/token' do if room_name.nil? || participant_name.nil? status 400 - return JSON.generate('roomName and participantName are required') + return json({errorMessage: 'roomName and participantName are required'}) end token = LiveKit::AccessToken.new(api_key: LIVEKIT_API_KEY, api_secret: LIVEKIT_API_SECRET) token.identity = participant_name token.add_grant(roomJoin: true, room: room_name) - return JSON.generate(token.to_jwt) + return json({token: token.to_jwt}) end post '/webhook' do @@ -39,7 +40,7 @@ post '/webhook' do token_verifier.verify(auth_header) body = JSON.parse(request.body.read) puts "LiveKit Webhook: #{body}" - return JSON.generate('ok') + return rescue => e puts "Authorization header is not valid: #{e}" end diff --git a/application-server/rust/README.md b/application-server/rust/README.md index 8ad52188..33729e14 100644 --- a/application-server/rust/README.md +++ b/application-server/rust/README.md @@ -1,17 +1,23 @@ -#### Prerequisites +# Basic PHP -To run this application you will need **Rust**: +Basic server application built for Rust with Axum. It internally uses [livekit-sdk-rust](https://crates.io/crates/livekit). -- [Rust](https://www.rust-lang.org/tools/install){:target="\_blank"} +For further information, check the [tutorial documentation](https://livekit-tutorials.openvidu.io/tutorials/application-server/rust/). -#### Download repository +## Prerequisites + +- [Rust](https://www.rust-lang.org/tools/install) + +## Run + +1. Download repository ```bash git clone https://github.com/OpenVidu/openvidu-livekit-tutorials.git cd openvidu-livekit-tutorials/application-server/rust ``` -#### Run application +2. Run the application ```bash cargo run diff --git a/application-server/rust/src/main.rs b/application-server/rust/src/main.rs index f1531358..8b7da175 100644 --- a/application-server/rust/src/main.rs +++ b/application-server/rust/src/main.rs @@ -8,18 +8,16 @@ use livekit_api::access_token::AccessToken; use livekit_api::access_token::TokenVerifier; use livekit_api::access_token::VideoGrants; use livekit_api::webhooks::WebhookReceiver; -use serde_json::Value; +use serde_json::{json, Value}; use std::env; +use tokio::net::TcpListener; use tower_http::cors::{Any, CorsLayer}; #[tokio::main] async fn main() { dotenv().ok(); // Load environment variables from .env - // Check that required environment variables are set - let server_port = env::var("SERVER_PORT").unwrap_or("6080".to_string()); - env::var("LIVEKIT_API_KEY").expect("LIVEKIT_API_KEY is not set"); - env::var("LIVEKIT_API_SECRET").expect("LIVEKIT_API_SECRET is not set"); + let server_port = env::var("SERVER_PORT").unwrap_or("6081".to_string()); let cors = CorsLayer::new() .allow_methods([Method::POST]) @@ -31,24 +29,37 @@ async fn main() { .route("/webhook", post(receive_webhook)) .layer(cors); - let listener = tokio::net::TcpListener::bind("0.0.0.0:".to_string() + &server_port) + let listener = TcpListener::bind("0.0.0.0:".to_string() + &server_port) .await .unwrap(); axum::serve(listener, app).await.unwrap(); } -async fn create_token(payload: Option>) -> (StatusCode, Json) { +async fn create_token(payload: Option>) -> (StatusCode, Json) { if let Some(payload) = payload { - let livekit_api_key = env::var("LIVEKIT_API_KEY").expect("LIVEKIT_API_KEY is not set"); - let livekit_api_secret = - env::var("LIVEKIT_API_SECRET").expect("LIVEKIT_API_SECRET is not set"); + let livekit_api_key = env::var("LIVEKIT_API_KEY").unwrap_or("devkey".to_string()); + let livekit_api_secret = env::var("LIVEKIT_API_SECRET").unwrap_or("secret".to_string()); - let room_name = payload.get("roomName").expect("roomName is required"); - let participant_name = payload - .get("participantName") - .expect("participantName is required"); + let room_name = match payload.get("roomName") { + Some(value) => value, + None => { + return ( + StatusCode::BAD_REQUEST, + Json(json!({ "errorMessage": "roomName is required" })), + ); + } + }; + let participant_name = match payload.get("participantName") { + Some(value) => value, + None => { + return ( + StatusCode::BAD_REQUEST, + Json(json!({ "errorMessage": "participantName is required" })), + ); + } + }; - let token = AccessToken::with_api_key(&livekit_api_key, &livekit_api_secret) + let token = match AccessToken::with_api_key(&livekit_api_key, &livekit_api_secret) .with_identity(&participant_name.to_string()) .with_name(&participant_name.to_string()) .with_grants(VideoGrants { @@ -57,41 +68,59 @@ async fn create_token(payload: Option>) -> (StatusCode, Json ..Default::default() }) .to_jwt() - .unwrap(); + { + Ok(token) => token, + Err(_) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "errorMessage": "Error creating token" })), + ); + } + }; - println!( - "Sending token for room {} to participant {}", - room_name, participant_name - ); - - return (StatusCode::OK, Json(token)); + return (StatusCode::OK, Json(json!({ "token": token }))); } else { return ( StatusCode::BAD_REQUEST, - Json("roomName and participantName are required".to_string()), + Json(json!({ "errorMessage": "roomName and participantName are required" })), ); } } -async fn receive_webhook(headers: HeaderMap, body: String) -> StatusCode { - let livekit_api_key = env::var("LIVEKIT_API_KEY").expect("LIVEKIT_API_KEY is not set"); - let livekit_api_secret = env::var("LIVEKIT_API_SECRET").expect("LIVEKIT_API_SECRET is not set"); +async fn receive_webhook(headers: HeaderMap, body: String) -> (StatusCode, String) { + let livekit_api_key = env::var("LIVEKIT_API_KEY").unwrap_or("devkey".to_string()); + let livekit_api_secret = env::var("LIVEKIT_API_SECRET").unwrap_or("secret".to_string()); let token_verifier = TokenVerifier::with_api_key(&livekit_api_key, &livekit_api_secret); let webhook_receiver = WebhookReceiver::new(token_verifier); - let auth_header: &str = headers - .get("Authorization") - .expect("Authorization header is required") - .to_str() - .unwrap(); + let auth_header = match headers.get("Authorization") { + Some(header_value) => match header_value.to_str() { + Ok(header_str) => header_str, + Err(_) => { + return ( + StatusCode::BAD_REQUEST, + "Invalid Authorization header format".to_string(), + ); + } + }, + None => { + return ( + StatusCode::BAD_REQUEST, + "Authorization header is required".to_string(), + ); + } + }; - let res = webhook_receiver.receive(&body, auth_header); - - if let Ok(event) = res { - println!("LiveKit WebHook: {:?}", event); - return StatusCode::OK; - } else { - println!("Error validating webhook event: {:?}", res); - return StatusCode::UNAUTHORIZED; + match webhook_receiver.receive(&body, auth_header) { + Ok(event) => { + println!("LiveKit WebHook: {:?}", event); + return (StatusCode::OK, "ok".to_string()); + } + Err(_) => { + return ( + StatusCode::UNAUTHORIZED, + "Error validating webhook event".to_string(), + ); + } } }