aba4173827
Share template keys across email and push with independent editors and revisions. Render localized actions and preserve version-one delivery. Ref: IT-1627
55 lines
3.2 KiB
C#
55 lines
3.2 KiB
C#
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;
|
|
using HrynCo.NotificationService.DAL.Abstract.Templates;
|
|
|
|
namespace HrynCo.NotificationService.Worker.Services.Push;
|
|
|
|
public sealed class FcmPushSender(
|
|
INotificationServiceRepository services, IPushChannelRepository channels, IFirebasePushClient client,
|
|
IPushTemplateDefinitionRepository templates) : IPushSender
|
|
{
|
|
public async Task<PushDeliveryOutcome> SendAsync(Guid messageId, SendPushMessageData data, CancellationToken cancellationToken)
|
|
{
|
|
if (!PushMessageValidator.IsSupportedVersion(data.Version))
|
|
return new PushDeliveryOutcome("Unsupported push payload version. Supported versions are 1 and 2.");
|
|
if (!PushMessageValidator.IsValidContent(data)) return new PushDeliveryOutcome("Invalid push content for the requested version.");
|
|
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.");
|
|
RenderedPushMessageData rendered;
|
|
try { rendered = await RenderAsync(service.Id, data, cancellationToken); }
|
|
catch (EmailTemplateWriteException) { return new PushDeliveryOutcome("Push template data or action configuration is invalid."); }
|
|
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, rendered, channel.FirebaseProjectId, credentials, cancellationToken);
|
|
}
|
|
|
|
private async Task<RenderedPushMessageData> RenderAsync(Guid serviceId, SendPushMessageData data, CancellationToken ct)
|
|
{
|
|
if (data.Version == 1) return new RenderedPushMessageData(data, data.Title!, data.Body!, null, []);
|
|
var template = await templates.GetAsync(serviceId, data.TemplateKey!, ct)
|
|
?? throw new EmailTemplateWriteException("Push template is not configured.", false);
|
|
return PushTemplateRenderer.Render(template, data);
|
|
}
|
|
|
|
private async Task<PushDeliveryOutcome> SendWithCredentialsAsync(Guid messageId, RenderedPushMessageData 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.");
|
|
}
|
|
}
|
|
}
|