Call audio video jitsi #36
@@ -10,6 +10,7 @@
|
|||||||
import ScheduleMessageModal from './ScheduleMessageModal.svelte';
|
import ScheduleMessageModal from './ScheduleMessageModal.svelte';
|
||||||
import SalaReuniaoManager from './SalaReuniaoManager.svelte';
|
import SalaReuniaoManager from './SalaReuniaoManager.svelte';
|
||||||
import CallWindow from '../call/CallWindow.svelte';
|
import CallWindow from '../call/CallWindow.svelte';
|
||||||
|
import ErrorModal from '../ErrorModal.svelte';
|
||||||
import { getAvatarUrl } from '$lib/utils/avatarGenerator';
|
import { getAvatarUrl } from '$lib/utils/avatarGenerator';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import {
|
import {
|
||||||
@@ -42,6 +43,12 @@
|
|||||||
let iniciandoChamada = $state(false);
|
let iniciandoChamada = $state(false);
|
||||||
let chamadaAtiva = $state<Id<'chamadas'> | null>(null);
|
let chamadaAtiva = $state<Id<'chamadas'> | null>(null);
|
||||||
|
|
||||||
|
// Estados para modal de erro
|
||||||
|
let showErrorModal = $state(false);
|
||||||
|
let errorTitle = $state('Erro');
|
||||||
|
let errorMessage = $state('');
|
||||||
|
let errorDetails = $state<string | undefined>(undefined);
|
||||||
|
|
||||||
const chamadaAtivaQuery = useQuery(api.chamadas.obterChamadaAtiva, {
|
const chamadaAtivaQuery = useQuery(api.chamadas.obterChamadaAtiva, {
|
||||||
conversaId: conversaId as Id<'conversas'>
|
conversaId: conversaId as Id<'conversas'>
|
||||||
});
|
});
|
||||||
@@ -139,7 +146,10 @@
|
|||||||
// Funções para chamadas
|
// Funções para chamadas
|
||||||
async function iniciarChamada(tipo: 'audio' | 'video'): Promise<void> {
|
async function iniciarChamada(tipo: 'audio' | 'video'): Promise<void> {
|
||||||
if (chamadaAtual) {
|
if (chamadaAtual) {
|
||||||
alert('Já existe uma chamada ativa nesta conversa');
|
errorTitle = 'Chamada Ativa';
|
||||||
|
errorMessage = 'Já existe uma chamada ativa nesta conversa';
|
||||||
|
errorDetails = undefined;
|
||||||
|
showErrorModal = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,13 +165,37 @@
|
|||||||
chamadaAtiva = chamadaId;
|
chamadaAtiva = chamadaId;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao iniciar chamada:', error);
|
console.error('Erro ao iniciar chamada:', error);
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Erro ao iniciar chamada';
|
|
||||||
alert(errorMessage);
|
// Tratar diferentes tipos de erro
|
||||||
|
if (error instanceof Error) {
|
||||||
|
errorTitle = 'Erro ao Iniciar Chamada';
|
||||||
|
|
||||||
|
// Verificar se é erro do Convex sobre função não encontrada
|
||||||
|
if (error.message.includes('Could not find public function')) {
|
||||||
|
errorMessage = 'O servidor de chamadas não está sincronizado. Por favor, aguarde alguns segundos e tente novamente.';
|
||||||
|
errorDetails = `Erro técnico: ${error.message}\n\nSugestão: Verifique se o backend Convex está rodando corretamente.`;
|
||||||
|
} else {
|
||||||
|
errorMessage = error.message || 'Erro ao iniciar chamada';
|
||||||
|
errorDetails = error.stack;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorTitle = 'Erro ao Iniciar Chamada';
|
||||||
|
errorMessage = 'Ocorreu um erro desconhecido ao iniciar a chamada. Por favor, tente novamente.';
|
||||||
|
errorDetails = String(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
showErrorModal = true;
|
||||||
} finally {
|
} finally {
|
||||||
iniciandoChamada = false;
|
iniciandoChamada = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fecharErrorModal(): void {
|
||||||
|
showErrorModal = false;
|
||||||
|
errorMessage = '';
|
||||||
|
errorDetails = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function fecharChamada(): void {
|
function fecharChamada(): void {
|
||||||
chamadaAtiva = null;
|
chamadaAtiva = null;
|
||||||
}
|
}
|
||||||
@@ -592,3 +626,12 @@
|
|||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!-- Modal de Erro -->
|
||||||
|
<ErrorModal
|
||||||
|
open={showErrorModal}
|
||||||
|
title={errorTitle}
|
||||||
|
message={errorMessage}
|
||||||
|
details={errorDetails}
|
||||||
|
onClose={fecharErrorModal}
|
||||||
|
/>
|
||||||
|
|||||||
2
packages/backend/convex/_generated/api.d.ts
vendored
2
packages/backend/convex/_generated/api.d.ts
vendored
@@ -18,6 +18,7 @@ import type * as ausencias from "../ausencias.js";
|
|||||||
import type * as autenticacao from "../autenticacao.js";
|
import type * as autenticacao from "../autenticacao.js";
|
||||||
import type * as auth from "../auth.js";
|
import type * as auth from "../auth.js";
|
||||||
import type * as auth_utils from "../auth/utils.js";
|
import type * as auth_utils from "../auth/utils.js";
|
||||||
|
import type * as chamadas from "../chamadas.js";
|
||||||
import type * as chamados from "../chamados.js";
|
import type * as chamados from "../chamados.js";
|
||||||
import type * as chat from "../chat.js";
|
import type * as chat from "../chat.js";
|
||||||
import type * as configuracaoEmail from "../configuracaoEmail.js";
|
import type * as configuracaoEmail from "../configuracaoEmail.js";
|
||||||
@@ -71,6 +72,7 @@ declare const fullApi: ApiFromModules<{
|
|||||||
autenticacao: typeof autenticacao;
|
autenticacao: typeof autenticacao;
|
||||||
auth: typeof auth;
|
auth: typeof auth;
|
||||||
"auth/utils": typeof auth_utils;
|
"auth/utils": typeof auth_utils;
|
||||||
|
chamadas: typeof chamadas;
|
||||||
chamados: typeof chamados;
|
chamados: typeof chamados;
|
||||||
chat: typeof chat;
|
chat: typeof chat;
|
||||||
configuracaoEmail: typeof configuracaoEmail;
|
configuracaoEmail: typeof configuracaoEmail;
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ function gerarRoomName(conversaId: Id<'conversas'>, tipo: 'audio' | 'video'): st
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verificar se usuário é anfitrião da chamada
|
* Verificar se usuário é anfitrião da chamada (helper interno)
|
||||||
*/
|
*/
|
||||||
async function verificarAnfitriao(
|
async function verificarSeEhAnfitriao(
|
||||||
ctx: QueryCtx | MutationCtx,
|
ctx: QueryCtx | MutationCtx,
|
||||||
chamadaId: Id<'chamadas'>,
|
chamadaId: Id<'chamadas'>,
|
||||||
usuarioId: Id<'usuarios'>
|
usuarioId: Id<'usuarios'>
|
||||||
@@ -77,18 +77,20 @@ export const criarChamada = mutation({
|
|||||||
// Verificar se já existe chamada ativa
|
// Verificar se já existe chamada ativa
|
||||||
const chamadasAtivas = await ctx.db
|
const chamadasAtivas = await ctx.db
|
||||||
.query('chamadas')
|
.query('chamadas')
|
||||||
.withIndex('by_conversa_ativa', (q) => q.eq('conversaId', args.conversaId))
|
.withIndex('by_conversa', (q) => q.eq('conversaId', args.conversaId).eq('status', 'aguardando'))
|
||||||
.filter((q) =>
|
|
||||||
q.or(
|
|
||||||
q.eq(q.field('status'), 'aguardando'),
|
|
||||||
q.eq(q.field('status'), 'em_andamento')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if (chamadasAtivas.length > 0) {
|
// Também verificar chamadas em andamento
|
||||||
|
const chamadasEmAndamento = await ctx.db
|
||||||
|
.query('chamadas')
|
||||||
|
.withIndex('by_conversa', (q) => q.eq('conversaId', args.conversaId).eq('status', 'em_andamento'))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
const todasAtivas = [...chamadasAtivas, ...chamadasEmAndamento];
|
||||||
|
|
||||||
|
if (todasAtivas.length > 0) {
|
||||||
// Retornar chamada ativa existente
|
// Retornar chamada ativa existente
|
||||||
return chamadasAtivas[0]._id;
|
return todasAtivas[0]._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obter participantes da conversa
|
// Obter participantes da conversa
|
||||||
@@ -218,7 +220,7 @@ export const cancelarChamada = mutation({
|
|||||||
if (!chamada) throw new Error('Chamada não encontrada');
|
if (!chamada) throw new Error('Chamada não encontrada');
|
||||||
|
|
||||||
// Apenas anfitrião pode cancelar
|
// Apenas anfitrião pode cancelar
|
||||||
const ehAnfitriao = await verificarAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
|
const ehAnfitriao = await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
|
||||||
if (!ehAnfitriao) {
|
if (!ehAnfitriao) {
|
||||||
throw new Error('Apenas o anfitrião pode cancelar a chamada');
|
throw new Error('Apenas o anfitrião pode cancelar a chamada');
|
||||||
}
|
}
|
||||||
@@ -307,7 +309,7 @@ export const removerParticipante = mutation({
|
|||||||
if (!chamada) throw new Error('Chamada não encontrada');
|
if (!chamada) throw new Error('Chamada não encontrada');
|
||||||
|
|
||||||
// Apenas anfitrião pode remover participantes
|
// Apenas anfitrião pode remover participantes
|
||||||
const ehAnfitriao = await verificarAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
|
const ehAnfitriao = await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
|
||||||
if (!ehAnfitriao) {
|
if (!ehAnfitriao) {
|
||||||
throw new Error('Apenas o anfitrião pode remover participantes');
|
throw new Error('Apenas o anfitrião pode remover participantes');
|
||||||
}
|
}
|
||||||
@@ -357,7 +359,7 @@ export const toggleAudioVideoParticipante = mutation({
|
|||||||
if (!chamada) throw new Error('Chamada não encontrada');
|
if (!chamada) throw new Error('Chamada não encontrada');
|
||||||
|
|
||||||
// Apenas anfitrião pode controlar outros participantes
|
// Apenas anfitrião pode controlar outros participantes
|
||||||
const ehAnfitriao = await verificarAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
|
const ehAnfitriao = await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
|
||||||
const ehOProprioParticipante = args.participanteId === usuarioAtual._id;
|
const ehOProprioParticipante = args.participanteId === usuarioAtual._id;
|
||||||
|
|
||||||
if (!ehAnfitriao && !ehOProprioParticipante) {
|
if (!ehAnfitriao && !ehOProprioParticipante) {
|
||||||
@@ -417,7 +419,7 @@ export const iniciarGravacao = mutation({
|
|||||||
if (!chamada) throw new Error('Chamada não encontrada');
|
if (!chamada) throw new Error('Chamada não encontrada');
|
||||||
|
|
||||||
// Apenas anfitrião pode iniciar gravação
|
// Apenas anfitrião pode iniciar gravação
|
||||||
const ehAnfitriao = await verificarAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
|
const ehAnfitriao = await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
|
||||||
if (!ehAnfitriao) {
|
if (!ehAnfitriao) {
|
||||||
throw new Error('Apenas o anfitrião pode iniciar a gravação');
|
throw new Error('Apenas o anfitrião pode iniciar a gravação');
|
||||||
}
|
}
|
||||||
@@ -453,7 +455,7 @@ export const finalizarGravacao = mutation({
|
|||||||
if (!chamada) throw new Error('Chamada não encontrada');
|
if (!chamada) throw new Error('Chamada não encontrada');
|
||||||
|
|
||||||
// Apenas anfitrião pode finalizar gravação
|
// Apenas anfitrião pode finalizar gravação
|
||||||
const ehAnfitriao = await verificarAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
|
const ehAnfitriao = await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
|
||||||
if (!ehAnfitriao) {
|
if (!ehAnfitriao) {
|
||||||
throw new Error('Apenas o anfitrião pode finalizar a gravação');
|
throw new Error('Apenas o anfitrião pode finalizar a gravação');
|
||||||
}
|
}
|
||||||
@@ -491,23 +493,24 @@ export const obterChamadaAtiva = query({
|
|||||||
if (!participa) return null;
|
if (!participa) return null;
|
||||||
|
|
||||||
// Buscar chamada ativa
|
// Buscar chamada ativa
|
||||||
const chamadasAtivas = await ctx.db
|
const chamadasAguardando = await ctx.db
|
||||||
.query('chamadas')
|
.query('chamadas')
|
||||||
.withIndex('by_conversa_ativa', (q) => q.eq('conversaId', args.conversaId))
|
.withIndex('by_conversa', (q) => q.eq('conversaId', args.conversaId).eq('status', 'aguardando'))
|
||||||
.filter((q) =>
|
|
||||||
q.or(
|
|
||||||
q.eq(q.field('status'), 'aguardando'),
|
|
||||||
q.eq(q.field('status'), 'em_andamento')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if (chamadasAtivas.length === 0) {
|
const chamadasEmAndamento = await ctx.db
|
||||||
|
.query('chamadas')
|
||||||
|
.withIndex('by_conversa', (q) => q.eq('conversaId', args.conversaId).eq('status', 'em_andamento'))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
const todasAtivas = [...chamadasAguardando, ...chamadasEmAndamento];
|
||||||
|
|
||||||
|
if (todasAtivas.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retornar a chamada mais recente
|
// Retornar a chamada mais recente
|
||||||
return chamadasAtivas.sort((a, b) => (b.criadoEm || 0) - (a.criadoEm || 0))[0];
|
return todasAtivas.sort((a, b) => (b.criadoEm || 0) - (a.criadoEm || 0))[0];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -549,7 +552,7 @@ export const verificarAnfitriao = query({
|
|||||||
const usuarioAtual = await getUsuarioAutenticado(ctx);
|
const usuarioAtual = await getUsuarioAutenticado(ctx);
|
||||||
if (!usuarioAtual) return false;
|
if (!usuarioAtual) return false;
|
||||||
|
|
||||||
return await verificarAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
|
return await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -860,7 +860,6 @@ export default defineSchema({
|
|||||||
criadoEm: v.number(),
|
criadoEm: v.number(),
|
||||||
})
|
})
|
||||||
.index("by_conversa", ["conversaId", "status"])
|
.index("by_conversa", ["conversaId", "status"])
|
||||||
.index("by_conversa_ativa", ["conversaId", "status"])
|
|
||||||
.index("by_criado_por", ["criadoPor"])
|
.index("by_criado_por", ["criadoPor"])
|
||||||
.index("by_status", ["status"])
|
.index("by_status", ["status"])
|
||||||
.index("by_room_name", ["roomName"]),
|
.index("by_room_name", ["roomName"]),
|
||||||
|
|||||||
Reference in New Issue
Block a user