using Grpc.Core; using Grpc.Net.Client; using MxGateway.Contracts.Proto; namespace MxGateway.Client; /// /// Provides the .NET client entry point for the public MXAccess Gateway gRPC API. /// public sealed class MxGatewayClient : IAsyncDisposable { private readonly GrpcChannel _channel; private readonly IMxGatewayClientTransport _transport; private bool _disposed; internal MxGatewayClient( MxGatewayClientOptions options, IMxGatewayClientTransport transport) { ArgumentNullException.ThrowIfNull(options); options.Validate(); Options = options; _transport = transport ?? throw new ArgumentNullException(nameof(transport)); _channel = null!; } private MxGatewayClient( GrpcChannel channel, IMxGatewayClientTransport transport) { _channel = channel; _transport = transport; Options = transport.Options; } public MxGatewayClientOptions Options { get; } public MxAccessGateway.MxAccessGatewayClient RawClient => _transport.RawClient ?? throw new InvalidOperationException("The raw generated gRPC client is not available for this client instance."); public static MxGatewayClient Create(MxGatewayClientOptions options) { ArgumentNullException.ThrowIfNull(options); options.Validate(); var channel = GrpcChannel.ForAddress( options.Endpoint, new GrpcChannelOptions { LoggerFactory = options.LoggerFactory, }); return new MxGatewayClient( channel, new GrpcMxGatewayClientTransport( options, new MxAccessGateway.MxAccessGatewayClient(channel))); } public async Task OpenSessionAsync( OpenSessionRequest? request = null, CancellationToken cancellationToken = default) { OpenSessionReply reply = await OpenSessionRawAsync( request ?? new OpenSessionRequest(), cancellationToken) .ConfigureAwait(false); return new MxGatewaySession(this, reply); } public Task OpenSessionRawAsync( OpenSessionRequest request, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(request); ThrowIfDisposed(); return _transport.OpenSessionAsync(request, CreateCallOptions(cancellationToken)); } public Task CloseSessionRawAsync( CloseSessionRequest request, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(request); ThrowIfDisposed(); return _transport.CloseSessionAsync(request, CreateCallOptions(cancellationToken)); } public Task InvokeAsync( MxCommandRequest request, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(request); ThrowIfDisposed(); return _transport.InvokeAsync(request, CreateCallOptions(cancellationToken)); } public IAsyncEnumerable StreamEventsAsync( StreamEventsRequest request, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(request); ThrowIfDisposed(); return _transport.StreamEventsAsync(request, CreateCallOptions(cancellationToken)); } public ValueTask DisposeAsync() { if (_disposed) { return ValueTask.CompletedTask; } _disposed = true; _channel?.Dispose(); return ValueTask.CompletedTask; } internal CallOptions CreateCallOptions(CancellationToken cancellationToken) { Metadata headers = new() { { "authorization", $"Bearer {Options.ApiKey}" }, }; return new CallOptions( headers, DateTime.UtcNow.Add(Options.DefaultCallTimeout), cancellationToken); } private void ThrowIfDisposed() { ObjectDisposedException.ThrowIf(_disposed, this); } }