feat: implement audio/video call functionality in chat
- Added a new schema for managing audio/video calls, including fields for call type, room name, and participant management. - Enhanced ChatWindow component to support initiating audio and video calls with dynamic loading of the CallWindow component. - Updated package dependencies to include 'lib-jitsi-meet' for call handling. - Refactored existing code to accommodate new call features and improve user experience.
This commit is contained in:
701
PLANO_IMPLEMENTACAO_JITSI.md
Normal file
701
PLANO_IMPLEMENTACAO_JITSI.md
Normal file
@@ -0,0 +1,701 @@
|
||||
# Plano de Implementação - Chamadas de Áudio e Vídeo com Jitsi Meet
|
||||
|
||||
## Opção Escolhida: Docker Local (Desenvolvimento)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Etapas Fora do Código - Configuração Docker
|
||||
|
||||
### Etapa 1: Preparar Ambiente Docker
|
||||
|
||||
**Requisitos:**
|
||||
|
||||
- Docker Desktop instalado e rodando
|
||||
- Mínimo 4GB RAM disponível
|
||||
- Portas livres: 8000, 8443, 10000-20000/udp
|
||||
|
||||
**Passos:**
|
||||
|
||||
1. **Criar diretório para configuração Docker Jitsi:**
|
||||
|
||||
```bash
|
||||
mkdir -p ~/jitsi-docker
|
||||
cd ~/jitsi-docker
|
||||
```
|
||||
|
||||
2. **Clonar repositório oficial:**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/jitsi/docker-jitsi-meet.git
|
||||
cd docker-jitsi-meet
|
||||
```
|
||||
|
||||
3. **Configurar variáveis de ambiente:**
|
||||
|
||||
```bash
|
||||
cp env.example .env
|
||||
```
|
||||
|
||||
4. **Editar arquivo `.env` com as seguintes configurações:**
|
||||
|
||||
```env
|
||||
# Configuração básica para desenvolvimento local
|
||||
CONFIG=~/.jitsi-meet-cfg
|
||||
TZ=America/Recife
|
||||
|
||||
# Desabilitar Let's Encrypt (não necessário para localhost)
|
||||
ENABLE_LETSENCRYPT=0
|
||||
|
||||
# Portas HTTP/HTTPS
|
||||
HTTP_PORT=8000
|
||||
HTTPS_PORT=8443
|
||||
|
||||
# Domínio local
|
||||
PUBLIC_URL=http://localhost:8000
|
||||
DOMAIN=localhost
|
||||
|
||||
# Desabilitar autenticação para facilitar testes
|
||||
ENABLE_AUTH=0
|
||||
ENABLE_GUESTS=1
|
||||
|
||||
# Desabilitar transcrissão (não necessário para desenvolvimento)
|
||||
ENABLE_TRANSCRIPTION=0
|
||||
|
||||
# Desabilitar gravação no servidor (usaremos gravação local)
|
||||
ENABLE_RECORDING=0
|
||||
|
||||
# Configurações de vídeo (ajustar conforme necessidade)
|
||||
ENABLE_PREJOIN_PAGE=0
|
||||
START_AUDIO_MUTED=0
|
||||
START_VIDEO_MUTED=0
|
||||
|
||||
# Configurações de segurança
|
||||
ENABLE_XMPP_WEBSOCKET=0
|
||||
ENABLE_P2P=1
|
||||
|
||||
# Limites
|
||||
MAX_NUMBER_OF_PARTICIPANTS=10
|
||||
RESOLUTION_WIDTH=1280
|
||||
RESOLUTION_HEIGHT=720
|
||||
```
|
||||
|
||||
5. **Criar diretórios necessários:**
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.jitsi-meet-cfg/{web/letsencrypt,transcripts,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb}
|
||||
```
|
||||
|
||||
6. **Iniciar containers:**
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
7. **Verificar status:**
|
||||
|
||||
```bash
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
8. **Ver logs se necessário:**
|
||||
|
||||
```bash
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
9. **Testar acesso:**
|
||||
|
||||
- Acessar: http://localhost:8000
|
||||
- Criar uma sala de teste e verificar se funciona
|
||||
|
||||
**Troubleshooting:**
|
||||
|
||||
- Se houver erro de permissão nos diretórios: `sudo chown -R $USER:$USER ~/.jitsi-meet-cfg`
|
||||
- Se portas estiverem em uso, alterar HTTP_PORT e HTTPS_PORT no .env
|
||||
- Para parar: `docker-compose down`
|
||||
- Para reiniciar: `docker-compose restart`
|
||||
|
||||
---
|
||||
|
||||
## 📦 Etapas no Código - Backend Convex
|
||||
|
||||
### Etapa 2: Atualizar Schema
|
||||
|
||||
**Arquivo:** `packages/backend/convex/schema.ts`
|
||||
|
||||
**Adicionar nova tabela `chamadas`:**
|
||||
|
||||
```typescript
|
||||
chamadas: defineTable({
|
||||
conversaId: v.id('conversas'),
|
||||
tipo: v.union(v.literal('audio'), v.literal('video')),
|
||||
roomName: v.string(), // Nome único da sala Jitsi
|
||||
criadoPor: v.id('usuarios'), // Anfitrião/criador
|
||||
participantes: v.array(v.id('usuarios')),
|
||||
status: v.union(
|
||||
v.literal('aguardando'),
|
||||
v.literal('em_andamento'),
|
||||
v.literal('finalizada'),
|
||||
v.literal('cancelada')
|
||||
),
|
||||
iniciadaEm: v.optional(v.number()),
|
||||
finalizadaEm: v.optional(v.number()),
|
||||
duracaoSegundos: v.optional(v.number()),
|
||||
gravando: v.boolean(),
|
||||
gravacaoIniciadaPor: v.optional(v.id('usuarios')),
|
||||
gravacaoIniciadaEm: v.optional(v.number()),
|
||||
gravacaoFinalizadaEm: v.optional(v.number()),
|
||||
configuracoes: v.optional(
|
||||
v.object({
|
||||
audioHabilitado: v.boolean(),
|
||||
videoHabilitado: v.boolean(),
|
||||
participantesConfig: v.optional(
|
||||
v.array(
|
||||
v.object({
|
||||
usuarioId: v.id('usuarios'),
|
||||
audioHabilitado: v.boolean(),
|
||||
videoHabilitado: v.boolean(),
|
||||
forcadoPeloAnfitriao: v.optional(v.boolean()) // Se foi forçado pelo anfitrião
|
||||
})
|
||||
)
|
||||
)
|
||||
})
|
||||
),
|
||||
criadoEm: v.number()
|
||||
})
|
||||
.index('by_conversa', ['conversaId', 'status'])
|
||||
.index('by_conversa_ativa', ['conversaId', 'status'])
|
||||
.index('by_criado_por', ['criadoPor'])
|
||||
.index('by_status', ['status'])
|
||||
.index('by_room_name', ['roomName']);
|
||||
```
|
||||
|
||||
### Etapa 3: Criar Backend de Chamadas
|
||||
|
||||
**Arquivo:** `packages/backend/convex/chamadas.ts`
|
||||
|
||||
**Funções a implementar:**
|
||||
|
||||
#### Mutations:
|
||||
|
||||
1. `criarChamada` - Criar nova chamada
|
||||
2. `iniciarChamada` - Marcar como em andamento
|
||||
3. `finalizarChamada` - Finalizar e calcular duração
|
||||
4. `adicionarParticipante` - Adicionar participante
|
||||
5. `removerParticipante` - Remover participante
|
||||
6. `toggleAudioVideo` - Anfitrião controla áudio/vídeo de participante
|
||||
7. `atualizarConfiguracaoParticipante` - Atualizar configuração individual
|
||||
8. `iniciarGravacao` - Marcar início de gravação
|
||||
9. `finalizarGravacao` - Marcar fim de gravação
|
||||
|
||||
#### Queries:
|
||||
|
||||
1. `obterChamadaAtiva` - Buscar chamada ativa de uma conversa
|
||||
2. `listarChamadas` - Listar histórico
|
||||
3. `verificarAnfitriao` - Verificar se usuário é anfitrião
|
||||
4. `obterParticipantesChamada` - Listar participantes
|
||||
|
||||
**Tipos TypeScript (sem usar `any`):**
|
||||
|
||||
```typescript
|
||||
import type { Id } from './_generated/dataModel';
|
||||
import type { QueryCtx, MutationCtx } from './_generated/server';
|
||||
|
||||
type ChamadaTipo = 'audio' | 'video';
|
||||
type ChamadaStatus = 'aguardando' | 'em_andamento' | 'finalizada' | 'cancelada';
|
||||
|
||||
interface ParticipanteConfig {
|
||||
usuarioId: Id<'usuarios'>;
|
||||
audioHabilitado: boolean;
|
||||
videoHabilitado: boolean;
|
||||
forcadoPeloAnfitriao?: boolean;
|
||||
}
|
||||
|
||||
interface ConfiguracoesChamada {
|
||||
audioHabilitado: boolean;
|
||||
videoHabilitado: boolean;
|
||||
participantesConfig?: ParticipanteConfig[];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Etapas no Código - Frontend Svelte
|
||||
|
||||
### Etapa 4: Instalar Dependências
|
||||
|
||||
**Arquivo:** `apps/web/package.json`
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
bun add lib-jitsi-meet
|
||||
```
|
||||
|
||||
**Dependências adicionais necessárias:**
|
||||
|
||||
- `lib-jitsi-meet` - Biblioteca oficial Jitsi
|
||||
- (Possivelmente tipos) `@types/lib-jitsi-meet` se disponível
|
||||
|
||||
### Etapa 5: Configurar Variáveis de Ambiente
|
||||
|
||||
**Arquivo:** `apps/web/.env`
|
||||
|
||||
```env
|
||||
# Jitsi Meet Configuration (Docker Local)
|
||||
VITE_JITSI_DOMAIN=localhost:8443
|
||||
VITE_JITSI_APP_ID=sgse-app
|
||||
VITE_JITSI_ROOM_PREFIX=sgse
|
||||
VITE_JITSI_USE_HTTPS=false
|
||||
```
|
||||
|
||||
### Etapa 6: Criar Utilitários Jitsi
|
||||
|
||||
**Arquivo:** `apps/web/src/lib/utils/jitsi.ts`
|
||||
|
||||
**Funções:**
|
||||
|
||||
- `gerarRoomName(conversaId: string, tipo: "audio" | "video"): string` - Gerar nome único da sala
|
||||
- `obterConfiguracaoJitsi()` - Retornar configuração do Jitsi baseada em .env
|
||||
- `validarDispositivos()` - Validar disponibilidade de microfone/webcam
|
||||
- `obterDispositivosDisponiveis()` - Listar dispositivos de mídia
|
||||
|
||||
**Tipos (sem `any`):**
|
||||
|
||||
```typescript
|
||||
interface ConfiguracaoJitsi {
|
||||
domain: string;
|
||||
appId: string;
|
||||
roomPrefix: string;
|
||||
useHttps: boolean;
|
||||
}
|
||||
|
||||
interface DispositivoMedia {
|
||||
deviceId: string;
|
||||
label: string;
|
||||
kind: 'audioinput' | 'audiooutput' | 'videoinput';
|
||||
}
|
||||
|
||||
interface DispositivosDisponiveis {
|
||||
microphones: DispositivoMedia[];
|
||||
speakers: DispositivoMedia[];
|
||||
cameras: DispositivoMedia[];
|
||||
}
|
||||
```
|
||||
|
||||
### Etapa 7: Criar Store de Chamadas
|
||||
|
||||
**Arquivo:** `apps/web/src/lib/stores/callStore.ts`
|
||||
|
||||
**Estado gerenciado:**
|
||||
|
||||
- Chamada ativa (se houver)
|
||||
- Estado de mídia (áudio/vídeo ligado/desligado)
|
||||
- Dispositivos selecionados
|
||||
- Status de gravação
|
||||
- Lista de participantes
|
||||
- Duração da chamada
|
||||
- É anfitrião ou não
|
||||
|
||||
**Tipos:**
|
||||
|
||||
```typescript
|
||||
interface EstadoChamada {
|
||||
chamadaId: Id<'chamadas'> | null;
|
||||
conversaId: Id<'conversas'> | null;
|
||||
tipo: 'audio' | 'video' | null;
|
||||
roomName: string | null;
|
||||
estaConectado: boolean;
|
||||
audioHabilitado: boolean;
|
||||
videoHabilitado: boolean;
|
||||
gravando: boolean;
|
||||
ehAnfitriao: boolean;
|
||||
participantes: Array<{
|
||||
usuarioId: Id<'usuarios'>;
|
||||
nome: string;
|
||||
avatar?: string;
|
||||
audioHabilitado: boolean;
|
||||
videoHabilitado: boolean;
|
||||
}>;
|
||||
duracaoSegundos: number;
|
||||
dispositivos: {
|
||||
microphoneId: string | null;
|
||||
cameraId: string | null;
|
||||
speakerId: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
interface EventosChamada {
|
||||
'participant-joined': (participant: ParticipanteJitsi) => void;
|
||||
'participant-left': (participantId: string) => void;
|
||||
'audio-mute-status-changed': (isMuted: boolean) => void;
|
||||
'video-mute-status-changed': (isMuted: boolean) => void;
|
||||
'connection-failed': (error: Error) => void;
|
||||
'connection-disconnected': () => void;
|
||||
}
|
||||
```
|
||||
|
||||
**Métodos principais:**
|
||||
|
||||
- `iniciarChamada(conversaId, tipo)`
|
||||
- `finalizarChamada()`
|
||||
- `toggleAudio()`
|
||||
- `toggleVideo()`
|
||||
- `iniciarGravacao()`
|
||||
- `finalizarGravacao()`
|
||||
- `atualizarDispositivos()`
|
||||
|
||||
### Etapa 8: Criar Utilitários de Gravação
|
||||
|
||||
**Arquivo:** `apps/web/src/lib/utils/mediaRecorder.ts`
|
||||
|
||||
**Funções:**
|
||||
|
||||
- `iniciarGravacaoAudio(stream: MediaStream): MediaRecorder` - Gravar apenas áudio
|
||||
- `iniciarGravacaoVideo(stream: MediaStream): MediaRecorder` - Gravar áudio + vídeo
|
||||
- `pararGravacao(recorder: MediaRecorder): Promise<Blob>` - Parar e retornar blob
|
||||
- `salvarGravacao(blob: Blob, nomeArquivo: string): void` - Salvar localmente
|
||||
- `obterDuracaoGravacao(recorder: MediaRecorder): number` - Obter duração
|
||||
|
||||
**Tipos:**
|
||||
|
||||
```typescript
|
||||
interface OpcoesGravacao {
|
||||
audioBitsPerSecond?: number;
|
||||
videoBitsPerSecond?: number;
|
||||
mimeType?: string;
|
||||
}
|
||||
|
||||
interface ResultadoGravacao {
|
||||
blob: Blob;
|
||||
duracaoSegundos: number;
|
||||
nomeArquivo: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Etapa 9: Criar Componente CallWindow
|
||||
|
||||
**Arquivo:** `apps/web/src/lib/components/call/CallWindow.svelte`
|
||||
|
||||
**Características:**
|
||||
|
||||
- Janela flutuante redimensionável e arrastável
|
||||
- Integração com lib-jitsi-meet
|
||||
- Container para vídeo dos participantes
|
||||
- Barra de controles
|
||||
- Indicador de gravação
|
||||
- Contador de duração
|
||||
|
||||
**Props (TypeScript estrito):**
|
||||
|
||||
```typescript
|
||||
interface Props {
|
||||
chamadaId: Id<'chamadas'>;
|
||||
conversaId: Id<'conversas'>;
|
||||
tipo: 'audio' | 'video';
|
||||
roomName: string;
|
||||
ehAnfitriao: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
**Estrutura:**
|
||||
|
||||
- `<script lang="ts">` com tipos explícitos
|
||||
- Uso de `$state`, `$derived`, `$effect` (Svelte 5)
|
||||
- Integração com `callStore`
|
||||
- Eventos do Jitsi tratados tipados
|
||||
|
||||
**Bibliotecas para janela flutuante:**
|
||||
|
||||
- Usar eventos nativos de mouse/touch para drag
|
||||
- CSS para redimensionamento com handles
|
||||
- localStorage para persistir posição/tamanho
|
||||
|
||||
### Etapa 10: Criar Componente CallControls
|
||||
|
||||
**Arquivo:** `apps/web/src/lib/components/call/CallControls.svelte`
|
||||
|
||||
**Controles:**
|
||||
|
||||
- Botão toggle áudio
|
||||
- Botão toggle vídeo
|
||||
- Botão gravação (se anfitrião)
|
||||
- Botão configurações
|
||||
- Botão encerrar chamada
|
||||
- Contador de duração (HH:MM:SS)
|
||||
|
||||
**Props:**
|
||||
|
||||
```typescript
|
||||
interface Props {
|
||||
audioHabilitado: boolean;
|
||||
videoHabilitado: boolean;
|
||||
gravando: boolean;
|
||||
ehAnfitriao: boolean;
|
||||
duracaoSegundos: number;
|
||||
onToggleAudio: () => void;
|
||||
onToggleVideo: () => void;
|
||||
onIniciarGravacao: () => void;
|
||||
onPararGravacao: () => void;
|
||||
onAbrirConfiguracoes: () => void;
|
||||
onEncerrar: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
### Etapa 11: Criar Componente CallSettings
|
||||
|
||||
**Arquivo:** `apps/web/src/lib/components/call/CallSettings.svelte`
|
||||
|
||||
**Funcionalidades:**
|
||||
|
||||
- Listar microfones disponíveis
|
||||
- Listar webcams disponíveis
|
||||
- Listar alto-falantes disponíveis
|
||||
- Preview de vídeo antes de aplicar
|
||||
- Teste de áudio
|
||||
- Botões aplicar/cancelar
|
||||
|
||||
**Props:**
|
||||
|
||||
```typescript
|
||||
interface Props {
|
||||
open: boolean;
|
||||
dispositivoAtual: {
|
||||
microphoneId: string | null;
|
||||
cameraId: string | null;
|
||||
speakerId: string | null;
|
||||
};
|
||||
onClose: () => void;
|
||||
onAplicar: (dispositivos: {
|
||||
microphoneId: string | null;
|
||||
cameraId: string | null;
|
||||
speakerId: string | null;
|
||||
}) => void;
|
||||
}
|
||||
```
|
||||
|
||||
### Etapa 12: Criar Componente HostControls
|
||||
|
||||
**Arquivo:** `apps/web/src/lib/components/call/HostControls.svelte`
|
||||
|
||||
**Funcionalidades (apenas para anfitrião):**
|
||||
|
||||
- Lista de participantes
|
||||
- Toggle áudio por participante
|
||||
- Toggle vídeo por participante
|
||||
- Indicador visual de quem está gravando
|
||||
- Status de cada participante
|
||||
|
||||
**Props:**
|
||||
|
||||
```typescript
|
||||
interface Props {
|
||||
participantes: Array<{
|
||||
usuarioId: Id<'usuarios'>;
|
||||
nome: string;
|
||||
avatar?: string;
|
||||
audioHabilitado: boolean;
|
||||
videoHabilitado: boolean;
|
||||
forcadoPeloAnfitriao?: boolean;
|
||||
}>;
|
||||
onToggleParticipanteAudio: (usuarioId: Id<'usuarios'>) => void;
|
||||
onToggleParticipanteVideo: (usuarioId: Id<'usuarios'>) => void;
|
||||
}
|
||||
```
|
||||
|
||||
### Etapa 13: Criar Componente RecordingIndicator
|
||||
|
||||
**Arquivo:** `apps/web/src/lib/components/call/RecordingIndicator.svelte`
|
||||
|
||||
**Características:**
|
||||
|
||||
- Banner visível no topo da janela
|
||||
- Ícone animado de gravação
|
||||
- Mensagem clara de que está gravando
|
||||
- Informação de quem iniciou (se disponível)
|
||||
|
||||
**Props:**
|
||||
|
||||
```typescript
|
||||
interface Props {
|
||||
gravando: boolean;
|
||||
iniciadoPor?: string; // Nome do usuário que iniciou
|
||||
}
|
||||
```
|
||||
|
||||
### Etapa 14: Criar Utilitário de Janela Flutuante
|
||||
|
||||
**Arquivo:** `apps/web/src/lib/utils/floatingWindow.ts`
|
||||
|
||||
**Funções:**
|
||||
|
||||
- `criarDragHandler(element: HTMLElement, handle: HTMLElement): () => void` - Criar handler de arrastar
|
||||
- `criarResizeHandler(element: HTMLElement, handles: HTMLElement[]): () => void` - Criar handler de redimensionar
|
||||
- `salvarPosicaoJanela(id: string, posicao: { x: number; y: number; width: number; height: number }): void` - Salvar no localStorage
|
||||
- `restaurarPosicaoJanela(id: string): { x: number; y: number; width: number; height: number } | null` - Restaurar do localStorage
|
||||
|
||||
**Tipos:**
|
||||
|
||||
```typescript
|
||||
interface PosicaoJanela {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
interface LimitesJanela {
|
||||
minWidth: number;
|
||||
minHeight: number;
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
}
|
||||
```
|
||||
|
||||
### Etapa 15: Integrar com ChatWindow
|
||||
|
||||
**Arquivo:** `apps/web/src/lib/components/chat/ChatWindow.svelte`
|
||||
|
||||
**Modificações:**
|
||||
|
||||
- Adicionar botão de chamada de áudio
|
||||
- Adicionar botão de chamada de vídeo
|
||||
- Mostrar indicador quando há chamada ativa
|
||||
- Importar e usar CallWindow quando houver chamada
|
||||
|
||||
**Adicionar no topo (junto com outros botões):**
|
||||
|
||||
```svelte
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-circle"
|
||||
onclick={() => iniciarChamada('audio')}
|
||||
title="Ligação de áudio"
|
||||
>
|
||||
<Phone class="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-circle"
|
||||
onclick={() => iniciarChamada('video')}
|
||||
title="Ligação de vídeo"
|
||||
>
|
||||
<Video class="h-4 w-4" />
|
||||
</button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Ordem de Implementação Recomendada
|
||||
|
||||
1. ✅ **Etapa 1:** Configurar Docker Jitsi (fora do código)
|
||||
2. ✅ **Etapa 2:** Atualizar schema com tabela chamadas
|
||||
3. ✅ **Etapa 3:** Criar backend chamadas.ts com todas as funções
|
||||
4. ✅ **Etapa 4:** Instalar dependências frontend
|
||||
5. ✅ **Etapa 5:** Configurar variáveis de ambiente
|
||||
6. ✅ **Etapa 6:** Criar utilitários Jitsi (jitsi.ts)
|
||||
7. ✅ **Etapa 7:** Criar store de chamadas (callStore.ts)
|
||||
8. ✅ **Etapa 8:** Criar utilitários de gravação (mediaRecorder.ts)
|
||||
9. ✅ **Etapa 9:** Criar CallWindow básico (apenas estrutura)
|
||||
10. ✅ **Etapa 10:** Integrar lib-jitsi-meet no CallWindow
|
||||
11. ✅ **Etapa 11:** Criar CallControls e integrar
|
||||
12. ✅ **Etapa 12:** Implementar contador de duração
|
||||
13. ✅ **Etapa 13:** Implementar janela flutuante (drag & resize)
|
||||
14. ✅ **Etapa 14:** Criar CallSettings e integração de dispositivos
|
||||
15. ✅ **Etapa 15:** Criar HostControls e lógica de anfitrião
|
||||
16. ✅ **Etapa 16:** Implementar gravação local
|
||||
17. ✅ **Etapa 17:** Criar RecordingIndicator
|
||||
18. ✅ **Etapa 18:** Integrar botões no ChatWindow
|
||||
19. ✅ **Etapa 19:** Testes completos
|
||||
20. ✅ **Etapa 20:** Ajustes finais e tratamento de erros
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Segurança e Boas Práticas
|
||||
|
||||
### TypeScript
|
||||
|
||||
- ❌ **NUNCA** usar `any`
|
||||
- ✅ Usar tipos explícitos em todas as funções
|
||||
- ✅ Usar tipos inferidos do Convex quando possível
|
||||
- ✅ Criar interfaces para objetos complexos
|
||||
|
||||
### Svelte 5
|
||||
|
||||
- ✅ Usar `$props()` para props
|
||||
- ✅ Usar `$state()` para estado reativo
|
||||
- ✅ Usar `$derived()` para valores derivados
|
||||
- ✅ Usar `$effect()` para side effects
|
||||
|
||||
### Validação
|
||||
|
||||
- ✅ Validar permissões no backend antes de mutações
|
||||
- ✅ Validar entrada de dados
|
||||
- ✅ Tratar erros adequadamente
|
||||
- ✅ Logs de segurança (criação/finalização de chamadas)
|
||||
|
||||
### Performance
|
||||
|
||||
- ✅ Cleanup adequado de event listeners
|
||||
- ✅ Desconectar Jitsi ao fechar janela
|
||||
- ✅ Parar gravação ao finalizar chamada
|
||||
- ✅ Liberar streams de mídia
|
||||
|
||||
---
|
||||
|
||||
## 📝 Notas Importantes
|
||||
|
||||
1. **Room Names:** Gerar room names únicos usando conversaId + timestamp + hash
|
||||
2. **Persistência:** Salvar posição/tamanho da janela no localStorage
|
||||
3. **Notificações:** Notificar participantes quando chamada é criada/finalizada
|
||||
4. **Limpeza:** Sempre limpar recursos ao finalizar chamada
|
||||
5. **Erros:** Tratar erros de conexão, permissões de mídia, etc.
|
||||
6. **Acessibilidade:** Adicionar labels, ARIA attributes, suporte a teclado
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testes
|
||||
|
||||
### Testes Funcionais
|
||||
|
||||
- [ ] Criar chamada de áudio individual
|
||||
- [ ] Criar chamada de vídeo individual
|
||||
- [ ] Criar chamada em grupo
|
||||
- [ ] Toggle áudio/vídeo
|
||||
- [ ] Anfitrião controlar participantes
|
||||
- [ ] Iniciar/parar gravação
|
||||
- [ ] Contador de duração
|
||||
- [ ] Configuração de dispositivos
|
||||
- [ ] Janela flutuante drag/resize
|
||||
|
||||
### Testes de Segurança
|
||||
|
||||
- [ ] Não anfitrião não pode controlar outros
|
||||
- [ ] Não anfitrião não pode iniciar gravação
|
||||
- [ ] Validação de participantes
|
||||
- [ ] Rate limiting de criação de chamadas
|
||||
|
||||
### Testes de Erros
|
||||
|
||||
- [ ] Conexão perdida
|
||||
- [ ] Sem permissão de mídia
|
||||
- [ ] Dispositivos não disponíveis
|
||||
- [ ] Servidor Jitsi offline
|
||||
|
||||
---
|
||||
|
||||
## 📚 Referências
|
||||
|
||||
- [Jitsi Meet Docker](https://github.com/jitsi/docker-jitsi-meet)
|
||||
- [lib-jitsi-meet Documentation](https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-ljm-api)
|
||||
- [Svelte 5 Documentation](https://svelte.dev/docs)
|
||||
- [Convex Documentation](https://docs.convex.dev)
|
||||
- [WebRTC API](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API)
|
||||
- [MediaRecorder API](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder)
|
||||
|
||||
---
|
||||
|
||||
**Data de Criação:** 2025-01-XX
|
||||
**Versão:** 1.0
|
||||
**Opção:** Docker Local (Desenvolvimento)
|
||||
Reference in New Issue
Block a user