gigafibre-fsm/services/targo-hub/lib/ratelimit.js
louispaulb 512c4a5f1b feat(ops): dispatch auto complet + perf Boîte/rapports + fix session
Planificateur « Suggérer » : 4 stratégies (smart / meilleurs d'abord / équilibré /
juste ce qu'il faut), compétences+niveaux par tech (édition inline), niveau requis
par compétence + par job, carte des tournées (1 couleur/tech, domicile→arrêts,
sélecteur de jour), fenêtre de dispatch auj.+demain (dates sélectionnées), règle
week-end + placeholder « en attente du quart », clustering + lasso + filtre-date
sur la carte, accès rapide « À assigner » (badge).

Boîte : liste /conversations allégée (45 Mo → ~1 Mo, 17×) + messages chargés à
l'ouverture. Rapports : cache SWR sur revenue-explorer (22×). Session : keep-alive
+ timeout fetch global + authFetch durci → fin des rechargements manuels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 08:49:35 -04:00

34 lines
1.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict'
// Rate-limiter EN MÉMOIRE (fenêtre glissante), par IP × bucket. Aucune dépendance, FAIL-OPEN (toute erreur =
// autorise). But : freiner l'énumération de tokens / brute-force OTP sur les routes PUBLIQUES sensibles, sans
// jamais bloquer un vrai usager (limites généreuses). Derrière Traefik → vraie IP dans X-Forwarded-For.
const buckets = new Map() // "ip|bucket" → [timestamps ms]
let _lastSweep = 0
function clientIp (req) {
const xff = String(req.headers['x-forwarded-for'] || '').split(',')[0].trim()
return xff || (req.socket && req.socket.remoteAddress) || 'unknown'
}
// true = la requête DOIT être bloquée (limite atteinte dans la fenêtre).
function limited (req, bucket, max, windowMs) {
try {
const key = clientIp(req) + '|' + bucket
const now = Date.now(); const cut = now - windowMs
let arr = buckets.get(key); if (!arr) { arr = []; buckets.set(key, arr) }
while (arr.length && arr[0] < cut) arr.shift()
if (arr.length >= max) return true
arr.push(now)
return false
} catch (e) { return false } // fail-open : ne jamais casser une requête à cause du limiteur
}
// Purge périodique des buckets vidés/anciens (borne la mémoire) — appelée à l'occasion, pas de timer.
function maybeSweep (windowMs) {
const now = Date.now(); if (now - _lastSweep < 60000) return; _lastSweep = now
const cut = now - Math.max(windowMs || 60000, 60000)
for (const [k, arr] of buckets) { while (arr.length && arr[0] < cut) arr.shift(); if (!arr.length) buckets.delete(k) }
}
module.exports = { limited, clientIp, maybeSweep }