using Blazored.LocalStorage; using Grpc.Core; using Grpc.Net.Client.Web; using Grpc.Net.Client; using MudBlazor.Services; using System.Text.Json; using System.Text.Json.Serialization; // Updated imports to use CMS protobuf instead of FrontOffice.BFF using CMSMicroservice.Protobuf.Protos.Category; using CMSMicroservice.Protobuf.Protos.City; using CMSMicroservice.Protobuf.Protos.Package; using CMSMicroservice.Protobuf.Protos.Products; using CMSMicroservice.Protobuf.Protos.Transactions; using CMSMicroservice.Protobuf.Protos.User; using CMSMicroservice.Protobuf.Protos.UserCarts; using CMSMicroservice.Protobuf.Protos.UserOrder; using CMSMicroservice.Protobuf.Protos.UserWallet; using CMSMicroservice.Protobuf.Protos.UserWalletHistory; using CMSMicroservice.Protobuf.Protos.UserAddress; using CMSMicroservice.Protobuf.Protos.Configuration; using CMSMicroservice.Protobuf.Protos.NetworkMembership; using CMSMicroservice.Protobuf.Protos.Commission; using CMSMicroservice.Protobuf.Protos.AppVersion; using CMSMicroservice.Protobuf.Protos.SitePage; using CMSMicroservice.Protobuf.Protos.SitePageSettings; using CMSMicroservice.Protobuf.Protos.BlogPost; using CMSMicroservice.Protobuf.Protos.BlogCategory; using CMSMicroservice.Protobuf.Protos.DiscountProduct; using CMSMicroservice.Protobuf.Protos.DiscountCategory; using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; using CMSMicroservice.Protobuf.Protos.DiscountOrder; using FrontOffice.Main.Utilities; namespace Microsoft.Extensions.DependencyInjection; public static class ConfigureServices { public static IServiceCollection AddCommonServices(this IServiceCollection services) { services.AddMudServices(); services.AddBlazoredLocalStorage(config => { config.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; config.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; config.JsonSerializerOptions.IgnoreReadOnlyProperties = true; config.JsonSerializerOptions.PropertyNameCaseInsensitive = true; config.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; config.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip; config.JsonSerializerOptions.WriteIndented = false; }); // Access HttpContext for User-Agent services.AddHttpContextAccessor(); // Register custom services services.AddSingleton(); services.AddScoped(); services.AddScoped(); services.AddScoped(); // Storefront services services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); // Singleton برای cache روزانه // Package service services.AddScoped(); // New services for Club, Network, Commission services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); // App Version Service for cache invalidation services.AddScoped(); // Main Service for core functionality services.AddScoped(); // Site Page service for dynamic content (About, Contact) services.AddScoped(); // Blog Post service for landing page latest posts & blog pages services.AddScoped(); // Blog Category service for blog sidebar filters services.AddScoped(); // Discount Store services services.AddScoped(); services.AddScoped(); services.AddScoped(); // Site Page Settings Service (simplified page management) services.AddScoped(); // Device detection: very light, dependency-free services.AddTransient(); // PDF generation (Chromium only) services.AddSingleton(); // SignalR Token Notification Service services.AddScoped(); return services; } public static IServiceCollection AddGrpcServices(this IServiceCollection services, IConfiguration configuration) { var baseUrl = ResolveGatewayUrl(configuration) ?? throw new InvalidOperationException("Gateway URL is missing. Set GW_URL or GwUrl."); var isHttp = baseUrl.StartsWith("http://", StringComparison.OrdinalIgnoreCase); // When the base URL is plain HTTP (e.g. in-cluster http://cms-svc:8080), we must force HTTP/1.1 // so that GrpcChannel does not upgrade to h2c and trigger HTTP_1_1_REQUIRED from Kestrel. // For HTTPS the default negotiation is fine. services.AddScoped(sp => { HttpMessageHandler inner = isHttp ? new SocketsHttpHandler { MaxConnectionsPerServer = 10, AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate } : new HttpClientHandler { MaxConnectionsPerServer = 10, AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate }; return new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, inner)) { Timeout = TimeSpan.FromMinutes(10), BaseAddress = new Uri(baseUrl), DefaultRequestVersion = isHttp ? System.Net.HttpVersion.Version11 : System.Net.HttpVersion.Version20, DefaultVersionPolicy = isHttp ? System.Net.Http.HttpVersionPolicy.RequestVersionExact : System.Net.Http.HttpVersionPolicy.RequestVersionOrHigher }; }); // Register gRPC clients with authentication - Updated for CMS services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); // Image Resolver Service services.AddScoped(CreateAuthenticatedClient); services.AddScoped(); // Discount Store gRPC clients services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); services.AddScoped(CreateAuthenticatedClient); return services; } private static string? ResolveGatewayUrl(IConfiguration configuration) { var envUrl = configuration["GW_URL"]; if (!string.IsNullOrWhiteSpace(envUrl)) return envUrl.TrimEnd('/'); return configuration["GwUrl"]?.TrimEnd('/'); } private static TClient CreateAuthenticatedClient(IServiceProvider sp) where TClient : class { var httpClient = sp.GetRequiredService(); var localStorage = sp.GetRequiredService(); var baseUrl = httpClient.BaseAddress?.ToString() ?? throw new InvalidOperationException("Base URL not configured"); var isHttps = baseUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase); var credentials = CallCredentials.FromInterceptor(async (context, metadata) => { try { var token = await localStorage.GetItemAsync("auth:token"); if (!string.IsNullOrWhiteSpace(token)) { metadata.Add("Authorization", $"Bearer {token}"); } } catch (Exception ex) { #if DEBUG Console.WriteLine($"Token retrieval error: {ex.Message}"); #endif } }); var channelCredentials = isHttps ? ChannelCredentials.Create(new SslCredentials(), credentials) : ChannelCredentials.Create(ChannelCredentials.Insecure, credentials); var channel = GrpcChannel.ForAddress(baseUrl, new GrpcChannelOptions { UnsafeUseInsecureChannelCallCredentials = !isHttps, Credentials = channelCredentials, HttpClient = httpClient, MaxReceiveMessageSize = 1000 * 1024 * 1024, // 1 GB MaxSendMessageSize = 1000 * 1024 * 1024 // 1 GB }); return (TClient)Activator.CreateInstance(typeof(TClient), channel)!; } }