Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
using UnityEngine;
using LiveKit;

[CustomEditor(typeof(TokenSourceConfig))]
public class TokenSourceConfigEditor : Editor
[CustomEditor(typeof(TokenSourceComponentConfig))]
public class TokenSourceComponentConfigEditor : Editor
{
public override void OnInspectorGUI()
{
Expand Down
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ Use the samples of the package to see how to use the SDK.

You need a token to join a LiveKit room as a participant. Read more about tokens here: https://docs.livekit.io/frontends/reference/tokens-grants/

To help getting started with tokens, use `TokenSource.cs` with a `TokenSourceConfig` ScriptableObject (see https://docs.livekit.io/frontends/build/authentication/#tokensource). Create a config asset via **Right Click > Create > LiveKit > Token Source Config** and select one of three token source types:
To help getting started with tokens, use `TokenSourceComponent.cs` with a `TokenSourceComponentConfig` ScriptableObject (see https://docs.livekit.io/frontends/build/authentication/#tokensource). Create a config asset via **Right Click > Create > LiveKit > TokenSourceComponentConfig** and select one of three token source types:

#### 1. Literal
Use this to pass a pregenerated server URL and token. Generate tokens via the [LiveKit CLI](https://docs.livekit.io/frontends/build/authentication/custom/#manual-token-creation) or from your [LiveKit Cloud](https://cloud.livekit.io/) project's API key page.
Expand All @@ -236,10 +236,10 @@ For production. Point to your own token endpoint URL and add any required authen

#### Usage

Add a `TokenSource` component to a GameObject, assign your `TokenSourceConfig` asset, then fetch connection details before connecting:
Add a `TokenSourceComponent` to a GameObject, assign your `TokenSourceComponentConfig` asset, then fetch connection details before connecting:

```cs
var connectionDetailsTask = _tokenSource.FetchConnectionDetails();
var connectionDetailsTask = _tokenSourceComponent.FetchConnectionDetails();
yield return new WaitUntil(() => connectionDetailsTask.IsCompleted);

if (connectionDetailsTask.IsFaulted)
Expand All @@ -253,6 +253,25 @@ _room = new Room();
var connect = _room.Connect(details.ServerUrl, details.ParticipantToken, new RoomOptions());
```

Per-call overrides (e.g. dynamic room or participant names) can be passed via `TokenSourceFetchOptions`; any field set there wins over the asset, and unset fields fall back to the config:

```cs
var task = _tokenSourceComponent.FetchConnectionDetails(new TokenSourceFetchOptions
{
RoomName = "lobby-" + System.Guid.NewGuid(),
ParticipantName = playerName,
});
```

To skip the ScriptableObject entirely, instantiate a token source directly:

```cs
ITokenSourceFixed source = new TokenSourceLiteral("wss://your.livekit.host", "<join-token>");
// or: new TokenSourceSandbox("<sandbox-id>");
// or: new TokenSourceEndpoint("https://your.token-server/api/token", headers);
// or: new TokenSourceCustom(async () => await MyAuthFlow());
```




Expand Down
4 changes: 4 additions & 0 deletions Runtime/Scripts/Internal/IsExternalInit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace System.Runtime.CompilerServices
{
internal static class IsExternalInit { }
}
11 changes: 11 additions & 0 deletions Runtime/Scripts/Internal/IsExternalInit.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

188 changes: 102 additions & 86 deletions Runtime/Scripts/TokenSource/TokenSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,105 @@
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using UnityEngine;

namespace LiveKit
{
public class TokenSource : MonoBehaviour
/// <summary>
/// Marker interface for any source of LiveKit <see cref="ConnectionDetails"/>.
/// Implementations are either <see cref="ITokenSourceFixed"/> or <see cref="ITokenSourceConfigurable"/>.
/// </summary>
public interface ITokenSource
{
[SerializeField] private TokenSourceConfig _config;
}

private static readonly string SandboxUrl = "https://cloud-api.livekit.io/api/v2/sandbox/connection-details";
private static readonly HttpClient HttpClient = new HttpClient();
/// <summary>
/// A token source whose connection details are fully determined at construction time and cannot be
/// influenced by per-call options (e.g. literal credentials or a user-supplied callback).
/// </summary>
public interface ITokenSourceFixed : ITokenSource
{
public Task<ConnectionDetails> FetchConnectionDetails();
}

public async Task<ConnectionDetails> FetchConnectionDetails()
/// <summary>
/// A token source that accepts per-call <see cref="TokenSourceFetchOptions"/> to parameterize the
/// request (e.g. an HTTP endpoint that needs room/participant info per fetch).
/// </summary>
public interface ITokenSourceConfigurable : ITokenSource
{
public Task<ConnectionDetails> FetchConnectionDetails(TokenSourceFetchOptions options);
}

/// <summary>
/// Returns a fixed server URL and participant token. Suitable when credentials are pregenerated
/// (e.g. via the LiveKit CLI or LiveKit Cloud project page).
/// </summary>
public class TokenSourceLiteral : ITokenSourceFixed
{
private string _serverUrl;
private string _participantToken;

public TokenSourceLiteral(string serverUrl, string participantToken)
{
if (_config == null)
throw new InvalidOperationException("Token source configuration was not provided");
if (!_config.IsValid)
throw new InvalidOperationException("Token source configuration is invalid");
_serverUrl = serverUrl;
_participantToken = participantToken;
}

switch (_config.TokenSourceType)
{
case TokenSourceType.Sandbox:
return await FetchFromTokenSource(SandboxUrl, new[] { new StringPair { key = "X-Sandbox-ID", value = _config.SandboxId } });
public Task<ConnectionDetails> FetchConnectionDetails()
{
var result = new ConnectionDetails { ServerUrl = _serverUrl, ParticipantToken = _participantToken };
return Task.FromResult(result);
}
}

case TokenSourceType.Endpoint:
return await FetchFromTokenSource(_config.EndpointUrl, _config.EndpointHeaders);
/// <summary>
/// Delegates connection-detail retrieval to a user-supplied async function. Use this when your
/// app already has its own token-fetching code (custom auth flow, cached tokens, etc.).
/// </summary>
public class TokenSourceCustom : ITokenSourceFixed
{
public delegate Task<ConnectionDetails> CustomTokenFunction();

case TokenSourceType.Literal:
return new ConnectionDetails
{
ServerUrl = _config.ServerUrl,
ParticipantToken = _config.Token
};
private CustomTokenFunction _customTokenFunction;

default:
throw new InvalidOperationException("Unknown token source type");
}
public TokenSourceCustom(CustomTokenFunction customTokenFunction)
{
_customTokenFunction = customTokenFunction;
}

public Task<ConnectionDetails> FetchConnectionDetails()
{
return _customTokenFunction();
}
}

/// <summary>
/// Posts a JSON request to a token-server endpoint and returns the parsed <see cref="ConnectionDetails"/>.
/// The body is built from per-call <see cref="TokenSourceFetchOptions"/> (room name, participant info,
/// agent dispatch, etc.). Use for production token servers — see
/// https://docs.livekit.io/frontends/build/authentication/endpoint/.
/// </summary>
public class TokenSourceEndpoint : ITokenSourceConfigurable
{
private string _endpointUrl;
IEnumerable<StringPair> _headers;
private static readonly HttpClient HttpClient = new HttpClient();

public TokenSourceEndpoint(string endpointUrl, IEnumerable<StringPair> headers)
{
_endpointUrl = endpointUrl;
_headers = headers;
}

private async Task<ConnectionDetails> FetchFromTokenSource(string url, IEnumerable<StringPair> headers)
public async Task<ConnectionDetails> FetchConnectionDetails(TokenSourceFetchOptions options)
{
var requestBody = BuildRequest(_config);
var requestBody = BuildRequest(options);
var jsonBody = JsonConvert.SerializeObject(requestBody);
Comment thread
MaxHeimbrock marked this conversation as resolved.

var request = new HttpRequestMessage(HttpMethod.Post, url);
if (headers != null)
var request = new HttpRequestMessage(HttpMethod.Post, _endpointUrl);
if (_headers != null)
{
foreach (var header in headers)
foreach (var header in _headers)
{
if (!string.IsNullOrEmpty(header.key))
request.Headers.TryAddWithoutValidation(header.key, header.value);
Expand All @@ -69,35 +121,35 @@ private async Task<ConnectionDetails> FetchFromTokenSource(string url, IEnumerab
return JsonConvert.DeserializeObject<ConnectionDetails>(jsonContent);
}

private static TokenSourceRequest BuildRequest(TokenSourceConfig config)
private static TokenSourceRequest BuildRequest(TokenSourceFetchOptions options)
{
var request = new TokenSourceRequest
{
RoomName = NullIfEmpty(config.RoomName),
ParticipantName = NullIfEmpty(config.ParticipantName),
ParticipantIdentity = NullIfEmpty(config.ParticipantIdentity),
ParticipantMetadata = NullIfEmpty(config.ParticipantMetadata),
RoomName = NullIfEmpty(options.RoomName),
ParticipantName = NullIfEmpty(options.ParticipantName),
ParticipantIdentity = NullIfEmpty(options.ParticipantIdentity),
ParticipantMetadata = NullIfEmpty(options.ParticipantMetadata),
};

if (config.ParticipantAttributes != null && config.ParticipantAttributes.Count > 0)
if (options.ParticipantAttributes != null && options.ParticipantAttributes.Count > 0)
{
request.ParticipantAttributes = config.ParticipantAttributes
.Where(a => !string.IsNullOrEmpty(a.key))
.ToDictionary(a => a.key, a => a.value);
request.ParticipantAttributes = options.ParticipantAttributes
.Where(a => !string.IsNullOrEmpty(a.Key))
.ToDictionary(a => a.Key, a => a.Value);
if (request.ParticipantAttributes.Count == 0)
request.ParticipantAttributes = null;
}

if (!string.IsNullOrEmpty(config.AgentName) || !string.IsNullOrEmpty(config.AgentMetadata))
if (!string.IsNullOrEmpty(options.AgentName) || !string.IsNullOrEmpty(options.AgentMetadata))
{
request.RoomConfig = new RoomConfig
{
Agents = new List<AgentDispatch>
{
new AgentDispatch
{
AgentName = NullIfEmpty(config.AgentName),
Metadata = NullIfEmpty(config.AgentMetadata)
AgentName = NullIfEmpty(options.AgentName),
Metadata = NullIfEmpty(options.AgentMetadata)
}
}
};
Expand All @@ -110,49 +162,13 @@ private static string NullIfEmpty(string value) =>
string.IsNullOrEmpty(value) ? null : value;
}

class TokenSourceRequest
{
[JsonProperty("room_name", NullValueHandling = NullValueHandling.Ignore)]
public string RoomName;

[JsonProperty("participant_name", NullValueHandling = NullValueHandling.Ignore)]
public string ParticipantName;

[JsonProperty("participant_identity", NullValueHandling = NullValueHandling.Ignore)]
public string ParticipantIdentity;

[JsonProperty("participant_metadata", NullValueHandling = NullValueHandling.Ignore)]
public string ParticipantMetadata;

[JsonProperty("participant_attributes", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, string> ParticipantAttributes;

[JsonProperty("room_config", NullValueHandling = NullValueHandling.Ignore)]
public RoomConfig RoomConfig;
}

class RoomConfig
{
[JsonProperty("agents", NullValueHandling = NullValueHandling.Ignore)]
public List<AgentDispatch> Agents;
}

class AgentDispatch
/// <summary>
/// Convenience <see cref="TokenSourceEndpoint"/> preconfigured for LiveKit Cloud sandbox token servers.
/// Intended for development and testing only — see
/// https://docs.livekit.io/frontends/build/authentication/sandbox-token-server/.
/// </summary>
public class TokenSourceSandbox : TokenSourceEndpoint
{
[JsonProperty("agent_name", NullValueHandling = NullValueHandling.Ignore)]
public string AgentName;

[JsonProperty("metadata", NullValueHandling = NullValueHandling.Ignore)]
public string Metadata;
}

[Serializable]
public struct ConnectionDetails
{
[JsonProperty("server_url")]
public string ServerUrl;

[JsonProperty("participant_token")]
public string ParticipantToken;
public TokenSourceSandbox(string sandboxId) : base("https://cloud-api.livekit.io/api/v2/sandbox/connection-details", new[] { new StringPair { key = "X-Sandbox-ID", value = sandboxId } }) {}
}
}
}
2 changes: 1 addition & 1 deletion Runtime/Scripts/TokenSource/TokenSource.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading