using System.Security.Cryptography; using System.Text.Json; using HrynCo.NotificationService.Contracts.Messages; using HrynCo.NotificationService.DAL.Abstract.Push; using HrynCo.NotificationService.DAL.Abstract.PushChannels; using HrynCo.NotificationService.DAL.Abstract.Repositories; namespace HrynCo.NotificationService.Worker.Services.Push; public sealed class FcmPushSender( INotificationServiceRepository services, IPushChannelRepository channels, IFirebasePushClient client) : IPushSender { public async Task SendAsync(Guid messageId, SendPushMessageData data, CancellationToken cancellationToken) { if (data.ExpiresAt <= DateTimeOffset.UtcNow) return new PushDeliveryOutcome("Push expired."); var service = await services.GetByNameAsync(data.ServiceName, cancellationToken); if (service is null) return new PushDeliveryOutcome("Push service is not configured."); var channel = await channels.GetActiveForServiceAsync(service.Id, cancellationToken); if (channel is null) return new PushDeliveryOutcome("No active push channel is configured for this service."); var credentials = await channels.GetCredentialsAsync(channel.Id, cancellationToken); return await SendWithCredentialsAsync(messageId, data, channel.FirebaseProjectId, credentials, cancellationToken); } private async Task SendWithCredentialsAsync(Guid messageId, SendPushMessageData data, string projectId, string? json, CancellationToken ct) { try { var credentials = FirebaseServiceAccountCredentials.Parse(json ?? string.Empty, projectId); return await client.SendAsync(messageId, data, credentials, ct); } catch (Exception error) when (error is JsonException or FormatException or ArgumentException or CryptographicException) { // Never propagate credential parser details into broker retry logs or replies. return new PushDeliveryOutcome("The push channel Firebase configuration is invalid."); } } }