PageSourceSearch

https://www.digilabs.fr/wp-content/cdzjqx-6694/pxnetwork-tools-psi/assets/js/waterfall-collector.js?ver=1.0

js digilabs.fr collected 2026-10-02 22:31:18 UTC 12,053 bytes, 353 lines download raw bytes

1/**
2 * Collecteur de données waterfall pour les sessions réelles
3 * S'exécute sur toutes les pages publiques pour collecter les données de performance
4 */
5(function() {
6  'use strict';
7
8  // Vérifie que l'API Performance est disponible
9  if (typeof window.performance === 'undefined' || typeof window.performance.getEntriesByType !== 'function') {
10    return;
11  }
12
13  // URL actuelle (normalisée)
14  const currentUrl = window.location.href.split('?')[0].split('#')[0];
15
16  // Collecte les informations du navigateur et de l'environnement
17  function collectBrowserInfo() {
18    return {
19      screen: {
20        width: window.screen.width || null,
21        height: window.screen.height || null,
22        availWidth: window.screen.availWidth || null,
23        availHeight: window.screen.availHeight || null,
24        colorDepth: window.screen.colorDepth || null,
25        pixelDepth: window.screen.pixelDepth || null
26      },
27      viewport: {
28        width: window.innerWidth || document.documentElement.clientWidth || null,
29        height: window.innerHeight || document.documentElement.clientHeight || null
30      },
31      devicePixelRatio: window.devicePixelRatio || null,
32      language: navigator.language || navigator.userLanguage || null,
33      languages: navigator.languages || null,
34      timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || null,
35      cookieEnabled: navigator.cookieEnabled !== undefined ? navigator.cookieEnabled : null,
36      onLine: navigator.onLine !== undefined ? navigator.onLine : null
37    };
38  }
39
40  // Vérifie si une mesure est nécessaire (dernière mesure > 24h)
41  function checkIfMeasurementNeeded(callback) {
42    // Détermine l'URL AJAX
43    let ajaxUrl = (typeof ajaxurl !== 'undefined') ? ajaxurl : 
44                  (typeof wp !== 'undefined' && wp.ajax && wp.ajax.settings && wp.ajax.settings.url) ?
45                  wp.ajax.settings.url : 
46                  '/wp-admin/admin-ajax.php';
47    
48    if (!ajaxUrl) {
49      return;
50    }
51
52    fetch(ajaxUrl, {
53      method: 'POST',
54      headers: {
55        'Content-Type': 'application/x-www-form-urlencoded',
56      },
57      body: new URLSearchParams({
58        action: 'pxn_psi_check_waterfall_needed',
59        url: currentUrl
60      })
61    })
62    .then(response => response.json())
63    .then(data => {
64      if (data && data.success && data.data && data.data.needed === true) {
65        callback();
66      }
67    })
68    .catch(err => {
69      console.error('Erreur vérification waterfall:', err);
70    });
71  }
72
73  // Collecte les métriques Core Web Vitals et navigation
74  function collectWebVitals() {
75    const vitals = {
76      navigation: {},
77      paint: {},
78      lcp: null,
79      fid: null,
80      inp: null,
81      cls: 0
82    };
83
84    // Navigation timing
85    if (performance.timing) {
86      const timing = performance.timing;
87      vitals.navigation = {
88        domContentLoaded: timing.domContentLoadedEventEnd - timing.navigationStart,
89        loadComplete: timing.loadEventEnd - timing.navigationStart,
90        domInteractive: timing.domInteractive - timing.navigationStart,
91        firstByte: timing.responseStart - timing.navigationStart
92      };
93    }
94
95    // Paint metrics (FCP)
96    try {
97      const paintEntries = performance.getEntriesByType('paint');
98      paintEntries.forEach(entry => {
99        if (entry.name === 'first-contentful-paint') {
100          vitals.paint.fcp = Math.round(entry.startTime);
101        }
102      });
103    } catch(e) {}
104
105    // LCP (Largest Contentful Paint)
106    try {
107      const lcpEntries = performance.getEntriesByType('largest-contentful-paint');
108      if (lcpEntries.length > 0) {
109        const lastLcp = lcpEntries[lcpEntries.length - 1];
110        vitals.lcp = {
111          value: Math.round(lastLcp.renderTime || lastLcp.loadTime),
112          size: lastLcp.size || 0,
113          url: lastLcp.url || ''
114        };
115      }
116    } catch(e) {}
117
118    // FID (First Input Delay) / INP
119    try {
120      const fidEntries = performance.getEntriesByType('first-input');
121      if (fidEntries.length > 0) {
122        vitals.fid = {
123          delay: Math.round(fidEntries[0].processingStart - fidEntries[0].startTime),
124          duration: Math.round(fidEntries[0].duration || 0)
125        };
126      }
127      
128      // INP peut être calculé via Event Timing API si disponible
129      if (performance.eventCounts) {
130        const eventCounts = performance.eventCounts;
131        vitals.inp = {
132          click: eventCounts.get('click') || 0,
133          keydown: eventCounts.get('keydown') || 0,
134          pointerdown: eventCounts.get('pointerdown') || 0
135        };
136      }
137    } catch(e) {}
138
139    // CLS (Cumulative Layout Shift)
140    try {
141      const clsEntries = performance.getEntriesByType('layout-shift');
142      if (clsEntries.length > 0) {
143        clsEntries.forEach(entry => {
144          if (!entry.hadRecentInput) {
145            vitals.cls += entry.value;
146          }
147        });
148      }
149    } catch(e) {}
150
151    return vitals;
152  }
153
154  // Détecte les opportunités d'optimisation
155  function detectOptimizationOpportunities(entries) {
156    const opportunities = {
157      largeImages: [],
158      unoptimizedImages: [],
159      largeScripts: [],
160      largeCSS: [],
161      unusedCSS: [],
162      thirdPartyHeavy: [],
163      missingCache: [],
164      slowThirdParty: []
165    };
166
167    entries.forEach(e => {
168      const size = e.transferSize || e.decodedBodySize || 0;
169      const sizeInKB = size / 1024;
170      const url = e.name.toLowerCase();
171      
172      // Images lourdes ou non optimisées
173      if (e.initiatorType === 'img' || url.match(/\.(jpg|jpeg|png|gif|webp|svg|avif)(\?|$)/i)) {
174        if (sizeInKB > 200) {
175          opportunities.largeImages.push({
176            url: e.name,
177            size: size,
178            type: url.includes('.webp') || url.includes('.avif') ? 'optimized' : 'unoptimized'
179          });
180        }
181        if (sizeInKB > 100 && !url.match(/\.(webp|avif)/i)) {
182          opportunities.unoptimizedImages.push({
183            url: e.name,
184            size: size
185          });
186        }
187      }
188      
189      // Scripts lourds
190      if (e.initiatorType === 'script' || url.match(/\.js(\?|$)/i)) {
191        if (sizeInKB > 100) {
192          opportunities.largeScripts.push({
193            url: e.name,
194            size: size,
195            duration: e.duration
196          });
197        }
198      }
199      
200      // CSS lourd
201      if (e.initiatorType === 'link' || url.match(/\.css(\?|$)/i)) {
202        if (sizeInKB > 50) {
203          opportunities.largeCSS.push({
204            url: e.name,
205            size: size
206          });
207        }
208      }
209      
210      // Ressources tierces lourdes
211      try {
212        const resourceDomain = new URL(e.name).hostname;
213        const currentDomain = window.location.hostname;
214        if (resourceDomain !== currentDomain) {
215          if (sizeInKB > 200) {
216            opportunities.thirdPartyHeavy.push({
217              url: e.name,
218              domain: resourceDomain,
219              size: size
220            });
221          }
222          if (e.duration > 1000) {
223            opportunities.slowThirdParty.push({
224              url: e.name,
225              domain: resourceDomain,
226              duration: e.duration
227            });
228          }
229        }
230      } catch(err) {}
231      
232      // Ressources sans cache (transferSize === encodedBodySize et > 0)
233      if (e.transferSize > 0 && e.transferSize === e.encodedBodySize && sizeInKB > 10) {
234        opportunities.missingCache.push({
235          url: e.name,
236          size: size
237        });
238      }
239    });
240
241    return opportunities;
242  }
243
244  // Collecte les données de performance
245  function collectPerformanceData() {
246    try {
247      // Attend que la page soit complètement chargée
248      if (document.readyState === 'loading') {
249        window.addEventListener('load', collectPerformanceData);
250        return;
251      }
252
253      // Attend un peu pour que toutes les ressources soient chargées et LCP soit capturé
254      setTimeout(function() {
255        const entries = performance.getEntriesByType('resource');
256        const data = entries.map(e => {
257          // Détermine le type de ressource
258          let type = e.initiatorType || 'other';
259          if (type === 'link' && (e.name.includes('.css') || e.name.match(/\.css\?/))) {
260            type = 'css';
261          } else if (type === 'link' && !e.name.match(/\.(css|js|png|jpg|gif|svg|woff|woff2|ttf|eot)/)) {
262            type = 'other';
263          }
264
265          // Calculs réseau avancés
266          const compressionRatio = e.decodedBodySize > 0 ? 
267            ((e.decodedBodySize - e.encodedBodySize) / e.decodedBodySize * 100).toFixed(1) : 0;
268          const cacheHit = e.transferSize === 0 && e.decodedBodySize > 0;
269          const priority = e.fetchStart > 0 ? e.fetchStart - e.startTime : null;
270
271          return {
272            name: e.name,
273            type: type,
274            start: parseFloat(e.startTime.toFixed(2)),
275            duration: parseFloat(e.duration.toFixed(2)),
276            dns: parseFloat((e.domainLookupEnd - e.domainLookupStart).toFixed(2)),
277            connect: parseFloat((e.connectEnd - e.connectStart).toFixed(2)),
278            ttfb: parseFloat((e.responseStart - e.requestStart).toFixed(2)),
279            transfer: parseFloat((e.responseEnd - e.responseStart).toFixed(2)),
280            transferSize: e.transferSize || 0,
281            encodedBodySize: e.encodedBodySize || 0,
282            decodedBodySize: e.decodedBodySize || 0,
283            responseStatus: e.responseStatus || 0,
284            nextHopProtocol: e.nextHopProtocol || '',
285            renderBlockingStatus: e.renderBlockingStatus || null,
286            compressionRatio: parseFloat(compressionRatio),
287            cacheHit: cacheHit,
288            priority: priority !== null ? parseFloat(priority.toFixed(2)) : null
289          };
290        });
291
292        // Collecte les Web Vitals
293        const webVitals = collectWebVitals();
294        
295        // Détecte les opportunités d'optimisation
296        const opportunities = detectOptimizationOpportunities(entries);
297
298        // Collecte les infos du navigateur
299        const browserInfo = collectBrowserInfo();
300
301        // Envoie les données via AJAX
302        if (data.length > 0) {
303          sendWaterfallData(data, browserInfo, webVitals, opportunities);
304        }
305      }, 3000); // Attendre 3 secondes pour capturer LCP et autres métriques
306    } catch (err) {
307      console.error('Erreur collecte waterfall:', err);
308    }
309  }
310
311  // Envoie les données waterfall au serveur
312  function sendWaterfallData(data, browserInfo, webVitals, opportunities) {
313    // Détermine l'URL AJAX
314    let ajaxUrl = (typeof ajaxurl !== 'undefined') ? ajaxurl : 
315                  (typeof wp !== 'undefined' && wp.ajax && wp.ajax.settings && wp.ajax.settings.url) ?
316                  wp.ajax.settings.url : 
317                  '/wp-admin/admin-ajax.php';
318    
319    if (!ajaxUrl) {
320      return;
321    }
322
323    fetch(ajaxUrl, {
324      method: 'POST',
325      headers: {
326        'Content-Type': 'application/x-www-form-urlencoded',
327      },
328      body: new URLSearchParams({
329        action: 'pxn_psi_save_waterfall_frontend',
330        url: currentUrl,
331        resources: JSON.stringify(data),
332        browser_info: JSON.stringify(browserInfo),
333        web_vitals: JSON.stringify(webVitals),
334        opportunities: JSON.stringify(opportunities)
335      })
336    })
337    .then(response => response.json())
338    .then(data => {
339      if (data && data.success) {
340        console.log('Waterfall data saved successfully');
341      } else {
342        console.error('Erreur sauvegarde waterfall:', data);
343      }
344    })
345    .catch(err => {
346      console.error('Erreur envoi waterfall:', err);
347    });
348  }
349
350  // Démarre le processus
351  checkIfMeasurementNeeded(collectPerformanceData);
352})();
353

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.