feat: add publisher and browser client SDK (#9)
quality-gate / quality (push) Successful in 1m6s

Closes #9
This commit is contained in:
KyuubiYoru
2026-07-16 06:27:44 +02:00
parent a9a2b3db35
commit 06c3973ce7
13 changed files with 1534 additions and 1 deletions
@@ -0,0 +1,110 @@
using FinalFactory.Rendezvous.Contracts;
namespace FinalFactory.Rendezvous.Client;
public sealed class RendezvousSessionBrowserClient : IRendezvousSessionBrowserClient
{
private readonly RendezvousHttpTransport _transport;
public RendezvousSessionBrowserClient(
HttpClient httpClient,
RendezvousClientOptions? options = null,
IRendezvousDelay? delay = null)
{
_transport = new(httpClient, options, delay);
}
public Task<RendezvousClientResult<BrowseSessionsResponse>> BrowseAsync(
BrowseSessionsRequest request,
CancellationToken cancellationToken = default)
{
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}
string query = $"v1/sessions?contractVersion={request.ContractVersion}"
+ $"&gameId={Escape(request.GameId.Value)}"
+ $"&environmentId={Escape(request.EnvironmentId.Value)}"
+ $"&protocolVersion={request.ProtocolVersion}"
+ $"&pageSize={request.PageSize}"
+ $"&excludeFull={request.ExcludeFull.ToString().ToLowerInvariant()}"
+ (request.RegionId.HasValue ? $"&regionId={Escape(request.RegionId.Value.Value)}" : string.Empty)
+ (request.Cursor is not null ? $"&cursor={Escape(request.Cursor)}" : string.Empty);
return _transport.SendSafeAsync<BrowseSessionsResponse>(
() => new HttpRequestMessage(HttpMethod.Get, query),
cancellationToken);
}
public async Task<RendezvousClientResult<IReadOnlyList<SessionListing>>> BrowseAllAsync(
BrowseSessionsRequest request,
int maximumPages = 100,
CancellationToken cancellationToken = default)
{
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}
if (maximumPages is < 1 or > 1000)
{
throw new ArgumentOutOfRangeException(nameof(maximumPages));
}
List<SessionListing> items = [];
string? cursor = request.Cursor;
for (int page = 0; page < maximumPages; page++)
{
BrowseSessionsRequest pageRequest = new()
{
ContractVersion = request.ContractVersion,
GameId = request.GameId,
EnvironmentId = request.EnvironmentId,
ProtocolVersion = request.ProtocolVersion,
RegionId = request.RegionId,
PageSize = request.PageSize,
ExcludeFull = request.ExcludeFull,
Cursor = cursor,
};
RendezvousClientResult<BrowseSessionsResponse> result = await BrowseAsync(
pageRequest,
cancellationToken).ConfigureAwait(false);
if (!result.IsSuccess || result.Value is null)
{
return RendezvousClientResult.Failure<IReadOnlyList<SessionListing>>(
result.Error,
result.Message,
result.RetryAfterSeconds);
}
items.AddRange(result.Value.Items);
cursor = result.Value.NextCursor;
if (string.IsNullOrEmpty(cursor))
{
return RendezvousClientResult.Success<IReadOnlyList<SessionListing>>(items.AsReadOnly());
}
}
return RendezvousClientResult.Failure<IReadOnlyList<SessionListing>>(
RendezvousErrorCode.CapacityExceeded,
$"Browsing exceeded the configured {maximumPages}-page limit.");
}
public Task<RendezvousClientResult<GetSessionResponse>> GetAsync(
SessionListingId listingId,
GameId gameId,
EnvironmentId environmentId,
uint protocolVersion,
CancellationToken cancellationToken = default)
{
string query = $"v1/sessions/{listingId}?contractVersion={ContractLimits.ContractVersion}"
+ $"&gameId={Escape(gameId.Value)}"
+ $"&environmentId={Escape(environmentId.Value)}"
+ $"&protocolVersion={protocolVersion}";
return _transport.SendSafeAsync<GetSessionResponse>(
() => new HttpRequestMessage(HttpMethod.Get, query),
cancellationToken);
}
private static string Escape(string value) => Uri.EscapeDataString(value ?? string.Empty);
}