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>
34 lines
1.6 KiB
JavaScript
34 lines
1.6 KiB
JavaScript
'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 }
|