Stand-in import of elffuss/code
One parentless commit with only the files that the open seed tasks touch and the modules they import. The full squashed import (SPEC 8.6) is a later step: this tree is not the cutoff tree. T2T-StandIn-Source-Commit: 43f95b7333efe73aeaebf806023f17bffea27aba
This commit is contained in:
63
web/js/telemetry.js
Normal file
63
web/js/telemetry.js
Normal file
@@ -0,0 +1,63 @@
|
||||
// Buzón opt-in de errores/feedback. Apagado por defecto — la promesa de
|
||||
// «nada sale de tu máquina» sigue siendo cierta salvo que el usuario decida
|
||||
// lo contrario en Ajustes. Cuando está activo:
|
||||
// - errores no capturados (window.onerror / unhandledrejection) y los
|
||||
// puntos concretos del código que llaman a reportError() se mandan a
|
||||
// /proxy/report (mismo origen, vía el proxy compartido de Elffuss)
|
||||
// - sendFeedback() manda texto libre que el usuario escriba a propósito
|
||||
// Nunca se manda código ni contenido del proyecto — solo el mensaje técnico,
|
||||
// la pila, la URL y el user-agent. Sin cola ni reintentos: si el envío
|
||||
// falla, se descarta sin más (evita que un fallo de red se convierta en
|
||||
// almacenamiento sin límite).
|
||||
let appName = 'elffuss';
|
||||
let enabled = false;
|
||||
let hooked = false;
|
||||
|
||||
const storageKey = () => 'elffuss.telemetry.' + appName;
|
||||
|
||||
export function isEnabled() { return enabled; }
|
||||
|
||||
export function setEnabled(v) {
|
||||
enabled = !!v;
|
||||
try { localStorage.setItem(storageKey(), enabled ? '1' : '0'); } catch { /* localStorage lleno/bloqueado */ }
|
||||
if (enabled) hookGlobalErrors();
|
||||
}
|
||||
|
||||
async function post(kind, message, opts = {}) {
|
||||
if (!enabled) return;
|
||||
try {
|
||||
await fetch('/proxy/report', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
app: appName, kind, message: String(message ?? '').slice(0, 2000),
|
||||
stack: String(opts.stack || '').slice(0, 4000),
|
||||
url: location.href, userAgent: navigator.userAgent,
|
||||
extra: String(opts.extra || '').slice(0, 2000),
|
||||
}),
|
||||
});
|
||||
} catch { /* sin conexión o proxy caído: se descarta, no hay reintento */ }
|
||||
}
|
||||
|
||||
export function reportError(message, opts = {}) { return post('error', message, opts); }
|
||||
export function sendFeedback(text) { return post('feedback', text); }
|
||||
|
||||
function hookGlobalErrors() {
|
||||
if (hooked) return;
|
||||
hooked = true;
|
||||
window.addEventListener('error', e => {
|
||||
reportError(e.message || String(e.error) || 'error', { stack: e.error?.stack || '' });
|
||||
});
|
||||
window.addEventListener('unhandledrejection', e => {
|
||||
const r = e.reason;
|
||||
reportError('unhandledrejection: ' + (r?.message || r), { stack: r?.stack || '' });
|
||||
});
|
||||
}
|
||||
|
||||
// Llamar UNA vez al arrancar, con el nombre de la app (para distinguir
|
||||
// elffuss-code de elffuss-claw en los informes) — lee la preferencia guardada.
|
||||
export function init(name) {
|
||||
appName = name || appName;
|
||||
try { enabled = localStorage.getItem(storageKey()) === '1'; } catch { enabled = false; }
|
||||
if (enabled) hookGlobalErrors();
|
||||
}
|
||||
Reference in New Issue
Block a user