/* streamer-tools OBS Camera Plugin - streamer-tools API client Copyright (C) 2026 CyberCoveLLC 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 */ #pragma once // Client for the two read-key-scoped endpoints in // apps/server/src/obs/plugin.routes.ts (streamer-tools repo): // // GET /api/obs/:slug/slots?key= // 200 { slots: [ { identity, displayName, live } ] } // 404 { error: 'not found' } wrong key OR unknown room // 503 { error: 'livekit not configured' } // // POST /api/obs/:slug/token?key= // 200 { lkToken, wsUrl, identity } // 404 / 503 as above // // The server deliberately answers a wrong key and an unknown slug identically // (404), so this client must not claim to know which it was. #include #include #include #include "stplugin/core.h" #include "stplugin/http.h" namespace stplugin { enum class ApiStatus { Ok, /// server URL / slug / key were not all filled in InvalidConfig, /// request never completed (DNS, TLS, timeout, ...) NetworkError, /// HTTP 404: unknown room slug or wrong read key -- indistinguishable NotFound, /// HTTP 503: the server has no LiveKit credentials configured Unavailable, /// any other non-2xx status HttpError, /// 2xx but the body was not the JSON shape this client expects MalformedResponse, }; /// A short, operator-facing description. Never includes the read key. const char *describeApiStatus(ApiStatus status); struct SlotInfo { /// LiveKit participant identity -- this is what the session wrapper /// subscribes to, and what gets persisted in the OBS source settings. std::string identity; /// Human label for the dropdown; the server falls back to identity. std::string display_name; /// Currently publishing camera video. bool live = false; }; struct SlotsResult { ApiStatus status = ApiStatus::InvalidConfig; /// Detail for logs/UI. Never contains the read key. std::string message; std::vector slots; bool ok() const { return status == ApiStatus::Ok; } }; struct TokenResult { ApiStatus status = ApiStatus::InvalidConfig; std::string message; /// LiveKit JWT for a hidden, subscribe-only participant. std::string lk_token; /// LiveKit websocket URL to connect to. std::string ws_url; /// The obs:: identity the server minted for us. std::string identity; bool ok() const { return status == ApiStatus::Ok; } }; class ApiClient { public: /// Takes ownership of the HTTP client, so tests can inject a fake. explicit ApiClient(std::shared_ptr http); /// @param timeout_ms whole-request timeout. Kept as a parameter because /// the properties dialog's "Refresh" button runs on OBS's UI thread with /// an operator waiting, and must give up sooner than a background /// reconnect would. SlotsResult fetchSlots(const ConnectionConfig &config, int timeout_ms = 10000) const; TokenResult requestToken(const ConnectionConfig &config, int timeout_ms = 10000) const; /// Accepts what an operator would actually paste: a bare hostname, a URL /// with a trailing slash, extra whitespace. Returns an empty string if /// nothing usable is left. Defaults to https:// when no scheme is given, /// because the read key must never be sent in the clear by accident. static std::string normalizeServerUrl(const std::string &raw); /// Exposed for tests and for logging: the exact URL a call will hit, /// with the read key replaced by "***". static std::string redactedUrl(const std::string &url); /// Scrubs "access_token=" and "key=" out of arbitrary /// text -- not necessarily a bare URL/query string -- replacing each /// value with "". Unlike redactedUrl (which only has to /// handle "&"-delimited query parameters), a value here can be followed /// by a quote or whitespace, because the text this scrubs is a free-form /// log line that may merely *contain* a URL. Used by the OBS adapter's /// LiveKit SDK log bridge: LiveKit's signaling URL carries the access /// token as a query parameter, and the SDK's own log lines could /// include it. static std::string redactSensitiveParams(const std::string &text); private: std::shared_ptr http_; }; } // namespace stplugin