
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@meerkat-coding/chat-widget
Advanced tools
Biblioteca modular de widgets de chat para React com arquitetura escalável
Uma biblioteca React modular, escalável e totalmente personalizável para criar widgets de chat com suporte a Markdown, temas customizáveis e incorporação standalone.
npm install @meerkat-coding/chat-widget
<!-- Configuração -->
<script>
window.ChatWidgetConfig = {
loadMessagesUrl: "https://seu-servidor.com/api/messages",
sendMessageUrl: "https://seu-servidor.com/api/send",
// Internacionalização (obrigatório)
i18n: {
pt: {
title: "Chat de Suporte",
subtitle: "Estamos aqui para ajudar!",
footer: "",
getStarted: "Iniciar Chat",
inputPlaceholder: "Digite sua mensagem...",
initialMessages: ["Olá! Como posso ajudar?"],
},
},
defaultLanguage: "pt",
// Recursos
enableMarkdown: true,
enablePolling: true,
refetchInterval: 3000,
// Opcional: Personalização de tema
theme: {
colors: {
headerBackground: "#667eea",
messageUserBackground: "#764ba2",
},
},
// Opcional: Callbacks
onMessageSent: function (message) {
console.log("Mensagem enviada:", message);
},
onError: function (error) {
console.error("Erro no chat:", error);
},
};
</script>
<!-- Script do Widget via CDN -->
<!-- Opção 1: unpkg (recomendado) -->
<script src="https://unpkg.com/@meerkat-coding/chat-widget@latest/dist/chat-widget.min.js"></script>
<!-- Opção 2: jsDelivr -->
<!-- <script src="https://cdn.jsdelivr.net/npm/@meerkat-coding/chat-widget@latest/dist/chat-widget.min.js"></script> -->
💡 Dica sobre CDN:
- unpkg e jsDelivr servem automaticamente packages do npm
- Use
@2.0.3para versão específica ou@latestpara sempre pegar a mais recente- O cache do CDN garante carregamento rápido globalmente
- Sem necessidade de hospedar o arquivo no seu próprio servidor!
import { ChatWidget } from "@meerkat-coding/chat-widget";
function App() {
return (
<ChatWidget
loadMessagesUrl="https://api.exemplo.com/messages"
sendMessageUrl="https://api.exemplo.com/send"
i18n={{
pt: {
title: "Atendimento",
subtitle: "Estamos aqui para ajudar!",
footer: "",
getStarted: "Iniciar Chat",
inputPlaceholder: "Digite sua mensagem...",
initialMessages: ["Olá! Como posso ajudar?"],
},
}}
defaultLanguage="pt"
enableMarkdown={true}
theme={{
colors: {
headerBackground: "#667eea",
messageUserBackground: "#764ba2",
},
}}
/>
);
}
import {
MessageList,
MessageInput,
useSessionId,
useMessages,
useSendMessage,
} from "@meerkat-coding/chat-widget";
function CustomChatWidget() {
const { sessionId } = useSessionId();
const { messages, isLoading, isBlocked } = useMessages({
loadMessagesUrl: "https://api.exemplo.com/messages",
sessionId,
enablePolling: true,
});
const { sendMessage, isSending } = useSendMessage({
sendMessageUrl: "https://api.exemplo.com/send",
sessionId,
});
return (
<div className="my-chat">
<MessageList
messages={messages}
isLoading={isLoading}
enableMarkdown={true}
/>
<MessageInput
onSend={sendMessage}
isDisabled={isBlocked}
isSending={isSending}
/>
</div>
);
}
O tema é organizado em 4 categorias principais:
interface ChatWidgetTheme {
general?: {
title?: string;
subtitle?: string;
placeholder?: string;
initialMessage?: string;
fontFamily?: string;
widgetIcon?: React.ReactNode; // Ícone do botão flutuante (mode: window)
iconSize?: string; // Tamanho do ícone do header (ex: "24px", padrão: "24px")
iconPadding?: string; // Padding do ícone do header (ex: "4px", padrão: "0")
};
colors?: {
// Header (4 propriedades)
headerBackground?: string;
headerText?: string;
headerSubtitleText?: string;
headerIconColor?: string;
// Mensagens (6 propriedades)
messageUserBackground?: string;
messageUserText?: string;
messageBotBackground?: string;
messageBotText?: string;
messageTimestampColor?: string;
// Input
inputBackground?: string;
inputText?: string;
inputBorder?: string;
inputPlaceholder?: string;
sendButtonBackground?: string;
sendButtonText?: string;
// Toggle Button (mode: window)
toggleBackground?: string; // Cor de fundo do botão flutuante
toggleHover?: string; // Cor no hover
toggleIconStroke?: string; // Cor do ícone SVG
// Outros
lightBackground?: string;
disabledText?: string;
};
layout?: {
windowWidth?: string;
windowHeight?: string;
borderRadius?: string;
spacing?: string;
headerHeight?: string;
// Toggle Button (mode: window)
toggleSize?: string; // Tamanho do botão (ex: "60px")
toggleBorderRadius?: string; // Arredondamento (ex: "50%" para círculo, "12px" para quadrado)
messageMaxWidth?: string;
messageBorderRadius?: string;
transitionDuration?: string; // Duração das animações (ex: "0.3s")
};
typography?: {
titleFontSize?: string;
subtitleFontSize?: string;
messageFontSize?: string;
messageLineHeight?: string;
subtitleLineHeight?: string;
};
}
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
// Tema customizado
theme={{
general: {
title: "Suporte Premium",
subtitle: "Online 24/7",
fontFamily: "Inter, sans-serif",
},
colors: {
headerBackground: "#6366f1",
messageUserBackground: "#8b5cf6",
messageBotBackground: "#f3f4f6",
},
layout: {
windowWidth: "450px",
windowHeight: "650px",
borderRadius: "1rem",
},
}}
// Sobrescritas específicas via customStyles (opcional)
customStyles={{
header: {
background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
},
}}
/>
defaultTheme (base)
↓
theme (suas customizações)
↓
customStyles (sobrescritas específicas)
↓
Estilos finais renderizados
O widget é totalmente responsivo e se adapta automaticamente a diferentes tamanhos de tela:
Dimensões Responsivas:
min(420px, calc(100vw - 40px)) - Adapta a telas pequenas com margem de 20pxmin(600px, calc(100vh - 100px)) - Acompanha altura da viewport com margem de 50px topo/basemin(75%, 280px) - Largura máxima garante legibilidade em mobileSafe Areas (Notch/Rounded Corners):
env(safe-area-inset-*) em iOS/Android modernosMobile-First:
// Em mobile (< 380px viewport):
// - Widget ocupa 100vw - 40px (deixa margem)
// - Altura se ajusta a 100vh - 100px
// Em desktop (> 420px viewport):
// - Widget usa tamanho fixo 420x600px
// - Comportamento padrão mantido
Customização de Dimensões:
<ChatWidget
theme={{
layout: {
// Sobrescrever dimensões responsivas
windowWidth: "min(500px, 95vw)", // Widget maior
windowHeight: "min(700px, 85vh)", // Mais alto
},
}}
/>
Dica para Mobile: Se o widget parecer muito grande em mobile, ajuste as margens:
theme={{
layout: {
windowWidth: "min(420px, calc(100vw - 60px))", // Margem 30px
windowHeight: "min(600px, calc(100vh - 120px))", // Margem 60px
},
}}
O widget suporta GitHub Flavored Markdown completo:
**negrito**, *itálico*, ~~riscado~~[texto](url)`inline` ou blocos com ```1.) e não-ordenadas (-)> texto\n (quebra simples) ou \n\n (novo parágrafo)
remark-breaks ativado - \n simples quebra linha automaticamente<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
enableMarkdown={true}
initialMessage={
"Olá! 👋 Eu suporto **Markdown**!\n" +
"Esta é uma nova linha.\n\n" +
"Experimente:\n" +
"- `código inline`\n" +
"- **negrito** e *itálico*\n" +
"- [links clicáveis](https://example.com)"
}
/>
⚠️ Importante sobre quebras de linha:
Em JSX, use {...} com concatenação de strings JavaScript:
✅ Correto:
initialMessage={
"Linha 1\n" +
"Linha 2\n\n" +
"Parágrafo 2"
}
❌ Incorreto:
initialMessage = "Linha 1\nLinha 2"; // \n pode não ser interpretado corretamente
Resultado renderizado:
Olá! 👋 Eu suporto Markdown!
Esta é uma nova linha.
Experimente:
- código inline
- negrito e itálico
- links clicáveis
1. Copie e cole este código no final do <body> do seu HTML:
<!-- Configuração do Widget -->
<script>
window.ChatWidgetConfig = {
// Obrigatório
loadMessagesUrl: "https://api.exemplo.com/api/messages",
sendMessageUrl: "https://api.exemplo.com/api/send",
// Internacionalização (obrigatório)
i18n: {
pt: {
title: "Chat de Suporte",
subtitle: "Estamos aqui para ajudar!",
footer: "",
getStarted: "Iniciar Chat",
inputPlaceholder: "Digite sua mensagem...",
initialMessages: ["Olá! Como posso ajudar?"],
},
en: {
title: "Support Chat",
subtitle: "We are here to help!",
footer: "",
getStarted: "Start Chat",
inputPlaceholder: "Type your message...",
initialMessages: ["Hello! How can I help you?"],
},
},
defaultLanguage: "pt",
// Recursos
enableMarkdown: true,
enablePolling: true,
refetchInterval: 3000,
// Welcome Screen & Mode (opcional)
showWelcomeScreen: false,
mode: "window",
// Session ID (opcional - útil para integração com autenticação)
sessionId: "user-session-123", // ou obtenha de cookies/localStorage
// Carregar sessão anterior (opcional - padrão: false)
loadPreviousSession: false, // true = mantém sessão entre reloads, false = sempre nova sessão
// Tema (opcional)
theme: {
colors: {
headerBackground: "#667eea",
messageUserBackground: "#764ba2",
},
},
// Callbacks (opcional)
onMessageSent: function (message) {
console.log("Mensagem enviada:", message);
},
onError: function (error) {
console.error("Erro:", error);
},
};
</script>
<!-- Script do Widget via CDN -->
<!-- Opção 1: unpkg (recomendado) -->
<script src="https://unpkg.com/@meerkat-coding/chat-widget@latest/dist/chat-widget.min.js"></script>
<!-- Opção 2: jsDelivr -->
<!-- <script src="https://cdn.jsdelivr.net/npm/@meerkat-coding/chat-widget@latest/dist/chat-widget.min.js"></script> -->
2. Pronto! O widget aparecerá automaticamente no canto inferior direito.
Para gerar os arquivos de embed:
npm run build # Gera dist/chat-widget.js e chat-widget.min.js
Tamanho do Bundle:
chat-widget.min.js: ~80KB (~25KB gzip)O projeto inclui um gerador visual de código em http://localhost:5173. Role a página para baixo para ver a seção "🔗 Embed Code Generator" onde você pode:
Veja example/public/embed-demo.html para uma demonstração funcional do widget incorporado em HTML puro.
{
loadMessagesUrl: string; // GET endpoint para carregar mensagens
sendMessageUrl: string; // POST endpoint para enviar mensagens
}
{
i18n: I18nConfig; // Configuração de idiomas (obrigatório)
defaultLanguage?: string; // Idioma padrão (padrão: "en")
}
interface I18nConfig {
[language: string]: I18nTranslations;
}
interface I18nTranslations {
title: string; // Título do header
subtitle: string; // Subtítulo do header
footer: string; // Texto do footer
getStarted: string; // Texto do botão "Get Started" (welcome screen)
inputPlaceholder: string; // Placeholder do input
initialMessages?: string[]; // Mensagens iniciais do bot (array)
}
{
// UI
showWelcomeScreen?: boolean; // Mostrar tela de boas-vindas (padrão: false)
mode?: 'window' | 'fullscreen'; // Modo de exibição (padrão: 'window')
// - window: botão flutuante minimizado, clique para expandir
// - fullscreen: chat sempre aberto
icon?: React.ReactNode | string; // Ícone customizado (ReactNode, URL, SVG, emoji ou base64)
// Dados
initialMessages?: Message[]; // Mensagens pré-carregadas (além das do i18n)
// Comportamento
enableMarkdown?: boolean; // Ativa Markdown (padrão: true)
showTimestamps?: boolean; // Exibir timestamps nas mensagens (padrão: true)
enablePolling?: boolean; // Ativa polling automático (padrão: true)
refetchInterval?: number; // Intervalo de polling em ms (padrão: 3000)
authHeader?: AuthConfig; // Configuração de autenticação
sessionId?: string; // Session ID fixo (prioridade sobre generateSessionId)
generateSessionId?: () => string; // Gerador de session ID customizado
loadPreviousSession?: boolean; // Carregar sessão anterior (padrão: false)
}
{
theme?: ChatWidgetTheme; // Tema estruturado (50+ propriedades)
customStyles?: ChatWidgetStyles; // Sobrescritas CSS específicas
}
{
onMessageSent?: (message: Message) => void;
onMessagesLoaded?: (messages: Message[]) => void;
onError?: (error: Error) => void;
onBlockStateChange?: (isBlocked: boolean) => void;
}
Headers:
X-Session-ID: uuid-da-sessao
Authorization: Bearer token (opcional)
Response:
{
"messages": [
{
"id": "msg-1",
"message": "Olá!",
"sender": "customer",
"timestamp": "2025-01-01T12:00:00Z",
"type": "text"
},
{
"id": "msg-2",
"message": "Oi! Como posso ajudar?",
"sender": "system",
"timestamp": "2025-01-01T12:00:01Z",
"type": "text"
}
],
"blockNewMessages": false
}
Headers:
X-Session-ID: uuid-da-sessao
Authorization: Bearer token (opcional)
Content-Type: application/json
Body:
{
"message": "Preciso de ajuda",
"sessionId": "uuid-da-sessao",
"timestamp": "2025-01-01T12:00:02Z",
"idempotencyKey": "msg-uuid-da-sessao-1234567890"
}
Response:
{
"success": true,
"message": {
"id": "msg-uuid-da-sessao-1234567890",
"message": "Preciso de ajuda",
"sender": "customer",
"timestamp": "2025-01-01T12:00:02Z",
"type": "text"
},
"blockNewMessages": true
}
O campo blockNewMessages controla o estado do input:
true: Input desabilitado (bot processando)false: Input habilitadoO widget automaticamente gera e envia idempotency keys para prevenir mensagens duplicadas em cenários de race condition.
Como funciona:
msg-{sessionId}-{timestamp}idempotencyKey do payload POST /api/sendidempotencyKey como id da mensagemImplementação no Backend:
app.post('/api/send', (req, res) => {
const { message, sessionId, idempotencyKey } = req.body;
// Verifica se já existe mensagem com este ID (previne duplicata)
const existingMessage = findMessageById(sessionId, idempotencyKey);
if (existingMessage) {
// Retorna mensagem existente (evita duplicata)
return res.json({
success: true,
message: existingMessage,
blockNewMessages: false
});
}
// Salva nova mensagem usando idempotencyKey como ID
const newMessage = saveMessage({
id: idempotencyKey, // ← Usa idempotency key como ID
message,
sessionId,
timestamp: new Date().toISOString(),
sender: 'customer',
type: 'text'
});
res.json({
success: true,
message: newMessage,
blockNewMessages: false
});
});
Benefícios:
Backward Compatibility:
O campo idempotencyKey é opcional no payload. Se o backend não implementar idempotency, o widget continuará funcionando normalmente (mas sem a proteção contra duplicatas).
Gerencia sessionID com localStorage.
const {
sessionId, // UUID da sessão
resetSession, // Gera novo sessionID
clearSession // Remove da localStorage
} = useSessionId(customGenerator?);
Carrega e faz polling de mensagens.
const {
messages, // Array de mensagens
isLoading, // Estado de loading
isBlocked, // Estado de bloqueio
error, // Erro (se houver)
loadMessages, // Função para recarregar
addMessage, // Adiciona mensagem local
updateMessage, // Atualiza mensagem
removeMessage, // Remove mensagem
setBlockState, // Define estado de bloqueio
startPolling, // Inicia polling
stopPolling, // Para polling
} = useMessages({
loadMessagesUrl,
sessionId,
authConfig,
refetchInterval,
enablePolling,
onMessagesLoaded,
onError,
onBlockStateChange,
});
Envia mensagens para o backend.
const {
sendMessage, // Função de envio
isSending, // Estado de envio
error, // Erro (se houver)
clearError, // Limpa erro
} = useSendMessage({
sendMessageUrl,
sessionId,
authConfig,
onSuccess,
onError,
});
Processa tema e gera estilos CSS.
const {
theme, // Tema processado (merged)
styles // Estilos CSS prontos
} = useTheme(theme?, customStyles?);
Componente principal all-in-one.
<ChatWidget {...props} />
Header com título, subtítulo e ícone.
<Header
title="Chat"
subtitle="Online"
icon={<Icon />} // Ou URL, emoji, SVG ou base64
containerStyle={...}
titleStyle={...}
subtitleStyle={...}
iconStyle={...}
/>
Lista de mensagens com scroll automático.
<MessageList
messages={messages}
isLoading={isLoading}
enableMarkdown={true}
customStyles={...}
/>
Renderiza conteúdo com Markdown.
<MessageContent
content="**Olá**!"
enableMarkdown={true}
isUser={false}
customStyles={...}
/>
Input com validação e estado.
<MessageInput
onSend={handleSend}
isDisabled={isBlocked}
isSending={isSending}
placeholder="Digite..."
customStyles={...}
/>
<ChatWidget
loadMessagesUrl="https://api.exemplo.com/messages"
sendMessageUrl="https://api.exemplo.com/send"
i18n={{
en: {
title: "Support Chat",
subtitle: "We're online!",
footer: "",
getStarted: "Start Chat",
inputPlaceholder: "Type your message...",
},
}}
/>
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
en: {
title: "Support Chat",
subtitle: "We're online!",
footer: "",
getStarted: "Start Chat",
inputPlaceholder: "Type your message...",
},
}}
authHeader={{
headerName: "Authorization",
headerValue: "Bearer seu-token-jwt",
}}
/>
Opção A: Session ID Fixo (recomendado quando você já tem o ID)
const userSessionId = "user-123-abc-xyz";
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
en: {
title: "Support Chat",
subtitle: "We're online!",
footer: "",
getStarted: "Start Chat",
inputPlaceholder: "Type your message...",
},
}}
sessionId={userSessionId}
/>;
Opção B: Função Geradora (quando precisa gerar dinamicamente)
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
en: {
title: "Support Chat",
subtitle: "We're online!",
footer: "",
getStarted: "Start Chat",
inputPlaceholder: "Type your message...",
},
}}
generateSessionId={() => `user-${userId}-${Date.now()}`}
/>
Hierarquia de Prioridade:
sessionId (prop direta) - se fornecido, usa elegenerateSessionId() (função) - se fornecida, chama elaComportamento do loadPreviousSession:
false (padrão): Sempre cria uma nova sessão ao carregar o widgettrue: Mantém a sessão entre reloads usando localStoragesessionId fixo não é fornecido<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
pt: {
title: "Suporte Premium",
subtitle: "Atendimento VIP 24/7",
footer: "",
getStarted: "Iniciar",
inputPlaceholder: "Digite...",
},
}}
theme={{
general: {
fontFamily: "Inter, sans-serif",
},
colors: {
headerBackground: "#6366f1",
messageUserBackground: "#8b5cf6",
},
layout: {
windowWidth: "500px",
borderRadius: "16px",
},
}}
/>
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
pt: {
title: "Suporte",
subtitle: "Estamos aqui!",
footer: "",
getStarted: "Iniciar",
inputPlaceholder: "Digite...",
},
}}
// React Component
icon={<MyCustomIcon />}
// OU URL de imagem
icon="https://exemplo.com/chat-icon.png"
// OU Emoji
icon="💬"
// OU SVG inline
icon={
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M20 2H4C2.9 2 2 2.9 2 4V22L6 18H20C21.1 18 22 17.1 22 16V4C22 2.9 21.1 2 20 2Z" />
</svg>
}
// OU Base64
icon="data:image/png;base64,iVBORw0KG..."
/>
Formatos suportados:
<Component /> ou <svg>...</svg>"https://..." ou "//cdn.example.com/icon.png""💬", "Chat", etc."data:image/png;base64,..."Controlando o tamanho e padding do ícone:
<ChatWidget
icon="💬"
theme={{
general: {
iconSize: "32px", // Tamanho padrão: "24px"
iconPadding: "4px", // Padding padrão: "0"
},
}}
/>
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
en: {
title: "Support",
subtitle: "We're here!",
footer: "",
getStarted: "Start",
inputPlaceholder: "Type...",
},
}}
onMessageSent={(msg) => {
console.log("Enviada:", msg);
analytics.track("message_sent");
}}
onMessagesLoaded={(msgs) => {
console.log("Carregadas:", msgs.length);
}}
onError={(error) => {
Sentry.captureException(error);
}}
onBlockStateChange={(blocked) => {
console.log("Bloqueado:", blocked);
}}
/>
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
pt: {
title: "Chat de Suporte",
subtitle: "Online agora!",
footer: "",
getStarted: "Iniciar",
inputPlaceholder: "Digite sua mensagem...",
initialMessages: [
"Olá! 👋",
"Experimente enviar:\n- **negrito**\n- *itálico*\n- `código`\n- [links](https://exemplo.com)",
],
},
en: {
title: "Support Chat",
subtitle: "Online now!",
footer: "",
getStarted: "Start",
inputPlaceholder: "Type your message...",
initialMessages: [
"Hello! 👋",
"Try sending:\n- **bold**\n- *italic*\n- `code`\n- [links](https://example.com)",
],
},
}}
defaultLanguage="pt"
enableMarkdown={true}
/>
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
en: {
title: "Support",
subtitle: "24/7 Available",
footer: "",
getStarted: "Start",
inputPlaceholder: "Type...",
},
}}
theme={{
colors: {
headerBackground: "#1f2937",
headerText: "#f9fafb",
lightBackground: "#111827",
darkBackground: "#030712",
messageUserBackground: "#3b82f6",
messageBotBackground: "#374151",
messageBotText: "#e5e7eb",
inputBackground: "#1f2937",
inputText: "#f9fafb",
inputBorder: "#4b5563",
},
}}
/>
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
pt: {
title: "Bem-vindo!",
subtitle: "Clique para iniciar o chat",
footer: "",
getStarted: "Começar Conversa",
inputPlaceholder: "Digite...",
initialMessages: ["Olá! Estou aqui para ajudar."],
},
}}
showWelcomeScreen={true}
mode="fullscreen"
/>
<ChatWidget
loadMessagesUrl="..."
sendMessageUrl="..."
i18n={{
pt: {
title: "Suporte",
subtitle: "Estamos online",
footer: "",
getStarted: "Iniciar",
inputPlaceholder: "Digite sua mensagem...",
initialMessages: ["Olá! Como posso ajudar?"],
},
}}
mode="window"
theme={{
general: {
// Ícone customizado do botão flutuante
widgetIcon: (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
>
<path d="M12 2L2 7l10 5 10-5-10-5z" />
<path d="M2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
),
},
colors: {
toggleBackground: "#10b981", // Verde
toggleHover: "#059669", // Verde escuro no hover
toggleIconStroke: "#ffffff", // Ícone branco
},
layout: {
toggleSize: "64px", // Botão maior
toggleBorderRadius: "16px", // Quadrado arredondado (use "50%" para círculo)
transitionDuration: "0.3s", // Animações suaves
},
}}
/>
Customizações disponíveis para o botão flutuante:
toggleBackground: Cor de fundotoggleHover: Cor no hover (com efeito de escala automático)toggleIconStroke: Cor do ícone SVGtoggleSize: Tamanho do botãotoggleBorderRadius: "50%" para círculo, "12px" para quadrado arredondadowidgetIcon: ReactNode customizado para o íconeAnimações automáticas:
function CustomLayout() {
const { sessionId, resetSession } = useSessionId();
const { messages, isLoading, isBlocked, loadMessages } = useMessages({...});
const { sendMessage, isSending } = useSendMessage({...});
return (
<div className="custom-layout">
<header>
<h1>Suporte</h1>
<button onClick={resetSession}>Nova Conversa</button>
<button onClick={loadMessages}>Recarregar</button>
</header>
<aside>
<p>Session: {sessionId}</p>
<p>Mensagens: {messages.length}</p>
<p>Status: {isBlocked ? '🔒' : '✅'}</p>
</aside>
<main>
<MessageList messages={messages} isLoading={isLoading} enableMarkdown />
</main>
<footer>
<MessageInput
onSend={sendMessage}
isDisabled={isBlocked}
isSending={isSending}
/>
</footer>
</div>
);
}
src/
├── components/
│ └── ChatWidget/
│ ├── ChatWidget.tsx # Componente principal
│ ├── MessageList.tsx # Lista de mensagens
│ ├── MessageInput.tsx # Input de envio
│ ├── MessageContent.tsx # Renderização de Markdown
│ ├── Header.tsx # Header com título/subtitle
│ └── types.ts # Tipos dos componentes
│
├── hooks/
│ ├── useSessionId.ts # Gerenciamento de sessão
│ ├── useMessages.ts # Carregamento de mensagens
│ ├── useSendMessage.ts # Envio de mensagens
│ └── useTheme.ts # Processamento de tema
│
├── utils/
│ ├── sessionStorage.ts # Utilitários de sessão
│ └── api.ts # Cliente HTTP
│
├── types/
│ └── index.ts # Tipos globais
│
├── themes/
│ └── defaultTheme.ts # Tema padrão
│
├── embed.tsx # Entry point standalone
└── index.ts # Entry point NPM
ChatWidget (Container)
↓
useSessionId → Cria/recupera sessionID
↓
useMessages → Carrega mensagens + polling
↓
useSendMessage → Envia mensagens
↓
MessageList + MessageInput (Presentational)
Totalmente tipado! Importe os tipos:
import type {
// Componentes
ChatWidgetProps,
ChatWidgetTheme,
ChatWidgetStyles,
// Data
Message,
// API
AuthConfig,
LoadMessagesResponse,
SendMessageResponse,
// Hooks
UseMessagesOptions,
UseSendMessageOptions,
} from "@meerkat-coding/chat-widget";
# Instalar dependências
npm install
# Build da biblioteca
npm run build # Build completo (NPM + standalone)
npm run build:lib # Apenas NPM
npm run build:embed # Apenas standalone
# Desenvolvimento
npm run dev # Inicia servidor mock + exemplo
npm run dev:server # Apenas servidor mock
npm run dev:example # Apenas app de exemplo
dist/index.js - CommonJS (NPM)dist/index.esm.js - ES Modules (NPM)dist/chat-widget.js - IIFE standalone com sourcemapsdist/chat-widget.min.js - IIFE standalone minificadoO projeto inclui um ambiente completo de desenvolvimento em example/:
http://localhost:3001Acesse: http://localhost:5173
Correções:
iconSize - Removidos width/height 100% que faziam imagem ocupar todo o containericonHelper.tsx para renderização de imagensMelhorias:
iconPadding no tema - Controle o padding dos ícones do header
general.iconPadding ao theme (ex: "4px", "0.5rem")"0"Melhorias:
iconSize no tema - Controle o tamanho dos ícones do header via tema
general.iconSize ao theme (ex: "32px", "1.5em")"24px"iconPadding no tema - Controle o padding dos ícones do header
general.iconPadding ao theme (ex: "4px", "0.5rem")"0"Novas Features:
<MyIcon /> ou <svg>...</svg>"https://exemplo.com/icon.png""💬", "Chat", etc."data:image/png;base64,..."Melhorias:
renderIcon() para renderização inteligente de íconesCorreções:
isBlocked das dependências de loadMessages, usando functional setState para evitar leitura de estadoloadMessagesRef usado no polling para quebrar ciclo de dependênciasi18n e theme movidas para fora do componente, todos os callbacks memoizados com useCallbackPadrões defensivos aplicados:
Correções:
useMessages e useSendMessage para evitar recriação quando passado inlinestartPolling/stopPolling do useEffectCorreções:
useMessages e useSendMessage usando refs para evitar "Maximum update depth exceeded" no Next.js e outros frameworks React
onMessagesLoaded, onError, onBlockStateChange, onMessageSent, onSuccess agora são gerenciados via refsuseCallbacks internosCorreções:
& > *:first-child, & > *:last-child) que causavam erros no Next.js e outros frameworks ReactNovas Features:
widgetIcon)buttonBackground, buttonText)showTimestamps para mostrar/ocultar horários das mensagensMelhorias:
Novas Features:
sessionId - Passe session ID fixo diretamente como propremark-breaks - Quebras de linha com \n simples agora funcionamMelhorias:
Novas Features:
Melhorias:
Breaking Changes:
MIT
Contribuições são bem-vindas! Por favor:
git checkout -b feature/MinhaFeature)git commit -m 'Adiciona MinhaFeature')git push origin feature/MinhaFeature)Desenvolvido com ❤️
FAQs
Biblioteca modular de widgets de chat para React com arquitetura escalável
We found that @meerkat-coding/chat-widget demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.