using Deal.Grpc.Telegram; using Deal.Infrastructure.Integrations.Options; using Grpc.Core; using Grpc.Net.Client; namespace Deal.Infrastructure.Integrations.Models; /// /// Транспорт gRPC-клиента telegram-service /// public sealed class TelegramGrpcConnection : IDisposable { /// /// Env-ключ ожидаемого service-token. /// public const string ServiceTokenEnvKey = "DEAL_SERVICE_TOKEN"; /// /// Ключ gRPC-metadata с tenant-id. /// public const string TenantIdMetadataKey = "tenant-id"; /// /// Ключ gRPC-metadata с service-token. /// public const string ServiceTokenMetadataKey = "service-token"; private readonly GrpcChannel _channel; private readonly string _serviceToken; /// /// Создаёт транспорт telegram-service по конфигурации и env-токену /// /// Конфигурация секции Services:Telegram (endpoint). /// Сертификаты mTLS: null — plaintext-канал (dev, флаг выключен). /// Пустой endpoint или пустой DEAL_SERVICE_TOKEN. public TelegramGrpcConnection(TelegramServiceOptions options, MtlsCertificates? mtlsCertificates = null) { ArgumentNullException.ThrowIfNull(options); if (string.IsNullOrWhiteSpace(options.Endpoint)) { throw new InvalidOperationException( $"TelegramGrpcConnection: не задан endpoint telegram-service (секция \"{TelegramServiceOptions.SectionName}:Endpoint\")."); } _serviceToken = Environment.GetEnvironmentVariable(ServiceTokenEnvKey) ?? string.Empty; if (_serviceToken.Length == 0) { throw new InvalidOperationException( $"TelegramGrpcConnection: не задан env {ServiceTokenEnvKey} — telegram-service отвергнет вызовы (fail-closed, Ruling 1)."); } GrpcChannelOptions channelOptions = new() { MaxRetryAttempts = 0 }; if (mtlsCertificates is not null) { channelOptions.HttpHandler = mtlsCertificates.CreateClientHttpHandler(); } _channel = GrpcChannel.ForAddress(options.Endpoint, channelOptions); } /// /// Создаёт клиент RPC TelegramService поверх общего канала /// /// Клиент сервиса Telegram (команды ядра наружу). public TelegramService.TelegramServiceClient CreateClient() => new(_channel); /// /// Собирает обязательные metadata вызова /// /// Id тенанта (строка, формат N — как в сессиях telegram-service). /// Metadata для CallOptions вызова. public Metadata CreateMetadata(string tenantId) { var metadata = new Metadata { { TenantIdMetadataKey, tenantId }, { ServiceTokenMetadataKey, _serviceToken }, }; return metadata; } /// public void Dispose() => _channel.Dispose(); }