@flareapp/js 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2802 @@
1
+ let _flareapp_core = require("@flareapp/core");
2
+
3
+ //#region src/breadcrumbs/utils/documentEvent.ts
4
+ function onDocumentEvent(name, handle) {
5
+ document.addEventListener(name, handle, true);
6
+ return () => document.removeEventListener(name, handle, true);
7
+ }
8
+
9
+ //#endregion
10
+ //#region src/breadcrumbs/utils/elementSelector.ts
11
+ const INTERACTIVE_HTML_ELEMENTS = "button, a, input, select, textarea, label, [role], [tabindex], [onclick]";
12
+ const MAX_ANCESTOR_DEPTH = 5;
13
+ function interactiveTarget(target) {
14
+ let element = target;
15
+ for (let depth = 0; element && depth < MAX_ANCESTOR_DEPTH; depth++) {
16
+ if (element.matches?.(INTERACTIVE_HTML_ELEMENTS)) return element;
17
+ element = element.parentElement;
18
+ }
19
+ return target;
20
+ }
21
+ function elementSelector(element) {
22
+ let selector = element.tagName.toLowerCase();
23
+ if (element.id) selector += `#${element.id}`;
24
+ for (const className of element.classList) selector += `.${className}`;
25
+ return selector;
26
+ }
27
+ function elementTestId(element) {
28
+ return element.getAttribute?.("data-testid") ?? void 0;
29
+ }
30
+ function elementAttributes(element) {
31
+ const attributes = { "browser.element.selector": elementSelector(element) };
32
+ const testId = elementTestId(element);
33
+ if (testId) attributes["browser.element.test_id"] = testId;
34
+ return attributes;
35
+ }
36
+
37
+ //#endregion
38
+ //#region src/breadcrumbs/ClickRecorder.ts
39
+ var ClickRecorder = class {
40
+ type = _flareapp_core.BrowserSpanEventType.Click;
41
+ constructor(host) {
42
+ this.host = host;
43
+ this.onClick = this.onClick.bind(this);
44
+ }
45
+ install() {
46
+ return onDocumentEvent("click", this.onClick);
47
+ }
48
+ onClick(event) {
49
+ const target = event.target;
50
+ if (!(target instanceof Element)) return;
51
+ this.host.record(this.type, elementAttributes(interactiveTarget(target)), (0, _flareapp_core.defaultNowNano)());
52
+ }
53
+ };
54
+
55
+ //#endregion
56
+ //#region src/breadcrumbs/FormChangeRecorder.ts
57
+ var FormChangeRecorder = class {
58
+ type = _flareapp_core.BrowserSpanEventType.Input;
59
+ constructor(host) {
60
+ this.host = host;
61
+ this.onChange = this.onChange.bind(this);
62
+ }
63
+ install() {
64
+ return onDocumentEvent("change", this.onChange);
65
+ }
66
+ onChange(event) {
67
+ const target = event.target;
68
+ if (!(target instanceof Element)) return;
69
+ this.host.record(this.type, elementAttributes(target), (0, _flareapp_core.defaultNowNano)());
70
+ }
71
+ };
72
+
73
+ //#endregion
74
+ //#region src/tracing/utils/absoluteHref.ts
75
+ /**
76
+ * Resolve a router-reported href against the page we are on. Returns the `URL`, so a caller that
77
+ * wants the pathname as well as the href does not parse it a second time.
78
+ *
79
+ * Undefined outside a browser or for an unparseable href, so the caller can leave its attribute alone.
80
+ */
81
+ function absoluteUrl(href) {
82
+ if (href == null || typeof window === "undefined") return;
83
+ try {
84
+ return new URL(href, window.location.href);
85
+ } catch {
86
+ return;
87
+ }
88
+ }
89
+ /**
90
+ * The href form of `absoluteUrl`. Pass one built by the router's own `createHref`/`resolve` (see
91
+ * `resolveHref`), not a bare path: routers strip the app's base path, so `origin + path` yields an
92
+ * address the server does not have.
93
+ */
94
+ function absoluteHref(href) {
95
+ return absoluteUrl(href)?.href;
96
+ }
97
+
98
+ //#endregion
99
+ //#region src/instrumentation/navigation/utils.ts
100
+ function currentPath() {
101
+ return typeof location !== "undefined" ? location.pathname : "";
102
+ }
103
+ function currentHref() {
104
+ return typeof location !== "undefined" ? location.href : "";
105
+ }
106
+ function routeName(derive, fallbackPath, url) {
107
+ try {
108
+ const name = derive();
109
+ if (name) return {
110
+ name,
111
+ source: "route",
112
+ url
113
+ };
114
+ } catch {}
115
+ return {
116
+ name: fallbackPath,
117
+ source: "url",
118
+ url
119
+ };
120
+ }
121
+ /**
122
+ * `build` is the router's own href builder (vue-router `resolve`, React Router `createHref`). It puts
123
+ * the app's base path and hash prefix back. Without it, an app served from `/app/` reports
124
+ * `/product/p01` instead of `/app/product/p01`. If `build` throws, we use `fallback`.
125
+ */
126
+ function resolveHref(build, fallbackHref) {
127
+ let href = fallbackHref;
128
+ try {
129
+ href = build() ?? fallbackHref;
130
+ } catch {}
131
+ return absoluteHref(href);
132
+ }
133
+
134
+ //#endregion
135
+ //#region src/tracing/utils/fill.ts
136
+ /**
137
+ * Replace `source[name]` with `replacer(original)`, tagging the wrapper with a
138
+ * non-enumerable `__flare_original__` so the patch is idempotent and reversible.
139
+ * Ported from Sentry's `fill` (packages/core/src/utils/object.ts), minus the
140
+ * prototype/own-property copying we do not need for `fetch`.
141
+ */
142
+ function fill(source, name, replacer) {
143
+ const original = source[name];
144
+ if (typeof original !== "function") return;
145
+ if (original.__flare_original__) return;
146
+ const wrapped = replacer(original);
147
+ Object.defineProperty(wrapped, "__flare_original__", {
148
+ value: original,
149
+ enumerable: false,
150
+ configurable: true,
151
+ writable: true
152
+ });
153
+ source[name] = wrapped;
154
+ }
155
+ /** Restore a previously `fill`ed property to its original. Safe if never filled. */
156
+ function unfill(source, name) {
157
+ const current = source[name];
158
+ if (current && current.__flare_original__) source[name] = current.__flare_original__;
159
+ }
160
+
161
+ //#endregion
162
+ //#region src/instrumentation/navigation/navigationBus.ts
163
+ const subscribers$1 = /* @__PURE__ */ new Set();
164
+ let source = null;
165
+ let currentRoute = null;
166
+ let lastPath = "";
167
+ let uninstallHistory = null;
168
+ function broadcast(callback) {
169
+ for (const subscriber of subscribers$1) try {
170
+ callback(subscriber);
171
+ } catch {}
172
+ }
173
+ function onHistoryChange() {
174
+ if (!uninstallHistory) return;
175
+ const path = currentPath();
176
+ if (path === lastPath) return;
177
+ lastPath = path;
178
+ if (source) return;
179
+ broadcast((subscriber) => subscriber.onUrlChanged?.(path));
180
+ }
181
+ function installHistory() {
182
+ if (uninstallHistory) return;
183
+ if (typeof window === "undefined" || typeof history === "undefined" || typeof location === "undefined") return;
184
+ lastPath = currentPath();
185
+ function wrapHistoryMethod(original) {
186
+ return function(...args) {
187
+ const result = original.apply(this, args);
188
+ onHistoryChange();
189
+ return result;
190
+ };
191
+ }
192
+ fill(history, "pushState", wrapHistoryMethod);
193
+ fill(history, "replaceState", wrapHistoryMethod);
194
+ window.addEventListener("popstate", onHistoryChange);
195
+ uninstallHistory = () => {
196
+ unfill(history, "pushState");
197
+ unfill(history, "replaceState");
198
+ window.removeEventListener("popstate", onHistoryChange);
199
+ };
200
+ }
201
+ function subscribeToNavigation(subscriber) {
202
+ if (subscribers$1.size === 0) installHistory();
203
+ subscribers$1.add(subscriber);
204
+ if (currentRoute) try {
205
+ subscriber.onRouteName?.(currentRoute.route, currentRoute.owner);
206
+ } catch {}
207
+ let removed = false;
208
+ return () => {
209
+ if (removed) return;
210
+ removed = true;
211
+ subscribers$1.delete(subscriber);
212
+ if (subscribers$1.size === 0) {
213
+ uninstallHistory?.();
214
+ uninstallHistory = null;
215
+ lastPath = "";
216
+ }
217
+ };
218
+ }
219
+ /**
220
+ * Hands navigation to a framework router: while registered, the built-in History detection stays
221
+ * quiet and the router drives every step through the returned handle. The newest registration wins
222
+ * and a stale handle no-ops, because HMR can replace a router that still holds one.
223
+ */
224
+ function registerNavigationSource() {
225
+ const token = {};
226
+ if (source) console.debug("Flare: navigation source replaced");
227
+ source = token;
228
+ const active = () => source === token;
229
+ return {
230
+ startNavigation(opts) {
231
+ if (!active()) return;
232
+ const path = opts?.path ?? currentPath();
233
+ lastPath = path;
234
+ broadcast((subscriber) => subscriber.onNavigationStart?.({
235
+ path,
236
+ url: opts?.url,
237
+ hold: opts?.hold
238
+ }));
239
+ },
240
+ setActiveRouteName(route) {
241
+ if (!active()) return;
242
+ currentRoute = {
243
+ route,
244
+ owner: token
245
+ };
246
+ broadcast((subscriber) => subscriber.onRouteName?.(route, token));
247
+ },
248
+ settleNavigation(route) {
249
+ if (!active()) return;
250
+ currentRoute = {
251
+ route,
252
+ owner: token
253
+ };
254
+ broadcast((subscriber) => subscriber.onNavigationSettle?.(route, token));
255
+ },
256
+ unregister() {
257
+ if (!active()) return;
258
+ broadcast((subscriber) => subscriber.onSourceUnregistered?.());
259
+ source = null;
260
+ currentRoute = null;
261
+ lastPath = currentPath();
262
+ }
263
+ };
264
+ }
265
+ /** A name from an earlier call is only valid while that source is still registered. */
266
+ function isActiveNavigationSource(token) {
267
+ return token !== null && source === token;
268
+ }
269
+
270
+ //#endregion
271
+ //#region src/breadcrumbs/NavigationRecorder.ts
272
+ var NavigationRecorder = class {
273
+ type = _flareapp_core.BrowserSpanEventType.RouteChange;
274
+ previousHref = "";
275
+ constructor(host) {
276
+ this.host = host;
277
+ this.onUrlChanged = this.onUrlChanged.bind(this);
278
+ this.onNavigationSettle = this.onNavigationSettle.bind(this);
279
+ }
280
+ install() {
281
+ this.record(currentHref());
282
+ return subscribeToNavigation({
283
+ onUrlChanged: this.onUrlChanged,
284
+ onNavigationSettle: this.onNavigationSettle
285
+ });
286
+ }
287
+ onUrlChanged() {
288
+ this.record(currentHref());
289
+ }
290
+ onNavigationSettle(route) {
291
+ this.record(route.url ?? currentHref(), route);
292
+ }
293
+ record(href, route) {
294
+ const attributes = { "browser.route.to": this.clean(href) };
295
+ if (this.previousHref) attributes["browser.route.from"] = this.clean(this.previousHref);
296
+ if (route?.source === "route") {
297
+ attributes["flare.entry_point.handler.identifier"] = route.name;
298
+ attributes["flare.route.source"] = route.source;
299
+ }
300
+ this.previousHref = href;
301
+ this.host.record(this.type, attributes, (0, _flareapp_core.defaultNowNano)());
302
+ }
303
+ clean(href) {
304
+ return (0, _flareapp_core.breadcrumbUrl)(href, this.host.config().urlDenylist);
305
+ }
306
+ };
307
+
308
+ //#endregion
309
+ //#region src/instrumentation/requests/requestBus.ts
310
+ const subscribers = /* @__PURE__ */ new Set();
311
+ let mutator = null;
312
+ function subscribeToRequests(subscriber) {
313
+ subscribers.add(subscriber);
314
+ return () => {
315
+ subscribers.delete(subscriber);
316
+ };
317
+ }
318
+ function claimRequestMutation(owner) {
319
+ if (mutator !== null) console.warn(`%c FLARE %c
320
+
321
+ What: two things tried to add headers to outgoing requests. Only one can.
322
+
323
+ Why it matters: the first one stopped. Requests can now go out without a traceparent header, so Flare cannot link a browser request to its server trace.
324
+
325
+ How to fix: use one Flare instance, and check your bundle for two copies of @flareapp/js.`, "background:#e11d48;color:#fff;font-weight:bold;font-size:14px;padding:2px 6px", "color:#e11d48;font-size:13px");
326
+ mutator = owner;
327
+ return () => {
328
+ if (mutator === owner) mutator = null;
329
+ };
330
+ }
331
+ function hasRequestSubscribers() {
332
+ return subscribers.size > 0 || mutator !== null;
333
+ }
334
+ /**
335
+ * Tells every subscriber a request is about to go out. Returns the (possibly mutated) `init` and
336
+ * `headers` plus one `settle` callback that fans the result out to every subscriber. Returns null
337
+ * when nothing acted on the request; the wrapper must then call the real fetch or send untouched.
338
+ * A subscriber that throws is skipped, so instrumentation never breaks the app's request.
339
+ */
340
+ function publishRequestStart(start) {
341
+ const handlers = [];
342
+ for (const subscriber of subscribers) try {
343
+ const handler = subscriber(start);
344
+ if (handler) handlers.push(handler);
345
+ } catch {}
346
+ let init = start.init;
347
+ let headers;
348
+ if (mutator) try {
349
+ const handler = mutator(start);
350
+ if (handler) {
351
+ handlers.push(handler);
352
+ if (handler.init !== void 0) init = handler.init;
353
+ headers = handler.headers;
354
+ }
355
+ } catch {}
356
+ if (handlers.length === 0 && init === start.init && headers === void 0) return null;
357
+ return {
358
+ init,
359
+ headers,
360
+ settle(result) {
361
+ for (const handler of handlers) try {
362
+ handler.onSettle?.(result);
363
+ } catch {}
364
+ }
365
+ };
366
+ }
367
+
368
+ //#endregion
369
+ //#region src/tracing/requests/internalRequest.ts
370
+ /**
371
+ * Marks a request the SDK makes for its own bookkeeping (right now: fetching a source file so a
372
+ * stack frame can show a code snippet). The fetch patch passes those straight through: they are
373
+ * not the app's traffic, so tracing them puts a span in the customer's waterfall for a request
374
+ * their code never made, and propagating a `traceparent` on them is just as wrong.
375
+ *
376
+ * Flare's ingest calls are excluded by URL instead (`isFlareIngestUrl`), because their endpoints
377
+ * are known up front. A snippet fetch targets the customer's own asset, so only the caller knows.
378
+ */
379
+ const INTERNAL_REQUEST_KEY = "__flare_internal_request__";
380
+ /** An init that marks the request as Flare's own. Unknown init keys are ignored by `fetch`. */
381
+ function internalRequestInit(init) {
382
+ return {
383
+ ...init,
384
+ [INTERNAL_REQUEST_KEY]: true
385
+ };
386
+ }
387
+ function isInternalRequest(init) {
388
+ return init?.[INTERNAL_REQUEST_KEY] === true;
389
+ }
390
+
391
+ //#endregion
392
+ //#region src/tracing/requests/supportsNativeFetch.ts
393
+ /** True if `fn` is the browser's native fetch (not a polyfill/wrapper). */
394
+ function isNativeFetch(fn) {
395
+ return typeof fn === "function" && /native code/.test(Function.prototype.toString.call(fn));
396
+ }
397
+ /**
398
+ * Whether the current global `fetch` is native. A polyfilled fetch (e.g. whatwg-fetch) is
399
+ * XHR-backed; skip instrumenting it so the XHR patch is the single source for those requests.
400
+ * Ported from Sentry, including the hidden-iframe fallback used when another library has already
401
+ * wrapped `fetch` and the direct toString check is unreliable.
402
+ */
403
+ function supportsNativeFetch() {
404
+ const globals = globalThis;
405
+ if (typeof globals.fetch !== "function") return false;
406
+ if (isNativeFetch(globals.fetch)) return true;
407
+ let result = false;
408
+ const document = globals.document;
409
+ if (document && typeof document.createElement === "function") {
410
+ let sandbox = null;
411
+ try {
412
+ sandbox = document.createElement("iframe");
413
+ sandbox.hidden = true;
414
+ document.head.appendChild(sandbox);
415
+ const sandboxWindow = sandbox.contentWindow;
416
+ if (sandboxWindow && typeof sandboxWindow.fetch === "function") result = isNativeFetch(sandboxWindow.fetch);
417
+ } catch {
418
+ result = false;
419
+ } finally {
420
+ try {
421
+ sandbox?.remove();
422
+ } catch {}
423
+ }
424
+ }
425
+ return result;
426
+ }
427
+
428
+ //#endregion
429
+ //#region src/tracing/utils/createPatcher.ts
430
+ /**
431
+ * One `installed` flag for the whole set, not one per method: `open` remembers the URL that `send`
432
+ * reads, so a half patched set is broken.
433
+ */
434
+ function createPatcher() {
435
+ let installed = false;
436
+ let names = [];
437
+ return {
438
+ get installed() {
439
+ return installed;
440
+ },
441
+ install(target, patches) {
442
+ if (installed) return;
443
+ function applyOne(name) {
444
+ const wrap = patches[name];
445
+ if (wrap) fill(target, name, wrap);
446
+ }
447
+ names = Object.keys(patches);
448
+ for (const name of names) applyOne(name);
449
+ installed = true;
450
+ },
451
+ uninstall(target) {
452
+ if (!installed) return;
453
+ if (!names.every((name) => {
454
+ const current = target[name];
455
+ return typeof current !== "function" || Boolean(current.__flare_original__);
456
+ })) return;
457
+ for (const name of names) unfill(target, name);
458
+ installed = false;
459
+ }
460
+ };
461
+ }
462
+
463
+ //#endregion
464
+ //#region src/instrumentation/requests/instrumentFetch.ts
465
+ function resolveRequest(input, init) {
466
+ let url;
467
+ let method = init?.method;
468
+ if (typeof Request !== "undefined" && input instanceof Request) {
469
+ url = input.url;
470
+ method = method ?? input.method;
471
+ } else url = typeof input === "string" ? input : String(input);
472
+ return {
473
+ method: (method ?? "GET").toUpperCase(),
474
+ url
475
+ };
476
+ }
477
+ function createFetchWrapper(original) {
478
+ return function(input, init) {
479
+ const call = (i) => original.call(this, input, i);
480
+ let watched = null;
481
+ try {
482
+ if (hasRequestSubscribers() && !isInternalRequest(init)) {
483
+ const request = resolveRequest(input, init);
484
+ watched = publishRequestStart({
485
+ kind: "fetch",
486
+ method: request.method,
487
+ url: request.url,
488
+ input,
489
+ init
490
+ });
491
+ }
492
+ } catch {
493
+ watched = null;
494
+ }
495
+ if (!watched) return call(init);
496
+ const settle = watched.settle;
497
+ const finishError = (error) => {
498
+ settle({ error });
499
+ return Promise.reject(error);
500
+ };
501
+ let promise;
502
+ try {
503
+ promise = call(watched.init);
504
+ } catch (error) {
505
+ return finishError(error);
506
+ }
507
+ return promise.then((response) => {
508
+ settle({ status: response.status });
509
+ return response;
510
+ }, finishError);
511
+ };
512
+ }
513
+ const patcher$1 = createPatcher();
514
+ function instrumentFetch() {
515
+ if (patcher$1.installed) return;
516
+ const globals = globalThis;
517
+ if (typeof globals.fetch !== "function") return;
518
+ if (!supportsNativeFetch()) return;
519
+ patcher$1.install(globals, { fetch: (original) => createFetchWrapper(original) });
520
+ }
521
+ function unpatchFetch() {
522
+ patcher$1.uninstall(globalThis);
523
+ }
524
+
525
+ //#endregion
526
+ //#region src/instrumentation/requests/instrumentXHR.ts
527
+ const XHR_DONE = 4;
528
+ const xhrState = /* @__PURE__ */ new WeakMap();
529
+ function releaseRequestRefs(state) {
530
+ state.watched = void 0;
531
+ state.onDone = void 0;
532
+ }
533
+ function settleOnce(state, result) {
534
+ state.ended = true;
535
+ state.watched?.settle(result);
536
+ releaseRequestRefs(state);
537
+ }
538
+ function createXHROpen(original) {
539
+ return function(method, url, ...rest) {
540
+ const prior = xhrState.get(this);
541
+ if (prior && prior.watched && !prior.ended) {
542
+ if (prior.onDone) this.removeEventListener("readystatechange", prior.onDone);
543
+ settleOnce(prior, { aborted: true });
544
+ }
545
+ try {
546
+ if (method && url != null) xhrState.set(this, {
547
+ method: String(method).toUpperCase(),
548
+ url: String(url),
549
+ appHeaders: /* @__PURE__ */ new Set(),
550
+ ended: false
551
+ });
552
+ else xhrState.delete(this);
553
+ } catch {
554
+ xhrState.delete(this);
555
+ }
556
+ return original.apply(this, [
557
+ method,
558
+ url,
559
+ ...rest
560
+ ]);
561
+ };
562
+ }
563
+ function createXHRSetRequestHeader(original) {
564
+ return function(name, value) {
565
+ original.call(this, name, value);
566
+ if (typeof name === "string") xhrState.get(this)?.appHeaders.add(name.toLowerCase());
567
+ };
568
+ }
569
+ function applyHeaders(xhr, state) {
570
+ const headers = state.watched?.headers;
571
+ if (!headers) return;
572
+ for (const [name, value] of Object.entries(headers)) {
573
+ if (state.appHeaders.has(name.toLowerCase())) continue;
574
+ try {
575
+ xhr.setRequestHeader(name, value);
576
+ } catch {}
577
+ }
578
+ }
579
+ function createXHRSend(original) {
580
+ return function(body) {
581
+ const send = () => original.call(this, body);
582
+ const state = xhrState.get(this);
583
+ if (!state || !hasRequestSubscribers()) return send();
584
+ if (state.ended) return send();
585
+ let watched = null;
586
+ try {
587
+ watched = publishRequestStart({
588
+ kind: "xhr",
589
+ method: state.method,
590
+ url: state.url
591
+ });
592
+ } catch {
593
+ watched = null;
594
+ }
595
+ if (!watched) return send();
596
+ state.watched = watched;
597
+ applyHeaders(this, state);
598
+ const onDone = () => {
599
+ if (this.readyState !== XHR_DONE) return;
600
+ this.removeEventListener("readystatechange", onDone);
601
+ if (state.ended) return;
602
+ let status = 0;
603
+ try {
604
+ status = this.status;
605
+ } catch {}
606
+ settleOnce(state, { status });
607
+ };
608
+ this.addEventListener("readystatechange", onDone);
609
+ state.onDone = onDone;
610
+ try {
611
+ return send();
612
+ } catch (error) {
613
+ this.removeEventListener("readystatechange", onDone);
614
+ settleOnce(state, { error });
615
+ throw error;
616
+ }
617
+ };
618
+ }
619
+ const patcher = createPatcher();
620
+ let patchedPrototype = null;
621
+ function instrumentXHR() {
622
+ if (patcher.installed) return;
623
+ const xhrConstructor = globalThis.XMLHttpRequest;
624
+ if (typeof xhrConstructor !== "function" || !xhrConstructor.prototype) return;
625
+ patcher.install(xhrConstructor.prototype, {
626
+ open: (original) => createXHROpen(original),
627
+ setRequestHeader: (original) => createXHRSetRequestHeader(original),
628
+ send: (original) => createXHRSend(original)
629
+ });
630
+ patchedPrototype = xhrConstructor.prototype;
631
+ }
632
+ function unpatchXHR() {
633
+ if (!patchedPrototype) return;
634
+ patcher.uninstall(patchedPrototype);
635
+ if (!patcher.installed) patchedPrototype = null;
636
+ }
637
+
638
+ //#endregion
639
+ //#region src/instrumentation/requests/requestPatches.ts
640
+ let subscriptions = 0;
641
+ /**
642
+ * Keeps fetch and XHR patched while at least one subscriber lives. Counted, so turning tracing off
643
+ * cannot remove a patch that breadcrumbs still need.
644
+ *
645
+ * @param subscribe registers one subscriber and returns its own teardown
646
+ */
647
+ function withRequestPatches(subscribe) {
648
+ if (subscriptions === 0) {
649
+ instrumentFetch();
650
+ instrumentXHR();
651
+ }
652
+ subscriptions++;
653
+ const unsubscribe = subscribe();
654
+ let removed = false;
655
+ return () => {
656
+ if (removed) return;
657
+ removed = true;
658
+ unsubscribe();
659
+ subscriptions--;
660
+ if (subscriptions === 0) {
661
+ unpatchFetch();
662
+ unpatchXHR();
663
+ }
664
+ };
665
+ }
666
+
667
+ //#endregion
668
+ //#region src/tracing/requests/propagation.ts
669
+ /** Follows OTel/Sentry `tracePropagationTargets`: same-origin by default, `[]` disables all. */
670
+ function shouldPropagate(url, absoluteUrl, currentOrigin, targets) {
671
+ if (targets) {
672
+ if (targets.length === 0) return false;
673
+ return targets.some((t) => typeof t === "string" ? url.includes(t) : t.test(url));
674
+ }
675
+ return absoluteUrl !== null && absoluteUrl.origin === currentOrigin;
676
+ }
677
+ /** Null on a throwing or malformed entry: the caller then passes the source through untouched, so a
678
+ * bad merge never breaks the host request. */
679
+ function headerPairsFrom(source) {
680
+ try {
681
+ const pairs = [];
682
+ for (const entry of source) {
683
+ if (entry === null || typeof entry !== "object") return null;
684
+ const pair = Array.from(entry);
685
+ if (pair.length !== 2) return null;
686
+ pairs.push([String(pair[0]), String(pair[1])]);
687
+ }
688
+ return pairs;
689
+ } catch {
690
+ return null;
691
+ }
692
+ }
693
+ /** Fetch accepts any iterable of string pairs as HeadersInit (Map, URLSearchParams, cross-realm Headers). */
694
+ function isIterable(value) {
695
+ return value !== null && (typeof value === "object" || typeof value === "function") && typeof value[Symbol.iterator] === "function";
696
+ }
697
+ /**
698
+ * A new `RequestInit` carrying `traceparent`, without mutating the caller's `Request` or `init`.
699
+ * Caller-wins: a `traceparent` the caller already set is left alone, matching XHR's
700
+ * `hasAppTraceparent` skip. Returning an init rather than a rebuilt `Request` keeps the caller's
701
+ * single-shot body intact.
702
+ */
703
+ function mergeTraceparentHeader(input, init, traceparent) {
704
+ const source = init?.headers ?? (typeof Request !== "undefined" && input instanceof Request ? input.headers : void 0);
705
+ let headers;
706
+ if (source instanceof Headers) {
707
+ if (source.has("traceparent")) return init;
708
+ headers = new Headers(source);
709
+ headers.set("traceparent", traceparent);
710
+ } else if (Array.isArray(source)) {
711
+ if (source.some(([k]) => String(k).toLowerCase() === "traceparent")) return init;
712
+ headers = [...source, ["traceparent", traceparent]];
713
+ } else if (isIterable(source)) {
714
+ const pairs = headerPairsFrom(source);
715
+ if (pairs === null) headers = source;
716
+ else if (pairs.some(([k]) => k.toLowerCase() === "traceparent")) return init;
717
+ else headers = [...pairs, ["traceparent", traceparent]];
718
+ } else if (source) {
719
+ if (Object.keys(source).some((k) => k.toLowerCase() === "traceparent")) return init;
720
+ headers = {
721
+ ...source,
722
+ traceparent
723
+ };
724
+ } else headers = { traceparent };
725
+ const result = { headers };
726
+ if (init) {
727
+ const descriptors = Object.getOwnPropertyDescriptors(init);
728
+ delete descriptors.headers;
729
+ Object.defineProperties(result, descriptors);
730
+ }
731
+ if (result.duplex === void 0 && typeof Request !== "undefined" && input instanceof Request && input.body != null) result.duplex = "half";
732
+ return result;
733
+ }
734
+
735
+ //#endregion
736
+ //#region src/tracing/requests/httpRequestSpan.ts
737
+ const REQUEST_SPAN_TYPES = {
738
+ fetch: _flareapp_core.BrowserSpanType.Fetch,
739
+ xhr: _flareapp_core.BrowserSpanType.Xhr
740
+ };
741
+ const INLINE_SCHEMES = new Set(["data:", "blob:"]);
742
+ /** The real browser context. Falls back to the origin where there is no document (SSR, tests). */
743
+ function browserUrlContext() {
744
+ const origin = globalThis.location?.origin ?? "";
745
+ return {
746
+ origin,
747
+ base: () => globalThis.document?.baseURI || origin
748
+ };
749
+ }
750
+ /** Resolve `url` to an absolute URL against `base`, or null if it cannot be parsed. */
751
+ function safeAbsolute(url, base) {
752
+ try {
753
+ return new URL(url, base || void 0);
754
+ } catch {
755
+ return null;
756
+ }
757
+ }
758
+ let ingestCacheKey = null;
759
+ let ingestCacheHrefs = [];
760
+ function resolvedIngestHrefs(config, base) {
761
+ const raw = [
762
+ config.ingestUrl,
763
+ config.logsIngestUrl,
764
+ config.tracesIngestUrl
765
+ ];
766
+ const key = `${base} ${raw.join(" ")}`;
767
+ if (key !== ingestCacheKey) {
768
+ ingestCacheKey = key;
769
+ ingestCacheHrefs = raw.filter((u) => typeof u === "string" && u.length > 0).map((u) => safeAbsolute(u, base)).filter((u) => u !== null).map((u) => u.href);
770
+ }
771
+ return ingestCacheHrefs;
772
+ }
773
+ function matchesIngestHref(href, ingestHref) {
774
+ if (!href.startsWith(ingestHref)) return false;
775
+ const next = href.charAt(ingestHref.length);
776
+ return next === "" || next === "/" || next === "?" || next === "#";
777
+ }
778
+ /**
779
+ * True when `resolved` targets one of Flare's own ingest endpoints (never traced). The configured
780
+ * URLs are resolved against `base` first: a relative one (a customer proxying ingest through their
781
+ * own origin) would otherwise never match, so every flush POST would open a span that arms the next
782
+ * flush, forever.
783
+ */
784
+ function isFlareIngestUrl(resolved, config, base) {
785
+ if (!resolved) return false;
786
+ return resolvedIngestHrefs(config, base).some((ingestHref) => matchesIngestHref(resolved.href, ingestHref));
787
+ }
788
+ /**
789
+ * Shared request-span attributes for a fetch/XHR call. The `url.*` attributes are redacted the same
790
+ * way as error reports, so tokens and reset codes never leak.
791
+ */
792
+ function requestSpanAttributes(method, resolved, url, config) {
793
+ return {
794
+ "http.request.method": method,
795
+ ...(0, _flareapp_core.urlAttributes)(resolved ? resolved.href : url, config.urlDenylist),
796
+ ...resolved ? { "server.address": resolved.hostname } : {},
797
+ ...resolved && resolved.port ? { "server.port": Number(resolved.port) } : {}
798
+ };
799
+ }
800
+ /**
801
+ * Completion mapping shared by fetch and XHR: record the status and mark an error on 5xx.
802
+ * `zeroIsError` additionally maps status 0 to error. XHR passes it only for http(s), where status
803
+ * 0 at DONE is always a network/CORS failure or abort; file:// and custom schemes return 0 on
804
+ * success, so it isn't set there. Fetch never passes it (an opaque no-cors response is 0, not error).
805
+ */
806
+ function endHttpRequestSpan(span, status, opts) {
807
+ span.setAttribute("http.response.status_code", status);
808
+ if (status >= 500 || opts?.zeroIsError && status === 0) span.setStatus({ code: _flareapp_core.SpanStatusCode.Error });
809
+ span.end();
810
+ }
811
+ function finishHttpSpanError(span, error) {
812
+ span.setStatus({
813
+ code: _flareapp_core.SpanStatusCode.Error,
814
+ message: error instanceof Error ? error.message : String(error)
815
+ });
816
+ span.end();
817
+ }
818
+ /**
819
+ * Propagation gate plus `traceparent` build shared by fetch and XHR. Returns null when
820
+ * `shouldPropagate` rejects the URL (caller then skips header injection).
821
+ */
822
+ function traceparentFor(span, resolved, url, origin, config) {
823
+ if (!shouldPropagate(resolved ? resolved.href : url, resolved, origin, config.tracePropagationTargets)) return null;
824
+ return (0, _flareapp_core.buildTraceparent)(span.traceId, span.spanId, span.isRecording);
825
+ }
826
+ /**
827
+ * Open a request span for one outgoing fetch or XHR call. Null means the URL is one of Flare's own
828
+ * ingest endpoints, so the caller passes the request through untraced.
829
+ *
830
+ * `absoluteUrl` comes back with the span because both callers need it afterwards: for the traceparent
831
+ * gate, and for XHR's http(s)-only status-0 rule.
832
+ */
833
+ function startHttpRequestSpan(tracer, request) {
834
+ const { method, url, urls, spanType } = request;
835
+ const config = tracer.config;
836
+ const base = urls.base();
837
+ const resolved = safeAbsolute(url, base);
838
+ if (isFlareIngestUrl(resolved, config, base)) return null;
839
+ if (resolved && INLINE_SCHEMES.has(resolved.protocol)) return null;
840
+ const pathname = resolved ? resolved.pathname : url;
841
+ return {
842
+ span: tracer.startSpan(`${method} ${pathname}`, {
843
+ spanType,
844
+ attributes: requestSpanAttributes(method, resolved, url, config)
845
+ }),
846
+ absoluteUrl: resolved
847
+ };
848
+ }
849
+
850
+ //#endregion
851
+ //#region src/tracing/requests/traceRequests.ts
852
+ /**
853
+ * For http and https, status 0 at DONE means the request got no response.
854
+ *
855
+ * Other schemes return 0 when they succeed. file:// does, and so do custom ones like Electron's
856
+ * registerFileProtocol. A URL we could not parse is not an error either.
857
+ */
858
+ function zeroIsError(absoluteUrl) {
859
+ return absoluteUrl !== null && (absoluteUrl.protocol === "http:" || absoluteUrl.protocol === "https:");
860
+ }
861
+ function propagate(span, absoluteUrl, start, urls, tracer) {
862
+ const traceparent = traceparentFor(span, absoluteUrl, start.url, urls.origin, tracer.config);
863
+ if (!traceparent) return {};
864
+ if (start.kind === "xhr") return { headers: { traceparent } };
865
+ if (start.input === void 0) return {};
866
+ return { init: mergeTraceparentHeader(start.input, start.init, traceparent) };
867
+ }
868
+ /** Tracing takes the mutation slot, not a plain subscription, because it adds a `traceparent` header. */
869
+ function traceRequests(tracer, urls) {
870
+ return claimRequestMutation((start) => {
871
+ if (!tracer.config.enableTracing) return;
872
+ const started = startHttpRequestSpan(tracer, {
873
+ method: start.method,
874
+ url: start.url,
875
+ urls,
876
+ spanType: REQUEST_SPAN_TYPES[start.kind]
877
+ });
878
+ if (!started) return;
879
+ const { span, absoluteUrl } = started;
880
+ let mutated = {};
881
+ try {
882
+ mutated = propagate(span, absoluteUrl, start, urls, tracer);
883
+ } catch {
884
+ mutated = {};
885
+ }
886
+ return {
887
+ ...mutated,
888
+ onSettle({ status, error, aborted }) {
889
+ if (aborted) {
890
+ span.setStatus({ code: _flareapp_core.SpanStatusCode.Error });
891
+ span.end();
892
+ return;
893
+ }
894
+ if (error !== void 0) {
895
+ finishHttpSpanError(span, error);
896
+ return;
897
+ }
898
+ if (start.kind === "xhr") {
899
+ endHttpRequestSpan(span, status ?? 0, { zeroIsError: zeroIsError(absoluteUrl) });
900
+ return;
901
+ }
902
+ endHttpRequestSpan(span, status ?? 0);
903
+ }
904
+ };
905
+ });
906
+ }
907
+
908
+ //#endregion
909
+ //#region src/breadcrumbs/RequestRecorder.ts
910
+ var RequestRecorder = class {
911
+ type = "browser_request";
912
+ constructor(host) {
913
+ this.host = host;
914
+ this.subscribe = this.subscribe.bind(this);
915
+ this.onStart = this.onStart.bind(this);
916
+ this.onSettle = this.onSettle.bind(this);
917
+ }
918
+ install() {
919
+ return withRequestPatches(this.subscribe);
920
+ }
921
+ subscribe() {
922
+ return subscribeToRequests(this.onStart);
923
+ }
924
+ onStart(start) {
925
+ const base = browserUrlContext().base();
926
+ const absolute = safeAbsolute(start.url, base);
927
+ if (isFlareIngestUrl(absolute, this.host.config(), base)) return;
928
+ return { onSettle: this.onSettle.bind(this, start, absolute) };
929
+ }
930
+ onSettle(start, absolute, settle) {
931
+ const url = absolute ? absolute.href : start.url;
932
+ const attributes = {
933
+ "http.request.method": start.method,
934
+ "url.full": (0, _flareapp_core.breadcrumbUrl)(url, this.host.config().urlDenylist)
935
+ };
936
+ if (absolute?.hostname) attributes["server.address"] = absolute.hostname;
937
+ if (settle.status !== void 0) attributes["http.response.status_code"] = settle.status;
938
+ this.host.record(REQUEST_SPAN_TYPES[start.kind], attributes, (0, _flareapp_core.defaultNowNano)());
939
+ }
940
+ };
941
+
942
+ //#endregion
943
+ //#region src/breadcrumbs/index.ts
944
+ /** Starts every recorder, returns one teardown. A recorder that fails to install is skipped. */
945
+ function startBreadcrumbs(host) {
946
+ if (typeof document === "undefined") return () => {};
947
+ const recorders = [
948
+ new ClickRecorder(host),
949
+ new FormChangeRecorder(host),
950
+ new RequestRecorder(host),
951
+ new NavigationRecorder(host)
952
+ ];
953
+ const teardowns = [];
954
+ for (const recorder of recorders) try {
955
+ teardowns.push(recorder.install());
956
+ } catch {}
957
+ return () => {
958
+ for (const teardown of teardowns) try {
959
+ teardown();
960
+ } catch {}
961
+ };
962
+ }
963
+
964
+ //#endregion
965
+ //#region src/browser/BrowserFlushScheduler.ts
966
+ var BrowserFlushScheduler = class {
967
+ register(flush) {
968
+ if (typeof document === "undefined" || !document) return;
969
+ document.addEventListener("visibilitychange", () => {
970
+ if (document.visibilityState === "hidden") flush({ keepalive: true });
971
+ });
972
+ if (typeof window !== "undefined" && window) window.addEventListener("pagehide", () => flush({ keepalive: true }));
973
+ }
974
+ };
975
+
976
+ //#endregion
977
+ //#region src/browser/context/cookie.ts
978
+ /**
979
+ * Parses `document.cookie` into `http.request.cookies`, redacting the value of any cookie whose name
980
+ * matches `denylist`. Null-prototype accumulator so a cookie named `__proto__` is stored, not dropped.
981
+ */
982
+ function cookie(denylist) {
983
+ if (!window.document.cookie) return {};
984
+ const cookies = Object.create(null);
985
+ window.document.cookie.split("; ").forEach((rawCookie) => {
986
+ const idx = rawCookie.indexOf("=");
987
+ if (idx === -1) {
988
+ cookies[rawCookie] = denylist.test(rawCookie) ? "[redacted]" : "";
989
+ return;
990
+ }
991
+ const name = rawCookie.slice(0, idx);
992
+ const value = rawCookie.slice(idx + 1);
993
+ cookies[name] = denylist.test(name) ? "[redacted]" : value;
994
+ });
995
+ return { "http.request.cookies": cookies };
996
+ }
997
+
998
+ //#endregion
999
+ //#region src/browser/context/request.ts
1000
+ /**
1001
+ * @param hrefOverride when set, the `url.*` attributes come from it instead of the live
1002
+ * `window.location.href` (a framework navigation root whose router knows the destination
1003
+ * before the URL commits). The override is pre-validated by the caller.
1004
+ */
1005
+ function request(urlDenylist, hrefOverride) {
1006
+ return {
1007
+ ...(0, _flareapp_core.urlAttributes)(hrefOverride ?? window.location.href, urlDenylist),
1008
+ "user_agent.original": window.navigator.userAgent,
1009
+ "http.request.referrer": (0, _flareapp_core.redactUrlQuery)(window.document.referrer, urlDenylist),
1010
+ "document.ready_state": window.document.readyState
1011
+ };
1012
+ }
1013
+
1014
+ //#endregion
1015
+ //#region src/browser/context/collectBrowser.ts
1016
+ function browserEntryPoint(config, urlOverride) {
1017
+ if (typeof window === "undefined") return { "flare.entry_point.type": "web" };
1018
+ const attrs = { "flare.entry_point.type": "web" };
1019
+ const href = urlOverride ? urlOverride.href : window?.location?.href;
1020
+ if (href) {
1021
+ attrs["flare.entry_point.value"] = (0, _flareapp_core.redactUrlQuery)(href, config.urlDenylist);
1022
+ const pathname = urlOverride ? urlOverride.pathname : window?.location?.pathname;
1023
+ if (pathname) {
1024
+ attrs["flare.entry_point.handler.identifier"] = pathname;
1025
+ attrs["http.route"] = pathname;
1026
+ attrs["flare.entry_point.handler.type"] = "browser";
1027
+ }
1028
+ }
1029
+ return attrs;
1030
+ }
1031
+ const collectBrowser = (config) => {
1032
+ const attrs = { ...browserEntryPoint(config) };
1033
+ if (typeof window === "undefined") return attrs;
1034
+ if (window?.location?.hostname) attrs["host.name"] = window.location.hostname;
1035
+ Object.assign(attrs, request(config.urlDenylist));
1036
+ Object.assign(attrs, cookie(config.urlDenylist));
1037
+ return attrs;
1038
+ };
1039
+
1040
+ //#endregion
1041
+ //#region src/browser/FetchFileReader.ts
1042
+ /**
1043
+ * Fetches source files so the stack-trace builder can render a snippet around the offending line.
1044
+ * Only http(s) is fetched: other schemes (chrome-extension://, file://, blob:, data:) would cross a
1045
+ * privilege boundary or hit a CORS/CSP wall for nothing. Returns null on any failure, never throws.
1046
+ */
1047
+ var FetchFileReader = class {
1048
+ read(url) {
1049
+ if (!/^https?:\/\//i.test(url)) return Promise.resolve(null);
1050
+ return fetch(url, internalRequestInit()).then((response) => {
1051
+ if (response.status !== 200) return null;
1052
+ return response.text();
1053
+ }).catch(() => null);
1054
+ }
1055
+ };
1056
+
1057
+ //#endregion
1058
+ //#region src/env/index.ts
1059
+ const CLIENT_VERSION = typeof process !== "undefined" && true ? "2.9.0" : "?";
1060
+
1061
+ //#endregion
1062
+ //#region src/tracing/utils/instrumentationGuard.ts
1063
+ /** For a callback the host invokes: a router guard, a store subscriber, ... */
1064
+ function insulate(fn) {
1065
+ return (...args) => {
1066
+ try {
1067
+ fn(...args);
1068
+ } catch {}
1069
+ };
1070
+ }
1071
+ /** Invoke a teardown fn now (if present), swallowing any throw. For cleanup chains. */
1072
+ function safeInvoke(fn) {
1073
+ try {
1074
+ fn?.();
1075
+ } catch {}
1076
+ }
1077
+ const instrumented = /* @__PURE__ */ new WeakMap();
1078
+ /**
1079
+ * Instrument `target` at most once at a time, tearing down any prior instrumentation of the same object
1080
+ * first. Vite HMR re-runs boot code against a router that survives the reload, so without this every
1081
+ * cycle appends another listener set that is never removed. Keyed on the object, so a genuinely new
1082
+ * router is unaffected.
1083
+ *
1084
+ * `install` hands each teardown to `track` as it produces it. A router's own `subscribe` / `on` / guard
1085
+ * registration can throw, and `install` runs during the host's bootstrap, so a throw part-way through
1086
+ * unwinds what already succeeded (newest first) and stops here rather than reaching the host.
1087
+ *
1088
+ * @returns the cleanup, or a no-op when the install failed and already unwound itself.
1089
+ */
1090
+ function instrumentOnce(target, install) {
1091
+ instrumented.get(target)?.();
1092
+ const teardowns = [];
1093
+ function unwind() {
1094
+ for (let i = teardowns.length - 1; i >= 0; i--) safeInvoke(teardowns[i]);
1095
+ }
1096
+ try {
1097
+ install((teardown) => {
1098
+ teardowns.push(teardown);
1099
+ });
1100
+ } catch {
1101
+ unwind();
1102
+ return () => {};
1103
+ }
1104
+ function cleanup() {
1105
+ unwind();
1106
+ if (instrumented.get(target) === cleanup) instrumented.delete(target);
1107
+ }
1108
+ instrumented.set(target, cleanup);
1109
+ return cleanup;
1110
+ }
1111
+
1112
+ //#endregion
1113
+ //#region src/browser/context/collectBrowserSpanContext.ts
1114
+ /**
1115
+ * Entry point plus request identity for a pageload/navigation root. Deliberately leaner than the report
1116
+ * context: no cookies, no structured query params, no host.name (that is resource-level). Captured at
1117
+ * span start, so a long-lived root reflects the page it represents rather than the page at close.
1118
+ *
1119
+ * @param hrefOverride destination href for a router that reports where it is going before the URL
1120
+ * commits. Only the URL-derived keys come from it; the rest always reflect the live document. An
1121
+ * unparseable override falls back to the live location instead of throwing into root creation.
1122
+ */
1123
+ function collectBrowserSpanContext(config, hrefOverride) {
1124
+ if (typeof window === "undefined") return {};
1125
+ const url = absoluteUrl(hrefOverride);
1126
+ return {
1127
+ ...browserEntryPoint(config, url),
1128
+ ...request(config.urlDenylist, url?.href)
1129
+ };
1130
+ }
1131
+ /**
1132
+ * Updates a root's url after a redirect, or when a newer navigation replaces this one. The root opened
1133
+ * with the first destination, so without this it reports a page the user never reached.
1134
+ *
1135
+ * Does not touch `flare.entry_point.handler.identifier` or `http.route`. Those hold the route template,
1136
+ * and reading them back from the href would turn `/product/[id]` into `/product/p01`.
1137
+ *
1138
+ * Always sets `url.query`, even to an empty string. You can overwrite a span attribute but not remove
1139
+ * it, so going from `/a?x=1` to `/b` would otherwise keep the old query.
1140
+ */
1141
+ function browserSpanUrlAttributes(config, href) {
1142
+ if (typeof window === "undefined") return {};
1143
+ const resolved = absoluteUrl(href);
1144
+ if (!resolved) return {};
1145
+ const attributes = (0, _flareapp_core.urlAttributes)(resolved.href, config.urlDenylist);
1146
+ return {
1147
+ "url.query": "",
1148
+ ...attributes,
1149
+ "flare.entry_point.value": attributes["url.full"]
1150
+ };
1151
+ }
1152
+
1153
+ //#endregion
1154
+ //#region src/tracing/vitals/webvitals/lib/bfcache.ts
1155
+ let bfcacheRestoreTime = -1;
1156
+ const getBFCacheRestoreTime = () => bfcacheRestoreTime;
1157
+ const onBFCacheRestore = (cb) => {
1158
+ addEventListener("pageshow", (event) => {
1159
+ if (event.persisted) {
1160
+ bfcacheRestoreTime = event.timeStamp;
1161
+ cb(event);
1162
+ }
1163
+ }, true);
1164
+ };
1165
+
1166
+ //#endregion
1167
+ //#region src/tracing/vitals/webvitals/lib/bindReporter.ts
1168
+ const getRating = (value, thresholds) => {
1169
+ if (value > thresholds[1]) return "poor";
1170
+ if (value > thresholds[0]) return "needs-improvement";
1171
+ return "good";
1172
+ };
1173
+ const bindReporter = (callback, metric, thresholds, reportAllChanges) => {
1174
+ let prevValue;
1175
+ let delta;
1176
+ return (forceReport) => {
1177
+ if (metric.value >= 0) {
1178
+ if (forceReport || reportAllChanges) {
1179
+ delta = metric.value - (prevValue ?? 0);
1180
+ if (delta || prevValue === void 0) {
1181
+ prevValue = metric.value;
1182
+ metric.delta = delta;
1183
+ metric.rating = getRating(metric.value, thresholds);
1184
+ callback(metric);
1185
+ }
1186
+ }
1187
+ }
1188
+ };
1189
+ };
1190
+
1191
+ //#endregion
1192
+ //#region src/tracing/vitals/webvitals/lib/doubleRAF.ts
1193
+ const doubleRAF = (cb) => {
1194
+ requestAnimationFrame(() => requestAnimationFrame(cb));
1195
+ };
1196
+
1197
+ //#endregion
1198
+ //#region src/tracing/vitals/webvitals/lib/getNavigationEntry.ts
1199
+ const getNavigationEntry = () => {
1200
+ const navigationEntry = performance.getEntriesByType("navigation")[0];
1201
+ if (navigationEntry && navigationEntry.responseStart > 0 && navigationEntry.responseStart < performance.now()) return navigationEntry;
1202
+ };
1203
+
1204
+ //#endregion
1205
+ //#region src/tracing/vitals/webvitals/lib/getActivationStart.ts
1206
+ const getActivationStart = () => {
1207
+ return getNavigationEntry()?.activationStart ?? 0;
1208
+ };
1209
+
1210
+ //#endregion
1211
+ //#region src/tracing/vitals/webvitals/lib/getVisibilityWatcher.ts
1212
+ let firstHiddenTime = -1;
1213
+ const onHiddenFunctions = /* @__PURE__ */ new Set();
1214
+ const initHiddenTime = () => {
1215
+ return document.visibilityState === "hidden" && !document.prerendering ? 0 : Infinity;
1216
+ };
1217
+ const onVisibilityUpdate = (event) => {
1218
+ if (document.visibilityState === "hidden") {
1219
+ if (event.type === "visibilitychange") for (const onHiddenFunction of onHiddenFunctions) onHiddenFunction();
1220
+ if (!isFinite(firstHiddenTime)) {
1221
+ firstHiddenTime = event.type === "visibilitychange" ? event.timeStamp : 0;
1222
+ removeEventListener("prerenderingchange", onVisibilityUpdate, true);
1223
+ }
1224
+ }
1225
+ };
1226
+ const getVisibilityWatcher = (reset = false) => {
1227
+ if (reset) firstHiddenTime = Infinity;
1228
+ if (firstHiddenTime < 0) {
1229
+ const activationStart = getActivationStart();
1230
+ firstHiddenTime = (!document.prerendering ? globalThis.performance.getEntriesByType("visibility-state").find((e) => e.name === "hidden" && e.startTime >= activationStart)?.startTime : void 0) ?? initHiddenTime();
1231
+ addEventListener("visibilitychange", onVisibilityUpdate, true);
1232
+ addEventListener("prerenderingchange", onVisibilityUpdate, true);
1233
+ onBFCacheRestore(() => {
1234
+ setTimeout(() => {
1235
+ firstHiddenTime = initHiddenTime();
1236
+ });
1237
+ });
1238
+ }
1239
+ return {
1240
+ get firstHiddenTime() {
1241
+ return firstHiddenTime;
1242
+ },
1243
+ onHidden(cb) {
1244
+ onHiddenFunctions.add(cb);
1245
+ }
1246
+ };
1247
+ };
1248
+
1249
+ //#endregion
1250
+ //#region src/tracing/vitals/webvitals/lib/generateUniqueID.ts
1251
+ /**
1252
+ * Performantly generate a unique, 30-char string by combining a version
1253
+ * number, the current timestamp with a 13-digit number integer.
1254
+ * @return {string}
1255
+ */
1256
+ const generateUniqueID = () => {
1257
+ return `v6-${Date.now()}-${Math.floor(Math.random() * 8999999999999) + 0xe8d4a51000}`;
1258
+ };
1259
+
1260
+ //#endregion
1261
+ //#region src/tracing/vitals/webvitals/lib/initMetric.ts
1262
+ const initMetric = (name, value = -1, navigationType, navigationId = 0, navigationInteractionId, navigationURL, navigationStartTime) => {
1263
+ const hardNavEntry = getNavigationEntry();
1264
+ const hardNavId = hardNavEntry?.navigationId || 0;
1265
+ let _navigationType = "navigate";
1266
+ if (navigationType) _navigationType = navigationType;
1267
+ else if (getBFCacheRestoreTime() >= 0) _navigationType = "back-forward-cache";
1268
+ else if (hardNavEntry) {
1269
+ if (document.prerendering || getActivationStart() > 0) _navigationType = "prerender";
1270
+ else if (document.wasDiscarded) _navigationType = "restore";
1271
+ else if (hardNavEntry.type) _navigationType = hardNavEntry.type.replace(/_/g, "-");
1272
+ }
1273
+ return {
1274
+ name,
1275
+ value,
1276
+ rating: "good",
1277
+ delta: 0,
1278
+ entries: [],
1279
+ id: generateUniqueID(),
1280
+ navigationType: _navigationType,
1281
+ navigationId: navigationId || hardNavId,
1282
+ navigationInteractionId,
1283
+ navigationURL: navigationURL || hardNavEntry?.name,
1284
+ navigationStartTime: navigationStartTime || 0
1285
+ };
1286
+ };
1287
+
1288
+ //#endregion
1289
+ //#region src/tracing/vitals/webvitals/lib/initUnique.ts
1290
+ const instanceMap = /* @__PURE__ */ new WeakMap();
1291
+ /**
1292
+ * A function that accepts and identity object and a class object and returns
1293
+ * either a new instance of that class or an existing instance, if the
1294
+ * identity object was previously used.
1295
+ */
1296
+ function initUnique(identityObj, ClassObj) {
1297
+ let classInstances = instanceMap.get(ClassObj);
1298
+ if (!classInstances) {
1299
+ classInstances = /* @__PURE__ */ new WeakMap();
1300
+ instanceMap.set(ClassObj, classInstances);
1301
+ }
1302
+ if (!classInstances.get(identityObj)) classInstances.set(identityObj, new ClassObj());
1303
+ return classInstances.get(identityObj);
1304
+ }
1305
+
1306
+ //#endregion
1307
+ //#region src/tracing/vitals/webvitals/lib/LayoutShiftManager.ts
1308
+ var LayoutShiftManager = class {
1309
+ _onAfterProcessingUnexpectedShift;
1310
+ _sessionValue = 0;
1311
+ _sessionEntries = [];
1312
+ _processEntry(entry) {
1313
+ if (entry.hadRecentInput) return;
1314
+ const firstSessionEntry = this._sessionEntries[0];
1315
+ const lastSessionEntry = this._sessionEntries.at(-1);
1316
+ if (this._sessionValue && firstSessionEntry && lastSessionEntry && entry.startTime - lastSessionEntry.startTime < 1e3 && entry.startTime - firstSessionEntry.startTime < 5e3) {
1317
+ this._sessionValue += entry.value;
1318
+ this._sessionEntries.push(entry);
1319
+ } else {
1320
+ this._sessionValue = entry.value;
1321
+ this._sessionEntries = [entry];
1322
+ }
1323
+ this._onAfterProcessingUnexpectedShift?.(entry);
1324
+ }
1325
+ };
1326
+
1327
+ //#endregion
1328
+ //#region src/tracing/vitals/webvitals/lib/observe.ts
1329
+ /**
1330
+ * Takes a performance entry type and a callback function, and creates a
1331
+ * `PerformanceObserver` instance that will observe the specified entry type
1332
+ * with buffering enabled and call the callback _for each entry_.
1333
+ *
1334
+ * This function also feature-detects entry support and wraps the logic in a
1335
+ * try/catch to avoid errors in unsupporting browsers.
1336
+ */
1337
+ const observe = (types, callback, opts = {}) => {
1338
+ try {
1339
+ const supportedTypes = types.filter((t) => PerformanceObserver.supportedEntryTypes.includes(t));
1340
+ if (supportedTypes.length > 0) {
1341
+ const po = new PerformanceObserver((list) => {
1342
+ queueMicrotask(() => {
1343
+ const entries = list.getEntries();
1344
+ if (supportedTypes.length > 1) entries.sort((a, b) => {
1345
+ return a.startTime + a.duration - (b.startTime + b.duration);
1346
+ });
1347
+ callback(entries);
1348
+ });
1349
+ });
1350
+ for (const t of supportedTypes) po.observe({
1351
+ type: t,
1352
+ buffered: true,
1353
+ ...opts
1354
+ });
1355
+ return po;
1356
+ }
1357
+ } catch {}
1358
+ };
1359
+
1360
+ //#endregion
1361
+ //#region src/tracing/vitals/webvitals/lib/softNavs.ts
1362
+ const checkSoftNavsEnabled = (opts) => {
1363
+ return globalThis.PerformanceObserver?.supportedEntryTypes.includes("soft-navigation") && typeof globalThis.PerformanceSoftNavigation?.prototype?.getLargestInteractionContentfulPaint === "function" && opts && opts.reportSoftNavs;
1364
+ };
1365
+ const storeSoftNavEntry = (map, entry) => {
1366
+ map.set(entry.navigationId, entry);
1367
+ if (map.size > 2) {
1368
+ const firstKey = map.keys().next().value;
1369
+ if (firstKey !== void 0) map.delete(firstKey);
1370
+ }
1371
+ };
1372
+
1373
+ //#endregion
1374
+ //#region src/tracing/vitals/webvitals/lib/runOnce.ts
1375
+ const runOnce = (cb) => {
1376
+ let called = false;
1377
+ return () => {
1378
+ if (!called) {
1379
+ cb();
1380
+ called = true;
1381
+ }
1382
+ };
1383
+ };
1384
+
1385
+ //#endregion
1386
+ //#region src/tracing/vitals/webvitals/lib/FCPEntryManager.ts
1387
+ var FCPEntryManager = class {
1388
+ _softNavigationEntryMap;
1389
+ };
1390
+
1391
+ //#endregion
1392
+ //#region src/tracing/vitals/webvitals/lib/whenActivated.ts
1393
+ const whenActivated = (callback) => {
1394
+ if (document.prerendering) addEventListener("prerenderingchange", callback, true);
1395
+ else callback();
1396
+ };
1397
+
1398
+ //#endregion
1399
+ //#region src/tracing/vitals/webvitals/onFCP.ts
1400
+ /** Thresholds for FCP. See https://web.dev/articles/fcp#what_is_a_good_fcp_score */
1401
+ const FCPThresholds = [1800, 3e3];
1402
+ /**
1403
+ * Calculates the [FCP](https://web.dev/articles/fcp) value for the current page and
1404
+ * calls the `callback` function once the value is ready, along with the
1405
+ * relevant `paint` performance entry used to determine the value. The reported
1406
+ * value is a `DOMHighResTimeStamp`.
1407
+ */
1408
+ const onFCP = (onReport, opts = {}) => {
1409
+ const softNavsEnabled = checkSoftNavsEnabled(opts);
1410
+ whenActivated(() => {
1411
+ const fcpEntryManager = initUnique(opts, FCPEntryManager);
1412
+ const visibilityWatcher = getVisibilityWatcher();
1413
+ let metric = initMetric("FCP");
1414
+ let report;
1415
+ const handleEntries = (entries) => {
1416
+ for (const entry of entries) if (entry.name === "first-contentful-paint") {
1417
+ po.disconnect();
1418
+ if (entry.startTime < visibilityWatcher.firstHiddenTime) {
1419
+ metric.value = Math.max(entry.startTime - getActivationStart(), 0);
1420
+ metric.entries.push(entry);
1421
+ metric.navigationId = entry.navigationId || metric.navigationId;
1422
+ report(true);
1423
+ }
1424
+ }
1425
+ };
1426
+ const po = observe(["paint"], handleEntries);
1427
+ if (po) {
1428
+ report = bindReporter(onReport, metric, FCPThresholds, opts.reportAllChanges);
1429
+ onBFCacheRestore((event) => {
1430
+ metric = initMetric("FCP", -1, "back-forward-cache", metric.navigationId, metric.navigationInteractionId, metric.navigationURL, getBFCacheRestoreTime());
1431
+ report = bindReporter(onReport, metric, FCPThresholds, opts.reportAllChanges);
1432
+ doubleRAF(() => {
1433
+ metric.value = performance.now() - event.timeStamp;
1434
+ report(true);
1435
+ });
1436
+ });
1437
+ }
1438
+ if (softNavsEnabled) {
1439
+ const handleSoftNavEntries = (entries) => {
1440
+ entries.forEach((entry) => {
1441
+ if (fcpEntryManager._softNavigationEntryMap && entry.navigationId) storeSoftNavEntry(fcpEntryManager._softNavigationEntryMap, entry);
1442
+ metric = initMetric("FCP", Math.max((entry.presentationTime || entry.paintTime || 0) - entry.startTime, 0), "soft-navigation", entry.navigationId, entry.interactionId, entry.name, entry.startTime);
1443
+ report = bindReporter(onReport, metric, FCPThresholds, opts.reportAllChanges);
1444
+ report(true);
1445
+ });
1446
+ };
1447
+ observe(["soft-navigation"], handleSoftNavEntries, opts);
1448
+ }
1449
+ });
1450
+ };
1451
+
1452
+ //#endregion
1453
+ //#region src/tracing/vitals/webvitals/onCLS.ts
1454
+ /** Thresholds for CLS. See https://web.dev/articles/cls#what_is_a_good_cls_score */
1455
+ const CLSThresholds = [.1, .25];
1456
+ /**
1457
+ * Calculates the [CLS](https://web.dev/articles/cls) value for the current page and
1458
+ * calls the `callback` function once the value is ready to be reported, along
1459
+ * with all `layout-shift` performance entries that were used in the metric
1460
+ * value calculation. The reported value is a `double` (corresponding to a
1461
+ * [layout shift score](https://web.dev/articles/cls#layout_shift_score)).
1462
+ *
1463
+ * If the `reportAllChanges` configuration option is set to `true`, the
1464
+ * `callback` function will be called as soon as the value is initially
1465
+ * determined as well as any time the value changes throughout the page
1466
+ * lifespan.
1467
+ *
1468
+ * _**Important:** CLS should be continually monitored for changes throughout
1469
+ * the entire lifespan of a page—including if the user returns to the page after
1470
+ * it's been hidden/backgrounded. However, since browsers often [will not fire
1471
+ * additional callbacks once the user has backgrounded a
1472
+ * page](https://developer.chrome.com/blog/page-lifecycle-api/#advice-hidden),
1473
+ * `callback` is always called when the page's visibility state changes to
1474
+ * hidden. As a result, the `callback` function might be called multiple times
1475
+ * during the same page load._
1476
+ */
1477
+ const onCLS = (onReport, opts = {}) => {
1478
+ const visibilityWatcher = getVisibilityWatcher();
1479
+ onFCP(runOnce(() => {
1480
+ let metric = initMetric("CLS", 0);
1481
+ let report;
1482
+ const layoutShiftManager = initUnique(opts, LayoutShiftManager);
1483
+ const initNewCLSMetric = (navigationType, navigationId, navigationInteractionId, navigationURL, navigationStartTime) => {
1484
+ metric = initMetric("CLS", 0, navigationType, navigationId, navigationInteractionId, navigationURL, navigationStartTime);
1485
+ layoutShiftManager._sessionValue = 0;
1486
+ report = bindReporter(onReport, metric, CLSThresholds, opts.reportAllChanges);
1487
+ };
1488
+ const updateAndReportMetric = (forceReport = false) => {
1489
+ if (layoutShiftManager._sessionValue > metric.value) {
1490
+ metric.value = layoutShiftManager._sessionValue;
1491
+ metric.entries = layoutShiftManager._sessionEntries;
1492
+ }
1493
+ report(forceReport);
1494
+ };
1495
+ const handleSoftNavEntry = (entry) => {
1496
+ updateAndReportMetric(true);
1497
+ initNewCLSMetric("soft-navigation", entry.navigationId, entry.interactionId, entry.name, entry.startTime);
1498
+ };
1499
+ const handleEntries = (entries) => {
1500
+ for (const entry of entries) {
1501
+ if (entry.entryType === "soft-navigation") {
1502
+ handleSoftNavEntry(entry);
1503
+ continue;
1504
+ }
1505
+ layoutShiftManager._processEntry(entry);
1506
+ }
1507
+ updateAndReportMetric();
1508
+ };
1509
+ const types = ["layout-shift"];
1510
+ if (checkSoftNavsEnabled(opts)) types.push("soft-navigation");
1511
+ const po = observe(types, handleEntries);
1512
+ if (po) {
1513
+ report = bindReporter(onReport, metric, CLSThresholds, opts.reportAllChanges);
1514
+ visibilityWatcher.onHidden(() => {
1515
+ handleEntries(po.takeRecords());
1516
+ report(true);
1517
+ });
1518
+ onBFCacheRestore(() => {
1519
+ initNewCLSMetric("back-forward-cache", metric.navigationId, metric.navigationInteractionId, metric.navigationURL, getBFCacheRestoreTime());
1520
+ doubleRAF(report);
1521
+ });
1522
+ setTimeout(report);
1523
+ }
1524
+ }));
1525
+ };
1526
+
1527
+ //#endregion
1528
+ //#region src/tracing/vitals/webvitals/lib/polyfills/interactionCountPolyfill.ts
1529
+ let interactionCountEstimate = 0;
1530
+ let minKnownInteractionId = Infinity;
1531
+ let maxKnownInteractionId = 0;
1532
+ const updateEstimate = (entries) => {
1533
+ for (const entry of entries) if (entry.interactionId) {
1534
+ minKnownInteractionId = Math.min(minKnownInteractionId, entry.interactionId);
1535
+ maxKnownInteractionId = Math.max(maxKnownInteractionId, entry.interactionId);
1536
+ interactionCountEstimate = maxKnownInteractionId ? (maxKnownInteractionId - minKnownInteractionId) / 7 + 1 : 0;
1537
+ }
1538
+ };
1539
+ let po;
1540
+ /**
1541
+ * Returns the `interactionCount` value using the native API (if available)
1542
+ * or the polyfill estimate in this module.
1543
+ */
1544
+ const getInteractionCount = () => {
1545
+ return po ? interactionCountEstimate : performance.interactionCount ?? 0;
1546
+ };
1547
+ /**
1548
+ * Feature detects native support or initializes the polyfill if needed.
1549
+ */
1550
+ const initInteractionCountPolyfill = () => {
1551
+ if ("interactionCount" in performance || po) return;
1552
+ po = observe(["event"], updateEstimate, { durationThreshold: 0 });
1553
+ };
1554
+
1555
+ //#endregion
1556
+ //#region src/tracing/vitals/webvitals/lib/InteractionManager.ts
1557
+ const MAX_INTERACTIONS_TO_CONSIDER = 10;
1558
+ let prevInteractionCount = 0;
1559
+ /**
1560
+ * Returns the interaction count since the last bfcache restore (or for the
1561
+ * full page lifecycle if there were no bfcache restores).
1562
+ */
1563
+ const getInteractionCountForNavigation = () => {
1564
+ return getInteractionCount() - prevInteractionCount;
1565
+ };
1566
+ var InteractionManager = class {
1567
+ /**
1568
+ * A list of longest interactions on the page (by latency) sorted so the
1569
+ * longest one is first. The list is at most MAX_INTERACTIONS_TO_CONSIDER
1570
+ * long.
1571
+ */
1572
+ _longestInteractionList = [];
1573
+ /**
1574
+ * A mapping of longest interactions by their interaction ID.
1575
+ * This is used for faster lookup.
1576
+ */
1577
+ _longestInteractionMap = /* @__PURE__ */ new Map();
1578
+ _onBeforeProcessingEntry;
1579
+ _onAfterProcessingINPCandidate;
1580
+ _resetInteractions() {
1581
+ prevInteractionCount = getInteractionCount();
1582
+ this._longestInteractionList.length = 0;
1583
+ this._longestInteractionMap.clear();
1584
+ }
1585
+ /**
1586
+ * Returns the estimated p98 longest interaction based on the stored
1587
+ * interaction candidates and the interaction count for the current page.
1588
+ */
1589
+ _estimateP98LongestInteraction(navigationType) {
1590
+ const interactionCountForNavigation = getInteractionCountForNavigation();
1591
+ const candidateInteractionIndex = Math.min(this._longestInteractionList.length - 1, Math.floor(interactionCountForNavigation / 50));
1592
+ if (interactionCountForNavigation && candidateInteractionIndex === -1 && (navigationType === "soft-navigation" || navigationType === "back-forward-cache")) return {
1593
+ _latency: 8,
1594
+ id: -1,
1595
+ entries: []
1596
+ };
1597
+ return this._longestInteractionList[candidateInteractionIndex];
1598
+ }
1599
+ /**
1600
+ * Takes a performance entry and adds it to the list of worst interactions
1601
+ * if its duration is long enough to make it among the worst. If the
1602
+ * entry is part of an existing interaction, it is merged and the latency
1603
+ * and entries list is updated as needed.
1604
+ */
1605
+ _processEntry(entry) {
1606
+ this._onBeforeProcessingEntry?.(entry);
1607
+ if (!(entry.interactionId || entry.entryType === "first-input")) return;
1608
+ const minLongestInteraction = this._longestInteractionList.at(-1);
1609
+ let interaction = this._longestInteractionMap.get(entry.interactionId);
1610
+ if (interaction || this._longestInteractionList.length < MAX_INTERACTIONS_TO_CONSIDER || entry.duration > minLongestInteraction._latency) {
1611
+ if (interaction) {
1612
+ if (entry.duration > interaction._latency) {
1613
+ interaction.entries = [entry];
1614
+ interaction._latency = entry.duration;
1615
+ } else if (entry.duration === interaction._latency && entry.startTime === interaction.entries[0].startTime) interaction.entries.push(entry);
1616
+ } else {
1617
+ interaction = {
1618
+ id: entry.interactionId,
1619
+ entries: [entry],
1620
+ _latency: entry.duration
1621
+ };
1622
+ this._longestInteractionMap.set(interaction.id, interaction);
1623
+ this._longestInteractionList.push(interaction);
1624
+ }
1625
+ this._longestInteractionList.sort((a, b) => b._latency - a._latency);
1626
+ if (this._longestInteractionList.length > MAX_INTERACTIONS_TO_CONSIDER) {
1627
+ const removedInteractions = this._longestInteractionList.splice(MAX_INTERACTIONS_TO_CONSIDER);
1628
+ for (const interaction of removedInteractions) this._longestInteractionMap.delete(interaction.id);
1629
+ }
1630
+ this._onAfterProcessingINPCandidate?.(interaction);
1631
+ }
1632
+ }
1633
+ };
1634
+
1635
+ //#endregion
1636
+ //#region src/tracing/vitals/webvitals/lib/whenIdleOrHidden.ts
1637
+ /**
1638
+ * Runs the passed callback during the next idle period, or immediately
1639
+ * if the browser's visibility state is (or becomes) hidden.
1640
+ */
1641
+ const whenIdleOrHidden = (cb) => {
1642
+ const timeout = "requestIdleCallback" in globalThis ? 1e3 : 0;
1643
+ const rIC = globalThis.requestIdleCallback || setTimeout;
1644
+ const cIC = globalThis.cancelIdleCallback || clearTimeout;
1645
+ if (document.visibilityState === "hidden") cb();
1646
+ else {
1647
+ const wrappedCb = runOnce(cb);
1648
+ let idleHandle = -1;
1649
+ const onHidden = () => {
1650
+ cIC(idleHandle);
1651
+ wrappedCb();
1652
+ };
1653
+ addEventListener("visibilitychange", onHidden, {
1654
+ once: true,
1655
+ capture: true
1656
+ });
1657
+ idleHandle = rIC(() => {
1658
+ removeEventListener("visibilitychange", onHidden, { capture: true });
1659
+ wrappedCb();
1660
+ }, { timeout });
1661
+ }
1662
+ };
1663
+
1664
+ //#endregion
1665
+ //#region src/tracing/vitals/webvitals/onINP.ts
1666
+ /** Thresholds for INP. See https://web.dev/articles/inp#what_is_a_good_inp_score */
1667
+ const INPThresholds = [200, 500];
1668
+ const DEFAULT_DURATION_THRESHOLD = 40;
1669
+ /**
1670
+ * Calculates the [INP](https://web.dev/articles/inp) value for the current
1671
+ * page and calls the `callback` function once the value is ready, along with
1672
+ * the `event` performance entries reported for that interaction. The reported
1673
+ * value is a `DOMHighResTimeStamp`.
1674
+ *
1675
+ * A custom `durationThreshold` configuration option can optionally be passed
1676
+ * to control what `event-timing` entries are considered for INP reporting. The
1677
+ * default threshold is `40`, which means INP scores of less than 40 will not
1678
+ * be reported. To avoid reporting no interactions in these cases, the library
1679
+ * will fall back to the input delay of the first interaction. Note that this
1680
+ * will not affect your 75th percentile INP value unless that value is also
1681
+ * less than 40 (well below the recommended
1682
+ * [good](https://web.dev/articles/inp#what_is_a_good_inp_score) threshold).
1683
+ *
1684
+ * If the `reportAllChanges` configuration option is set to `true`, the
1685
+ * `callback` function will be called as soon as the value is initially
1686
+ * determined as well as any time the value changes throughout the page
1687
+ * lifespan.
1688
+ *
1689
+ * _**Important:** INP should be continually monitored for changes throughout
1690
+ * the entire lifespan of a page—including if the user returns to the page after
1691
+ * it's been hidden/backgrounded. However, since browsers often [will not fire
1692
+ * additional callbacks once the user has backgrounded a
1693
+ * page](https://developer.chrome.com/blog/page-lifecycle-api/#advice-hidden),
1694
+ * `callback` is always called when the page's visibility state changes to
1695
+ * hidden. As a result, the `callback` function might be called multiple times
1696
+ * during the same page load._
1697
+ */
1698
+ const onINP = (onReport, opts = {}) => {
1699
+ if (!(globalThis.PerformanceEventTiming && "interactionId" in PerformanceEventTiming.prototype)) return;
1700
+ const visibilityWatcher = getVisibilityWatcher();
1701
+ whenActivated(() => {
1702
+ initInteractionCountPolyfill();
1703
+ let metric = initMetric("INP");
1704
+ let report;
1705
+ const interactionManager = initUnique(opts, InteractionManager);
1706
+ const initNewINPMetric = (navigationType, navigationId, navigationInteractionId, navigationURL, navigationStartTime) => {
1707
+ interactionManager._resetInteractions();
1708
+ metric = initMetric("INP", -1, navigationType, navigationId, navigationInteractionId, navigationURL, navigationStartTime);
1709
+ report = bindReporter(onReport, metric, INPThresholds, opts.reportAllChanges);
1710
+ };
1711
+ const updateINPMetric = () => {
1712
+ const inp = interactionManager._estimateP98LongestInteraction(metric.navigationType);
1713
+ if (inp && inp._latency !== metric.value) {
1714
+ metric.value = inp._latency;
1715
+ metric.entries = inp.entries;
1716
+ report();
1717
+ }
1718
+ };
1719
+ const handleSoftNavEntry = (entry) => {
1720
+ updateINPMetric();
1721
+ report(true);
1722
+ initNewINPMetric("soft-navigation", entry.navigationId, entry.interactionId, entry.name, entry.startTime);
1723
+ };
1724
+ const handleEntries = (entries, forceReport = false) => {
1725
+ whenIdleOrHidden(() => {
1726
+ for (const entry of entries) {
1727
+ if (entry.entryType === "soft-navigation") {
1728
+ handleSoftNavEntry(entry);
1729
+ continue;
1730
+ }
1731
+ interactionManager._processEntry(entry);
1732
+ }
1733
+ updateINPMetric();
1734
+ if (forceReport) report(true);
1735
+ });
1736
+ };
1737
+ const types = ["event", "first-input"];
1738
+ if (checkSoftNavsEnabled(opts)) types.push("soft-navigation");
1739
+ const po = observe(types, handleEntries, {
1740
+ ...opts,
1741
+ durationThreshold: opts.durationThreshold ?? DEFAULT_DURATION_THRESHOLD
1742
+ });
1743
+ report = bindReporter(onReport, metric, INPThresholds, opts.reportAllChanges);
1744
+ if (po) {
1745
+ visibilityWatcher.onHidden(() => {
1746
+ handleEntries(po.takeRecords(), true);
1747
+ });
1748
+ onBFCacheRestore(() => {
1749
+ initNewINPMetric("back-forward-cache", metric.navigationId, metric.navigationInteractionId, metric.navigationURL, getBFCacheRestoreTime());
1750
+ });
1751
+ }
1752
+ });
1753
+ };
1754
+
1755
+ //#endregion
1756
+ //#region src/tracing/vitals/webvitals/lib/LCPEntryManager.ts
1757
+ var LCPEntryManager = class {
1758
+ _onBeforeProcessingEntry;
1759
+ _softNavigationEntryMap;
1760
+ _processEntry(entry) {
1761
+ this._onBeforeProcessingEntry?.(entry);
1762
+ }
1763
+ };
1764
+
1765
+ //#endregion
1766
+ //#region src/tracing/vitals/webvitals/onLCP.ts
1767
+ /** Thresholds for LCP. See https://web.dev/articles/lcp#what_is_a_good_lcp_score */
1768
+ const LCPThresholds = [2500, 4e3];
1769
+ /**
1770
+ * Calculates the [LCP](https://web.dev/articles/lcp) value for the current page and
1771
+ * calls the `callback` function once the value is ready (along with the
1772
+ * relevant `largest-contentful-paint` performance entry used to determine the
1773
+ * value). The reported value is a `DOMHighResTimeStamp`.
1774
+ *
1775
+ * If the `reportAllChanges` configuration option is set to `true`, the
1776
+ * `callback` function will be called any time a new `largest-contentful-paint`
1777
+ * performance entry is dispatched, or once the final value of the metric has
1778
+ * been determined.
1779
+ */
1780
+ const onLCP = (onReport, opts = {}) => {
1781
+ let isFinalized = false;
1782
+ const softNavsEnabled = checkSoftNavsEnabled(opts);
1783
+ whenActivated(() => {
1784
+ let visibilityWatcher = getVisibilityWatcher();
1785
+ let metric = initMetric("LCP");
1786
+ let report;
1787
+ const lcpEntryManager = initUnique(opts, LCPEntryManager);
1788
+ const initNewLCPMetric = (navigation, navigationId, navigationInteractionId, navigationURL, navigationStartTime) => {
1789
+ metric = initMetric("LCP", -1, navigation, navigationId, navigationInteractionId, navigationURL, navigationStartTime);
1790
+ report = bindReporter(onReport, metric, LCPThresholds, opts.reportAllChanges);
1791
+ isFinalized = false;
1792
+ if (navigation === "soft-navigation") visibilityWatcher = getVisibilityWatcher(true);
1793
+ };
1794
+ const handleSoftNavEntry = (entry) => {
1795
+ if (lcpEntryManager._softNavigationEntryMap && entry.navigationId) storeSoftNavEntry(lcpEntryManager._softNavigationEntryMap, entry);
1796
+ if (!isFinalized) report(true);
1797
+ initNewLCPMetric("soft-navigation", entry.navigationId, entry.interactionId, entry.name, entry.startTime);
1798
+ const largestInteractionContentfulPaint = entry.getLargestInteractionContentfulPaint?.();
1799
+ if (largestInteractionContentfulPaint) handleEntries([largestInteractionContentfulPaint]);
1800
+ };
1801
+ const handleEntries = (entries) => {
1802
+ if (!opts.reportAllChanges && !softNavsEnabled) entries = entries.slice(-1);
1803
+ for (const entry of entries) {
1804
+ if (!entry) continue;
1805
+ if (entry.entryType === "soft-navigation") {
1806
+ handleSoftNavEntry(entry);
1807
+ continue;
1808
+ }
1809
+ let value = 0;
1810
+ let metricEntries = [];
1811
+ let renderTime = entry.startTime;
1812
+ if (entry.entryType === "largest-contentful-paint") {
1813
+ value = Math.max(entry.startTime - getActivationStart(), 0);
1814
+ lcpEntryManager._processEntry(entry);
1815
+ metricEntries = [entry];
1816
+ } else if (entry.entryType === "interaction-contentful-paint") {
1817
+ const ICPEntry = entry;
1818
+ if (!metric.navigationId) continue;
1819
+ if ("interactionId" in ICPEntry && ICPEntry.interactionId != metric.navigationInteractionId) continue;
1820
+ renderTime = ICPEntry.largestContentfulPaint?.renderTime || 0;
1821
+ value = Math.max(renderTime - entry.startTime, 0);
1822
+ if (ICPEntry.largestContentfulPaint) {
1823
+ lcpEntryManager._processEntry(ICPEntry.largestContentfulPaint);
1824
+ metricEntries = [ICPEntry.largestContentfulPaint];
1825
+ }
1826
+ }
1827
+ if (renderTime < visibilityWatcher.firstHiddenTime) {
1828
+ metric.value = value;
1829
+ metric.entries = metricEntries;
1830
+ report();
1831
+ }
1832
+ }
1833
+ };
1834
+ const types = ["largest-contentful-paint"];
1835
+ if (softNavsEnabled) types.push("interaction-contentful-paint", "soft-navigation");
1836
+ const po = observe(types, handleEntries);
1837
+ if (po) {
1838
+ report = bindReporter(onReport, metric, LCPThresholds, opts.reportAllChanges);
1839
+ const finalizeEventTypes = [
1840
+ "keydown",
1841
+ "click",
1842
+ "visibilitychange"
1843
+ ];
1844
+ const finalizeLCP = (event) => {
1845
+ if (event.isTrusted && !isFinalized) {
1846
+ const metricIdToFinalize = metric.id;
1847
+ whenIdleOrHidden(() => {
1848
+ if (!isFinalized) {
1849
+ if (!softNavsEnabled) {
1850
+ po.disconnect();
1851
+ for (const type of finalizeEventTypes) removeEventListener(type, finalizeLCP, { capture: true });
1852
+ }
1853
+ if (metricIdToFinalize === metric.id) {
1854
+ isFinalized = true;
1855
+ report(true);
1856
+ }
1857
+ }
1858
+ });
1859
+ }
1860
+ };
1861
+ for (const type of finalizeEventTypes) addEventListener(type, finalizeLCP, { capture: true });
1862
+ onBFCacheRestore((event) => {
1863
+ initNewLCPMetric("back-forward-cache", metric.navigationId, metric.navigationInteractionId, metric.navigationURL, getBFCacheRestoreTime());
1864
+ report = bindReporter(onReport, metric, LCPThresholds, opts.reportAllChanges);
1865
+ doubleRAF(() => {
1866
+ metric.value = performance.now() - event.timeStamp;
1867
+ isFinalized = true;
1868
+ report(true);
1869
+ });
1870
+ });
1871
+ }
1872
+ });
1873
+ };
1874
+
1875
+ //#endregion
1876
+ //#region src/tracing/vitals/webvitals/onTTFB.ts
1877
+ /** Thresholds for TTFB. See https://web.dev/articles/ttfb#what_is_a_good_ttfb_score */
1878
+ const TTFBThresholds = [800, 1800];
1879
+ /**
1880
+ * Runs in the next task after the page is done loading and/or prerendering.
1881
+ * @param callback
1882
+ */
1883
+ const whenReady = (callback) => {
1884
+ if (document.prerendering) whenActivated(() => whenReady(callback));
1885
+ else if (document.readyState !== "complete") addEventListener("load", () => whenReady(callback), true);
1886
+ else setTimeout(callback);
1887
+ };
1888
+ /**
1889
+ * Calculates the [TTFB](https://web.dev/articles/ttfb) value for the
1890
+ * current page and calls the `callback` function once the page has loaded,
1891
+ * along with the relevant `navigation` performance entry used to determine the
1892
+ * value. The reported value is a `DOMHighResTimeStamp`.
1893
+ *
1894
+ * Note, this function waits until after the page is loaded to call `callback`
1895
+ * in order to ensure all properties of the `navigation` entry are populated.
1896
+ * This is useful if you want to report on other metrics exposed by the
1897
+ * [Navigation Timing API](https://w3c.github.io/navigation-timing/). For
1898
+ * example, the TTFB metric starts from the page's [time
1899
+ * origin](https://www.w3.org/TR/hr-time-2/#sec-time-origin), which means it
1900
+ * includes time spent on DNS lookup, connection negotiation, network latency,
1901
+ * and server processing time.
1902
+ */
1903
+ const onTTFB = (onReport, opts = {}) => {
1904
+ const softNavsEnabled = checkSoftNavsEnabled(opts);
1905
+ let metric = initMetric("TTFB");
1906
+ let report = bindReporter(onReport, metric, TTFBThresholds, opts.reportAllChanges);
1907
+ whenReady(() => {
1908
+ const hardNavEntry = getNavigationEntry();
1909
+ if (hardNavEntry) {
1910
+ const responseStart = hardNavEntry.responseStart;
1911
+ metric.value = Math.max(responseStart - getActivationStart(), 0);
1912
+ metric.entries = [hardNavEntry];
1913
+ report(true);
1914
+ onBFCacheRestore(() => {
1915
+ metric = initMetric("TTFB", 0, "back-forward-cache", metric.navigationId, metric.navigationInteractionId, metric.navigationURL, getBFCacheRestoreTime());
1916
+ report = bindReporter(onReport, metric, TTFBThresholds, opts.reportAllChanges);
1917
+ report(true);
1918
+ });
1919
+ if (softNavsEnabled) {
1920
+ const reportSoftNavTTFBs = (entries) => {
1921
+ entries.forEach((entry) => {
1922
+ if (entry.navigationId) {
1923
+ metric = initMetric("TTFB", 0, "soft-navigation", entry.navigationId, entry.interactionId, entry.name, entry.startTime);
1924
+ metric.entries = [entry];
1925
+ report = bindReporter(onReport, metric, TTFBThresholds, opts.reportAllChanges);
1926
+ report(true);
1927
+ }
1928
+ });
1929
+ };
1930
+ observe(["soft-navigation"], reportSoftNavTTFBs, opts);
1931
+ }
1932
+ }
1933
+ });
1934
+ };
1935
+
1936
+ //#endregion
1937
+ //#region src/tracing/vitals/webVitals.ts
1938
+ /**
1939
+ * Final the moment they first report, so they can ride the pageload span itself. The other three keep
1940
+ * changing until the page goes away, and stamping an early value on the root would leave the root and
1941
+ * the later span disagreeing about the same vital.
1942
+ */
1943
+ const EARLY_VITALS = ["ttfb", "fcp"];
1944
+ /** The one place a vital becomes a wire key. Both the pageload stamp and the late span go through it. */
1945
+ function vitalAttributes(vitals) {
1946
+ const attributes = {};
1947
+ for (const [name, value] of Object.entries(vitals)) if (typeof value === "number") attributes[`browser.web_vital.${name}`] = value;
1948
+ return attributes;
1949
+ }
1950
+ /**
1951
+ * Turns the leftover values into one zero-duration span. Pure on purpose: the shape is what the backend
1952
+ * groups on, and this way it is testable without a tracer or a clock.
1953
+ *
1954
+ * Both timestamps sit at the pageload root's start. `spans_2` buckets on `start_time_unix_nano`, so
1955
+ * stamping the report moment would drop a tab left open for forty minutes into a minute forty minutes
1956
+ * after the page actually loaded.
1957
+ *
1958
+ * Returns null when nothing is left to report, so the caller emits no span at all.
1959
+ */
1960
+ function buildVitalsSpan(input) {
1961
+ const vitals = vitalAttributes(input.vitals);
1962
+ if (Object.keys(vitals).length === 0) return null;
1963
+ return {
1964
+ name: input.routeName,
1965
+ startTimeUnixNano: input.rootStartTimeUnixNano,
1966
+ endTimeUnixNano: input.rootStartTimeUnixNano,
1967
+ attributes: {
1968
+ ...input.contextAttributes,
1969
+ "flare.entry_point.handler.identifier": input.routeName,
1970
+ "http.route": input.routeName,
1971
+ "flare.route.source": input.routeSource,
1972
+ ...vitals
1973
+ }
1974
+ };
1975
+ }
1976
+ let collected = {};
1977
+ let subscribed = false;
1978
+ let recording = false;
1979
+ let taken = false;
1980
+ function defaultSubscribers() {
1981
+ return {
1982
+ onTTFB: (cb) => onTTFB(cb),
1983
+ onFCP: (cb) => onFCP(cb),
1984
+ onLCP: (cb) => onLCP(cb, { reportAllChanges: true }),
1985
+ onCLS: (cb) => onCLS(cb, { reportAllChanges: true }),
1986
+ onINP: (cb) => onINP(cb, { reportAllChanges: true })
1987
+ };
1988
+ }
1989
+ /**
1990
+ * Subscribes at most once per document: upstream's on* functions return no unsubscribe handle, so a
1991
+ * second call would attach a second set of observers with no way to detach either.
1992
+ */
1993
+ function startWebVitals(subscribers = defaultSubscribers()) {
1994
+ recording = true;
1995
+ if (subscribed) return;
1996
+ subscribed = true;
1997
+ subscribe(subscribers.onTTFB, "ttfb");
1998
+ subscribe(subscribers.onFCP, "fcp");
1999
+ subscribe(subscribers.onLCP, "lcp");
2000
+ subscribe(subscribers.onCLS, "cls");
2001
+ subscribe(subscribers.onINP, "inp");
2002
+ }
2003
+ function subscribe(on, name) {
2004
+ try {
2005
+ on((metric) => record(name, metric));
2006
+ } catch {}
2007
+ }
2008
+ function record(name, metric) {
2009
+ if (!recording || typeof metric?.value !== "number") return;
2010
+ collected[name] = metric.value;
2011
+ }
2012
+ /**
2013
+ * Stops recording and drops what was collected. The observers themselves cannot be detached, and both
2014
+ * the `subscribed` and `taken` latches deliberately survive: clearing `subscribed` would let a re-enable
2015
+ * attach a second set of observers that is just as undetachable as the first, and clearing `taken` would
2016
+ * let those surviving observers refill `collected` and ship a second `browser_web_vital` for the same
2017
+ * document once the page is re-enabled and later hidden.
2018
+ */
2019
+ function stopWebVitals() {
2020
+ recording = false;
2021
+ collected = {};
2022
+ }
2023
+ /**
2024
+ * The vitals that are already final when the pageload root closes, removed from `collected` so the late
2025
+ * span cannot report them a second time. Naturally idempotent: a second call finds the keys gone.
2026
+ */
2027
+ function takeEarlyVitals() {
2028
+ if (!recording) return null;
2029
+ const taking = {};
2030
+ for (const name of EARLY_VITALS) {
2031
+ const value = collected[name];
2032
+ if (value !== void 0) {
2033
+ taking[name] = value;
2034
+ delete collected[name];
2035
+ }
2036
+ }
2037
+ return Object.keys(taking).length === 0 ? null : taking;
2038
+ }
2039
+ /** Everything still outstanding, once. Null afterwards, and null when nothing is left. */
2040
+ function takeWebVitals() {
2041
+ if (taken || !recording || Object.keys(collected).length === 0) return null;
2042
+ taken = true;
2043
+ const taking = collected;
2044
+ collected = {};
2045
+ return taking;
2046
+ }
2047
+ /**
2048
+ * Undoes a take after the emit failed partway through, so the values are not lost and a later trigger
2049
+ * can retry. Safe to assign outright rather than merge: the whole emit runs synchronously between the
2050
+ * take and a catch block calling this, so nothing else can have written to `collected` in between.
2051
+ */
2052
+ function restoreWebVitals(vitals) {
2053
+ collected = vitals;
2054
+ taken = false;
2055
+ }
2056
+
2057
+ //#endregion
2058
+ //#region src/tracing/roots/IdleRootController.ts
2059
+ /** Browser defaults for the three idle-root timeouts, in ms. Overridable per Config. */
2060
+ const DEFAULT_IDLE_TIMEOUTS = {
2061
+ idleTimeout: 1e3,
2062
+ finalTimeout: 3e4,
2063
+ childSpanTimeout: 15e3
2064
+ };
2065
+ /**
2066
+ * Owns one root span's idle lifecycle: open while child spans in its trace are active, closing after
2067
+ * `idleTimeout` with no open children, or on the `finalTimeout` / `childSpanTimeout` backstops. Deps are
2068
+ * injected so this is testable without real timers or a real tracer.
2069
+ */
2070
+ var IdleRootController = class {
2071
+ openChildren = 0;
2072
+ lastChildEndTime = null;
2073
+ settleTime = null;
2074
+ idleTimer = null;
2075
+ finalTimer = null;
2076
+ childTimer = null;
2077
+ ended = false;
2078
+ held = false;
2079
+ unsubscribe;
2080
+ constructor(deps, timeouts) {
2081
+ this.deps = deps;
2082
+ this.timeouts = timeouts;
2083
+ deps.setActiveRoot(deps.root);
2084
+ this.unsubscribe = deps.addSpanListener((e) => this.onSpanEvent(e.phase, e.span));
2085
+ const elapsedMs = Math.max(0, (deps.now() - deps.rootStartTime) / 1e6);
2086
+ const remainingMs = Math.max(0, timeouts.finalTimeout - elapsedMs);
2087
+ this.finalTimer = deps.setTimeout(() => this.finish(deps.now()), remainingMs);
2088
+ this.held = !!deps.held;
2089
+ this.armIdle();
2090
+ }
2091
+ get isEnded() {
2092
+ return this.ended;
2093
+ }
2094
+ /** For a route change or pagehide. */
2095
+ endNow() {
2096
+ this.finish(this.openChildren > 0 || this.held ? this.deps.now() : this.trimmedEnd());
2097
+ }
2098
+ /**
2099
+ * Records the settle moment as a close floor and hands the root back to the normal idle lifecycle. It
2100
+ * deliberately does not close here: a router settles before the framework mounts the new route
2101
+ * component (vue-router runs `afterEach` in the route-update tick, Vue mounts on the next flush), so
2102
+ * closing at settle cleared the active root ahead of every post-navigation mount: every component span
2103
+ * read a null root, and a trailing fetch opened a root of its own.
2104
+ */
2105
+ releaseHold() {
2106
+ if (this.ended || !this.held) return;
2107
+ this.held = false;
2108
+ if (this.openChildren === 0) this.settleTime = this.deps.now();
2109
+ this.armIdle();
2110
+ }
2111
+ onSpanEvent(phase, span) {
2112
+ if (this.ended) return;
2113
+ if (span === this.deps.root) return;
2114
+ if (span.traceId !== this.deps.root.traceId) return;
2115
+ if (phase === "start") {
2116
+ this.onChildStarted();
2117
+ return;
2118
+ }
2119
+ this.onChildEnded(span);
2120
+ }
2121
+ onChildStarted() {
2122
+ this.openChildren++;
2123
+ this.clearIdle();
2124
+ if (this.openChildren === 1) this.armChildTimeout();
2125
+ }
2126
+ onChildEnded(span) {
2127
+ this.openChildren = Math.max(0, this.openChildren - 1);
2128
+ this.lastChildEndTime = span.endTimeUnixNano || this.deps.now();
2129
+ if (this.openChildren > 0) return;
2130
+ this.clearChildTimeout();
2131
+ this.armIdle();
2132
+ }
2133
+ armIdle() {
2134
+ this.clearIdle();
2135
+ if (this.held) return;
2136
+ this.idleTimer = this.deps.setTimeout(() => {
2137
+ if (this.openChildren > 0) return;
2138
+ this.finish(this.trimmedEnd());
2139
+ }, this.timeouts.idleTimeout);
2140
+ }
2141
+ /** The latest of the floor, a released hold's settle moment, and the last child's end, so a root
2142
+ * covers its children without ever padding out to `now()`. */
2143
+ trimmedEnd() {
2144
+ return Math.max(this.deps.endFloor(), this.settleTime ?? 0, this.lastChildEndTime ?? 0);
2145
+ }
2146
+ clearIdle() {
2147
+ if (this.idleTimer !== null) {
2148
+ this.deps.clearTimeout(this.idleTimer);
2149
+ this.idleTimer = null;
2150
+ }
2151
+ }
2152
+ armChildTimeout() {
2153
+ this.childTimer = this.deps.setTimeout(() => this.finish(this.deps.now()), this.timeouts.childSpanTimeout);
2154
+ }
2155
+ clearChildTimeout() {
2156
+ if (this.childTimer !== null) {
2157
+ this.deps.clearTimeout(this.childTimer);
2158
+ this.childTimer = null;
2159
+ }
2160
+ }
2161
+ finish(atTimeNano) {
2162
+ if (this.ended) return;
2163
+ this.ended = true;
2164
+ atTimeNano = Math.max(atTimeNano, this.deps.rootStartTime);
2165
+ this.clearIdle();
2166
+ this.clearChildTimeout();
2167
+ if (this.finalTimer !== null) {
2168
+ this.deps.clearTimeout(this.finalTimer);
2169
+ this.finalTimer = null;
2170
+ }
2171
+ this.unsubscribe();
2172
+ this.deps.beforeEnd?.();
2173
+ this.deps.root.end(atTimeNano);
2174
+ this.deps.setActiveRoot(void 0);
2175
+ }
2176
+ };
2177
+
2178
+ //#endregion
2179
+ //#region src/tracing/roots/navigationTiming.ts
2180
+ /** Split out so the timestamp maths is testable without a Navigation Timing entry. */
2181
+ function computePageloadStartNano(timeOriginMs, startTimeMs) {
2182
+ return Math.round((timeOriginMs + (startTimeMs ?? 0)) * 1e6);
2183
+ }
2184
+ /**
2185
+ * Choose the pageload root's start time: navigation start while that window is still open,
2186
+ * otherwise `now`. Starting at `now` (when tracing began after the final cap, or the pageload was
2187
+ * already traced) avoids a backdated root reporting a bogus multi-second duration.
2188
+ */
2189
+ function resolvePageloadStartNano(backdatedNano, nowNano, finalTimeoutNano, alreadyTraced) {
2190
+ if (alreadyTraced) return nowNano;
2191
+ if (nowNano - backdatedNano > finalTimeoutNano) return nowNano;
2192
+ return backdatedNano;
2193
+ }
2194
+ /** The Navigation Timing API, or null where it is missing or only partly implemented. */
2195
+ function navigationTiming() {
2196
+ const perf = globalThis.performance;
2197
+ if (!perf || typeof perf.getEntriesByType !== "function" || typeof perf.timeOrigin !== "number") return null;
2198
+ return perf;
2199
+ }
2200
+ function navigationEntry(perf) {
2201
+ return perf.getEntriesByType("navigation")[0];
2202
+ }
2203
+ /**
2204
+ * The pageload root's start time in unix nanoseconds, backdated to navigation start via the
2205
+ * Navigation Timing entry. Falls back to the tracer's clock when the API is unavailable.
2206
+ */
2207
+ function pageloadStartNano() {
2208
+ const perf = navigationTiming();
2209
+ if (!perf) return (0, _flareapp_core.defaultNowNano)();
2210
+ return computePageloadStartNano(perf.timeOrigin, navigationEntry(perf)?.startTime);
2211
+ }
2212
+ function computePageloadEndNano(timeOriginMs, loadEventEndMs, domContentLoadedEventEndMs, nowNano) {
2213
+ const endMs = loadEventEndMs || domContentLoadedEventEndMs || 0;
2214
+ if (!endMs) return nowNano;
2215
+ return Math.round((timeOriginMs + endMs) * 1e6);
2216
+ }
2217
+ /**
2218
+ * The pageload root's end time in unix nanoseconds, taken from the Navigation
2219
+ * Timing `loadEventEnd` (the browser's own "page finished loading" mark), falling
2220
+ * back to `domContentLoadedEventEnd`, then the tracer's clock when neither has
2221
+ * fired yet or the API is unavailable. Used as the pageload root's close floor so a
2222
+ * childless pageload reports its real load duration rather than idle-timeout padding.
2223
+ */
2224
+ function pageloadEndNano() {
2225
+ const perf = navigationTiming();
2226
+ if (!perf) return (0, _flareapp_core.defaultNowNano)();
2227
+ const entry = navigationEntry(perf);
2228
+ return computePageloadEndNano(perf.timeOrigin, entry?.loadEventEnd, entry?.domContentLoadedEventEnd, (0, _flareapp_core.defaultNowNano)());
2229
+ }
2230
+
2231
+ //#endregion
2232
+ //#region src/tracing/roots/browserTracing.ts
2233
+ let controller = null;
2234
+ let uninstall = null;
2235
+ let removeNavigationSubscription = null;
2236
+ let pageloadTraced = false;
2237
+ let activeFlare = null;
2238
+ let currentRoot = null;
2239
+ let pendingRouteName = null;
2240
+ let pendingRouteNameOwner = null;
2241
+ let pageloadRoot = null;
2242
+ let pageloadRootStartNano = 0;
2243
+ let pageloadRoute = null;
2244
+ let pageloadContext = {};
2245
+ function resolveTimeouts(config) {
2246
+ return {
2247
+ idleTimeout: config.idleTimeout ?? DEFAULT_IDLE_TIMEOUTS.idleTimeout,
2248
+ finalTimeout: config.finalTimeout ?? DEFAULT_IDLE_TIMEOUTS.finalTimeout,
2249
+ childSpanTimeout: config.childSpanTimeout ?? DEFAULT_IDLE_TIMEOUTS.childSpanTimeout
2250
+ };
2251
+ }
2252
+ /** No-ops once the controller has ended: it can close itself asynchronously via a timer, before this
2253
+ * module's `controller` reference is cleared. */
2254
+ function withLiveController(fn) {
2255
+ if (!controller || controller.isEnded) return;
2256
+ try {
2257
+ fn(controller);
2258
+ } catch (error) {
2259
+ if (activeFlare?.config.debug) console.error("Flare: browser tracing controller callback failed", error);
2260
+ }
2261
+ }
2262
+ /** Run `fn` only while the current root is still open. Swallows a throw, like `withLiveController`. */
2263
+ function ifRootLive(fn) {
2264
+ withLiveController(() => fn());
2265
+ }
2266
+ function startRoot(flare, options) {
2267
+ const { spanType, startTimeUnixNano, name = location.pathname, urlOverride, hold, backdated } = options;
2268
+ let root;
2269
+ try {
2270
+ const context = collectBrowserSpanContext(flare.config, urlOverride);
2271
+ root = flare.startSpan(name, {
2272
+ spanType,
2273
+ startTimeUnixNano,
2274
+ forceRoot: true,
2275
+ attributes: {
2276
+ ...context,
2277
+ "flare.route.source": "url"
2278
+ }
2279
+ });
2280
+ controller = new IdleRootController({
2281
+ root,
2282
+ addSpanListener: (fn) => flare.tracer.addSpanListener(fn),
2283
+ setActiveRoot: (span) => flare.tracer.setActiveRoot(span),
2284
+ now: _flareapp_core.defaultNowNano,
2285
+ setTimeout: (fn, ms) => setTimeout(fn, ms),
2286
+ clearTimeout: (handle) => clearTimeout(handle),
2287
+ rootStartTime: startTimeUnixNano,
2288
+ endFloor: spanType === _flareapp_core.BrowserSpanType.Pageload && backdated ? pageloadEndNano : () => startTimeUnixNano,
2289
+ held: hold,
2290
+ beforeEnd: spanType === _flareapp_core.BrowserSpanType.Pageload ? () => stampEarlyVitals(root, flare) : void 0
2291
+ }, resolveTimeouts(flare.config));
2292
+ currentRoot = root;
2293
+ if (spanType === _flareapp_core.BrowserSpanType.Pageload) {
2294
+ pageloadRoot = root;
2295
+ pageloadRootStartNano = startTimeUnixNano;
2296
+ pageloadRoute = {
2297
+ name,
2298
+ source: "url"
2299
+ };
2300
+ pageloadContext = { ...context };
2301
+ }
2302
+ } catch (error) {
2303
+ controller = null;
2304
+ currentRoot = null;
2305
+ if (spanType === _flareapp_core.BrowserSpanType.Pageload) {
2306
+ pageloadRoot = null;
2307
+ pageloadRootStartNano = 0;
2308
+ pageloadRoute = null;
2309
+ pageloadContext = {};
2310
+ }
2311
+ try {
2312
+ root?.end();
2313
+ } catch {}
2314
+ try {
2315
+ flare.tracer.setActiveRoot(void 0);
2316
+ } catch {}
2317
+ if (flare.config.debug) console.error("Flare: failed to start browser tracing root", error);
2318
+ }
2319
+ }
2320
+ function openNavigationRoot(flare, opts) {
2321
+ withLiveController((live) => live.endNow());
2322
+ startRoot(flare, {
2323
+ spanType: _flareapp_core.BrowserSpanType.Navigation,
2324
+ startTimeUnixNano: (0, _flareapp_core.defaultNowNano)(),
2325
+ name: opts.path,
2326
+ urlOverride: opts.url,
2327
+ hold: opts.hold
2328
+ });
2329
+ }
2330
+ /**
2331
+ * Writes the already-final vitals onto the pageload root itself, from `IdleRootController`'s beforeEnd
2332
+ * hook. Whatever has not reported yet stays in `collected` and rides the later `browser_web_vital`
2333
+ * span instead, so no vital is ever sent twice and none is lost.
2334
+ *
2335
+ * Swallows its own failures: this runs inside the root's close path, and a throw here would leave the
2336
+ * root open forever.
2337
+ */
2338
+ function stampEarlyVitals(root, flare) {
2339
+ try {
2340
+ const early = takeEarlyVitals();
2341
+ if (!early) return;
2342
+ for (const [key, value] of Object.entries(vitalAttributes(early))) root.setAttribute(key, value);
2343
+ } catch (error) {
2344
+ if (flare.config.debug) console.error("Flare: failed to stamp web vitals on the pageload root", error);
2345
+ }
2346
+ }
2347
+ /**
2348
+ * Emits whatever the pageload root could not carry as one zero-duration `browser_web_vital` span,
2349
+ * parented to that root. Page hide only, and once per document.
2350
+ *
2351
+ * Deliberately NOT on navigation: LCP, CLS and INP keep moving all document long, so emitting at the
2352
+ * first route change froze them a second after load, and a session whose first action was a nav click
2353
+ * reported no INP at all. One span per document is a backend constraint, so the emit waits for the last
2354
+ * moment we get instead. The cost is a page whose hide event never fires reports no vitals.
2355
+ *
2356
+ * `pageloadRoot` has usually ended by now; reading `traceId` and `spanId` off an ended span is fine,
2357
+ * and passing the `Span` rather than a `{ traceId, spanId }` pair is what makes sampling inherit:
2358
+ * `resolveTrace()` reads `parent.isRecording` instead of re-rolling the sampler.
2359
+ */
2360
+ function emitWebVitals(flare) {
2361
+ const root = pageloadRoot;
2362
+ const route = pageloadRoute;
2363
+ if (!root || !route) return;
2364
+ const vitals = takeWebVitals();
2365
+ if (!vitals) return;
2366
+ try {
2367
+ const planned = buildVitalsSpan({
2368
+ vitals,
2369
+ rootStartTimeUnixNano: pageloadRootStartNano,
2370
+ routeName: route.name,
2371
+ routeSource: route.source,
2372
+ contextAttributes: pageloadContext
2373
+ });
2374
+ if (!planned) return;
2375
+ flare.startSpan(planned.name, {
2376
+ parent: root,
2377
+ forceRoot: true,
2378
+ spanType: _flareapp_core.BrowserSpanType.WebVital,
2379
+ startTimeUnixNano: planned.startTimeUnixNano,
2380
+ attributes: planned.attributes
2381
+ }).end(planned.endTimeUnixNano);
2382
+ } catch (error) {
2383
+ restoreWebVitals(vitals);
2384
+ if (flare.config.debug) console.error("Flare: failed to emit web vitals", error);
2385
+ }
2386
+ }
2387
+ /** Opens a backdated pageload root, then a navigation root per History change. No-op outside a browser. Idempotent. */
2388
+ function startBrowserTracing(flare) {
2389
+ if (typeof window === "undefined" || typeof history === "undefined" || typeof location === "undefined") return;
2390
+ if (uninstall) return;
2391
+ activeFlare = flare;
2392
+ removeNavigationSubscription = subscribeToNavigation({
2393
+ onUrlChanged: (path) => openNavigationRoot(flare, { path }),
2394
+ onNavigationStart: (opts) => openNavigationRoot(flare, opts),
2395
+ onRouteName: (route, owner) => applyRouteName(route, owner),
2396
+ onNavigationSettle: (route, owner) => {
2397
+ applyRouteName(route, owner);
2398
+ withLiveController((live) => live.releaseHold());
2399
+ },
2400
+ onSourceUnregistered: () => {
2401
+ withLiveController((live) => live.releaseHold());
2402
+ pendingRouteName = null;
2403
+ pendingRouteNameOwner = null;
2404
+ }
2405
+ });
2406
+ const finalTimeoutNano = resolveTimeouts(flare.config).finalTimeout * 1e6;
2407
+ const navigationStart = pageloadStartNano();
2408
+ const pageloadStart = resolvePageloadStartNano(navigationStart, (0, _flareapp_core.defaultNowNano)(), finalTimeoutNano, pageloadTraced);
2409
+ pageloadTraced = true;
2410
+ startRoot(flare, {
2411
+ spanType: _flareapp_core.BrowserSpanType.Pageload,
2412
+ startTimeUnixNano: pageloadStart,
2413
+ backdated: pageloadStart === navigationStart
2414
+ });
2415
+ startWebVitals();
2416
+ if (pendingRouteName) {
2417
+ const route = pendingRouteName;
2418
+ const owner = pendingRouteNameOwner;
2419
+ pendingRouteName = null;
2420
+ pendingRouteNameOwner = null;
2421
+ if (isActiveNavigationSource(owner)) applyRouteName(route);
2422
+ }
2423
+ function endRootAndFlush() {
2424
+ if (controller && !controller.isEnded) try {
2425
+ controller.endNow();
2426
+ } catch (error) {
2427
+ if (flare.config.debug) console.error("Flare: failed to end tracing root on page hide", error);
2428
+ }
2429
+ emitWebVitals(flare);
2430
+ try {
2431
+ flare.tracer.flush({ keepalive: true });
2432
+ } catch (error) {
2433
+ if (flare.config.debug) console.error("Flare: failed to flush spans on page hide", error);
2434
+ }
2435
+ }
2436
+ const onPageHide = () => endRootAndFlush();
2437
+ const onVisibilityChange = () => {
2438
+ if (document.visibilityState === "hidden") endRootAndFlush();
2439
+ };
2440
+ window.addEventListener("pagehide", onPageHide);
2441
+ document.addEventListener("visibilitychange", onVisibilityChange);
2442
+ uninstall = () => {
2443
+ window.removeEventListener("pagehide", onPageHide);
2444
+ document.removeEventListener("visibilitychange", onVisibilityChange);
2445
+ };
2446
+ }
2447
+ /** Idempotent. */
2448
+ function stopBrowserTracing() {
2449
+ withLiveController((live) => live.endNow());
2450
+ controller = null;
2451
+ if (uninstall) {
2452
+ uninstall();
2453
+ uninstall = null;
2454
+ }
2455
+ removeNavigationSubscription?.();
2456
+ removeNavigationSubscription = null;
2457
+ activeFlare = null;
2458
+ currentRoot = null;
2459
+ pendingRouteName = null;
2460
+ pendingRouteNameOwner = null;
2461
+ stopWebVitals();
2462
+ pageloadRoot = null;
2463
+ pageloadRootStartNano = 0;
2464
+ pageloadRoute = null;
2465
+ pageloadContext = {};
2466
+ }
2467
+ /**
2468
+ * Computes the url attributes a route rename carries, or null when there is nothing to add. Guarded
2469
+ * so the pin below (which runs outside ifRootLive's own try/catch) cannot throw into the host.
2470
+ */
2471
+ function urlAttributesFor(route) {
2472
+ if (route.url === void 0 || !activeFlare) return null;
2473
+ try {
2474
+ return browserSpanUrlAttributes(activeFlare.config, route.url);
2475
+ } catch {
2476
+ return null;
2477
+ }
2478
+ }
2479
+ /**
2480
+ * Rename the current root and update the attributes that go with the name, and pin the pageload's route
2481
+ * for the vitals emit. No-op once it closed; the pin is NOT gated the same way, see below.
2482
+ * With no root yet the name is held for the pageload root that opens next, rather than dropped.
2483
+ * `owner` stamps who is holding it, so a stale or superseded source cannot land its name later.
2484
+ */
2485
+ function applyRouteName(route, owner) {
2486
+ const root = currentRoot;
2487
+ if (!root) {
2488
+ pendingRouteName = route;
2489
+ pendingRouteNameOwner = owner ?? null;
2490
+ return;
2491
+ }
2492
+ const urlAttrs = urlAttributesFor(route);
2493
+ ifRootLive(() => {
2494
+ root.name = route.name;
2495
+ root.setAttribute("flare.entry_point.handler.identifier", route.name);
2496
+ root.setAttribute("http.route", route.name);
2497
+ root.setAttribute("flare.route.source", route.source);
2498
+ if (!urlAttrs) return;
2499
+ for (const [key, value] of Object.entries(urlAttrs)) root.setAttribute(key, value);
2500
+ });
2501
+ if (root !== pageloadRoot) return;
2502
+ pageloadRoute = {
2503
+ name: route.name,
2504
+ source: route.source
2505
+ };
2506
+ if (urlAttrs) pageloadContext = {
2507
+ ...pageloadContext,
2508
+ ...urlAttrs
2509
+ };
2510
+ }
2511
+ function activeTracingFlare() {
2512
+ return activeFlare;
2513
+ }
2514
+
2515
+ //#endregion
2516
+ //#region src/tracing/roots/componentProfiler.ts
2517
+ /** Unix nanos on the same clock the tracer uses for span timestamps. */
2518
+ const nowNano = _flareapp_core.defaultNowNano;
2519
+ /**
2520
+ * Reserved up front so descendants can point at a span before it is recorded. Null when the trace is at
2521
+ * its span cap: descendants record before this span does, so an id the cap will refuse orphans them.
2522
+ */
2523
+ function reserveSpanId(traceId) {
2524
+ if (traceId !== void 0 && !activeTracingFlare()?.tracer.claimSpanSlot(traceId)) return null;
2525
+ return (0, _flareapp_core.spanId)();
2526
+ }
2527
+ /** The root a top-level component nests under. Null when tracing is off or no root is recording. */
2528
+ function activeComponentRoot() {
2529
+ try {
2530
+ const root = activeTracingFlare()?.tracer.getActiveSpan();
2531
+ if (!root || !root.isRecording) return null;
2532
+ return {
2533
+ traceId: root.traceId,
2534
+ parentSpanId: root.spanId
2535
+ };
2536
+ } catch {
2537
+ return null;
2538
+ }
2539
+ }
2540
+ /**
2541
+ * An ancestor's context is only usable while it still belongs to the live trace. A profiled component
2542
+ * that survives a navigation (a layout around a swapped page body) froze its context under the pageload
2543
+ * trace, and `recordComponentSpan` would drop anything pointing at that closed root.
2544
+ */
2545
+ function resolveComponentParent(inherited, live) {
2546
+ if (inherited && live && inherited.traceId === live.traceId) return inherited;
2547
+ return live;
2548
+ }
2549
+ /**
2550
+ * Records only while the reserved root is still the live recording root, and drops the span otherwise.
2551
+ * Dropping avoids starting a fresh TraceState for a dead trace, which would re-run the sampler, and
2552
+ * avoids adding a child to a root that already shipped.
2553
+ */
2554
+ function recordComponentSpan(record) {
2555
+ try {
2556
+ const flare = activeTracingFlare();
2557
+ if (!flare) return;
2558
+ const root = flare.tracer.getActiveSpan();
2559
+ if (!root || root.traceId !== record.parent.traceId || !root.isRecording) return;
2560
+ flare.startSpan(record.name, {
2561
+ spanId: record.spanId,
2562
+ parent: {
2563
+ traceId: record.parent.traceId,
2564
+ spanId: record.parent.parentSpanId
2565
+ },
2566
+ spanType: _flareapp_core.BrowserSpanType.Component,
2567
+ startTimeUnixNano: record.startTimeUnixNano,
2568
+ attributes: {
2569
+ ...record.attributes,
2570
+ "flare.component.name": record.name
2571
+ },
2572
+ claimed: true
2573
+ }).end(record.endTimeUnixNano);
2574
+ } catch {}
2575
+ }
2576
+
2577
+ //#endregion
2578
+ //#region src/createFlareResolver.ts
2579
+ /**
2580
+ * `process.env.NODE_ENV` is replaced inline by bundlers. The try/catch keeps a process-less
2581
+ * environment safe: treat "undetermined" as production (warn, never crash).
2582
+ */
2583
+ function isDevMode() {
2584
+ try {
2585
+ return process.env.NODE_ENV !== "production";
2586
+ } catch {
2587
+ return false;
2588
+ }
2589
+ }
2590
+ /**
2591
+ * Builds a per-package Flare resolver: `registerDefaultFlare` (wired once by the web entry) and
2592
+ * `resolveFlare` (called at wiring time). Each call holds its own default-provider state. The check
2593
+ * that warns about the Electron `__flare` bridge uses `packageName` in its message;
2594
+ * `injectInstruction` replaces the closing hint for packages whose advice differs (for example
2595
+ * svelte, which points at the preprocessor's importSource).
2596
+ */
2597
+ function createFlareResolver(config) {
2598
+ const { packageName } = config;
2599
+ const injectInstruction = config.injectInstruction ?? `Import ${packageName}/inject and pass the @flareapp/electron/renderer instance instead.`;
2600
+ let defaultProvider = null;
2601
+ function registerDefaultFlare(provider) {
2602
+ if (typeof window !== "undefined" && window.__flare) {
2603
+ const message = `[flare] ${packageName} (web root) was imported in a renderer where the Electron bridge is present, pulling the keyed @flareapp/js singleton into the renderer. ` + injectInstruction;
2604
+ if (isDevMode()) throw new Error(message);
2605
+ console.warn(message);
2606
+ }
2607
+ defaultProvider = provider;
2608
+ }
2609
+ function resolveFlare(explicit) {
2610
+ if (explicit) return explicit;
2611
+ if (defaultProvider) return defaultProvider();
2612
+ throw new Error(`[flare] No Flare instance available. Pass \`flare\` (e.g. from @flareapp/electron/renderer), or import ${packageName} (the package root) to use the @flareapp/js default singleton.`);
2613
+ }
2614
+ return {
2615
+ registerDefaultFlare,
2616
+ resolveFlare
2617
+ };
2618
+ }
2619
+
2620
+ //#endregion
2621
+ //#region src/browser/catchWindowErrors.ts
2622
+ /**
2623
+ * Wire up global `error` and `unhandledrejection` listeners, routing reports through `window.flare`
2624
+ * (assigned by Flare.light()/configure()). Events are dropped, not queued, when the global is absent.
2625
+ */
2626
+ function catchWindowErrors() {
2627
+ if (typeof window === "undefined") return;
2628
+ window.addEventListener("error", (event) => {
2629
+ const flare = window.flare;
2630
+ if (!flare) return;
2631
+ if (event.error instanceof Error) flare.reportSilently(event.error);
2632
+ });
2633
+ window.addEventListener("unhandledrejection", (event) => {
2634
+ const flare = window.flare;
2635
+ if (!flare) return;
2636
+ (0, _flareapp_core.routeRejection)(flare, event.reason);
2637
+ });
2638
+ }
2639
+
2640
+ //#endregion
2641
+ Object.defineProperty(exports, 'BrowserFlushScheduler', {
2642
+ enumerable: true,
2643
+ get: function () {
2644
+ return BrowserFlushScheduler;
2645
+ }
2646
+ });
2647
+ Object.defineProperty(exports, 'CLIENT_VERSION', {
2648
+ enumerable: true,
2649
+ get: function () {
2650
+ return CLIENT_VERSION;
2651
+ }
2652
+ });
2653
+ Object.defineProperty(exports, 'FetchFileReader', {
2654
+ enumerable: true,
2655
+ get: function () {
2656
+ return FetchFileReader;
2657
+ }
2658
+ });
2659
+ Object.defineProperty(exports, 'absoluteHref', {
2660
+ enumerable: true,
2661
+ get: function () {
2662
+ return absoluteHref;
2663
+ }
2664
+ });
2665
+ Object.defineProperty(exports, 'absoluteUrl', {
2666
+ enumerable: true,
2667
+ get: function () {
2668
+ return absoluteUrl;
2669
+ }
2670
+ });
2671
+ Object.defineProperty(exports, 'activeComponentRoot', {
2672
+ enumerable: true,
2673
+ get: function () {
2674
+ return activeComponentRoot;
2675
+ }
2676
+ });
2677
+ Object.defineProperty(exports, 'browserUrlContext', {
2678
+ enumerable: true,
2679
+ get: function () {
2680
+ return browserUrlContext;
2681
+ }
2682
+ });
2683
+ Object.defineProperty(exports, 'catchWindowErrors', {
2684
+ enumerable: true,
2685
+ get: function () {
2686
+ return catchWindowErrors;
2687
+ }
2688
+ });
2689
+ Object.defineProperty(exports, 'collectBrowser', {
2690
+ enumerable: true,
2691
+ get: function () {
2692
+ return collectBrowser;
2693
+ }
2694
+ });
2695
+ Object.defineProperty(exports, 'createFlareResolver', {
2696
+ enumerable: true,
2697
+ get: function () {
2698
+ return createFlareResolver;
2699
+ }
2700
+ });
2701
+ Object.defineProperty(exports, 'currentHref', {
2702
+ enumerable: true,
2703
+ get: function () {
2704
+ return currentHref;
2705
+ }
2706
+ });
2707
+ Object.defineProperty(exports, 'currentPath', {
2708
+ enumerable: true,
2709
+ get: function () {
2710
+ return currentPath;
2711
+ }
2712
+ });
2713
+ Object.defineProperty(exports, 'instrumentOnce', {
2714
+ enumerable: true,
2715
+ get: function () {
2716
+ return instrumentOnce;
2717
+ }
2718
+ });
2719
+ Object.defineProperty(exports, 'insulate', {
2720
+ enumerable: true,
2721
+ get: function () {
2722
+ return insulate;
2723
+ }
2724
+ });
2725
+ Object.defineProperty(exports, 'nowNano', {
2726
+ enumerable: true,
2727
+ get: function () {
2728
+ return nowNano;
2729
+ }
2730
+ });
2731
+ Object.defineProperty(exports, 'recordComponentSpan', {
2732
+ enumerable: true,
2733
+ get: function () {
2734
+ return recordComponentSpan;
2735
+ }
2736
+ });
2737
+ Object.defineProperty(exports, 'registerNavigationSource', {
2738
+ enumerable: true,
2739
+ get: function () {
2740
+ return registerNavigationSource;
2741
+ }
2742
+ });
2743
+ Object.defineProperty(exports, 'reserveSpanId', {
2744
+ enumerable: true,
2745
+ get: function () {
2746
+ return reserveSpanId;
2747
+ }
2748
+ });
2749
+ Object.defineProperty(exports, 'resolveComponentParent', {
2750
+ enumerable: true,
2751
+ get: function () {
2752
+ return resolveComponentParent;
2753
+ }
2754
+ });
2755
+ Object.defineProperty(exports, 'resolveHref', {
2756
+ enumerable: true,
2757
+ get: function () {
2758
+ return resolveHref;
2759
+ }
2760
+ });
2761
+ Object.defineProperty(exports, 'routeName', {
2762
+ enumerable: true,
2763
+ get: function () {
2764
+ return routeName;
2765
+ }
2766
+ });
2767
+ Object.defineProperty(exports, 'safeInvoke', {
2768
+ enumerable: true,
2769
+ get: function () {
2770
+ return safeInvoke;
2771
+ }
2772
+ });
2773
+ Object.defineProperty(exports, 'startBreadcrumbs', {
2774
+ enumerable: true,
2775
+ get: function () {
2776
+ return startBreadcrumbs;
2777
+ }
2778
+ });
2779
+ Object.defineProperty(exports, 'startBrowserTracing', {
2780
+ enumerable: true,
2781
+ get: function () {
2782
+ return startBrowserTracing;
2783
+ }
2784
+ });
2785
+ Object.defineProperty(exports, 'stopBrowserTracing', {
2786
+ enumerable: true,
2787
+ get: function () {
2788
+ return stopBrowserTracing;
2789
+ }
2790
+ });
2791
+ Object.defineProperty(exports, 'traceRequests', {
2792
+ enumerable: true,
2793
+ get: function () {
2794
+ return traceRequests;
2795
+ }
2796
+ });
2797
+ Object.defineProperty(exports, 'withRequestPatches', {
2798
+ enumerable: true,
2799
+ get: function () {
2800
+ return withRequestPatches;
2801
+ }
2802
+ });