Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ them:
#include <livekit/token_source.h>

auto source = /* EndpointTokenSource, SandboxTokenSource, CustomTokenSource, etc. */;
auto credentials = source->fetch(request_options).get();
auto credentials = source.fetch(request_options).get();
if (!credentials ||
!room.connect(credentials.value().server_url, credentials.value().participant_token, options)) {
// handle failure
Expand Down Expand Up @@ -122,15 +122,15 @@ Static credentials you already have, or credentials loaded asynchronously
#include <livekit/token_source.h>

// Static URL + JWT
auto source = livekit::LiteralTokenSource::fromLiteral(url, jwt);
auto credentials = source->fetch().get();
livekit::LiteralTokenSource source(url, jwt);
auto credentials = source.fetch().get();
if (!credentials ||
!room.connect(credentials.value().server_url, credentials.value().participant_token, options)) {
return 1;
}

// Async provider (same contract as JS TokenSource.literal(async () => …))
auto source2 = livekit::LiteralTokenSource::fromProvider([]() {
livekit::LiteralTokenSource source2([]() {
return std::async(std::launch::async, [] {
livekit::TokenSourceResponse details;
details.server_url = /* ... */;
Expand All @@ -157,11 +157,11 @@ livekit::TokenEndpointOptions endpoint_options;
endpoint_options.method = "POST"; // default; set to "GET" if your server requires it
endpoint_options.headers["Authorization"] = "Bearer your-api-token";

auto source = livekit::EndpointTokenSource::fromEndpoint(
livekit::EndpointTokenSource source(
"https://your-backend.example.com/token",
std::move(endpoint_options));

auto credentials = source->fetch(request).get();
auto credentials = source.fetch(request).get();
if (!credentials ||
!room.connect(credentials.value().server_url, credentials.value().participant_token, options)) {
return 1;
Expand All @@ -173,12 +173,12 @@ if (!credentials ||
Uses the LiveKit Cloud sandbox token server. Do not use in production.

```cpp
auto source = livekit::SandboxTokenSource::fromSandboxTokenServer("your-sandbox-id");
livekit::SandboxTokenSource source("your-sandbox-id");

livekit::TokenRequestOptions request;
request.agent_name = "my-agent"; // optional agent dispatch

auto credentials = source->fetch(request).get();
auto credentials = source.fetch(request).get();
if (!credentials ||
!room.connect(credentials.value().server_url, credentials.value().participant_token, options)) {
return 1;
Expand All @@ -193,7 +193,7 @@ Integrate an existing auth system without adopting the standard endpoint wire
format:

```cpp
auto source = livekit::CustomTokenSource::fromCustom(
livekit::CustomTokenSource source(
[](const livekit::TokenRequestOptions& options)
-> std::future<livekit::Result<livekit::TokenSourceResponse, livekit::TokenSourceError>> {
std::promise<livekit::Result<livekit::TokenSourceResponse, livekit::TokenSourceError>> promise;
Expand All @@ -215,10 +215,10 @@ so the next `fetch()` re-queries the inner source — useful when calling
session. `cachedResponse()` returns the currently cached credentials, if any.

```cpp
auto inner = livekit::EndpointTokenSource::fromEndpoint("https://your-backend.example.com/token");
auto source = livekit::CachingTokenSource::wrap(std::move(inner));
auto inner = std::make_unique<livekit::EndpointTokenSource>("https://your-backend.example.com/token");
livekit::CachingTokenSource source(std::move(inner));

auto credentials = source->fetch(request).get();
auto credentials = source.fetch(request).get();
if (!credentials ||
!room.connect(credentials.value().server_url, credentials.value().participant_token, options)) {
return 1;
Expand Down
11 changes: 6 additions & 5 deletions docs/token-source-factory-recommendation.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
This note summarizes possible public API shapes for constructing TokenSource
implementations in the C++ SDK.

## 1. Existing API
## 1. Previous API

Concrete classes expose mechanism-specific static factories:
Concrete classes exposed mechanism-specific static factories:

```cpp
LiteralTokenSource::fromLiteral(url, token);
Expand Down Expand Up @@ -103,13 +103,14 @@ auto source = TokenSource::fromSandboxTokenServer("sandbox-id");

## 4. Constructors

Make concrete sources directly constructible:
Make concrete sources directly constructible. This is the current branch shape:

```cpp
LiteralTokenSource source(url, token);
CustomTokenSource source(callback);
EndpointTokenSource source(url, options);
SandboxTokenSource source(sandbox_id, options);
CachingTokenSource source(std::move(inner));
```

Pros:
Expand All @@ -119,8 +120,8 @@ Pros:

Cons:

- Larger shift from the current `std::unique_ptr` ownership story.
- Caching/wrapping may require heap allocation or additional helper APIs.
- Caching still requires an owned configurable source, typically via
`std::make_unique`.

Example:

Expand Down
123 changes: 123 additions & 0 deletions docs/token-source-sdk-comparison.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# TokenSource SDK API Comparison

This report compares the C++ branch's public TokenSource API against the LiveKit
client SDKs that expose comparable public TokenSource surfaces. The closest
matches are Android, Swift, Flutter, and JS.

## Summary

C++ is conceptually aligned with the newer TokenSource work across LiveKit SDKs:
it has fixed and configurable token source categories, endpoint/sandbox/custom
implementations, request options for room/participant/agent fields, and
JWT-aware caching.

After the API alignment pass, the main remaining differences are:

- C++ uses concrete constructors, while Android centralizes factory methods on
`TokenSource` and JS centralizes them on a `TokenSource` object.
- C++ uses snake_case public fields, while Swift/Android/Flutter/JS use the
language-native field naming conventions.

## SDK Coverage

| SDK | TokenSource Shape | Factory Pattern? | Notes |
|---|---|---:|---|
| C++ branch | `TokenSourceFixed`, `TokenSourceConfigurable`, concrete classes | No | Direct constructors on concrete sources |
| Android | `TokenSource` companion factories returning `FixedTokenSource` / `ConfigurableTokenSource` | Yes | Closest factory naming precedent |
| JS | `TokenSource.literal/custom/endpoint/sandboxTokenServer` object factories | Yes | Closest conceptual taxonomy |
| Swift | Protocols plus concrete `LiteralTokenSource` / `SandboxTokenSource`; `EndpointTokenSource` is a protocol with default implementation | No | Closest protocol/native shape |
| Flutter | Abstract fixed/configurable classes plus concrete constructors | No | Very close type/class shape |
| Unity | `ITokenSourceFixed`, `ITokenSourceConfigurable`, direct constructors, and `TokenSourceComponentConfig` | Partial | Similar concepts, Unity-specific component/config surface |
| React Native | No native TokenSource surface found; component takes `serverUrl` + `token` | No | Uses URL + token directly |
| ESP32 / Unreal / Node | No first-class TokenSource surface found | No | N/A |
| Swift XCFramework / Unity Web / benchmarks / Expo plugin | No separate public TokenSource surface found | No | Packaging/support repos |

## Public Signature Comparison

| Concept | C++ Branch | Android | JS | Swift | Flutter |
|---|---|---|---|---|---|
| Fixed interface | `class TokenSourceFixed` | `interface FixedTokenSource` | `abstract class TokenSourceFixed` | `protocol TokenSourceFixed` | `abstract class TokenSourceFixed` |
| Configurable interface | `class TokenSourceConfigurable` | `interface ConfigurableTokenSource` | `abstract class TokenSourceConfigurable` | `protocol TokenSourceConfigurable` | `abstract class TokenSourceConfigurable` |
| Fixed fetch | `fetch()` | `fetch()` | `fetch()` | `fetch()` | `fetch()` |
| Config fetch | `fetch(const TokenRequestOptions& options = {})` | `fetch(options = TokenRequestOptions())` | `fetch(options, force?)` | `fetch(_ options)` | `fetch(options)` |
| Response | `server_url`, `participant_token`, `room_name?`, `participant_name?` | `serverUrl`, `participantToken`, `roomName?`, `participantName?` | `serverUrl`, `participantToken`, optional response fields | `serverURL`, `participantToken`, `participantName?`, `roomName?` | `serverUrl`, `participantToken`, `participantName?`, `roomName?` |
| Request options | `room_name`, `participant_name`, `participant_identity`, `participant_metadata`, `participant_attributes`, `agent_name`, `agent_metadata`, `agent_deployment` | `roomName`, `participantName`, `participantIdentity`, `participantMetadata`, `participantAttributes`, `agentName`, `agentMetadata`, `agentDeployment` | `roomName`, `participantName`, `participantIdentity`, `participantMetadata`, `participantAttributes`, `agentName`, `agentMetadata`, `deployment` | same as Android | same as Android |

## Factory And Construction Comparison

| Mechanism | C++ Branch | Android | JS | Swift | Flutter | Unity |
|---|---|---|---|---|---|---|
| Literal static credentials | `LiteralTokenSource(url, token)` | `TokenSource.fromLiteral(url, token)` | `TokenSource.literal(valueOrFn)` | `LiteralTokenSource(serverURL:participantToken:)` | `LiteralTokenSource(serverUrl:participantToken:)` | `new TokenSourceLiteral(serverUrl, token)` |
| Literal async provider | `LiteralTokenSource(fn)` | Not found | `TokenSource.literal(async () => ...)` | Implement `TokenSourceFixed` | Implement `TokenSourceFixed` | `TokenSourceCustom` is fixed |
| Custom configurable | `CustomTokenSource(fn)` | `TokenSource.fromCustom(block)` | `TokenSource.custom(fn)` | Implement `TokenSourceConfigurable` | `CustomTokenSource(fn)` | Not configurable; `TokenSourceCustom` is fixed |
| Endpoint | `EndpointTokenSource(url, options)` | `TokenSource.fromEndpoint(url, method, headers)` | `TokenSource.endpoint(url, options)` | conform to `EndpointTokenSource` protocol | `EndpointTokenSource(url:, method:, headers:)` | `new TokenSourceEndpoint(url, headers)` |
| Sandbox | `SandboxTokenSource(id, options)` | `TokenSource.fromSandboxTokenServer(id, options)` | `TokenSource.sandboxTokenServer(id, options)` | `SandboxTokenSource(id:)` | `SandboxTokenSource(sandboxId:)` | `new TokenSourceSandbox(sandboxId)` |
| Caching | `CachingTokenSource(inner)` | `source.cached(...)` | built into configurable sources; `fetch(..., force?)` | `source.cached(...)` | `source.cached(...)` | Not found in public TokenSource surface |

## TokenSource And Connect

| SDK | Core Room Connect Shape | TokenSource Relationship |
|---|---|---|
| C++ branch | `Room::connect(url, token, options)` | User fetches credentials, then passes URL + token to `connect` |
| JS | `room.connect(url, token, opts?)` | User fetches credentials, then passes URL + token to `connect` |
| Android | `room.connect(url, token, options)` | User fetches credentials, then passes URL + token to `connect` |
| Swift | `room.connect(url:token:connectOptions:roomOptions:)` | Core `Room` takes URL + token. `Session` accepts TokenSource, fetches, then calls `Room.connect` |
| Flutter | `room.connect(url, token, connectOptions:, roomOptions:)` | Core `Room` takes URL + token. `Session` accepts TokenSource, fetches, then calls `Room.connect` |
| Unity | `Room.Connect(serverUrl, participantToken)` | `TokenSourceComponent` or direct source fetch returns connection details before connect |
| React Native | `LiveKitRoom` props include `serverUrl` and `token` | No first-class TokenSource wrapper found |

## Findings

### Closest Matches

Android is the closest API naming precedent if C++ uses factory methods.
Android uses central `TokenSource.fromLiteral`, `fromCustom`, `fromEndpoint`,
and `fromSandboxTokenServer` factories; C++ now uses constructors on the
concrete source classes instead.

Swift and Flutter are closest in type shape: both distinguish fixed and
configurable token sources and use concrete `LiteralTokenSource`,
`SandboxTokenSource`, `EndpointTokenSource`, and `CachingTokenSource` concepts.
They do not centralize creation behind a factory object.

JS is closest in user-facing taxonomy: `literal`, `custom`, `endpoint`, and
`sandboxTokenServer`.

### Remaining Naming Differences

The most visible remaining C++ construction differences are:

- C++ constructs concrete sources directly, while Android and JS centralize
creation under `TokenSource`.
- C++ uses `LiteralTokenSource(fn)` for async fixed providers, conceptually close
to JS `TokenSource.literal(async () => ...)`.
- C++ uses `CachingTokenSource(inner)`, while Swift/Android/Flutter expose
`.cached(...)`.

### Connect Design

C++ now follows the common core Room pattern:

1. Fetch credentials from a token source.
2. Pass `serverUrl` and `participantToken` to `Room.connect`.

### Response Shape

C++ now matches the other SDK response shape conceptually:

- `server_url`
- `participant_token`
- `room_name`
- `participant_name`

Only `server_url` and `participant_token` are needed for connect. The optional
room and participant name fields are preserved as informational response
metadata when a token server provides them.

## Conclusion

The largest uniqueness has been removed: C++ no longer exposes TokenSource
`Room::connect` overloads. C++ now favors direct constructors for concrete
TokenSource types; the remaining alignment opportunity is centralizing factories
under a `TokenSource` facade if cross-SDK discoverability becomes more important
than C++-native construction.
24 changes: 7 additions & 17 deletions include/livekit/token_source.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ namespace livekit {
/// This is an output type: it is what a @ref TokenSourceFixed or
/// @ref TokenSourceConfigurable returns from @c fetch. Applications typically read
/// it rather than construct it. For static credentials, prefer
/// @ref LiteralTokenSource::fromLiteral, which takes the server URL and token
/// @ref LiteralTokenSource, which takes the server URL and token
/// directly instead of requiring you to populate this struct.
///
/// Mirrors the @c livekit.TokenSourceResponse wire shape for connection
Expand Down Expand Up @@ -187,7 +187,7 @@ class LIVEKIT_API LiteralTokenSource final : public TokenSourceFixed {
///
/// @param server_url WebSocket URL of the LiveKit server.
/// @param participant_token JWT access token for the participant.
static std::unique_ptr<LiteralTokenSource> fromLiteral(std::string server_url, std::string participant_token);
LiteralTokenSource(std::string server_url, std::string participant_token);

/// @brief Create a token source from an async provider that returns full credentials.
///
Expand All @@ -198,15 +198,11 @@ class LIVEKIT_API LiteralTokenSource final : public TokenSourceFixed {
/// cannot be influenced by @ref TokenRequestOptions. If you need a configurable
/// source whose callback receives request options and can re-fetch on demand,
/// use @ref CustomTokenSource instead.
static std::unique_ptr<LiteralTokenSource> fromProvider(
std::function<std::future<Result<TokenSourceResponse, TokenSourceError>>()> provider);
explicit LiteralTokenSource(std::function<std::future<Result<TokenSourceResponse, TokenSourceError>>()> provider);

std::future<Result<TokenSourceResponse, TokenSourceError>> fetch() override;

private:
explicit LiteralTokenSource(TokenSourceResponse details);
explicit LiteralTokenSource(std::function<std::future<Result<TokenSourceResponse, TokenSourceError>>()> provider);

TokenSourceResponse details_;
std::function<std::future<Result<TokenSourceResponse, TokenSourceError>>()> provider_;
};
Expand All @@ -222,17 +218,14 @@ class LIVEKIT_API CustomTokenSource final : public TokenSourceConfigurable {
///
/// The callback receives @ref TokenRequestOptions for each fetch and returns
/// @ref TokenSourceResponse produced by your application.
static std::unique_ptr<CustomTokenSource> fromCustom(
explicit CustomTokenSource(
std::function<std::future<Result<TokenSourceResponse, TokenSourceError>>(const TokenRequestOptions&)> provider);

/// @note This source holds no cache and invokes the provider fresh on every
/// call. Wrap it in @ref CachingTokenSource to reuse credentials.
std::future<Result<TokenSourceResponse, TokenSourceError>> fetch(const TokenRequestOptions& options) override;

private:
explicit CustomTokenSource(
std::function<std::future<Result<TokenSourceResponse, TokenSourceError>>(const TokenRequestOptions&)> provider);

std::function<std::future<Result<TokenSourceResponse, TokenSourceError>>(const TokenRequestOptions&)> provider_;
};

Expand All @@ -249,7 +242,7 @@ class LIVEKIT_API EndpointTokenSource final : public TokenSourceConfigurable {
///
/// @param endpoint_url URL of your backend token endpoint.
/// @param options HTTP transport options (method, headers, timeout).
static std::unique_ptr<EndpointTokenSource> fromEndpoint(std::string endpoint_url, TokenEndpointOptions options = {});
explicit EndpointTokenSource(std::string endpoint_url, TokenEndpointOptions options = {});

/// @note Every fetch issues a fresh HTTP request. Wrap it in
/// @ref CachingTokenSource to reuse credentials between calls.
Expand Down Expand Up @@ -287,8 +280,7 @@ class LIVEKIT_API SandboxTokenSource final : public TokenSourceConfigurable {
///
/// @param sandbox_id Sandbox identifier from LiveKit Cloud (surrounding whitespace is trimmed).
/// @param options Sandbox token server options.
static std::unique_ptr<SandboxTokenSource> fromSandboxTokenServer(const std::string& sandbox_id,
const SandboxTokenServerOptions& options = {});
explicit SandboxTokenSource(const std::string& sandbox_id, const SandboxTokenServerOptions& options = {});

std::future<Result<TokenSourceResponse, TokenSourceError>> fetch(const TokenRequestOptions& options) override;

Expand All @@ -311,7 +303,7 @@ class LIVEKIT_API CachingTokenSource final : public TokenSourceConfigurable {
/// @brief Wrap @p inner with JWT-aware caching.
///
/// Cached values are keyed by @ref TokenRequestOptions.
static std::unique_ptr<CachingTokenSource> wrap(std::unique_ptr<TokenSourceConfigurable> inner);
explicit CachingTokenSource(std::unique_ptr<TokenSourceConfigurable> inner);

std::future<Result<TokenSourceResponse, TokenSourceError>> fetch(const TokenRequestOptions& options) override;

Expand All @@ -324,8 +316,6 @@ class LIVEKIT_API CachingTokenSource final : public TokenSourceConfigurable {
std::optional<TokenSourceResponse> cachedResponse() const;

private:
explicit CachingTokenSource(std::unique_ptr<TokenSourceConfigurable> inner);

std::unique_ptr<TokenSourceConfigurable> inner_;
mutable std::mutex mutex_;
std::optional<TokenRequestOptions> cached_options_;
Expand Down
8 changes: 4 additions & 4 deletions src/tests/integration/test_room.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ TEST_F(RoomTest, ConnectWithLiteralTokenSource) {
Room room;
RoomOptions options;

auto token_source = LiteralTokenSource::fromLiteral(server_url_, token_);
const auto details = token_source->fetch().get();
LiteralTokenSource token_source(server_url_, token_);
const auto details = token_source.fetch().get();
ASSERT_TRUE(details);

const bool connected = room.connect(details.value().server_url, details.value().participant_token, options);
Expand All @@ -113,7 +113,7 @@ TEST_F(RoomTest, ConnectWithCustomTokenSource) {
Room room;
RoomOptions options;

auto token_source = CustomTokenSource::fromCustom(
CustomTokenSource token_source(
[this](const TokenRequestOptions& options) -> std::future<Result<TokenSourceResponse, TokenSourceError>> {
std::promise<Result<TokenSourceResponse, TokenSourceError>> promise;
TokenSourceResponse details;
Expand All @@ -127,7 +127,7 @@ TEST_F(RoomTest, ConnectWithCustomTokenSource) {
TokenRequestOptions request;
request.room_name = "integration-room";

const auto details = token_source->fetch(request).get();
const auto details = token_source.fetch(request).get();
ASSERT_TRUE(details);

const bool connected = room.connect(details.value().server_url, details.value().participant_token, options);
Expand Down
Loading