Principal Conexões com canais externos (caixas de entrada) Como compartilhar a sessão do Web Widget entre abas do navegador

Como compartilhar a sessão do Web Widget entre abas do navegador

Última atualização em May 21, 2026

Quando usar

  • O usuário abre o seu site em múltiplas abas e você quer que o widget reconheça a mesma identidade em todas
  • Sua aplicação permite múltiplas abas abertas simultaneamente
  • Você já usa (ou quer usar) Validação de Identidade do widget e quer evitar exigir login em cada aba

Pré-requisitos

  • Web Widget já configurado com Validação de Identidade (Identity Validation) habilitada
  • user_id único por usuário no seu sistema
  • Backend disponível para gerar identifier_hash (opção recomendada)
  • Web Widget instalado — ver Como instalar o Web Widget do Cloud Chat no site


Sobre este artigo

Este guia explica como manter a mesma sessão de usuário do Web Widget Cloud Chat em duas ou mais abas, sem exigir que cada aba esteja autenticada.

Cenário típico: o usuário abre o site em duas abas e inicia uma conversa pelo widget em uma delas. Na segunda aba, o widget deve reconhecer o mesmo usuário e exibir o mesmo histórico de conversas.


Como funciona (explicação simplificada)

  1. user_id = "nome no crachá" — identifica quem é o usuário

  2. identifier_hash = "carimbo de autenticidade" — prova que o crachá é verdadeiro

  3. O Cloud Chat confere o carimbo (usando uma chave secreta que só o servidor conhece) antes de liberar acesso ao histórico

Fluxo entre abas

Ponto-chave: cada aba precisa chamar setUser() independentemente. O cookie cw_user_* é compartilhado automaticamente entre abas (mesmo domínio), mas o authToken interno do widget é por instância (por aba). Por isso, ambas as abas precisam ter acesso ao user_id e identifier_hash para inicializar corretamente.


Opção recomendada: Endpoint server-side

Esta é a opção mais segura. O identifier_hash nunca é armazenado no navegador — é obtido sob demanda a partir de um endpoint protegido pela autenticação da sua aplicação.

Backend (Node.js / Express)

const crypto = require('crypto');

app.get('/api/widget-identity', authMiddleware, (req, res) => {
  const userId = String(req.user.id);
  const hmacToken = process.env.CLOUDCHAT_HMAC_TOKEN;

  const identifierHash = crypto
    .createHmac('sha256', hmacToken)
    .update(userId)
    .digest('hex');

  res.json({
    user_id: userId,
    identifier_hash: identifierHash,
    name: req.user.name,
    email: req.user.email,
  });
});

Backend (Ruby on Rails)

class Api::WidgetIdentityController < ApplicationController
  before_action :authenticate_user!

  def show
    user_id = current_user.id.to_s
    hmac_token = ENV['CLOUDCHAT_HMAC_TOKEN']
    identifier_hash = OpenSSL::HMAC.hexdigest('sha256', hmac_token, user_id)

    render json: {
      user_id: user_id,
      identifier_hash: identifier_hash,
      name: current_user.name,
      email: current_user.email
    }
  end
end

Backend (Python / Django)

import hmac
import hashlib
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
from django.conf import settings

@login_required
def widget_identity(request):
    user_id = str(request.user.id)
    identifier_hash = hmac.new(
        settings.CLOUDCHAT_HMAC_TOKEN.encode(),
        user_id.encode(),
        hashlib.sha256
    ).hexdigest()

    return JsonResponse({
        'user_id': user_id,
        'identifier_hash': identifier_hash,
        'name': request.user.get_full_name(),
        'email': request.user.email,
    })

Frontend (qualquer aba)

<script>
  window.cloudchatSettings = {
    // ... configurações do widget
  };

  window.addEventListener('cloudchat:ready', async function () {
    try {
      const response = await fetch('/api/widget-identity', {
        credentials: 'include',
      });
      if (!response.ok) {
        console.error('Falha ao obter identidade do widget');
        return;
      }
      const { user_id, identifier_hash, name, email } = await response.json();

      window.$cloudchat.setUser(user_id, {
        identifier_hash: identifier_hash,
        name: name,
        email: email,
      });
    } catch (error) {
      console.error('Erro ao configurar usuário do widget:', error);
    }
  });
</script>

Vantagens:

  • O hmac_token nunca sai do servidor

  • O identifier_hash nunca é persistido no navegador

  • Cada aba obtém dados frescos, garantindo consistência

  • Logout retorna 401, e o widget não é identificado


Opção alternativa: localStorage (com ressalvas)

Use somente se sua aplicação não tem backend disponível para fornecer o identifier_hash (SPAs puramente estáticas).

Aviso de segurança: o localStorage é acessível por qualquer script JavaScript no mesmo domínio. Se o site for vulnerável a XSS, um atacante pode ler o identifier_hash armazenado.

Aba que gera os dados (ex: página de login)

const userData = {
  user_id: '12345',
  identifier_hash: 'abc123def456...',
  name: 'Maria Silva',
  email: '[email protected]',
};

localStorage.setItem('cloudchat_user', JSON.stringify(userData));

Qualquer aba que carrega o widget

window.addEventListener('cloudchat:ready', function () {
  const stored = localStorage.getItem('cloudchat_user');
  if (!stored) return;

  try {
    const { user_id, identifier_hash, name, email } = JSON.parse(stored);
    window.$cloudchat.setUser(user_id, {
      identifier_hash,
      name,
      email,
    });
  } catch (e) {
    console.error('Dados do widget inválidos no localStorage');
    localStorage.removeItem('cloudchat_user');
  }
});

Ao fazer logout

localStorage.removeItem('cloudchat_user');
window.$cloudchat.reset();

Opção avançada: sessionStorage + BroadcastChannel

Combina a não persistência do sessionStorage com sincronização entre abas via BroadcastChannel API. Os dados existem apenas enquanto pelo menos uma aba está aberta.

Inicialização (em todas as abas)

const CHANNEL_NAME = 'cloudchat-session';
const STORAGE_KEY = 'cloudchat_user';
const channel = new BroadcastChannel(CHANNEL_NAME);

channel.onmessage = (event) => {
  if (event.data.type === 'session-data') {
    sessionStorage.setItem(STORAGE_KEY, JSON.stringify(event.data.payload));
    initWidget(event.data.payload);
  }
  if (event.data.type === 'session-request') {
    const stored = sessionStorage.getItem(STORAGE_KEY);
    if (stored) {
      channel.postMessage({ type: 'session-data', payload: JSON.parse(stored) });
    }
  }
  if (event.data.type === 'session-clear') {
    sessionStorage.removeItem(STORAGE_KEY);
    window.$cloudchat.reset();
  }
};

function initWidget(userData) {
  window.$cloudchat.setUser(userData.user_id, {
    identifier_hash: userData.identifier_hash,
    name: userData.name,
    email: userData.email,
  });
}

window.addEventListener('cloudchat:ready', function () {
  const stored = sessionStorage.getItem(STORAGE_KEY);
  if (stored) {
    initWidget(JSON.parse(stored));
    return;
  }
  channel.postMessage({ type: 'session-request' });
  // Timeout: se nenhuma aba responder em 2s, esta é a primeira aba
});

Ao autenticar (aba principal)

const userData = { user_id, identifier_hash, name, email };
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(userData));
channel.postMessage({ type: 'session-data', payload: userData });
initWidget(userData);

Ao fazer logout

sessionStorage.removeItem(STORAGE_KEY);
channel.postMessage({ type: 'session-clear' });
window.$cloudchat.reset();

Vantagens:

  • Dados não persistem após fechar todas as abas

  • Sincronização em tempo real entre abas

  • Sem dependência de cookies acessíveis por JS

Limitações:

  • BroadcastChannel não é suportado no IE11 (mas funciona em todos os navegadores modernos)

  • Se o usuário fechar todas as abas, precisa autenticar novamente


Segurança

O que NUNCA fazer

Nunca exponha o hmac_token (chave secreta) no frontend. É a chave que o servidor usa para gerar o identifier_hash. Se um atacante a obtém, pode personificar qualquer usuário.

// ❌ ERRADO
const hmacToken = 'sua-chave-secreta-aqui';
const hash = CryptoJS.HmacSHA256(userId, hmacToken).toString();

Riscos de armazenar identifier_hash no localStorage

Contextualização do risco

O identifier_hash por si só não é um segredo de alto valor:

  • É uma assinatura (HMAC) do user_id, não um token de acesso

  • Um atacante precisa do user_id correspondente E da validação HMAC ativa para personificar alguém

  • Não concede acesso a sistemas além do widget de chat

  • O impacto de vazamento é limitado: o atacante poderia, no máximo, abrir conversas no widget como aquele usuário

Ainda assim, a opção server-side é preferível por eliminar a exposição.

Tabela comparativa de armazenamento

Boas práticas

  1. Proteja-se contra XSS — é a ameaça nº 1 para qualquer dado no navegador. Use Content Security Policy (CSP), sanitize inputs, mantenha dependências atualizadas

  2. Gere identifier_hash no servidor, nunca no frontend

  3. Use HTTPS sempre — sem exceções

  4. Limite a superfície de ataque — carregue o widget apenas em páginas necessárias

  5. Limpe dados ao fazer logout — remova localStorage/sessionStorage e chame $cloudchat.reset()

Referências:


Reset de sessão

Use window.$cloudchat.reset() para limpar a sessão. Internamente:

  1. Fecha o chat se estiver aberto

  2. Remove o cookie cw_conversation (token de sessão da conversa)

  3. Remove o cookie cw_user_{websiteToken} (identidade do usuário)

  4. Recarrega o iframe do widget, iniciando sessão limpa

Quando usar

Exemplo: logout completo

function onLogout() {
  // 1. Limpar dados armazenados (se localStorage)
  localStorage.removeItem('cloudchat_user');

  // 2. Resetar o widget
  if (window.$cloudchat) {
    window.$cloudchat.reset();
  }

  // 3. Redirecionar para página de login
  window.location.href = '/login';
}

Troubleshooting

Problema: Widget mostra "Iniciar nova conversa" em vez do histórico

Causa provável: setUser() não foi chamado, ou foi chamado com identifier_hash inválido.

Solução:

  1. Verifique no console se há erros de rede (HTTP 401 no endpoint /widget/contact)

  2. Confirme que o identifier_hash foi gerado com o hmac_token correto

  3. Confirme que o user_id passado para setUser() é o mesmo usado para gerar o hash

Problema: Abas mostram usuários diferentes

Causa provável: cada aba está chamando setUser() com dados diferentes.

Solução:

  1. Endpoint server-side: confirme que ambas as abas chamam o mesmo endpoint e usuário logado

  2. localStorage: verifique se os dados foram gravados antes da segunda aba ler

Problema: Após logout, widget ainda mostra dados do usuário anterior

Causa provável: $cloudchat.reset() não foi chamado, ou os cookies não foram limpos.

Solução:

  1. Chame window.$cloudchat.reset() antes ou durante o logout

  2. Limpe localStorage/sessionStorage se estiver armazenando dados lá

  3. Verifique se o cookie cw_user_* foi removido (DevTools → Application → Cookies)

Problema: setUser() lança erro "Identifier should be a string or a number"

Causa: o user_id está como undefined, null ou objeto.

Solução: garanta que é string ou número:

window.$cloudchat.setUser(String(userData.user_id), { ... });

Problema: setUser() lança erro "User object should have one of the keys..."

Causa: o objeto não contém nenhuma das chaves obrigatórias.

Solução: inclua ao menos uma de name, email ou avatar_url:

window.$cloudchat.setUser(userId, {
  identifier_hash: hash,
  name: 'Nome do Usuário',
});

Problema: BroadcastChannel não sincroniza entre abas

Causa provável: abas em domínios diferentes (ex: app.exemplo.com vs www.exemplo.com).

Solução: BroadcastChannel só funciona entre páginas do mesmo domínio (same-origin). Garanta que todas as abas usam exatamente a mesma URL base.


Observações