bug-report-client
v1.0.1
Published
Framework-agnostic bug report widget: FAB, element picker, DOM capture, modal and BFF multipart client
Maintainers
Readme
bug-report-client
Widget de feedback in-app, agnóstico a framework, para reportar bugs e sugerir melhorias com contexto visual.
O usuário seleciona um elemento na tela ou envia feedback geral, descreve o problema e o pacote envia screenshot + metadados para a sua API via multipart/form-data.
Documentação
| Documento | Descrição | |-----------|-----------| | Índice | Visão geral e navegação | | Arquitetura | Módulos, fluxo e diagramas | | Integração | React, Vue, Angular, vanilla JS | | Referência da API | Tipos, classes e configuração | | Backend | Contrato da API REST | | Segurança | PII, redação e políticas de acesso | | Desenvolvimento | Build e publicação npm |
Recursos
- FAB flutuante com label no hover e cor configurável (
accentColor) - Seleção visual opcional de elemento com banner de instrução e botão para enviar sem selecionar
- Screenshot do elemento ou da viewport (
html-to-image, fundo branco, sem UI do widget na captura geral) - Modal responsivo para revisão, tipo de report e descrição
- Redação de PII e políticas de acesso (auth, steps, perfil de segurança)
- API headless para integrações customizadas (sem UI embutida)
- TypeScript com tipos exportados
Compatível com React, Vue, Svelte, Angular e vanilla JS.
Instalação
npm install bug-report-clienthtml-to-image já é dependência do pacote — não instale separadamente.
Requisitos: ambiente com DOM (browser). Node.js >= 18 apenas para build do pacote.
Início rápido
import { createBugReportWidget } from 'bug-report-client';
const widget = createBugReportWidget({
productId: 'my-app',
apiUrl: 'https://api.example.com',
environment: 'prod',
appVersion: '1.0.0',
enabled: true,
onSuccess: (reportId) => console.log('Enviado:', reportId),
onError: (error) => showToast(error.message), // apenas falha no POST /feedback/reports
});
widget.mount();Fluxo
- Usuário clica no FAB (label aparece ao passar o mouse)
- Vê o banner "O que podemos melhorar?" e pode:
- Clicar em um elemento → captura do trecho selecionado
- Enviar feedback sem selecionar → captura da viewport (sem UI do plugin)
- ESC → cancela silenciosamente (sem
onError)
- Revisa a captura no modal, escolhe Bug ou Melhoria e descreve
- O client envia
POST {apiUrl}/feedback/reportscom screenshot e metadados
Configuração
| Campo | Obrigatório | Descrição |
|-------|:-----------:|-----------|
| productId | sim | Identificador do produto no backend |
| apiUrl | sim | URL base da API (sem trailing slash) |
| environment | sim | Ambiente (dev, staging, prod, …) |
| appVersion | sim | Versão da aplicação host |
| enabled | não | Habilita ou desabilita o widget (padrão: true) |
| securityProfile | não | internal ou external |
| authMode | não | required ou optional |
| authProvider | não | Headers de autenticação assíncronos |
| metadataProviders | não | Metadados extras por produto |
| stepProvider | não | Step atual (para desabilitar em telas específicas) |
| disableOnSteps | não | Lista de steps onde o FAB fica oculto |
| captchaProvider | não | Token captcha opcional no envio |
| excludeSelectors | não | Seletores ignorados no picker |
| maxScreenshotSizeKb | não | Limite de tamanho (padrão: 2048) |
| showTechnicalMetadata | não | Exibe JSON técnico no modal |
| allowSkipSelection | não | Permite enviar sem selecionar elemento (padrão: true) |
| accentColor | não | Cor do FAB, botão skip e ações primárias (padrão: #E31C79) |
| fabLabel | não | Label do FAB no hover (padrão: Reportar bug ou sugerir melhoria) |
| instructionTitle / instructionSubtitle | não | Textos do banner no picker |
| skipSelectionLabel | não | Label do botão "enviar sem selecionar" |
| container | não | Elemento DOM pai do FAB (padrão: document.body) |
| onSuccess | não | Callback após envio bem-sucedido |
| onError | não | Callback somente quando POST /feedback/reports falha |
Autenticação
authProvider: {
async getAuthHeaders() {
const token = sessionStorage.getItem('token');
return token ? { Authorization: `Bearer ${token}` } : {};
},
},Metadados customizados
metadataProviders: [{
async collect() {
return {
module: 'billing',
route: location.pathname,
};
},
}],Atualizar visibilidade
Chame refresh() quando rota, step ou permissões mudarem:
widget.refresh();Ciclo de vida
widget.mount(); // injeta estilos e FAB
widget.destroy(); // remove FAB, modal e listenersReact
import { useEffect, useRef } from 'react';
import { createBugReportWidget, type BugReportWidget } from 'bug-report-client';
export function BugReportProvider() {
const widgetRef = useRef<BugReportWidget | null>(null);
useEffect(() => {
widgetRef.current = createBugReportWidget({
productId: 'my-app',
apiUrl: 'https://api.example.com',
environment: 'prod',
appVersion: '1.0.0',
});
widgetRef.current.mount();
return () => widgetRef.current?.destroy();
}, []);
return null;
}Com React Router, invoque widgetRef.current?.refresh() a cada mudança de rota.
Vue 3
import { onMounted, onUnmounted } from 'vue';
import { createBugReportWidget } from 'bug-report-client';
const widget = createBugReportWidget({
productId: 'my-app',
apiUrl: 'https://api.example.com',
environment: 'prod',
appVersion: '1.0.0',
});
onMounted(() => widget.mount());
onUnmounted(() => widget.destroy());API headless
Use os módulos de baixo nível para montar uma UI própria:
import {
BugReportFlowCancelledError,
ElementPicker,
captureElement,
captureViewport,
collectMetadata,
collectPageMetadata,
BugReportClient,
} from 'bug-report-client';
const picker = new ElementPicker();
try {
const result = await picker.pick();
if (result.kind === 'skipped') {
const capture = await captureViewport();
const metadata = await collectPageMetadata({
productId: 'my-app',
environment: 'prod',
appVersion: '1.0.0',
});
// enviar capture + metadata...
return;
}
const { element, selector } = result;
const capture = await captureElement(element);
const metadata = await collectMetadata(element, {
productId: 'my-app',
environment: 'prod',
appVersion: '1.0.0',
});
metadata.selectedElement!.selector = selector;
const client = new BugReportClient();
await client.submit(
{
type: 'bug',
description: 'Botão não responde ao clique.',
screenshot: capture.blob,
metadata,
},
{
productId: 'my-app',
apiUrl: 'https://api.example.com',
environment: 'prod',
}
);
} catch (error) {
if (error instanceof BugReportFlowCancelledError) {
return; // ESC — cancelamento intencional, sem notificação
}
throw error;
}Contrato com o backend
Endpoint: POST {apiUrl}/feedback/reports
Content-Type: multipart/form-data
| Campo | Tipo | Descrição |
|-------|------|-----------|
| type | string | bug ou improvement |
| description | string | Texto do usuário (até 2000 caracteres) |
| productId | string | ID do produto |
| environment | string | Ambiente de origem |
| metadata | string | JSON serializado (ReportMetadata) |
| screenshot | file | Imagem PNG |
| captchaToken | string | Opcional |
Resposta esperada (JSON):
{
"id": "uuid",
"status": "received",
"createdAt": "2026-01-15T12:00:00.000Z"
}O backend deve registrar o productId e aplicar as regras de segurança desejadas.
Desenvolvimento local (monorepo)
Para apontar para o código-fonte sem publicar no npm:
{
"compilerOptions": {
"paths": {
"bug-report-client": ["./path/to/bug-report-client/src/index.ts"]
}
}
}Licença
MIT — Macelo Melo
