May 20, 2025
BroadcastChannel LeaderElection: Evitando Conexões WebSocket Duplicadas
Como usar BroadcastChannel com LeaderElection para eleger uma aba líder e evitar múltiplas conexões WebSocket, melhorando performance e UX em aplicações com microfrontends.

Imagine o seguinte cenário:
Um usuário com duas abas abertas do painel de atendimento. Ambas tentam se conectar ao WebSocket para escutar eventos em tempo real.
🚨 O Problema
Quando múltiplas abas da mesma aplicação tentam se conectar simultaneamente ao WebSocket, você enfrenta:
- ❌ Conexões duplicadas desnecessárias
- ❌ Custo computacional elevado no backend
- ❌ Tráfego de rede excessivo
- ❌ UX inconsistente entre abas
- ❌ Conflitos de estado em tempo real
✅ Solução: Eleger uma Aba Líder
Usamos a lib broadcast-channel que implementa LeaderElection de forma simples, confiável e com fallback pra browsers mais antigos.
Implementação Básica
import { BroadcastChannel, createLeaderElection } from 'broadcast-channel';
const channel = new BroadcastChannel('ws-coordinator');
const elector = createLeaderElection(channel);
elector.awaitLeadership().then(() => {
console.log('Aba Principal. Conectando ao WebSocket...');
const socket = new WebSocket('wss://api.example.com/ws');
socket.onmessage = (event) => {
// Processar mensagens do WebSocket
const data = JSON.parse(event.data);
console.log('Dados recebidos:', data);
};
socket.onclose = () => {
console.log('WebSocket desconectado');
};
});
🔄 Como Funciona
1. Eleição de Líder
- ✅ Primeira aba que executa o código se torna líder
- ✅ Apenas uma aba gerencia a conexão WebSocket
- ✅ Eleição automática quando a aba líder é fechada
2. Comunicação Entre Abas
- ✅ BroadcastChannel permite comunicação entre abas
- ✅ Dados compartilhados em tempo real
- ✅ Sincronização automática de estado
3. Fallback Robusto
- ✅ Suporte a browsers antigos via IndexedDB
- ✅ Detecção automática de aba fechada
- ✅ Reeleição transparente quando necessário
🛠️ Implementação Avançada
Classe WebSocketManager
import { BroadcastChannel, createLeaderElection } from 'broadcast-channel';
class WebSocketManager {
constructor(url, options = {}) {
this.url = url;
this.options = options;
this.socket = null;
this.channel = new BroadcastChannel('ws-coordinator');
this.elector = createLeaderElection(this.channel);
this.isLeader = false;
this.setupLeaderElection();
this.setupChannelListener();
}
setupLeaderElection() {
this.elector.awaitLeadership().then(() => {
this.isLeader = true;
console.log('🎯 Aba eleita como líder');
this.connectWebSocket();
});
}
setupChannelListener() {
this.channel.addEventListener('message', (event) => {
if (!this.isLeader) {
// Processar dados recebidos da aba líder
this.handleWebSocketData(event.data);
}
});
}
connectWebSocket() {
this.socket = new WebSocket(this.url);
this.socket.onopen = () => {
console.log('🔗 WebSocket conectado (aba líder)');
};
this.socket.onmessage = (event) => {
const data = JSON.parse(event.data);
// Broadcast para outras abas
this.channel.postMessage({
type: 'websocket-message',
data: data
});
// Processar localmente também
this.handleWebSocketData(data);
};
this.socket.onclose = () => {
console.log('🔌 WebSocket desconectado');
this.isLeader = false;
};
this.socket.onerror = (error) => {
console.error('❌ Erro no WebSocket:', error);
};
}
handleWebSocketData(data) {
// Implementar lógica específica da aplicação
console.log('📨 Dados processados:', data);
}
sendMessage(message) {
if (this.isLeader && this.socket?.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(message));
} else {
// Se não for líder, enviar via channel para o líder processar
this.channel.postMessage({
type: 'send-message',
data: message
});
}
}
disconnect() {
if (this.socket) {
this.socket.close();
}
this.channel.close();
}
}
Uso na Aplicação
// Inicializar o gerenciador
const wsManager = new WebSocketManager('wss://api.example.com/ws');
// Enviar mensagem (funciona de qualquer aba)
wsManager.sendMessage({
type: 'user-action',
payload: { action: 'click', element: 'button' }
});
// Cleanup ao sair da página
window.addEventListener('beforeunload', () => {
wsManager.disconnect();
});
🎯 Vantagens da Solução
Performance
- ✅ Evita conexões WebSocket duplicadas
- ✅ Reduz custo computacional no backend
- ✅ Diminui tráfego de rede desnecessário
- ✅ Otimiza uso de recursos do browser
Experiência do Usuário
- ✅ UX mais previsível e consistente
- ✅ Sincronização automática entre abas
- ✅ Comportamento uniforme independente da aba ativa
- ✅ Transição transparente quando aba líder é fechada
Arquitetura
- ✅ Ideal para Micro Frontends isolados
- ✅ Comunicação eficiente entre componentes
- ✅ Estado compartilhado sem complexidade
- ✅ Escalabilidade para múltiplas abas
🔧 Casos de Uso Ideais
Painéis de Atendimento
- ✅ Chat em tempo real sem duplicação
- ✅ Notificações sincronizadas entre abas
- ✅ Status de usuário consistente
Dashboards de Monitoramento
- ✅ Métricas em tempo real compartilhadas
- ✅ Alertas sincronizados entre abas
- ✅ Performance otimizada do sistema
Aplicações Colaborativas
- ✅ Edição simultânea sem conflitos
- ✅ Presença de usuários sincronizada
- ✅ Comunicação eficiente entre sessões
📋 Implementação com React
Hook Personalizado
import { useEffect, useRef, useState } from 'react';
import { BroadcastChannel, createLeaderElection } from 'broadcast-channel';
export function useWebSocketLeader(url) {
const [isLeader, setIsLeader] = useState(false);
const [data, setData] = useState(null);
const wsManagerRef = useRef(null);
useEffect(() => {
const channel = new BroadcastChannel('ws-coordinator');
const elector = createLeaderElection(channel);
elector.awaitLeadership().then(() => {
setIsLeader(true);
console.log('🎯 Aba eleita como líder');
const socket = new WebSocket(url);
socket.onmessage = (event) => {
const messageData = JSON.parse(event.data);
// Broadcast para outras abas
channel.postMessage({
type: 'websocket-data',
data: messageData
});
setData(messageData);
};
wsManagerRef.current = { socket, channel };
});
// Listener para dados de outras abas
channel.addEventListener('message', (event) => {
if (event.data.type === 'websocket-data') {
setData(event.data.data);
}
});
return () => {
if (wsManagerRef.current) {
wsManagerRef.current.socket.close();
wsManagerRef.current.channel.close();
}
};
}, [url]);
return { isLeader, data };
}
Uso no Componente
import React from 'react';
import { useWebSocketLeader } from './useWebSocketLeader';
function Dashboard() {
const { isLeader, data } = useWebSocketLeader('wss://api.example.com/ws');
return (
<div>
<h1>Dashboard</h1>
{isLeader && <span>🎯 Aba Líder</span>}
{data && <div>Dados: {JSON.stringify(data)}</div>}
</div>
);
}
🚀 Benefícios em Produção
Para Desenvolvedores
- ✅ Código mais limpo e organizado
- ✅ Debugging facilitado com logs claros
- ✅ Manutenção simplificada da comunicação
- ✅ Testes mais confiáveis e previsíveis
Para Usuários
- ✅ Performance melhorada da aplicação
- ✅ Experiência consistente entre abas
- ✅ Sincronização automática de dados
- ✅ Comportamento previsível do sistema
Para Infraestrutura
- ✅ Redução de carga no servidor WebSocket
- ✅ Menor uso de banda de rede
- ✅ Escalabilidade melhorada do sistema
- ✅ Custos otimizados de infraestrutura
Este artigo apresenta uma solução prática para comunicação eficiente entre abas usando BroadcastChannel e LeaderElection. Para discussões técnicas, implementações ou dúvidas sobre WebSockets e microfrontends, me encontre no LinkedIn ou GitHub.