Voltar ao Blog
    Microfrontends

    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.

    20 de maio de 20255 min

    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.