PageSourceSearch

https://thekebabsshop.co.uk/js/web-vitals-rum.js?v=w9OwrU2S8XOEUIIdvzjasfUjqpM6Ta67QsvZW__j6F8

js thekebabsshop.co.uk collected 2026-10-02 02:09:49 UTC 9,797 bytes, 304 lines download raw bytes

1/*
2 * INP collection for the server-rendered pages.
3 *
4 * These pages never boot the Angular SPA, so WebVitalsService — which only runs
5 * from AppComponent — has never seen them and they emit no WebVital events at
6 * all. Rather than pull the web-vitals bundle and the Application Insights SDK
7 * into a page whose whole point is a fast first paint, this posts a single
8 * pre-built envelope to the ingestion endpoint by beacon when the page hides.
9 *
10 * The candidate-selection below mirrors web-vitals' own estimator so the
11 * numbers stay comparable with the SPA's: the worst interaction for the first
12 * 50, then the second-worst, and so on. Diverging here would make the two page
13 * families look different when only the measurement differed.
14 *
15 * Configuration arrives as window.AW_RUM from the _WebVitalsRum partial.
16 */
17(function () {
18  'use strict';
19
20  var config = window.AW_RUM;
21
22  if (!config || !config.instrumentationKey || typeof PerformanceObserver !== 'function' || !navigator.sendBeacon) {
23    return;
24  }
25
26  // web-vitals reports one candidate per 50 interactions and keeps 10 in hand.
27  var MAX_CANDIDATES = 10;
28  var INTERACTIONS_PER_CANDIDATE = 50;
29  // Matches web-vitals' default durationThreshold: below this an interaction
30  // cannot affect a p75 that is anywhere near the 200ms "good" boundary.
31  var DURATION_THRESHOLD = 40;
32
33  var interactionsByIdentifier = {};
34  var candidates = [];
35  var interactionCount = 0;
36  var reported = false;
37  var longAnimationFrames = [];
38
39  function describeTarget(node) {
40    if (!node || node.nodeType !== 1) {
41      return '';
42    }
43
44    var parts = [];
45    var current = node;
46    var depth = 0;
47
48    while (current && current.nodeType === 1 && depth < 3) {
49      var part = current.nodeName.toLowerCase();
50
51      if (current.id) {
52        parts.unshift(part + '#' + current.id);
53        break;
54      }
55
56      var className = typeof current.className === 'string' ? current.className.trim().split(/\s+/)[0] : '';
57      if (className) {
58        part += '.' + className;
59      }
60
61      parts.unshift(part);
62      current = current.parentElement;
63      depth++;
64    }
65
66    return parts.join('>');
67  }
68
69  function recordEntry(entry) {
70    if (!entry.interactionId) {
71      return;
72    }
73
74    var existing = interactionsByIdentifier[entry.interactionId];
75
76    if (existing) {
77      // Several event entries (pointerdown, pointerup, click) share one
78      // interactionId; the interaction's duration is the longest of them.
79      if (entry.duration > existing.duration) {
80        interactionsByIdentifier[entry.interactionId] = toInteraction(entry);
81      }
82      return;
83    }
84
85    interactionCount++;
86    interactionsByIdentifier[entry.interactionId] = toInteraction(entry);
87  }
88
89  function toInteraction(entry) {
90    var processingStart = entry.processingStart != null ? entry.processingStart : entry.startTime;
91    var processingEnd = entry.processingEnd != null ? entry.processingEnd : processingStart;
92
93    return {
94      duration: entry.duration,
95      startTime: entry.startTime,
96      inputDelay: processingStart - entry.startTime,
97      processingDuration: processingEnd - processingStart,
98      presentationDelay: Math.max(0, entry.startTime + entry.duration - processingEnd),
99      interactionType: entry.name.indexOf('key') === 0 ? 'keyboard' : 'pointer',
100      interactionTarget: describeTarget(entry.target)
101    };
102  }
103
104  function selectCandidate() {
105    candidates = Object.keys(interactionsByIdentifier)
106      .map(function (key) {
107        return interactionsByIdentifier[key];
108      })
109      .filter(function (interaction) {
110        return interaction.duration >= DURATION_THRESHOLD;
111      })
112      .sort(function (left, right) {
113        return right.duration - left.duration;
114      })
115      .slice(0, MAX_CANDIDATES);
116
117    if (!candidates.length) {
118      return null;
119    }
120
121    var index = Math.min(candidates.length - 1, Math.floor(interactionCount / INTERACTIONS_PER_CANDIDATE));
122
123    return candidates[index];
124  }
125
126  /**
127   * The longest script running while the interaction was on screen, from the
128   * long-animation-frame entries — the light equivalent of the SPA's
129   * attribution.longestScript so Razor rows carry comparable blame. Subpart
130   * classification is a web-vitals derivation the raw entries do not offer,
131   * so that one field stays SPA-only.
132   */
133  function findLongestScript(interaction) {
134    var windowStart = interaction.startTime;
135    var windowEnd = interaction.startTime + interaction.duration;
136    var longest = null;
137
138    longAnimationFrames.forEach(function (frame) {
139      if (frame.startTime >= windowEnd || frame.startTime + frame.duration <= windowStart) {
140        return;
141      }
142
143      (frame.scripts || []).forEach(function (script) {
144        if (!longest || script.duration > longest.duration) {
145          longest = script;
146        }
147      });
148    });
149
150    if (!longest) {
151      return null;
152    }
153
154    var scriptStart = longest.startTime;
155    var scriptEnd = longest.startTime + longest.duration;
156
157    return {
158      duration: longest.duration,
159      intersectingDuration: Math.max(0, Math.min(scriptEnd, windowEnd) - Math.max(scriptStart, windowStart)),
160      source: longest.sourceURL || longest.invoker || ''
161    };
162  }
163
164  function rate(value) {
165    if (value <= 200) {
166      return 'good';
167    }
168
169    return value <= 500 ? 'needs-improvement' : 'poor';
170  }
171
172  function round(milliseconds) {
173    return Math.round(milliseconds * 10) / 10;
174  }
175
176  function buildEnvelope(interaction) {
177    var properties = {
178      metricName: 'INP',
179      name: 'INP',
180      page_type: config.pageType,
181      route: location.pathname,
182      rating: rate(interaction.duration),
183      interactionTarget: interaction.interactionTarget,
184      interactionType: interaction.interactionType,
185      navigationType: 'navigate'
186    };
187
188    if (config.tenantId) {
189      properties.tenantId = config.tenantId;
190    }
191
192    var connection = navigator.connection;
193    if (connection && connection.effectiveType) {
194      properties.effectiveType = connection.effectiveType;
195    }
196
197    if (typeof navigator.deviceMemory === 'number') {
198      properties.deviceMemory = String(navigator.deviceMemory);
199    }
200
201    var longestScript = findLongestScript(interaction);
202    if (longestScript) {
203      properties.longestScriptSource = longestScript.source;
204    }
205
206    // numeric fields go into measurements (customMeasurements) to match the SPA-side
207    // addInpAttribution rows — the same field name must land in the same bucket with the
208    // same type whichever page family reported it, or percentile queries silently skip rows
209    var measurements = {
210      value: round(interaction.duration),
211      inputDelay: round(interaction.inputDelay),
212      processingDuration: round(interaction.processingDuration),
213      presentationDelay: round(interaction.presentationDelay)
214    };
215
216    if (longestScript) {
217      measurements.longestScriptDuration = round(longestScript.duration);
218      measurements.longestScriptIntersectingDuration = round(longestScript.intersectingDuration);
219    }
220
221    var tags = {
222      'ai.operation.name': location.pathname,
223      'ai.internal.sdkVersion': 'aw-rum:1'
224    };
225
226    if (config.roleName) {
227      tags['ai.cloud.role'] = config.roleName;
228    }
229
230    if (config.tenantId) {
231      tags['ai.user.accountId'] = config.tenantId;
232    }
233
234    return {
235      name: 'Microsoft.ApplicationInsights.Event',
236      time: new Date().toISOString(),
237      iKey: config.instrumentationKey,
238      tags: tags,
239      data: {
240        baseType: 'EventData',
241        baseData: {
242          ver: 2,
243          name: 'WebVital.INP',
244          properties: properties,
245          measurements: measurements
246        }
247      }
248    };
249  }
250
251  function report() {
252    if (reported) {
253      return;
254    }
255
256    var interaction = selectCandidate();
257    if (!interaction) {
258      return;
259    }
260
261    reported = true;
262    navigator.sendBeacon(config.ingestionEndpoint, JSON.stringify(buildEnvelope(interaction)));
263  }
264
265  try {
266    // buffered:true picks up interactions that happened before this script ran,
267    // which for a deferred script on a server-rendered page is most of them.
268    new PerformanceObserver(function (list) {
269      list.getEntries().forEach(recordEntry);
270    }).observe({ type: 'event', buffered: true, durationThreshold: DURATION_THRESHOLD });
271  } catch (error) {
272    return;
273  }
274
275  try {
276    new PerformanceObserver(function (list) {
277      longAnimationFrames = longAnimationFrames.concat(list.getEntries()).slice(-50);
278    }).observe({ type: 'long-animation-frame', buffered: true });
279  } catch (error) {
280    // long-animation-frame unsupported — rows simply omit the longest-script fields.
281  }
282
283  // A back/forward-cache restore resumes this same script instance rather than
284  // re-running it, so the collector starts a fresh reporting cycle for the
285  // restored pageview — mirroring web-vitals' own pageshow reset.
286  window.addEventListener('pageshow', function (event) {
287    if (event.persisted) {
288      interactionsByIdentifier = {};
289      candidates = [];
290      interactionCount = 0;
291      reported = false;
292    }
293  });
294
295  // visibilitychange->hidden is the last point a browser reliably runs script;
296  // pagehide covers the bfcache path where visibilitychange may not fire.
297  document.addEventListener('visibilitychange', function () {
298    if (document.visibilityState === 'hidden') {
299      report();
300    }
301  });
302
303  window.addEventListener('pagehide', report);
304})();

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.