feat: implement end-to-end encryption for chat messages and files, including key management and decryption functionality; enhance chat components to support encrypted content display

This commit is contained in:
2025-12-09 01:31:09 -03:00
parent cae6d886de
commit e6f380d7cc
14 changed files with 1443 additions and 203 deletions

View File

@@ -11,6 +11,7 @@
import SalaReuniaoManager from './SalaReuniaoManager.svelte';
import CallWindow from '../call/CallWindow.svelte';
import ErrorModal from '../ErrorModal.svelte';
import E2EManagementModal from './E2EManagementModal.svelte';
//import { getAvatarUrl } from '$lib/utils/avatarGenerator';
import { browser } from '$app/environment';
import { traduzirErro } from '$lib/utils/erroHelpers';
@@ -19,14 +20,14 @@
X,
ArrowLeft,
LogOut,
MoreVertical,
Users,
Clock,
XCircle,
Phone,
Video,
ChevronDown,
Search
Search,
Lock,
MoreVertical,
XCircle
} from 'lucide-svelte';
//import { Bell, X, ArrowLeft, LogOut, MoreVertical, Users, Clock, XCircle } from 'lucide-svelte';
@@ -45,21 +46,18 @@
let showSalaManager = $state(false);
let showAdminMenu = $state(false);
let showNotificacaoModal = $state(false);
let showE2EModal = $state(false);
let iniciandoChamada = $state(false);
let chamadaAtiva = $state<Id<'chamadas'> | null>(null);
let showSearch = $state(false);
let searchQuery = $state('');
let searchResults = $state<Array<any>>([]);
let searchResults = $state<Array<unknown | undefined>>([]);
let searching = $state(false);
let selectedSearchResult = $state<number>(-1);
// Estados para modal de erro
let showErrorModal = $state(false);
let errorTitle = $state('Erro');
let errorMessage = $state('');
let errorInstructions = $state<string | undefined>(undefined);
let errorDetails = $state<string | undefined>(undefined);
const chamadaAtivaQuery = useQuery(api.chamadas.obterChamadaAtiva, {
conversaId: conversaId as Id<'conversas'>
});
@@ -70,6 +68,11 @@
conversaId: conversaId as Id<'conversas'>
});
// Verificar se a conversa tem criptografia E2E habilitada
const temCriptografiaE2E = useQuery(api.chat.verificarCriptografiaE2E, {
conversaId: conversaId as Id<'conversas'>
});
const conversa = $derived(() => {
console.log('🔍 [ChatWindow] Buscando conversa ID:', conversaId);
console.log('📋 [ChatWindow] Conversas disponíveis:', conversas?.data);
@@ -297,9 +300,29 @@
</div>
<div class="min-w-0 flex-1">
<p class="text-base-content truncate font-semibold">
{getNomeConversa()}
</p>
<!-- Nome da conversa com indicador de criptografia E2E -->
<div class="flex items-center gap-2">
<p class="text-base-content truncate font-semibold">
{getNomeConversa()}
</p>
{#if temCriptografiaE2E?.data}
<button
type="button"
class="shrink-0"
onclick={(e) => {
e.stopPropagation();
showE2EModal = true;
}}
title="Gerenciar criptografia end-to-end (E2E)"
aria-label="Gerenciar criptografia E2E"
>
<Lock
class="text-success hover:text-success/80 h-4 w-4 transition-colors"
strokeWidth={2}
/>
</button>
{/if}
</div>
{#if getStatusMensagem()}
<p class="text-base-content/60 truncate text-xs">
{getStatusMensagem()}
@@ -322,7 +345,7 @@
{conversa()?.participantesInfo?.length || 0}
{conversa()?.participantesInfo?.length === 1 ? 'participante' : 'participantes'}
</p>
{#if conversa()?.participantesInfo && conversa()?.participantesInfo.length > 0}
{#if conversa()?.participantesInfo && conversa()?.participantesInfo?.length > 0}
<div class="flex items-center gap-2">
<div class="flex -space-x-2">
{#each conversa()?.participantesInfo.slice(0, 5) as participante (participante._id)}
@@ -609,6 +632,27 @@
</div>
{/if}
<!-- Botão Gerenciar E2E -->
<button
type="button"
class="group relative flex h-9 w-9 items-center justify-center overflow-hidden rounded-lg transition-all duration-300"
style="background: rgba(34, 197, 94, 0.1); border: 1px solid rgba(34, 197, 94, 0.2);"
onclick={(e) => {
e.stopPropagation();
showE2EModal = true;
}}
aria-label="Gerenciar criptografia E2E"
title="Gerenciar criptografia end-to-end"
>
<div
class="absolute inset-0 bg-green-500/0 transition-colors duration-300 group-hover:bg-green-500/10"
></div>
<Lock
class="relative z-10 h-5 w-5 text-green-500 transition-transform group-hover:scale-110"
strokeWidth={2}
/>
</button>
<!-- Botão Agendar MODERNO -->
<button
type="button"
@@ -674,7 +718,8 @@
</div>
{:else if searchResults.length > 0}
<p id="search-results-info" class="sr-only">
{searchResults.length} resultado{searchResults.length !== 1 ? 's' : ''} encontrado{searchResults.length !== 1
{searchResults.length} resultado{searchResults.length !== 1 ? 's' : ''} encontrado{searchResults.length !==
1
? 's'
: ''}
</p>
@@ -698,7 +743,9 @@
aria-selected={index === selectedSearchResult}
aria-label="Mensagem de {resultado.remetente?.nome || 'Usuário'}"
>
<div class="bg-primary/20 flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full">
<div
class="bg-primary/20 flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full"
>
{#if resultado.remetente?.fotoPerfilUrl}
<img
src={resultado.remetente.fotoPerfilUrl}
@@ -750,6 +797,14 @@
conversaId={conversaId as Id<'conversas'>}
onClose={() => (showScheduleModal = false)}
/>
<!-- Modal de Gerenciamento E2E -->
{#if showE2EModal}
<E2EManagementModal
conversaId={conversaId as Id<'conversas'>}
onClose={() => (showE2EModal = false)}
/>
{/if}
{/if}
<!-- Modal de Gerenciamento de Sala -->