Frontend: Services¶
Visão Geral¶
Services do frontend em Ciba.Web/Services/. Encapsulam lógica de comunicação e funcionalidades transversais.
| Service | Responsabilidade |
|---|---|
ApiClient |
Comunicação HTTP com backend |
AuthService |
Autenticação e sessão |
SignalRService |
Real-time via WebSocket |
DialogHelper |
Abertura de dialogs |
ConfirmDeleteHelper |
Confirmação de exclusão |
SnackbarExtensions |
Feedback de erros |
StatusFormatService |
Formatação de status |
WhatsAppFormatter |
Formatação WhatsApp → HTML |
AuthorizationMessageHandler |
Injeção de JWT nas requests |
ApiClient¶
Client HTTP para comunicação com a API. Única forma de acessar o backend.
Arquivo: Services/ApiClient.cs
Características¶
- Retorna
Result<T>ouResult(Result pattern do Shared) - Usa rotas de
ApiRoutes(nunca strings literais) - Deserializa erros automaticamente para
Error - Tratamento de exceções para
Errors.Internal
Métodos Auxiliares¶
private async Task<Result<T>> GetAsync<T>(string url);
private async Task<Result<T>> PostAsync<T>(string url, object? body = null);
private async Task<Result> PostAsync(string url, object? body = null);
private async Task<Result<T>> PutAsync<T>(string url, object? body = null);
private async Task<Result> PutAsync(string url, object? body = null);
private async Task<Result> DeleteAsync(string url);
private async Task<Result<T>> DeleteWithResponseAsync<T>(string url);
Endpoints Disponíveis¶
Autenticação:
Agentes:
Task<Result<List<AgentListResponse>>> GetAgentsAsync()
Task<Result<AgentDetailResponse>> GetAgentByIdAsync(Guid id)
Task<Result<CreateAgentResponse>> CreateAgentAsync(CreateAgentRequest request)
Task<Result<UpdateAgentResponse>> UpdateAgentAsync(Guid id, UpdateAgentRequest request)
Task<Result> DeleteAgentAsync(Guid id)
Task<Result<PlaygroundChatResponse>> SendPlaygroundMessageAsync(Guid agentId, List<PlaygroundMessageDto> messages)
Task<Result<AgentAiSettingsResponse>> GetAgentAiSettingsAsync(Guid agentId)
Task<Result<AgentAiSettingsResponse>> UpdateAgentAiSettingsAsync(Guid agentId, UpdateAgentAiSettingsRequest request)
Instâncias:
Task<Result<List<InstanceListResponse>>> GetInstancesAsync()
Task<Result<InstanceDetailResponse>> GetInstanceByIdAsync(Guid id)
Task<Result<CreateInstanceResponse>> CreateInstanceAsync(CreateInstanceRequest request)
Task<Result<UpdateInstanceResponse>> UpdateInstanceAsync(Guid id, UpdateInstanceRequest request)
Task<Result> DeleteInstanceAsync(Guid id)
Task<Result<ConnectResponse>> ConnectInstanceAsync(Guid id)
Task<Result<ConnectionStatusResponse>> GetInstanceStatusAsync(Guid id)
Task<Result> DisconnectInstanceAsync(Guid id)
Conversas:
Task<Result<ConversationListResponse>> GetConversationsByInstanceAsync(Guid instanceId, ...)
Task<Result<ConversationDetailResponse>> GetConversationByIdAsync(Guid conversationId)
Task<Result<MessagePageResponse>> GetConversationMessagesAsync(Guid conversationId, ...)
Task<Result<SendMessageResponse>> SendMessageAsync(Guid conversationId, string content, ...)
Task<Result<ReactToMessageResponse>> ReactToMessageAsync(Guid conversationId, Guid messageId, string emoji)
Task<Result<ResolveConversationResponse>> ResolveConversationAsync(Guid conversationId, string? summary)
Task<Result> MarkConversationAsReadAsync(Guid conversationId)
Task<Result<StartConversationResponse>> StartConversationAsync(Guid instanceId, string contactPhone, ...)
Knowledge Blocks:
Task<Result<List<KnowledgeBlockResponse>>> GetKnowledgeBlocksAsync(Guid agentId)
Task<Result<KnowledgeBlockResponse>> CreateKnowledgeBlockAsync(Guid agentId, CreateKnowledgeBlockRequest)
Task<Result<KnowledgeBlockResponse>> UpdateKnowledgeBlockAsync(Guid agentId, Guid id, UpdateKnowledgeBlockRequest)
Task<Result> DeleteKnowledgeBlockAsync(Guid agentId, Guid id)
Task<Result<List<KnowledgeBlockResponse>>> ReorderKnowledgeBlocksAsync(Guid agentId, List<Guid> orderedIds)
Whitelist/Blacklist:
Task<Result<List<WhitelistEntryResponse>>> GetInstanceWhitelistAsync(Guid instanceId)
Task<Result<WhitelistEntryResponse>> CreateInstanceWhitelistEntryAsync(Guid instanceId, ...)
// ... mesmos métodos para Blacklist
Onboarding:
Task<Result<OnboardingStatusResponse>> GetOnboardingStatusAsync()
Task<Result> CompleteOnboardingAsync()
Task<Result> DismissOnboardingAsync()
Uso¶
public partial class AgentList
{
[Inject] private ApiClient ApiClient { get; set; } = null!;
private async Task Load()
{
var result = await ApiClient.GetAgentsAsync();
if (result.IsSuccess)
{
_agents = result.Value;
}
}
}
AuthService¶
Gerenciamento de autenticação e sessão do usuário.
Arquivo: Services/AuthService.cs
Responsabilidades¶
- Login e logout
- Armazenamento de token JWT no LocalStorage
- Verificação de roles (SuperAdmin, Admin)
- Seleção de tenant para SuperAdmin
Dados Armazenados (LocalStorage)¶
| Chave | Conteudo |
|---|---|
authToken |
JWT token |
userName |
Nome do usuario |
userRole |
Role (SuperAdmin, Admin, User) |
selectedTenantId |
Tenant selecionado (SuperAdmin) |
selectedTenantName |
Nome do tenant selecionado |
onboardingChecked |
Flag de cache do onboarding (limpo no logout) |
welcomeCardDismissed |
Flag de dismiss do welcome card (limpo no logout) |
Métodos¶
// Autenticação
Task<Result> LoginAsync(string email, string password)
Task LogoutAsync()
Task<string?> GetTokenAsync()
Task<string?> GetUserNameAsync()
Task<string?> GetRoleAsync()
// Verificação de roles
Task<bool> IsSuperAdminAsync()
Task<bool> IsAdminAsync()
// Seleção de tenant (SuperAdmin)
Task SetSelectedTenantAsync(Guid tenantId, string tenantName)
Task<Guid?> GetSelectedTenantIdAsync()
Task<string?> GetSelectedTenantNameAsync()
Task<bool> HasTenantSelectedAsync()
// Eventos
event Action? OnTenantChanged
Uso¶
// No Login
var result = await AuthService.LoginAsync(email, password);
if (result.IsSuccess)
{
Navigation.NavigateTo("/");
}
// No Logout
await AuthService.LogoutAsync();
Navigation.NavigateTo("/login");
// Verificação de role
if (await AuthService.IsSuperAdminAsync())
{
// Mostrar menu de tenants
}
AuthorizationMessageHandler¶
DelegatingHandler que injeta JWT e tenant em todas as requisições HTTP.
Arquivo: Services/AuthorizationMessageHandler.cs
Comportamento¶
- Adiciona
Authorization: Bearer {token}em todas as requests - Adiciona
X-Tenant-Idse SuperAdmin tem tenant selecionado - Redireciona para
/loginem caso de401 Unauthorized - Limpa LocalStorage no logout automático
Registro¶
// Program.cs
services.AddTransient<AuthorizationMessageHandler>();
services.AddHttpClient<ApiClient>(client =>
{
client.BaseAddress = new Uri(apiBaseUrl);
})
.AddHttpMessageHandler<AuthorizationMessageHandler>();
SignalRService¶
Conexão WebSocket para atualizações em tempo real.
Arquivo: Services/SignalRService.cs
Eventos¶
event Action<Guid, MessageNotification>? OnNewMessage
event Action<Guid, Guid, string, DateTime?>? OnMessageStatusUpdated
event Action<ConversationNotification>? OnConversationUpdated
event Action<ConversationNotification>? OnConversationCreated
event Action<Guid, string?>? OnProfilePictureUpdated
event Action<Guid, ReactionNotification>? OnMessageReaction
event Action<HubConnectionState>? OnConnectionStateChanged
Métodos¶
Task ConnectAsync()
Task DisconnectAsync()
Task JoinConversationAsync(Guid conversationId)
Task LeaveConversationAsync(Guid conversationId)
Task JoinTenantAsync(Guid tenantId)
Task LeaveTenantAsync(Guid tenantId)
Propriedades¶
Reconexão Automática¶
.WithAutomaticReconnect(new[]
{
TimeSpan.Zero, // Imediato
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(30)
})
DTOs¶
record MessageNotification(
Guid Id, string Role, string Type, string Content,
string? MediaUrl, string? MediaMimeType, string? MediaFileName,
string Status, DateTime CreatedAt);
record ConversationNotification(
Guid Id, Guid InstanceId, string ContactPhone, string? ContactName,
string? ContactProfilePictureUrl, string Status, string Mode,
DateTime LastMessageAt, string? LastMessage, string? LastMessageRole,
string? LastMessageStatus, int MessageCount, int UnreadCount);
record ReactionNotification(
Guid MessageId, string Emoji, string SenderPhone,
bool FromMe, DateTime CreatedAt);
Uso¶
public partial class ConversationChat : IDisposable
{
[Inject] private SignalRService SignalR { get; set; } = null!;
protected override async Task OnInitializedAsync()
{
SignalR.OnNewMessage += HandleNewMessage;
await SignalR.ConnectAsync();
await SignalR.JoinConversationAsync(ConversationId);
}
private void HandleNewMessage(Guid conversationId, MessageNotification message)
{
if (conversationId == ConversationId)
{
_messages.Add(message);
InvokeAsync(StateHasChanged);
}
}
public void Dispose()
{
SignalR.OnNewMessage -= HandleNewMessage;
}
}
SnackbarExtensions¶
Extensions para exibir feedback de erros via Snackbar.
Arquivo: Services/SnackbarExtensions.cs
Métodos¶
// Retorna true se erro foi exibido
bool ShowIfError(this ISnackbar snackbar, Result result)
bool ShowIfError<T>(this ISnackbar snackbar, Result<T> result)
Uso¶
var result = await ApiClient.DeleteAgentAsync(id);
if (Snackbar.ShowIfError(result)) return; // Early return se erro
Snackbar.Add("Agente excluído!", Severity.Success);
await Load();
DialogHelper¶
Padroniza abertura de dialogs de formulário. Sempre usar DialogHelper ao invés de criar DialogOptions manualmente.
Arquivo: Services/DialogHelper.cs
Métodos¶
// Retorna true se dialog foi submetido (não cancelado)
Task<bool> ShowFormAsync<TDialog>(
string? title = null,
DialogParameters<TDialog>? parameters = null,
MaxWidth maxWidth = MaxWidth.Small,
bool closeButton = false)
// Retorna dados do dialog (ou null se cancelado)
Task<TResult?> ShowFormAsync<TDialog, TResult>(
string? title = null,
DialogParameters<TDialog>? parameters = null,
MaxWidth maxWidth = MaxWidth.Small,
bool closeButton = false)
// Retorna DialogResult para controle total
Task<DialogResult?> ShowAsync<TDialog>(
string? title = null,
DialogParameters<TDialog>? parameters = null,
MaxWidth maxWidth = MaxWidth.Small,
bool closeButton = false,
bool allowClose = false) // habilita BackdropClick e EscapeKey
Uso¶
[Inject] private DialogHelper Dialog { get; set; } = null!;
// Simples - apenas verifica se não cancelou
if (await Dialog.ShowFormAsync<AgentCreateDialog>())
await LoadAgents();
// Com parâmetros
var parameters = new DialogParameters<KnowledgeBlockDialog>
{
{ x => x.AgentId, AgentId },
{ x => x.Block, null }
};
if (await Dialog.ShowFormAsync<KnowledgeBlockDialog>("Novo Bloco", parameters))
await LoadBlocks();
// Tamanho diferente
await Dialog.ShowFormAsync<MeuDialog>(maxWidth: MaxWidth.Medium);
// Retorna dados do dialog
var result = await Dialog.ShowFormAsync<StartConversationDialog, StartConversationDialogResult>(
"Nova Conversa", parameters, closeButton: true);
if (result != null)
Navigation.NavigateTo($"/conversations/{result.ConversationId}");
// Controle total (ex: para navegação com ID criado)
var result = await Dialog.ShowAsync<InstanceCreateDialog>();
if (result is { Canceled: false, Data: Guid createdId })
Navigation.NavigateTo($"/instances/{createdId}?connect=true");
Nota: Para
ShowMessageBox(confirmações simples), ainda usarIDialogServicediretamente.
ConfirmDeleteHelper¶
Padroniza confirmação de exclusão com feedback.
Arquivo: Services/ConfirmDeleteHelper.cs
Métodos¶
// Confirma → Executa → Feedback
// Retorna true se excluído com sucesso
Task<bool> ConfirmAndDelete(string itemName, Func<Task<Result>> deleteAction)
Uso¶
var deleted = await ConfirmDeleteHelper.ConfirmAndDelete(
_agent.Name,
() => ApiClient.DeleteAgentAsync(Id));
if (deleted)
{
Navigation.NavigateTo("/agents");
}
StatusFormatService¶
Utilitários estáticos para formatação de status e datas.
Arquivo: Services/StatusFormatService.cs
Métodos¶
// Status de conexão
static Color GetStatusColor(string? status) // connected → Success
static string GetStatusLabel(string? status) // connected → "Conectado"
// Status de conversa
static Color GetConversationStatusColor(string? status)
static string GetConversationStatusLabel(string? status)
// Formatação de data
static string FormatDateTime(DateTime dateTime, string format = "dd/MM/yyyy HH:mm")
static string FormatLocalDateTime(DateTime utcDateTime, string format = "dd/MM/yyyy HH:mm")
// Stage da conversa (aguardando cliente/atendimento)
static string GetConversationStage(string status, string? lastMessageRole)
static Color GetConversationStageColor(string status, string? lastMessageRole)
Mapeamento de Status¶
Conexão:
| Status | Label | Cor |
|--------|-------|-----|
| connected/open | "Conectado" | Success |
| connecting | "Conectando" | Warning |
| disconnected/close | "Desconectado" | Default |
Conversa:
| Status | Label | Cor |
|--------|-------|-----|
| Active | "Ativa" | Success |
| Escalated | "Escalada" | Warning |
| Resolved | "Resolvida" | Default |
Stage (Conversa Ativa):
| LastMessageRole | Stage | Cor |
|-----------------|-------|-----|
| User | "Aguardando Atendimento" | Error |
| Assistant | "Aguardando Cliente" | Success |
| null | "Nova Conversa" | Info |
Uso¶
<MudChip Color="@StatusFormatService.GetStatusColor(_instance.Status)">
@StatusFormatService.GetStatusLabel(_instance.Status)
</MudChip>
WhatsAppFormatter¶
Converte formatação WhatsApp em HTML para exibição no frontend. Classe estática com regex compilados.
Arquivo: Helpers/WhatsAppFormatter.cs
Formatação Suportada¶
| HTML | Exemplo | |
|---|---|---|
*texto* |
<b>texto</b> |
texto |
_texto_ |
<i>texto</i> |
texto |
~texto~ |
<s>texto</s> |
~~texto~~ |
`texto` |
<code>texto</code> |
texto |
\n |
<br> |
Quebra de linha |
Segurança¶
- Aplica
WebUtility.HtmlEncodeantes das substituições regex para prevenir XSS - Retorna
MarkupStringpara renderização segura no Blazor
Uso¶
Registro de Services¶
// Program.cs
services.AddBlazoredLocalStorage();
services.AddScoped<AuthService>();
services.AddScoped<SignalRService>();
services.AddScoped<DialogHelper>();
services.AddScoped<ConfirmDeleteHelper>();
services.AddTransient<AuthorizationMessageHandler>();
services.AddHttpClient<ApiClient>(client =>
{
client.BaseAddress = new Uri(apiBaseUrl);
})
.AddHttpMessageHandler<AuthorizationMessageHandler>();
Padrões de Uso¶
Carregamento de Dados¶
private bool _loading = true;
private Result<List<AgentListResponse>>? _result;
protected override async Task OnInitializedAsync() => await Load();
private async Task Load()
{
_loading = true;
_result = await ApiClient.GetAgentsAsync();
_loading = false;
}
Ação com Feedback¶
private async Task Save()
{
_saving = true;
var result = await ApiClient.CreateAgentAsync(_request);
_saving = false;
if (Snackbar.ShowIfError(result)) return;
Snackbar.Add("Agente criado!", Severity.Success);
Navigation.NavigateTo("/agents");
}