Ext Ops Panel
Abrir el panel

Inicio/Docs/Comentarios

Contrato de comentarios

Versión del contrato 1.0.0 · gratis en todos los planes

Un pequeño diálogo que se puede abrir en cualquier página de su sitio o producto para contarle qué no funciona o qué se echa en falta. Pegue una etiqueta script o llame al endpoint desde un diálogo de diseño propio. Lo que llega aparece en un bloque del panel junto a la encuesta de desinstalación y Rate Us, y el propietario recibe un aviso en su bandeja de entrada.

↓ Descargar OpenAPI 3.1 (YAML) /feedback-openapi.yaml · info.version 1.0.0

1. Pegue el widget

Una sola etiqueta script diferida, sin código en línea, así que funciona con una CSP estricta:

<script src="https://extops.dev/widget.js"
        data-extops-widget="wk_XXXXXXXXXXXXXXXXXXXXXXXXXXXX" defer></script>

Permita nuestro origen en script-src y connect-src. El archivo lo servimos nosotros, no tiene dependencias y pesa menos de 7 KB gzip; sus estilos pasan por el CSSOM dentro de un shadow root, así que no necesita 'unsafe-inline' y el CSS de su página no puede deformar el diálogo.

Atributos opcionales: data-locale, data-api, data-position="left" y data-button="false" cuando quiera abrirlo usted mismo con window.ExtOpsFeedback.open(). Además: data-button-style="icon" para un botón redondo de icono de 48 px, data-surface="app" para elevarlo sobre la navegación inferior de una app en el teléfono, y data-color-preset para fijar el color del botón desde la página.

Tema claro y oscuro

Cada preset de color se dibuja en un tema claro y en uno oscuro. Por defecto (data-theme="auto") el widget sigue a su página: <html data-theme>, una clase dark o light en <html>, o su color-scheme. Una página sin tema propio recibe el tema del sistema operativo del visitante. Cuando su selector de tema cambia una de estas propiedades, el widget se repinta al instante, sin recargar.

Para fijar el tema, ponga data-theme="light" o data-theme="dark". Si su página guarda el tema donde el widget no puede verlo, llame a window.ExtOpsFeedback.setTheme('dark') desde su selector, y a setTheme('auto') para volver a seguir la página.

2. O llame usted mismo al endpoint

Para un sitio que no permite ningún script de terceros o un producto que quiere el formulario dentro de su propio sistema de diseño. Es el mismo endpoint al que envía nuestro widget, así que nunca tiene que elegir entre su interfaz y nuestro pipeline.

await fetch('https://extops.dev/api/v1/feedback/widget', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    widget_key: 'wk_XXXXXXXXXXXXXXXXXXXXXXXXXXXX',
    topic: 'bug',
    comment: userText,
    page: location.href,
    locale: navigator.language,
    event_id: crypto.randomUUID(),
    dwell_ms: Date.now() - openedAt,
  }),
});

Lea los temas que configuró el propietario desde GET /api/v1/feedback/widget/config?key=… para que su diálogo ofrezca exactamente lo que ofrece el panel. Reutilice un mismo event_id en los reintentos: el servidor deduplica por él, así que una red inestable no convierte un mensaje en dos.

3. La clave del widget es pública a propósito

Aparece en el código fuente de su página, y está bien: su único poder es crear una fila de comentarios. No puede escribir eventos de instalación o desinstalación, no puede leer nada más allá de la configuración del diálogo y no puede nombrar un proyecto (no se acepta project_id en el cuerpo). Por eso un widget tiene su propia clave en lugar del webhook_token de su fuente, que puede escribir eventos de métricas y nunca debe aparecer en el HTML. Puede volver a emitir la clave en cualquier momento; la anterior deja de aceptarse al instante.

4. Qué guardamos y qué no

Guardamos el tema, el texto, la página normalizada, el idioma, un contacto opcional, la hora, el tipo de comentario y qué widget lo recogió.

La conservación es de 180 días, la misma regla que para cualquier otro tipo de comentario.

5. Respuestas y códigos de error

Un éxito se ve así: {"status":"ok","recorded":true}. recorded: false es también es un éxito: el envío era un duplicado o se activó una heurística antibots. Muestre el mismo agradecimiento en ambos casos: un script no debe saber qué regla lo detectó, y quien reintenta tras un timeout no debe ver un error por un mensaje que ya tenemos.

HTTPcódigoCuándo
400validation_errorsin clave, sin tema, un tema que el widget no ofrece, un comentario vacío, un cuerpo mal formado
403origin_not_allowedel origen de la página no está en la lista permitida del widget
404not_foundclave desconocida o un widget desactivado: la misma respuesta, para que una clave pública no pueda sondear qué proyectos existen
429rate_limited60/hora por clave, 10/hora por IP; incluye Retry-After
401source_token_invalidsolo la vía de servidor: token de fuente incorrecto
500internal_errorerror nuestro

6. Temas, tipos y estados

Un tema es un id más un label. El id es un slug porque se guarda en cada fila y se convierte en el valor de filtro del panel; la etiqueta la escribe el propietario. Un widget sin temas configurados ofrece los predeterminados: bug, idea, question, other.

El color del diálogo es uno de seis preajustes compartidos con las páginas alojadas: amber (el predeterminado), teal, indigo, violet, rose, slate. Una sola lista a propósito: el widget y las páginas alojadas del mismo propietario deben parecerse, y una segunda paleta acabaría separándose de la primera.

Las filas guardadas tienen un tipo: uninstall, rate_us o widget. El tipo es una ETIQUETA, no un pipeline aparte: el bloque del panel, las notificaciones y la conservación son comunes. El estado de revisión del propietario es new, read o spam; marcar como spam saca la fila de la lista de trabajo y de las notificaciones sin borrar lo que escribió la persona, y se puede deshacer.

7. La protección contra abusos está en el servidor

Como la clave es pública, una comprobación en el navegador sería un consejo, no una regla. La aplica el servidor: la lista de orígenes permitidos del widget (vacía significa cualquier origen, porque un cliente puede servir un sitio desde una docena de subdominios), dos límites de tasa independientes (por clave y por IP), un campo honeypot y un tiempo mínimo de permanencia, y deduplicación por (widget_id, event_id) en la base de datos en lugar de una comprobación previa, porque dos reintentos simultáneos pasarían ambos un SELECT.

Ambos limitadores fallan abiertos: si nuestro Redis cae, el comentario de una persona le sigue llegando. Para un endpoint que escribe un párrafo, es el mal menor. No añadimos un captcha externo, que rompería su CSP y la privacidad de sus visitantes.

8. El contrato legible por máquina

La descripción formal de los dos endpoints de recepción, la lectura de configuración, los enums y los códigos de error es el archivo OpenAPI 3.1. Lleva la misma versión de contrato (1.0.0).

↓ Descargar OpenAPI 3.1 (YAML) /feedback-openapi.yaml