Ce document explique comment Impact Trackers fonctionne techniquement : comment les trackers sont détectés (regex, patterns réseau), comment l'analyse se déroule étape par étape, comment la performance web est mesurée, et comment l'éco-conception est calculée indépendamment.
Public cible : développeurs, auditeurs techniques, curieux avancés.
Sommaire
Architecture Générale
Impact Trackers est composé de trois composants indépendants :
Interface utilisateur Vue 3 / Nuxt. Envoie une requête POST /api/analyze et lit la réponse en streaming SSE (Server-Sent Events) pour afficher la progression en temps réel.
Pages : index.vue, resultat/, definitions.vue, technique.vue
Orchestrateur principal. Reçoit les requêtes, lance Playwright, coordonne tous les services d'analyse et persiste les résultats en base de données PostgreSQL.
Fichier principal : main.py → route /api/analyze → analyzer.py
Stockage des analyses complètes (trackers, cookies, scores, données personnelles) via SQLAlchemy async. Permet de retrouver les analyses récentes et de partager les résultats via un ID unique.
Composant indépendant du backend Impact Trackers. Elle analyse l'HTML des sites via un fetch côté background script et écoute passivement le trafic réseau via chrome.webRequest.onBeforeRequest. Elle dispose de sa propre base de regex (plus simple) dans background.js.
Pipeline d'Analyse — Les 8 Étapes
Quand une URL est soumise, le backend analyzer.py orchestre une série d'étapes séquentielles. Chaque étape émet un événement SSE au frontend pour mettre à jour la barre de progression.
Génération d'un UUID unique pour l'analyse. Initialisation des variables. Normalisation de l'URL (ajout de https:// si absent).
Le service robots_parser.py télécharge et analyse le fichier /robots.txt du site. Il vérifie si la directive Disallow: / est présente pour User-agent: *. Si oui, le site est marqué comme "scrap interdit". Cette détection influence la décision d'afficher ou masquer les résultats.
Une requête GET est effectuée via httpx (client HTTP asynchrone, sans JavaScript). Les headers de réponse sont analysés (server, x-powered-by, content-security-policy, strict-transport-security). Les cookies définis directement dans les headers Set-Cookie sont extraits pour l'analyse ultérieure.
async with httpx.AsyncClient(
timeout=15.0,
follow_redirects=True,
headers={"User-Agent": HTTP_USER_AGENT},
) as client:
response = await client.get(url)
headers = dict(response.headers)
raw_set_cookies = response.headers.get_list("set-cookie")C'est l'étape la plus longue. Un navigateur Chromium headless (invisible) charge la page en deux phases distinctes. Voir la section dédiée pour les détails des délais.
La fonction detect_trackers(html, network_requests) du module tracker_detector.py applique toutes les regex de la base de données sur le HTML et les URLs réseau. Chaque tracker détecté est enrichi avec sa géolocalisation IP, l'analyse de son payload réseau, et son statut de consentement (pré/post/exempté).
Le module personal_data.py croise les trackers et les cookies détectés pour lister les catégories de données personnelles effectivement collectées (ip_address, unique_client_id, behavioral_profile, etc.).
Le module server_side_detector.py analyse les patterns réseaux, headers et sous-domaines pour détecter un éventuel tracking côté serveur (GTM SS, CNAME cloaking, Measurement Protocol).
Calcul du score de confidentialité (0–100), de l'impact performance (Main Thread cumulé), et du bilan éco-conception (EcoIndex global vs. propre, GES, eau). Puis sauvegarde en base de données et envoi de l'événement SSE final type: "done".
Playwright — Fonctionnement & Délais d'Attente
Playwright est une bibliothèque Python qui pilote un navigateur Chromium en mode headless (sans interface graphique). C'est le seul moyen fiable de détecter les trackers chargés dynamiquement par JavaScript (ce qu'une simple requête HTTP ne peut pas faire).
Pourquoi des délais d'attente ? Le problème du chargement asynchrone
Les scripts de tracking modernes ne se chargent pas tous immédiatement. Beaucoup sont chargés :
- Via un gestionnaire de balises (GTM) qui s'exécute après le DOM ;
- De manière lazy (au scroll, après un délai, au clic) ;
- Après l'acceptation du bandeau CMP ;
- Uniquement après que le framework JavaScript (React, Vue) a fini son initialisation.
Pour capturer tous ces cas, Playwright suit le séquencement précis suivant :
page.goto(url, wait_until="networkidle") : Playwright attend que le réseau soit au repos (networkidle = aucune requête réseau en cours depuis 500 ms).
Fallback : Si networkidle échoue (site trop actif), Playwright bascule sur wait_until="domcontentloaded".
Ensuite : attente de 2 000 ms supplémentaires pour laisser les scripts asynchrones s'initialiser (GTM, listeners, lazy-load initial).
PLAYWRIGHT_TIMEOUT)Les URLs réseau et les cookies à ce moment précis sont sauvegardés dans requests_pre et cookies_pre. Ce snapshot sert à identifier les trackers chargés sans consentement.
1. Clic CMP automatique : Impact Trackers tente de cliquer sur le bouton "Accepter tout" du bandeau cookies via une liste de 15+ sélecteurs CSS (Didomi, Axeptio, OneTrust, Cookiebot, sélecteurs génériques) + filtres textuels.
Après un clic réussi : attente de 3 000 ms pour que les scripts post-consentement s'initialisent.
Boucle d'attente CMP : 10 tentatives × 500 ms = 5 secondes max.
2. Simulation d'activité (_simulate_user_activity) : Scrolls progressifs (0 → 33% → 66% → 100% → retour à 0) + mouvements de souris simulés. Cela déclenche les scripts de lazy-loading (tracking du scroll, heatmaps, etc.).
3. Délai de sécurité post-consent : 2 500 ms supplémentaires pour les cookies et requêtes asynchrones post-consentement.
Récupération du HTML final (après exécution JavaScript complète) via page.content(). Toutes les requêtes réseau cumulées (pre + post) sont dans requests_all.
# Phase 1 : Chargement initial
await page.goto(url, timeout=30000, wait_until="networkidle")
await page.wait_for_timeout(2000) # ← +2s pour scripts asynchrones
# Snapshot pre-consentement
requests_pre = list(network_requests)
cookies_pre = await context.cookies()
# Phase 2 : Post-consentement
await _click_consent_banner(page) # ← 10 tentatives × 500ms + 3000ms après clic
await _simulate_user_activity(page) # ← Scrolls + mouvements souris
# Snapshot final
html = await page.content()
await page.wait_for_timeout(2500) # ← +2.5s pour cookies asynchrones
cookies_all = await context.cookies()
requests_all = list(network_requests)Configuration Playwright :
context = await browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
viewport={"width": 1280, "height": 800},
locale="fr-FR",
java_script_enabled=True,
ignore_https_errors=True,
)
# Arguments anti-détection bot
args=[
"--no-sandbox",
"--disable-dev-shm-usage",
"--disable-blink-features=AutomationControlled", # ← Cache le flag navigator.webdriver
]Mécanisme de Détection des Trackers
La détection est réalisée dans tracker_detector.py via la fonction detect_trackers(html, network_requests). Elle croise deux sources de données pour maximiser la couverture.
Source 1 — HTML statique
Deux stratégies sont appliquées :
- Extraction des
srcde balises<script>: Toutes les URLs de scripts externes sont extraites via une regex simple :re.findall(r'<script[^>]+src=["\']([^"\']+)["\']', html)
Chaque URL est ensuite testée contre les patterns du tracker. - Recherche inline dans le HTML brut : Si aucun match n'est trouvé dans les src, la regex est appliquée directement sur le contenu HTML complet (pour détecter les snippets inline, les configurations JavaScript).
Source 2 — Requêtes réseau (Playwright)
Chaque URL chargée par le navigateur (scripts, pixels, images, requêtes XHR/Fetch) est testée contre toutes les regex. C'est cette source qui permet de détecter les trackers chargés après le chargement initial (via GTM, lazy, post-consentement) que le HTML statique ne contient pas.
Extraction de l'ID de tag
Certains trackers ont un tag_pattern en plus des patterns de détection. Ce pattern secondaire (regex avec groupe capturant) extrait l'ID spécifique du tracker dans le contenu analysé :
# Google Tag Manager → extrait "GTM-ABCD123"
"tag_pattern": r"GTM-([A-Z0-9]{4,7})"
# Google Analytics → extrait "G-XXXXXXXXX" ou "UA-XXXXXX-X"
"tag_pattern": r"(?:G|UA)-[A-Z0-9]+-?\d*"
# Meta Pixel → extrait l'ID numérique du pixel
"tag_pattern": r"fbq\('init',\s*'(\d+)'"
# LinkedIn Insight → extrait l'ID partenaire
"tag_pattern": r"_linkedin_partner_id\s*=\s*['\"]?(\d+)['\"]?"Déduplication et priorisation
Un tracker n'est listé qu'une seule fois même si plusieurs de ses patterns matchent. Les patterns et URLs matchantes sont dédupliqués via list(dict.fromkeys(...)). Les URLs sont limitées à 10 pour ne pas surcharger le stockage.
Détection complémentaire par cookies
En plus de la détection HTML/réseau, Impact Trackers peut déduire des trackers à partir des cookies déposés. Si un cookie correspond à un tracker connu (ex: _fbp → Meta Pixel) mais que le tracker n'a pas été détecté par regex, il est ajouté à la liste avec detected_in: ["cookie"].
Galerie des Regex — Explication Détaillée
Toutes les regex sont compilées à l'initialisation du module (re.compile(p, re.IGNORECASE)) pour des performances optimales. Elles sont appliquées en mode case-insensitive.
google-analytics\.com/(?:(?:r/)?collect|analytics\.js|ga\.js)Détecte l'endpoint de collecte classique UA (/collect), le script principal (analytics.js) et l'ancien script (ga.js). Le (?:...) est un groupe non-capturant. (?:r/)? rend le préfixe r/ optionnel (pour les hits en redirect).
googletagmanager\.com/gtag/js\?id=(?:G|UA)-[A-Z0-9\-]+Détecte le script GA4/Universal chargé via gtag.js. La partie (?:G|UA)-[A-Z0-9\-]+ correspond aux identifiants GA4 (G-XXXXXXXX) ou Universal Analytics (UA-XXXXXX-X).
gtag\('config',\s*'(?:G|UA)-Détecte la configuration inline de gtag dans le HTML. \s* tolère des espaces entre la virgule et le premier guillemet.
_gaq\.pushDétecte l'ancienne API asynchrone Universal Analytics (pre-2012). Encore présent sur certains sites non mis à jour.
ga\('create'Détecte la création d'un tracker UA via l'API analytics.js : ga('create', 'UA-XXXXX-X', 'auto').
googletagmanager\.com/gtm\.jsURL canonique du script GTM chargé dans le <head>. Le point dans gtm.js est échappé (\.) pour ne pas matcher n'importe quel caractère.
GTM-[A-Z0-9]{4,7}Identifiant GTM universel. Format : GTM- suivi de 4 à 7 caractères alphanumériques majuscules. Présent dans le snippet HTML standard de toute installation GTM.
new Date\(\)\.getTime\(\),event:Ligne signature unique du snippet d'initialisation GTM (le code minifié inséré dans le <head>). Très fiable car cette chaîne est présente dans 100% des installations GTM sans variation.
piano-analytics(?:\.js)?Nom de fichier du SDK Piano. Le (?:\.js)? rend l'extension optionnelle pour couvrir les URLs sans extension.
xtcore\.jsAncien nom du SDK AT Internet (nom historique avant le rebranding en Piano Analytics).
aticdn\.netCDN officiel d'AT Internet/Piano pour la distribution de ses scripts.
xiti\.comAncien domaine de collecte AT Internet (logs.xiti.com). Encore utilisé par certaines configurations non migrées.
pa\.set\(Détecte l'appel JavaScript de l'API Piano pour configurer une propriété : pa.set('siteId', 123456). La parenthèse est échappée.
connect\.facebook\.netDomaine CDN officiel de Facebook pour fbevents.js. Présent dans toutes les installations du pixel.
fbevents\.jsNom du fichier JavaScript principal du pixel Meta. Peut être chargé localement (CNAME cloaking) — dans ce cas le pattern connect.facebook.net ne matchera pas mais fbevents.js oui.
fbq\(Fonction JavaScript globale du pixel Meta. Présente dans le snippet d'initialisation : fbq('init', 'PIXEL_ID'). La parenthèse est échappée.
_fbpNom du cookie persistant déposé par le pixel Meta (Facebook Browser ID). Détecte le pixel même si les scripts sont bloqués mais le cookie déjà déposé.
matomo\.jsScript principal Matomo auto-hébergé. Présent à l'URL personnalisée du serveur Matomo de l'organisation (ex: analytics.monsite.fr/matomo.js).
matomo\.phpEndpoint de collecte Matomo. Les hits sont envoyés à /matomo.php?idsite=X&rec=1&.... Présent dans les requêtes réseau Playwright.
piwik\.js & piwik\.phpAnciens noms de Matomo (Piwik avant le rebranding de 2018). Encore présents sur des installations non mises à jour.
(?:^|\.)matomo\.cloudDistingue Matomo Cloud de Self-Hosted. (?:^|\.) assure que la regex ne matche que les domaines se terminant par .matomo.cloud ou exactement matomo.cloud (évite les faux positifs comme notmatomo.cloud).
(?:^|\.)innocraft\.cloudNom de domaine alternatif de Matomo Cloud (InnoCraft est l'éditeur commercial de Matomo). Même logique d'ancrage de domaine.
clarity\.msDomaine officiel de collecte Microsoft Clarity. Les requêtes réseau de session recording sont envoyées à a.clarity.ms.
window\.clarityObjet JavaScript global injecté dans window lors du chargement de Clarity. Détecte les configurations inline. Exemple : window.clarity = window.clarity || function(){...}.
wysistat\.(?:com|net)Le pattern (?:com|net) est un groupe non-capturant alternant les deux extensions du domaine Wysistat. Simple et efficace car le domaine est très spécifique.
simpleanalytics\.comDomaine principal de Simple Analytics. Présent dans le script chargé : https://scripts.simpleanalyticscdn.com/latest.js.
simpleanalyticscdn\.comCDN dédié de Simple Analytics pour la distribution de ses scripts.
plausible\.ioDomaine du service Plausible hébergé. Présent dans le script : https://plausible.io/js/plausible.js.
plausible\.jsNom du fichier script Plausible. Couvre les instances auto-hébergées où le domaine change mais le nom de fichier reste identique.
data-website-idAttribut HTML caractéristique d'Umami. La balise script Umami est : <script async src="/umami.js" data-website-id="...">. Très spécifique à Umami.
Extension Chrome — Regex simplifiées
L'extension Chrome (background.js) utilise un jeu de regex plus compact (une seule regex par tracker) car elle opère dans un contexte de script de service avec des contraintes de performance. Ces regex sont compilées à la volée avec new RegExp(tracker.rule.source, "i") à chaque requête pour éviter les effets de bord d'état des RegExp globales (/g partagées entre plusieurs appels).
{ id: "piano",
rule: /(?:piano-analytics(?:\.js)?|xtcore\.js|atinternet\.js|ati-tag|
aticdn\.net\/piano-analytics|piano\.io|xiti\.com|pianoAnalytics|pa\.set)/gi
},
{ id: "gtm",
rule: /(?:googletagmanager\.com\/gtm\.js\?id=([A-Z0-9-]+))|
(?:['\"](GTM-[A-Z0-9-]+)['\"]) /gi
},Détection du Tracking Serveur — Patterns Techniques
Le module server_side_detector.py applique 7 méthodes de détection combinées pour calculer un score de confiance :
Recherche de headers spécifiques dans la réponse HTTP : x-measurement-id, x-analytics, x-tracking, x-gtm-server, x-stape-config. Ces headers sont ajoutés par certaines configurations de tracking serveur custom.
Requêtes vers des URLs du type monsite.fr/collect, monsite.fr/beacon, monsite.fr/analytics détectées dans le trafic réseau Playwright. Pattern : /collect(?:\?|$), /events?(?:\?|$), /mp/collect(?:\?|$) (GA4 Measurement Protocol), etc.
Détecte les paramètres GA/Mixpanel dans les URLs réseau :
r"[?&]tid=UA-" # GA Universal
r"[?&]tid=G-" # GA4
r"[?&]cid=[\w\-]+" # Client ID GA
r"[?&]v=1&" # Measurement Protocol version
r"[?&]t=pageview" # Type de hit GA
r"[?&]en=page_view" # GA4 MP event nameDétecte des sous-domaines du site analysé dont le nom ressemble à du tracking : analytics.monsite.fr, tracking.monsite.fr, metrics.monsite.fr, collect.monsite.fr, stat.monsite.fr, sgtm.monsite.fr... via une liste de 14 patterns de sous-domaines.
Patterns : sgtm., stape., server-side-tagging, server-container, servercontainer dans le HTML ou les URLs réseau. Stape est un hébergeur populaire de sGTM.
Si GTM est détecté côté client mais que moins de 4 scripts tiers ont été chargés en réseau et que des paramètres Measurement Protocol sont présents, c'est un signe fort que GTM s'exécute principalement côté serveur (les tags sont déclenchés server-side, pas browser-side).
Si des requêtes vers des endpoints de collecte connus (ex: google-analytics.com) sont détectées en réseau, mais qu'aucun script GA n'est chargé côté client, c'est un signe fort d'un hit GA4 envoyé depuis le serveur.
Calcul du verdict :
suspected = confidence_score >= 30 # Seuil minimal pour suspicion
if confidence_score >= 65: confidence = "high"
elif confidence_score >= 35: confidence = "medium"
elif confidence_score > 0: confidence = "low"
else: confidence = "none"Éco-conception — Calcul Technique
L'éco-conception est calculée séparément et après la détection des trackers (dans l'étape 8 du pipeline). Elle est gérée entièrement dans analyzer.py, indépendamment du module tracker_detector.py.
Données d'entrée :
- Nombre total de requêtes réseau Playwright (
len(requests_all)) - Taille HTML de la page (
len(html) / 1024en ko) - Nombre de nœuds DOM (estimé :
max(380, len(html) / 120)) - Profils éco de chaque tracker (table statique
ECO_TRACKER_PROFILESdansanalyzer.py)
Calcul EcoIndex officiel :
from ecoindex.compute import compute_ecoindex
# 1. EcoIndex Global (état réel avec traceurs)
ecoindex_global = await compute_ecoindex(
nodes = global_dom_elements,
size = int(global_page_weight_kb * 1024), # en octets
requests = global_requests_count
)
# 2. EcoIndex Propre (hypothétique sans traceurs)
ecoindex_clean = await compute_ecoindex(
nodes = clean_dom,
size = int(clean_weight_kb * 1024),
requests = clean_requests
)Formule de repli (fallback) :
En cas d'échec de la bibliothèque officielle, une formule simplifiée est utilisée :
# Quantiles de référence EcoIndex
score_dom = metric_score(dom_elements, q50=600, q90=2400)
score_req = metric_score(requests, q50=35, q90=120)
score_size = metric_score(size_kb, q50=1000, q90=5000)
ecoindex = max(0, 100 - (3 * score_dom + 2 * score_req + 1 * score_size) / 6)
# GES et eau (formule EcoIndex officielle simplifiée)
ges = 2.0 + 2.0 * (50.0 - ecoindex_score) / 100.0 # gCO2e
water = 0.5 + 0.5 * (50.0 - ecoindex_score) / 100.0 # clPoints perdus EcoIndex dus aux traceurs :
ecoindex_points_lost = round(
0.3 * trackers_requests_sum # Impact requêtes réseau
+ 0.3 * (trackers_weight_sum / 100) # Impact poids (ko)
+ 0.4 * (trackers_dom_sum / 100), # Impact nœuds DOM
1
)Web Performance — Mesure Synthétique Double Passe (Lab Data)
Impact Trackers intègre un **nouveau moteur d'analyse synthétique automatisée (Lab Data)** qui remplace les estimations statistiques par de la mesure réelle et physique. Il exécute un protocole de test double passe (A/B Test) sous émulation et bridages stricts.
1. Paramètres stricts de l'Émulation Mobile
Le navigateur headless Chromium est configuré pour simuler précisément un smartphone moderne récent :
- User-Agent :
Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X)... Mobile/15E148 Safari/604.1 - Viewport :
390x844avec ratio de pixels de3et support du tactile (touch events).
2. Throttling CPU et Réseau (CDP)
Pour obtenir des mesures de performance stables et représentatives des conditions mobiles réelles, Impact Trackers se connecte directement à la session **CDP (Chrome DevTools Protocol)** de la page pour y injecter deux bridages physiques stricts :
- CPU Throttling (Ralentissement 4x) : Émule la puissance calculatoire modérée d'un smartphone milieu de gamme.
- Network Throttling (Réseau 3G/4G bridé) : Établit des conditions réseau fixes (Latence de
40ms, Download de10 Mbpssoit 1.25 Mo/s, Upload de1.5 Mbpssoit 187.5 Ko/s).
cdp = await page.context.new_cdp_session(page)
# Throttling Réseau
await cdp.send("Network.emulateNetworkConditions", {
"offline": False,
"latency": 40,
"downloadThroughput": (10 * 1024 * 1024) // 8, # 10 Mbps
"uploadThroughput": (1.5 * 1024 * 1024) // 8 # 1.5 Mbps
})
# Throttling CPU (4x)
await cdp.send("Emulation.setCPUThrottlingRate", {
"rate": 4
})3. Mesure par Double Passe (A/B Test)
Le calcul de l'impact réel des traceurs se fait par différence directe entre deux chargements de page successifs :
- Passe A (Contrôle) : Chargement initial complet du site avec l'ensemble de ses traceurs et scripts tiers actifs sous throttling mobile. On mesure le TBT, le poids réseau et le temps de chargement.
- Passe B (Bloquée) : Chargement du même site en interceptant à la volée toutes les requêtes réseau (via
route.abort()) dont le domaine appartient à la liste des traceurs détectés, bloquant ainsi leur exécution. On enregistre les nouvelles métriques.
async def intercept_route(route, request):
req_url = request.url
domain = urlparse(req_url).netloc
# Si le domaine appartient aux traceurs détectés -> blocage
if any(block_d in domain for block_d in liste_domaines_traceurs):
await route.abort()
else:
await route.continue_()
await page.route("**/*", intercept_route)4. Calcul Physique du TBT (Total Blocking Time)
Plutôt que d'estimer la charge CPU, Impact Trackers injecte dès le démarrage de chaque passe un script d'écoute de tâches longues (Long Tasks) via l'API PerformanceObserver native. Le TBT correspond au cumul de la partie supérieure à 50ms pour toutes les tâches bloquantes mesurées lors du chargement :
window.tbtLongTasks = [];
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
window.tbtLongTasks.push({
startTime: entry.startTime,
duration: entry.duration
});
}
}).observe({ entryTypes: ["longtask"] });5. File d'Attente FIFO et Garantie CPU
Comme le throttling CPU simule une réduction logicielle de la puissance du processeur, toute autre tâche en parallèle sur le serveur hôte fausserait les calculs. Impact Trackers intègre donc un **verrou d'exclusion mutuelle asynchrone (Mutex)** qui force une file d'attente FIFO stricte (concurrence = 1). Une seule analyse synthétique s'exécute à la fois sur le serveur, lui garantissant 100% de la ressource processeur.
Score de Confidentialité — Algorithme
score = 100 # Départ
# Pénalités par niveau de risque (hors CMP et exemptés CNIL)
score -= min(very_high_count * 20, 60) # Max -60
score -= min(high_count * 10, 40) # Max -40
score -= min(medium_count * 5, 20) # Max -20
# Pénalité session recording (Hotjar, Clarity, Contentsquare)
if has_session_recording: score -= 15
# Pas de CMP + trackers invasifs
if not has_cmp and has_invasive_trackers: score -= 10
# Plus de 10 cookies tiers
if len(third_party_cookies) > 10: score -= 5
# Tracking server-side suspecté
if server_side.suspected: score -= 10
return max(0, score) # Minimum 0Catégories de score :
Extension Chrome — Architecture & Détection
L'extension Chrome est totalement indépendante du backend Impact Trackers. Elle embarque ses propres regex dans background.js et utilise deux méthodes de détection :
Méthode 1 — Sniffer réseau passif
Le background script écoute en permanence toutes les requêtes réseau via chrome.webRequest.onBeforeRequest. Pour chaque requête, il teste l'URL contre les regex de tous les trackers connus. C'est une écoute non-bloquante : elle ne ralentit pas le chargement des pages.
chrome.webRequest.onBeforeRequest.addListener(
function(details) {
const tabId = details.tabId;
if (tabId < 0) return; // Ignorer les onglets "background"
TRACKER_PATTERNS.forEach((tracker) => {
// Regex locale pour éviter les effets de bord du mode /g
const regex = new RegExp(tracker.rule.source, "i");
if (regex.test(details.url)) {
if (!detectedTrackers[tabId]) {
detectedTrackers[tabId] = new Set(); // Set = pas de doublons
}
const beforeSize = detectedTrackers[tabId].size;
detectedTrackers[tabId].add(tracker.id);
// Notifier le Side Panel seulement si c'est un NOUVEAU tracker
if (detectedTrackers[tabId].size > beforeSize) {
chrome.runtime.sendMessage({
action: "NETWORK_TRACKER_FOUND",
tabId, trackerId: tracker.id, trackerName: tracker.name
});
}
}
});
},
{ urls: ["<all_urls>"] }
);Méthode 2 — Analyse HTML par fetch
Quand une URL de site tiers est saisie manuellement, le background script effectue un fetch() de l'URL et applique les regex sur le HTML reçu. Cette méthode est complémentaire car le sniffer réseau ne capture que les requêtes postérieures à l'ouverture du Side Panel.
Boucle de détection LinkedIn (content.js) :
Pour détecter les changements de page sur LinkedIn (SPA — Single Page Application), le content script utilise un setInterval qui vérifie toutes les 1 000 ms si le window.location.pathname a changé. Si oui, il notifie le background.
let lastPath = window.location.pathname;
const intervalId = setInterval(() => {
// Sécurité : arrêt si contexte d'extension invalidé
if (!chrome.runtime || !chrome.runtime.id) {
clearInterval(intervalId);
return;
}
if (window.location.pathname !== lastPath) {
lastPath = window.location.pathname;
if (window.location.hostname.includes("linkedin.com")) {
chrome.runtime.sendMessage({
action: "url_changed",
url: window.location.href
});
}
}
}, 1000); // ← Vérification toutes les secondesBoucle de retry pour le scan LinkedIn :
Les données LinkedIn sont extraites via le JSON interne de l'API Voyager (objets <code> cachés dans le DOM) ou par scraping DOM. Comme la page SPA ne recharge pas entre les profils, les données peuvent être "périmées". Une logique de retry est implémentée :
const tryScan = (retryCount) => {
chrome.tabs.sendMessage(activeTab.id, {action: "scan_profile"}, (response) => {
if (chrome.runtime.lastError) {
if (retryCount < 3) {
setTimeout(() => tryScan(retryCount + 1), 1000); // Retry toutes les secondes
} else {
setStatus("Prêt (ouvrez un profil)"); // Abandon après 3 tentatives
}
return;
}
if (response && response.success) {
// Afficher les données
} else if (retryCount < 3) {
setTimeout(() => tryScan(retryCount + 1), 1500);
}
});
};
setTimeout(() => tryScan(0), 1200); // Premier essai après 1.2s (DOM LinkedIn)