feat: integrate Jitsi configuration and dynamic loading in CallWindow

- Added support for Jitsi configuration retrieval from the backend, allowing for dynamic room name generation based on the active configuration.
- Implemented a polyfill for BlobBuilder to ensure compatibility with the lib-jitsi-meet library across different browsers.
- Enhanced error handling during the loading of the Jitsi library, providing clearer feedback for missing modules and connection issues.
- Updated Vite configuration to exclude lib-jitsi-meet from SSR and allow dynamic loading in the browser.
- Introduced a new route for Jitsi settings in the dashboard for user configuration of Jitsi Meet parameters.
This commit is contained in:
2025-11-21 22:03:01 -03:00
parent 41f7942dd1
commit 52823a9fac
9 changed files with 1100 additions and 23 deletions

View File

@@ -153,12 +153,13 @@
const chamadaQuery = useQuery(api.chamadas.obterChamada, { chamadaId });
const chamada = $derived(chamadaQuery?.data);
const meuPerfil = useQuery(api.auth.getCurrentUser, {});
const configJitsiBackend = useQuery(api.configuracaoJitsi.obterConfigJitsi, {});
// Estado derivado do store
const estadoChamada = $derived(get(callState));
// Configuração Jitsi
const configJitsi = $derived.by(() => obterConfiguracaoJitsi());
// Configuração Jitsi (busca do backend primeiro, depois fallback para env vars)
const configJitsi = $derived.by(() => obterConfiguracaoJitsi(configJitsiBackend?.data || null));
// Handler de erro
function handleError(message: string, details?: string): void {
@@ -171,12 +172,137 @@
}
// Carregar Jitsi dinamicamente
// Polyfill para BlobBuilder (API antiga que lib-jitsi-meet pode usar)
// Deve ser executado antes de qualquer import da biblioteca
function adicionarBlobBuilderPolyfill(): void {
if (!browser || typeof window === 'undefined') return;
// Verificar se já foi adicionado (evitar múltiplas execuções)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((window as any).__blobBuilderPolyfillAdded) {
return;
}
// Implementar BlobBuilder usando Blob moderno
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const BlobBuilderClass = class BlobBuilder {
private parts: BlobPart[] = [];
append(data: BlobPart): void {
this.parts.push(data);
}
getBlob(contentType?: string): Blob {
return new Blob(this.parts, contentType ? { type: contentType } : undefined);
}
};
// Adicionar em todos os possíveis locais onde a biblioteca pode procurar
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const win = window as any;
if (typeof win.BlobBuilder === 'undefined') {
win.BlobBuilder = BlobBuilderClass;
}
if (typeof win.WebKitBlobBuilder === 'undefined') {
win.WebKitBlobBuilder = BlobBuilderClass;
}
if (typeof win.MozBlobBuilder === 'undefined') {
win.MozBlobBuilder = BlobBuilderClass;
}
if (typeof win.MSBlobBuilder === 'undefined') {
win.MSBlobBuilder = BlobBuilderClass;
}
// Também adicionar no global scope caso a biblioteca procure lá
if (typeof globalThis !== 'undefined') {
if (typeof (globalThis as any).BlobBuilder === 'undefined') {
(globalThis as any).BlobBuilder = BlobBuilderClass;
}
if (typeof (globalThis as any).WebKitBlobBuilder === 'undefined') {
(globalThis as any).WebKitBlobBuilder = BlobBuilderClass;
}
}
// Marcar que o polyfill foi adicionado
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any).__blobBuilderPolyfillAdded = true;
console.log('✅ Polyfill BlobBuilder adicionado para todos os navegadores');
}
// Executar polyfill imediatamente se estiver no browser
// Isso garante que esteja disponível antes de qualquer import
if (browser && typeof window !== 'undefined') {
adicionarBlobBuilderPolyfill();
}
async function carregarJitsi(): Promise<void> {
if (!browser || JitsiMeetJS) return;
try {
console.log('🔄 Tentando carregar lib-jitsi-meet...');
// Adicionar polyfill antes de carregar a biblioteca
adicionarBlobBuilderPolyfill();
// Tentar carregar o módulo lib-jitsi-meet dinamicamente
// Usar import dinâmico para evitar problemas de SSR e permitir carregamento apenas no browser
const module = await import('lib-jitsi-meet');
JitsiMeetJS = module.default as unknown as JitsiMeetJSLib;
console.log('📦 Módulo carregado, verificando exportações...', {
hasDefault: !!module.default,
hasJitsiMeetJS: !!module.JitsiMeetJS,
keys: Object.keys(module)
});
// Tentar múltiplas formas de acessar o JitsiMeetJS
// A biblioteca pode exportar de diferentes formas dependendo da versão
let jitsiModule: unknown = null;
// Tentativa 1: export default
if (module.default) {
if (typeof module.default === 'object' && 'init' in module.default) {
jitsiModule = module.default;
console.log('✅ Encontrado em module.default');
}
}
// Tentativa 2: export nomeado JitsiMeetJS
if (!jitsiModule && module.JitsiMeetJS) {
jitsiModule = module.JitsiMeetJS;
console.log('✅ Encontrado em module.JitsiMeetJS');
}
// Tentativa 3: o próprio módulo pode ser o JitsiMeetJS
if (!jitsiModule && typeof module === 'object' && 'init' in module) {
jitsiModule = module;
console.log('✅ Encontrado no próprio módulo');
}
if (!jitsiModule) {
throw new Error(
'Não foi possível encontrar JitsiMeetJS no módulo. ' +
'Verifique se lib-jitsi-meet está instalado corretamente.'
);
}
JitsiMeetJS = jitsiModule as unknown as JitsiMeetJSLib;
// Verificar se JitsiMeetJS foi inicializado corretamente
if (!JitsiMeetJS || !JitsiMeetJS.init || typeof JitsiMeetJS.init !== 'function') {
throw new Error('JitsiMeetJS não possui método init válido');
}
// Verificar se JitsiConnection existe
if (!JitsiMeetJS.JitsiConnection) {
throw new Error('JitsiConnection não está disponível no módulo carregado');
}
console.log('🔧 Inicializando Jitsi Meet JS...');
// Inicializar Jitsi
JitsiMeetJS.init({
@@ -188,16 +314,35 @@
disableThirdPartyRequests: false
});
// Configurar nível de log para DEBUG em desenvolvimento
JitsiMeetJS.setLogLevel(JitsiMeetJS.constants.logLevels.INFO);
// Configurar nível de log
if (JitsiMeetJS.setLogLevel && typeof JitsiMeetJS.setLogLevel === 'function') {
if (JitsiMeetJS.constants && JitsiMeetJS.constants.logLevels) {
JitsiMeetJS.setLogLevel(JitsiMeetJS.constants.logLevels.INFO);
}
}
console.log('✅ Jitsi Meet JS carregado e inicializado');
} catch (error) {
console.error('Erro ao carregar lib-jitsi-meet:', error);
handleError(
'Erro ao carregar biblioteca de vídeo',
'Não foi possível carregar a biblioteca necessária para chamadas de vídeo. Por favor, recarregue a página.'
);
console.log('✅ Jitsi Meet JS carregado e inicializado com sucesso');
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error('❌ Erro ao carregar lib-jitsi-meet:', error);
console.error('Detalhes do erro:', {
message: errorMessage,
stack: error instanceof Error ? error.stack : undefined,
error
});
// Verificar se é um erro de módulo não encontrado
if (errorMessage.includes('Failed to fetch') || errorMessage.includes('Cannot find module')) {
handleError(
'Biblioteca de vídeo não encontrada',
'A biblioteca Jitsi não pôde ser encontrada. Verifique se o pacote "lib-jitsi-meet" está instalado. Se o problema persistir, tente limpar o cache do navegador e recarregar a página.'
);
} else {
handleError(
'Erro ao carregar biblioteca de vídeo',
`Não foi possível carregar a biblioteca necessária para chamadas de vídeo. Erro: ${errorMessage}. Por favor, recarregue a página e tente novamente.`
);
}
}
}
@@ -855,6 +1000,10 @@
onMount(async () => {
if (!browser) return;
// Adicionar polyfill BlobBuilder o mais cedo possível
// Isso deve ser feito antes de qualquer tentativa de carregar lib-jitsi-meet
adicionarBlobBuilderPolyfill();
// Inicializar store primeiro
inicializarStore();