Descrição |
|||||||
---|---|---|---|---|---|---|---|
Data de elaboração | 02/09/2024 |
||||||
Responsável pelo estudo | José Lucas da Silva Costa |
||||||
Equipe do estudo |
José Lucas da Silva Costa (Analista de Desenvolvimento Full-Stack) Emanuel Rufino Alcântara de Lima (Analista de Tecnologia da Informação e Comunicação) |
||||||
Alvo | Portal do Cidadão |
||||||
Origem | Abordagem do mecanismo de preferências de notificar o cidadão por e-mail ou push. |
||||||
Objetivo | Verificar a viabilidade de perguntar novamente ao usuário suas preferências de navegação. | ||||||
Documentação correlata | -/- |
||||||
Observações | -/- |
Push: Mensagens enviadas por um servidor diretamente para a interface do usuário em um navegador ou aplicativo.
No contexto atual, o sistema do Portal do Cidadão permite que os usuários escolham receber notificações push através do navegador Chrome. Essas preferências são armazenadas no banco de dados. Contudo, quando o usuário revoga a permissão de notificação no navegador, o sistema não reflete essa alteração, resultando em uma discrepância onde o banco de dados registra as notificações como ativas, mas o navegador bloqueia a exibição dessas notificações.
O principal o objetivo é solucionar o problema de falta de sincronização entre as preferências de notificação armazenadas no sistema e aquelas configuradas no navegador Chrome. Isso pode gerar inconsistências, onde o sistema tenta enviar notificações que o navegador bloqueia, causando uma má experiência para o usuário e possíveis falhas na comunicação esperada.
As soluções propostas para abordar a discrepância entre as preferências de notificação salvas no banco de dados e as configuradas no navegador Chrome foram desenvolvidas a partir de uma análise detalhada do problema. O objetivo é garantir que as notificações push estejam sempre sincronizadas com as permissões ativas no navegador do usuário, evitando falhas na comunicação e melhorando a experiência geral. As soluções consideram tanto a verificação automática das permissões quanto a interação com o usuário para garantir que suas preferências estejam sempre atualizadas e refletidas corretamente no sistema. A seguir, observamos uma imagem do cenário atual e detalhamos cada uma dessas abordagens, incluindo suas vantagens, desvantagens, e possíveis impactos na aplicação.
Fonte: SETIC-RO
Uma abordagem possível é implementar uma verificação periódica das permissões de notificação diretamente no navegador quando o usuário acessa a aplicação. Essa verificação pode ser feita utilizando a API de notificações do navegador, que permite consultar se as permissões de notificação estão ativas ou bloqueadas. Com base no resultado, o sistema poderia atualizar o banco de dados para refletir o estado atual das permissões no navegador.
Aplicação em ASP.NET Core:
Front-End: No lado do cliente, usando JavaScript, você pode utilizar a API Notification.permission
para verificar se a permissão para notificações está ativa, bloqueada ou se precisa ser solicitada novamente. Essa verificação pode ser executada quando o usuário acessa a aplicação ou em intervalos regulares, por exemplo, usando o setInterval
.
Back-End: No ASP.NET Core, você pode criar uma API que receba os resultados dessa verificação. Essa API pode ser implementada como um método POST
no controlador, que atualiza as preferências do usuário no banco de dados. Usando o Entity Framework Core, você pode fazer algo como:
[HttpPost]
public async Task AtualizarPreferencias([FromBody] PreferenciasNotificacaoDto dto)
{
var usuario = await _context.Usuarios.FindAsync(dto.UsuarioId);
if (usuario != null)
{
usuario.NotificacoesAtivas = dto.NotificacoesAtivas;
await _context.SaveChangesAsync();
}
return Ok();
}
Tecnologias Envolvidas:
- JavaScript para a verificação de permissões.
- ASP.NET Core para a implementação da API.
- Entity Framework Core para a manipulação de dados no banco de dados.
Outra solução seria escutar eventos de alteração das permissões de notificação. Caso o usuário revogue a permissão, o sistema seria notificado imediatamente, possibilitando a atualização do banco de dados em tempo real. Esta abordagem requer a utilização de um serviço em segundo plano que monitore as alterações de configuração no navegador.
Aplicação em ASP.NET Core:
Service Worker: No navegador, você pode utilizar um Service Worker para monitorar alterações nas permissões. O Service Worker pode interceptar eventos e enviar uma requisição para o servidor sempre que detectar que as permissões foram revogadas. Esse tipo de funcionalidade pode ser implementado usando a API PushManager
em combinação com o Service Worker.
Back-End: No ASP.NET Core, você pode criar um WebSocket ou utilizar o SignalR para manter uma conexão em tempo real com o cliente. Quando o cliente detecta que a permissão foi revogada, ele envia uma mensagem ao servidor via WebSocket, e o servidor, por sua vez, atualiza o banco de dados.
public class NotificacaoHub : Hub
{
public async Task AtualizarPreferencias(string usuarioId, bool notificacoesAtivas)
{
var usuario = await _context.Usuarios.FindAsync(usuarioId);
if (usuario != null)
{
usuario.NotificacoesAtivas = notificacoesAtivas;
await _context.SaveChangesAsync();
}
}
}
Tecnologias Envolvidas:
- Service Worker para monitoramento de eventos no lado do cliente.
- ASP.NET Core SignalR para comunicação em tempo real.
- Entity Framework Core para a manipulação de dados no banco de dados.
Uma estratégia adicional poderia ser solicitar ao usuário que confirme suas preferências de notificação periodicamente, especialmente ao detectar inconsistências entre o banco de dados e o navegador. Ao reabrir a aplicação, o sistema poderia solicitar que o usuário reconfirme suas preferências, garantindo que as configurações estejam alinhadas.
Aplicação em ASP.NET Core:
Front-End: Ao carregar a aplicação, você pode verificar as permissões do usuário utilizando Notification.permission
e, se necessário, solicitar a permissão novamente com Notification.requestPermission()
. A resposta do usuário é então enviada ao servidor para atualizar o banco de dados.
Back-End: No ASP.NET Core, ao receber a confirmação das permissões, a aplicação pode atualizar o banco de dados conforme necessário. Isso pode ser feito de maneira semelhante ao exemplo anterior, mas com uma lógica adicional para tratar quando as permissões foram rejeitadas.
O código do lado do cliente poderia ser algo assim:
if (Notification.permission !== 'granted') {
Notification.requestPermission().then(function(permission) {
fetch('/api/notificacoes/atualizar-preferencias', {
method: 'POST',
body: JSON.stringify({ notificacoesAtivas: permission === 'granted' }),
headers: {
'Content-Type': 'application/json'
}
});
});
}
E no ASP.NET Core, o controlador pode tratar essa atualização:
[HttpPost]
public async Task AtualizarPreferencias([FromBody] PreferenciasNotificacaoDto dto)
{
var usuario = await _context.Usuarios.FindAsync(dto.UsuarioId);
if (usuario != null)
{
usuario.NotificacoesAtivas = dto.NotificacoesAtivas;
await _context.SaveChangesAsync();
}
return Ok();
}
Tecnologias Envolvidas:
- JavaScript para solicitar e capturar a permissão.
- ASP.NET Core para receber e processar as atualizações de preferências.
- Entity Framework Core para a manipulação de dados no banco de dados.
Quando o usuário clica para receber uma notificação por e-mail, o sistema Hermes envia junto com a notificação do Chrome (Quando ativa) um e-mail, quando observamos o código é possível verificar que não há interferência nos tipos de notificação, vejamos:
using Microsoft.Extensions.Options;
using Api.Repositories.Interfaces;
using Api.Services.Interfaces;
using Api.Data.Constants;
using Api.Models.Dtos;
using Api.Models.Enum;
using Api.Factories;
using Api.Models;
namespace Api.Services
{
public class EnviarNotificacaoService: IEnviarNotificacaoService
{
private readonly INotificacaoRepository _notificacaoRepository;
private readonly IPentagonoPfRepository _pentagonoPfRepository;
private readonly IHermesRepository _hermesRepository;
private readonly IPushRepository _pushRepository;
private readonly MaestroKeyDto _maestroKeyDto;
public EnviarNotificacaoService(
INotificacaoRepository notificacaoRepository,
IPentagonoPfRepository pentagonoPfRepository,
IHermesRepository hermesRepository,
IPushRepository pushRepository,
IOptions options)
{
_notificacaoRepository = notificacaoRepository;
_pentagonoPfRepository = pentagonoPfRepository;
_hermesRepository = hermesRepository;
_pushRepository = pushRepository;
_maestroKeyDto = options.Value;
}
public async Task EnviarTodasAsNotificacoesAtivas()
{
var listaNotificacoes = await _notificacaoRepository.BuscarTodasAtivas();
await EnviarNotificacoes(listaNotificacoes);
}
public async Task EnviarNotificacaoAtiva(Notificacao notificacao)
{
if (notificacao.Ativo && notificacao.PodeExecutar())
{
var notificacaoBanco = await _notificacaoRepository.BuscarComDetalhes(notificacao.Id, notificacao.ObterCpf());
var lista = new List() { notificacaoBanco! };
await EnviarNotificacoes(lista);
}
}
private async Task EnviarNotificacoes(IList notificacaos)
{
foreach (var notificacao in notificacaos.Where(x => x.PodeExecutar()))
{
await NotificarPorPush(notificacao);
await NotificarPorEmail(notificacao);
if (!notificacao.TeveAlgumErroAoNotificar())
await Concluir(notificacao);
}
}
private async Task Concluir(Notificacao notificacao)
{
notificacao.Inativar();
await _notificacaoRepository.Atualizar(notificacao);
}
private async Task NotificarPorPush(Notificacao notificacao)
{
var preferencia = notificacao.ObterPreferencia(TipoDeNotificacao.PushApi);
if (preferencia is null)
return;
var dispositivos = await BuscarTodosOsDispositivosDaNotificacao(notificacao, preferencia);
if (dispositivos is null)
return;
await SalvarStatusDoEnvio(enviado: false, notificacao, preferencia, "Gerando link para o push.");
var notificarDispositivoPushDto = NotificacaoFactory.ConverterNotificarParaDispositivoPushDto(notificacao, dispositivos, delay: 200, _maestroKeyDto.UrlOrigin!);
var task = _pushRepository.NotificarOsDispositivos(notificarDispositivoPushDto);
await EnviarEAdcionarStatusDoEnvio(task, notificacao, preferencia);
}
private async Task?> BuscarTodosOsDispositivosDaNotificacao(Notificacao notificacao, Preferencia preferencia)
{
List? dispositivos;
try
{
var usuarioPush = await _pushRepository.BuscarUsuarioEDispositivo(notificacao.ObterCpf());
dispositivos = usuarioPush?.DeviceSubscriptions?.Select(p => p.Id).ToList();
if (dispositivos == null || !dispositivos.Any())
throw new ArgumentException("Erro ao buscar dispositivos no pushApi.");
return dispositivos;
}
catch (Exception ex)
{
dispositivos = null;
await SalvarStatusDoEnvio(enviado: false, notificacao, preferencia, ex.Message);
return dispositivos;
}
}
private async Task NotificarPorEmail(Notificacao notificacao)
{
var preferencia = notificacao.ObterPreferencia(TipoDeNotificacao.HermesApi);
if (preferencia is null)
return;
var email = await BuscarEmailDoUsuarioDaNotificacao(notificacao, preferencia);
if (email is null)
return;
var nome = await BuscarNomeDoUsuarioDaNotificacao(notificacao, preferencia);
if (nome is null)
return;
await SalvarStatusDoEnvio(enviado: false, notificacao, preferencia, "Gerando link para o email.");
var hermesDto = await HermesFactory.ConverterNotificacaoParaHermesDto(notificacao, email, nome, _maestroKeyDto.UrlOrigin!);
var task = _hermesRepository.Enviar(hermesDto);
await EnviarEAdcionarStatusDoEnvio(task, notificacao, preferencia);
}
private async Task BuscarEmailDoUsuarioDaNotificacao(Notificacao notificacao, Preferencia preferencia)
{
string? email;
try
{
var pessoaFisica = await _pentagonoPfRepository.BuscarComDetalhes(notificacao.ObterCpf());
email = pessoaFisica?.Pessoa?.Contatos?.OrderBy(p => p.Id).LastOrDefault(p => p.TipoContatoId == PentagonoConstants.Contato.Email)?.Valor;
if (string.IsNullOrEmpty(email))
throw new ArgumentException("Erro ao buscar email no pentagono.");
return email;
}
catch (Exception ex)
{
email = null;
await SalvarStatusDoEnvio(enviado: false, notificacao, preferencia, ex.Message);
return email;
}
}
private async Task BuscarNomeDoUsuarioDaNotificacao(Notificacao notificacao, Preferencia preferencia)
{
string? nome;
try
{
var pessoaFisica = await _pentagonoPfRepository.BuscarComDetalhes(notificacao.ObterCpf());
nome = pessoaFisica?.Pessoa?.Nome;
if (string.IsNullOrEmpty(nome))
throw new ArgumentException("Erro ao buscar nome no pentagono.");
return nome;
}
catch (Exception ex)
{
nome = null;
await SalvarStatusDoEnvio(enviado: false, notificacao, preferencia, ex.Message);
return nome;
}
}
private async Task EnviarEAdcionarStatusDoEnvio(Task task, Notificacao notificacao, Preferencia preferencia)
{
bool enviado = false;
string mensagem = string.Empty;
try
{
await task;
enviado = true;
mensagem = "Sucesso ao notificar usuário.";
}
catch
{
enviado = false;
mensagem = "Erro ao notificar usuário.";
}
finally
{
await SalvarStatusDoEnvio(enviado, notificacao, preferencia, mensagem);
}
}
private async Task SalvarStatusDoEnvio(bool enviado, Notificacao notificacao, Preferencia preferencia, string mensagem)
{
notificacao.SalvarStatusDoEnvio(enviado, preferencia.TipoDeNotificacao, mensagem);
await _notificacaoRepository.Atualizar(notificacao);
}
}
}
Por fim, no contexto atual, a notificação por push não se limita apenas às notificações via navegador (como o Chrome), mas também o Maestro API é utilizado para orquestrar o envio de notificações push via e-mail. Quando tratamos do envio de e-mails automáticos como uma forma de notificação push.
Ao implementar as soluções acima, alguns problemas podem surgir, como:
A implementação dessas soluções contribuiria para uma melhor sincronização entre as preferências do usuário e as configurações do sistema, evitando falhas de comunicação e melhorando a experiência do usuário. Além disso, garantiria que as notificações fossem entregues de forma eficiente, alinhando-se com as preferências atuais do usuário.
Com base no estudo realizado, recomenda-se a implementação de uma ou mais das soluções propostas para resolver a discrepância entre as preferências de notificação salvas no banco de dados e as configuradas no navegador Chrome. A combinação de verificações periódicas com monitoramento de eventos de revogação e solicitações de confirmação de preferências pode proporcionar um equilíbrio entre precisão e usabilidade. Contudo, é importante considerar os potenciais impactos no desempenho e na experiência do usuário ao decidir a frequência e o método de sincronização a ser adotado.