Mesure du temps actif, événements et bugs pour rémunérer les testeurs (Web, RGPD, anti-fraude).
📊 SDK JavaScript léger pour mesurer le temps réellement actif sur une application web, collecter des événements métier, permettre aux testeurs de soumettre des bugs, et alimenter un serveur pour rémunérer automatiquement le temps/bounties.
* ✅ Web SDK (Vanilla JS, ESM, CJS, IIFE pour CDN)
* ✅ Adapter React ( + useMetrics)
* ✅ Respect RGPD & Do Not Track
* ✅ Anti-fraude : inactivité, onglets multiples, jitter, rythme plausible
* ✅ Transport fiable (sendBeacon → fetch + queue offline)
* ✅ Identify + JWT optionnel (propagation userId, rate policy)
* ✅ Screenshot auto pour bugs critiques (opt-in)
* ✅ Poids < 12 kB gzip (core sans bug reporter UI)
---
* Sessions : ouverture/fermeture automatique, session.start & session.end.
* Heartbeats : signaux réguliers envoyés seulement si actif (onglet visible + interaction récente).
* Événements : trackEvent(name, props) pour instrumenter vos étapes métier.
* Bugs : reportBug({title, description, severity}) pour collecter les remontées des testeurs.
* Identify : user.profile obligatoire pour appeler /identify avant ingestion.
* Sampling : taux configurables pour heartbeats/events/bugs.
* Offline : queue persistante (configurable).
* Observability : hooks onTransportSuccess/onTransportError.
Consentement RGPD : démarre en pending*, active la mesure après setConsent.
* Anti-fraude :
* pause si onglet caché,
* pause si inactif > X secondes,
* un seul onglet “leader” envoie les heartbeats,
* jitter sur l’intervalle pour éviter les patterns parfaits.
* Transport : batching, retries, offline queue, sendBeacon en priorité.
* Interopérabilité : IIFE (CDN global window.PouletMetrics), ESM/CJS (import { init } from 'poulet'), React (poulet/react).
---
``bash`
npm install poulet
`html`
---
`html`
---
`tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { MetricsProvider, useMetrics } from 'poulet/react';
function Demo() {
const { trackEvent, reportBug, setConsent } = useMetrics();
return (
createRoot(document.getElementById('root')!)
.render(
endpoint: 'https://collector.example.com',
user: { id: 'user_42', profile: { pseudo: 'Lina' } },
consent: { default: 'pending' },
privacy: { dntRespect: true }
}}>
);
`
---
Initialise le SDK.
Principaux champs :
`ts`
{
projectKey: string, // Identifiant projet
endpoint: string, // URL collector
consent?: { default:'pending'|'granted'|'denied'; requireAnalytics?: boolean },
user: { id?: string; testerPseudoId?: string; profile: UserProfile }, // profile obligatoire pour /identify
billing?: { heartbeatSec?:number; idleSec?:number; maxHoursPerDay?:number },
privacy?: { piiFilter?:boolean; dntRespect?:boolean; countryHint?:string },
security?: { hmacPublicKey?:string; authSecret?:string; tokenTtlSec?:number },
features?: { bugReporter?:boolean; replay?:boolean; sentryBridge?:boolean },
sampling?: { heartbeats?: number; events?: number; bugs?: number },
offline?: { enabled?: boolean; maxItems?: number; maxAgeSec?: number },
observability?: {
onTransportError?: (info: TransportErrorInfo) => void;
onTransportSuccess?: (info: TransportSuccessInfo) => void;
},
screenshot?: {
enabled?: boolean;
selector?: string;
maxBytes?: number;
mime?: 'image/jpeg' | 'image/png';
quality?: number;
minQuality?: number;
},
debug?: boolean
}
Active ou désactive la collecte selon le consentement.
Déclare un événement métier.
Envoie un bug (texte + option capture/pièce jointe).
Si screenshot.enabled=true, une capture auto peut etre ajouteemajor
pour les severites ou blocker. La zone capturee est cibleedata-poulet-screenshot
par (configurable via screenshot.selector).
Ferme la session et flush la queue.
Retourne l’ID de la session courante.
Stoppe la collecte et purge localement (utile pour RGPD).
---
* session.start → ouverture d’une session (id, ua, tz, consentFlags).heartbeat
* → ping régulier si actif.event
* → événement métier.bug
* → bug report.session.end
* → fermeture volontaire ou pagehide.idle.start
* / idle.end → changements d'inactivite.
---
* Actif uniquement si : onglet visible + interaction récente (< idleSec).
* Un seul onglet leader émet.
* Jitter ±10% sur l’intervalle heartbeat.
* Contrôles côté serveur :
* rythme plausible (7s–45s),
* plafonds d’heures/jour/testeur,
* rejet des heartbeats inactifs.
---
* Consentement requis (setConsent).navigator.doNotTrack === "1"
* Respect DNT ().forgetMe()
* Minimisation : pas de PII, identifiants pseudonymisés.
* Droits utilisateurs : côté SDK, endpoint côté serveur pour effacement complet.
* DPA requis si usage de SaaS tiers (Sentry, PostHog).
---
* Envoi via sendBeacon si possible, sinon fetch.security.authSecret
* Batching (par 10–20 événements).
* Queue offline (IndexedDB en option).
* Retry exponentiel.
* Auth optionnelle : si est fournie, le SDK signe un JWT HS256 (sub=userId) envoyé en Authorization: Bearer (fetch) et dans le body access_token pour les transports sans headers.user.profile
* est requis pour appeler /identify avant ingestion. Si absent, init() est ignore.
---
`js`
app.post('/ingest', (req,res) => {
const { projectKey, items } = req.body || {};
items.forEach(it => console.log(it));
res.json({ ok: true, received: items.length });
});
---
`js`
minutes = (#heartbeats_valides × heartbeatSec) / 60
Valide → active:true, vis:true, idle:false + intervalle plausible.
---
Disponible via :
* jsDelivr :
https://cdn.jsdelivr.net/npm/poulet/dist/index.global.jshttps://unpkg.com/poulet/dist/index.global.js
* unpkg :
Ajoute un SRI hash en prod pour renforcer la sécurité.
---
`bash`
npm run build
npm publish --access public
* Exports :
* poulet → core ESM/CJSpoulet/react
* → adapter Reactpoulet/iife
* → [bundle]() global pour