/*
* Copyright 2026 LiveKit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the License governing permissions and limitations.
*/
#include
#include
#include
#include "token_source_internal.h"
namespace livekit {
namespace {
using nlohmann::json;
// Read a string field, accepting either the snake_case or camelCase spelling.
// Returns the value only when it is a non-empty JSON string.
std::optional readStringField(const json& obj, const char* snake_key, const char* camel_key) {
for (const char* key : {snake_key, camel_key}) {
const auto it = obj.find(key);
if (it != obj.end() && it->is_string()) {
std::string value = it->get();
if (!value.empty()) {
return value;
}
}
}
return std::nullopt;
}
} // namespace
std::string buildTokenSourceRequestJson(const TokenRequestOptions& options) {
json body = json::object();
const auto set_optional = [&body](const char* key, const std::optional& value) {
if (value.has_value() && !value->empty()) {
body[key] = *value;
}
};
set_optional("room_name", options.room_name);
set_optional("participant_name", options.participant_name);
set_optional("participant_identity", options.participant_identity);
set_optional("participant_metadata", options.participant_metadata);
if (!options.participant_attributes.empty()) {
json attributes = json::object();
for (const auto& [key, value] : options.participant_attributes) {
if (!key.empty()) {
attributes[key] = value;
}
}
body["participant_attributes"] = std::move(attributes);
}
if (options.agent_name.has_value() || options.agent_metadata.has_value() || options.agent_deployment.has_value()) {
json agent = json::object();
if (options.agent_name.has_value() && !options.agent_name->empty()) {
agent["agent_name"] = *options.agent_name;
}
if (options.agent_metadata.has_value() && !options.agent_metadata->empty()) {
agent["metadata"] = *options.agent_metadata;
}
if (options.agent_deployment.has_value() && !options.agent_deployment->empty()) {
agent["deployment"] = *options.agent_deployment;
}
body["room_config"] = json{{"agents", json::array({std::move(agent)})}};
}
return body.dump();
}
Result parseTokenSourceResponseJson(const std::string& json_text) {
// Parse without exceptions: malformed input yields a discarded value, which we
// treat the same as a response missing the required fields.
const json parsed = json::parse(json_text, nullptr, /*allow_exceptions=*/false);
TokenSourceResponse details;
if (parsed.is_object()) {
if (const auto server_url = readStringField(parsed, "server_url", "serverUrl")) {
details.server_url = *server_url;
}
if (const auto participant_token = readStringField(parsed, "participant_token", "participantToken")) {
details.participant_token = *participant_token;
}
details.participant_name = readStringField(parsed, "participant_name", "participantName");
details.room_name = readStringField(parsed, "room_name", "roomName");
}
if (details.server_url.empty()) {
return Result::failure(
TokenSourceError{"token server response missing server_url"});
}
if (details.participant_token.empty()) {
return Result::failure(
TokenSourceError{"token server response missing participant_token"});
}
return Result::success(std::move(details));
}
} // namespace livekit