Files

127 lines
4.5 KiB
C++
Raw Permalink Normal View History

/*
streamer-tools OBS Camera Plugin - streamer-tools API client
Copyright (C) 2026 CyberCoveLLC <jknapp85@gmail.com>
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=<readKey>
// 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=<readKey>
// 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 <memory>
#include <string>
#include <vector>
#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<SlotInfo> 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:<slug>:<nonce> 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<HttpClient> 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=<value>" and "key=<value>" out of arbitrary
/// text -- not necessarily a bare URL/query string -- replacing each
/// value with "<redacted>". 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<HttpClient> http_;
};
} // namespace stplugin