using System; using System.Threading; using System.Threading.Tasks; using RudderSdk.Core.Abstractions; namespace RudderSdk.Core; /// /// Realtime websocket channel. Requires /// and /// . /// public sealed class RealtimeService { private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(10); private readonly RudderClient _client; private RealtimeSession? _session; internal RealtimeService(RudderClient client) => _client = client; /// The current session, or null when not connected. public RealtimeSession? Session => _session; /// True while a session is connected. public bool IsConnected => _session?.IsConnected == true; /// Connects to the configured realtime URL. public Task ConnectAsync(TimeSpan? timeout = null, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(_client.RealtimeUrl)) throw new InvalidOperationException("RealtimeUrl is not configured."); return ConnectAsync(new Uri(_client.RealtimeUrl), timeout, cancellationToken); } /// Connects to an explicit realtime URL. public async Task ConnectAsync(Uri uri, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { var factory = _client.Options.RealtimeTransportFactory ?? throw new InvalidOperationException("RealtimeTransportFactory is not configured."); var token = _client.TokenStore.GetAccessToken(); if (string.IsNullOrEmpty(token)) throw new InvalidOperationException("LiveOps access token is required for realtime authorization."); var transport = factory.Create(); await transport.ConnectAsync(uri, timeout ?? DefaultTimeout, cancellationToken).ConfigureAwait(false); _session = new RealtimeSession(transport, token); return _session; } /// Closes the current session, if any. public Task DisconnectAsync(CancellationToken cancellationToken = default) => _session?.DisconnectAsync(cancellationToken) ?? Task.CompletedTask; /// Pumps the underlying transport; call every frame. public void Update(float deltaTime) { _session?.Update(deltaTime); } } /// An open realtime connection. public sealed class RealtimeSession { private readonly IRealtimeTransport _transport; internal RealtimeSession(IRealtimeTransport transport, string accessToken) { _transport = transport; AccessToken = accessToken; _transport.Closed += () => Closed?.Invoke(); _transport.Error += ex => Error?.Invoke(ex); _transport.Received += data => MessageReceived?.Invoke(data); } /// Access token the connection was authorized with. public string AccessToken { get; } /// True while the connection is open. public bool IsConnected => _transport.IsConnected; /// Raised when the connection closes. public event Action? Closed; /// Raised on transport errors. public event Action? Error; /// Raised for every incoming message. public event Action>? MessageReceived; /// Sends one message. public Task SendAsync(byte[] payload, CancellationToken cancellationToken = default) => _transport.SendAsync(new ArraySegment(payload ?? Array.Empty()), cancellationToken); /// Closes the connection. public Task DisconnectAsync(CancellationToken cancellationToken = default) => _transport.CloseAsync(cancellationToken); internal void Update(float deltaTime) { _transport.Update(deltaTime); } }