@flareapp/js 2.11.0 → 2.12.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.
- package/dist/browser.cjs +1 -2
- package/dist/browser.d.cts +1 -2
- package/dist/browser.d.mts +1 -2
- package/dist/browser.mjs +1 -2
- package/dist/{catchWindowErrors-BgmDgTlR.cjs → catchWindowErrors-BWKVBTkt.cjs} +99 -252
- package/dist/{catchWindowErrors-I_8ksYoj.mjs → catchWindowErrors-ChRN4Lq1.mjs} +100 -253
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{spanTypes-TMgJEZ7G.d.cts → spanTypes-Cdgtuu6-.d.cts} +35 -32
- package/dist/{spanTypes-7-nEycOc.d.mts → spanTypes-RYeFN_pN.d.mts} +35 -32
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BrowserSpanEventType, BrowserSpanType, BrowserSpanType as BrowserSpanType$1, SpanStatusCode, breadcrumbUrl, buildTraceparent, defaultNowNano, deviceInfoToAttributes, redactUrlQuery, routeRejection, spanId, urlAttributes } from "@flareapp/core";
|
|
1
|
+
import { BrowserSpanEventType, BrowserSpanType, BrowserSpanType as BrowserSpanType$1, SpanStatusCode, breadcrumbUrl, buildTraceparent, defaultNowNano, deviceInfoToAttributes, evictLruIfNew, redactUrlQuery, routeRejection, spanId, urlAttributes } from "@flareapp/core";
|
|
2
2
|
|
|
3
3
|
//#region src/breadcrumbs/utils/documentEvent.ts
|
|
4
4
|
function onDocumentEvent(name, handle) {
|
|
@@ -73,10 +73,11 @@ var FormChangeRecorder = class {
|
|
|
73
73
|
//#endregion
|
|
74
74
|
//#region src/tracing/utils/absoluteHref.ts
|
|
75
75
|
/**
|
|
76
|
-
*
|
|
77
|
-
* wants the pathname
|
|
76
|
+
* Resolves a router-reported href against the current page. Returns the `URL`, so a caller that also
|
|
77
|
+
* wants the pathname does not have to parse it again.
|
|
78
78
|
*
|
|
79
|
-
*
|
|
79
|
+
* Returns undefined outside a browser or for an unparseable href, so the caller can leave its
|
|
80
|
+
* attribute alone.
|
|
80
81
|
*/
|
|
81
82
|
function absoluteUrl(href) {
|
|
82
83
|
if (href == null || typeof window === "undefined") return;
|
|
@@ -87,9 +88,9 @@ function absoluteUrl(href) {
|
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
/**
|
|
90
|
-
* The href form of `absoluteUrl`. Pass
|
|
91
|
-
*
|
|
92
|
-
*
|
|
91
|
+
* The href form of `absoluteUrl`. Pass a value built by the router's own `createHref`/`resolve`, not
|
|
92
|
+
* a bare path — routers strip the app's base path, so `origin + path` gives an address the server
|
|
93
|
+
* does not have.
|
|
93
94
|
*/
|
|
94
95
|
function absoluteHref(href) {
|
|
95
96
|
return absoluteUrl(href)?.href;
|
|
@@ -100,9 +101,14 @@ function absoluteHref(href) {
|
|
|
100
101
|
function currentPath() {
|
|
101
102
|
return typeof location !== "undefined" ? location.pathname : "";
|
|
102
103
|
}
|
|
104
|
+
/** The whole address, query string included. */
|
|
103
105
|
function currentHref() {
|
|
104
106
|
return typeof location !== "undefined" ? location.href : "";
|
|
105
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Prefers the router's route template (`/product/:id`) over the raw path, so all urls of one route
|
|
110
|
+
* group together. If `derive` throws, the fallback path is used instead of breaking the app.
|
|
111
|
+
*/
|
|
106
112
|
function routeName(derive, fallbackPath, url) {
|
|
107
113
|
try {
|
|
108
114
|
const name = derive();
|
|
@@ -119,9 +125,9 @@ function routeName(derive, fallbackPath, url) {
|
|
|
119
125
|
};
|
|
120
126
|
}
|
|
121
127
|
/**
|
|
122
|
-
* `build` is the router's own href builder (vue-router `resolve`, React Router `createHref`). It
|
|
123
|
-
* the app's base path and hash prefix
|
|
124
|
-
*
|
|
128
|
+
* `build` is the router's own href builder (vue-router `resolve`, React Router `createHref`). It restores
|
|
129
|
+
* the app's base path and hash prefix, so an app served from `/app/` reports `/app/product/p01` instead
|
|
130
|
+
* of `/product/p01`. Falls back to `fallbackHref` if `build` throws.
|
|
125
131
|
*/
|
|
126
132
|
function resolveHref(build, fallbackHref) {
|
|
127
133
|
let href = fallbackHref;
|
|
@@ -133,12 +139,6 @@ function resolveHref(build, fallbackHref) {
|
|
|
133
139
|
|
|
134
140
|
//#endregion
|
|
135
141
|
//#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
142
|
function fill(source, name, replacer) {
|
|
143
143
|
const original = source[name];
|
|
144
144
|
if (typeof original !== "function") return;
|
|
@@ -152,7 +152,6 @@ function fill(source, name, replacer) {
|
|
|
152
152
|
});
|
|
153
153
|
source[name] = wrapped;
|
|
154
154
|
}
|
|
155
|
-
/** Restore a previously `fill`ed property to its original. Safe if never filled. */
|
|
156
155
|
function unfill(source, name) {
|
|
157
156
|
const current = source[name];
|
|
158
157
|
if (current && current.__flare_original__) source[name] = current.__flare_original__;
|
|
@@ -262,7 +261,6 @@ function registerNavigationSource() {
|
|
|
262
261
|
}
|
|
263
262
|
};
|
|
264
263
|
}
|
|
265
|
-
/** A name from an earlier call is only valid while that source is still registered. */
|
|
266
264
|
function isActiveNavigationSource(token) {
|
|
267
265
|
return token !== null && source === token;
|
|
268
266
|
}
|
|
@@ -331,12 +329,6 @@ How to fix: use one Flare instance, and check your bundle for two copies of @fla
|
|
|
331
329
|
function hasRequestSubscribers() {
|
|
332
330
|
return subscribers.size > 0 || mutator !== null;
|
|
333
331
|
}
|
|
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
332
|
function publishRequestStart(start) {
|
|
341
333
|
const handlers = [];
|
|
342
334
|
for (const subscriber of subscribers) try {
|
|
@@ -367,17 +359,7 @@ function publishRequestStart(start) {
|
|
|
367
359
|
|
|
368
360
|
//#endregion
|
|
369
361
|
//#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
362
|
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
363
|
function internalRequestInit(init) {
|
|
382
364
|
return {
|
|
383
365
|
...init,
|
|
@@ -390,16 +372,9 @@ function isInternalRequest(init) {
|
|
|
390
372
|
|
|
391
373
|
//#endregion
|
|
392
374
|
//#region src/tracing/requests/supportsNativeFetch.ts
|
|
393
|
-
/** True if `fn` is the browser's native fetch (not a polyfill/wrapper). */
|
|
394
375
|
function isNativeFetch(fn) {
|
|
395
376
|
return typeof fn === "function" && /native code/.test(Function.prototype.toString.call(fn));
|
|
396
377
|
}
|
|
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
378
|
function supportsNativeFetch() {
|
|
404
379
|
const globals = globalThis;
|
|
405
380
|
if (typeof globals.fetch !== "function") return false;
|
|
@@ -427,10 +402,6 @@ function supportsNativeFetch() {
|
|
|
427
402
|
|
|
428
403
|
//#endregion
|
|
429
404
|
//#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
405
|
function createPatcher() {
|
|
435
406
|
let installed = false;
|
|
436
407
|
let names = [];
|
|
@@ -638,12 +609,6 @@ function unpatchXHR() {
|
|
|
638
609
|
//#endregion
|
|
639
610
|
//#region src/instrumentation/requests/requestPatches.ts
|
|
640
611
|
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
612
|
function withRequestPatches(subscribe) {
|
|
648
613
|
if (subscriptions === 0) {
|
|
649
614
|
instrumentFetch();
|
|
@@ -666,7 +631,6 @@ function withRequestPatches(subscribe) {
|
|
|
666
631
|
|
|
667
632
|
//#endregion
|
|
668
633
|
//#region src/tracing/requests/propagation.ts
|
|
669
|
-
/** Follows OTel/Sentry `tracePropagationTargets`: same-origin by default, `[]` disables all. */
|
|
670
634
|
function shouldPropagate(url, absoluteUrl, currentOrigin, targets) {
|
|
671
635
|
if (targets) {
|
|
672
636
|
if (targets.length === 0) return false;
|
|
@@ -674,8 +638,6 @@ function shouldPropagate(url, absoluteUrl, currentOrigin, targets) {
|
|
|
674
638
|
}
|
|
675
639
|
return absoluteUrl !== null && absoluteUrl.origin === currentOrigin;
|
|
676
640
|
}
|
|
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
641
|
function headerPairsFrom(source) {
|
|
680
642
|
try {
|
|
681
643
|
const pairs = [];
|
|
@@ -690,16 +652,9 @@ function headerPairsFrom(source) {
|
|
|
690
652
|
return null;
|
|
691
653
|
}
|
|
692
654
|
}
|
|
693
|
-
/** Fetch accepts any iterable of string pairs as HeadersInit (Map, URLSearchParams, cross-realm Headers). */
|
|
694
655
|
function isIterable(value) {
|
|
695
656
|
return value !== null && (typeof value === "object" || typeof value === "function") && typeof value[Symbol.iterator] === "function";
|
|
696
657
|
}
|
|
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
658
|
function mergeTraceparentHeader(input, init, traceparent) {
|
|
704
659
|
const source = init?.headers ?? (typeof Request !== "undefined" && input instanceof Request ? input.headers : void 0);
|
|
705
660
|
let headers;
|
|
@@ -739,7 +694,6 @@ const REQUEST_SPAN_TYPES = {
|
|
|
739
694
|
xhr: BrowserSpanType.Xhr
|
|
740
695
|
};
|
|
741
696
|
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
697
|
function browserUrlContext() {
|
|
744
698
|
const origin = globalThis.location?.origin ?? "";
|
|
745
699
|
return {
|
|
@@ -747,7 +701,6 @@ function browserUrlContext() {
|
|
|
747
701
|
base: () => globalThis.document?.baseURI || origin
|
|
748
702
|
};
|
|
749
703
|
}
|
|
750
|
-
/** Resolve `url` to an absolute URL against `base`, or null if it cannot be parsed. */
|
|
751
704
|
function safeAbsolute(url, base) {
|
|
752
705
|
try {
|
|
753
706
|
return new URL(url, base || void 0);
|
|
@@ -775,20 +728,10 @@ function matchesIngestHref(href, ingestHref) {
|
|
|
775
728
|
const next = href.charAt(ingestHref.length);
|
|
776
729
|
return next === "" || next === "/" || next === "?" || next === "#";
|
|
777
730
|
}
|
|
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
731
|
function isFlareIngestUrl(resolved, config, base) {
|
|
785
732
|
if (!resolved) return false;
|
|
786
733
|
return resolvedIngestHrefs(config, base).some((ingestHref) => matchesIngestHref(resolved.href, ingestHref));
|
|
787
734
|
}
|
|
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
735
|
function requestSpanAttributes(method, resolved, url, config) {
|
|
793
736
|
return {
|
|
794
737
|
"http.request.method": method,
|
|
@@ -797,12 +740,6 @@ function requestSpanAttributes(method, resolved, url, config) {
|
|
|
797
740
|
...resolved && resolved.port ? { "server.port": Number(resolved.port) } : {}
|
|
798
741
|
};
|
|
799
742
|
}
|
|
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
743
|
function endHttpRequestSpan(span, status, opts) {
|
|
807
744
|
span.setAttribute("http.response.status_code", status);
|
|
808
745
|
if (status >= 500 || opts?.zeroIsError && status === 0) span.setStatus({ code: SpanStatusCode.Error });
|
|
@@ -815,21 +752,10 @@ function finishHttpSpanError(span, error) {
|
|
|
815
752
|
});
|
|
816
753
|
span.end();
|
|
817
754
|
}
|
|
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
755
|
function traceparentFor(span, resolved, url, origin, config) {
|
|
823
756
|
if (!shouldPropagate(resolved ? resolved.href : url, resolved, origin, config.tracePropagationTargets)) return null;
|
|
824
757
|
return buildTraceparent(span.traceId, span.spanId, span.isRecording);
|
|
825
758
|
}
|
|
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
759
|
function startHttpRequestSpan(tracer, request) {
|
|
834
760
|
const { method, url, urls, spanType } = request;
|
|
835
761
|
const config = tracer.config;
|
|
@@ -849,12 +775,6 @@ function startHttpRequestSpan(tracer, request) {
|
|
|
849
775
|
|
|
850
776
|
//#endregion
|
|
851
777
|
//#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
778
|
function zeroIsError(absoluteUrl) {
|
|
859
779
|
return absoluteUrl !== null && (absoluteUrl.protocol === "http:" || absoluteUrl.protocol === "https:");
|
|
860
780
|
}
|
|
@@ -865,7 +785,6 @@ function propagate(span, absoluteUrl, start, urls, tracer) {
|
|
|
865
785
|
if (start.input === void 0) return {};
|
|
866
786
|
return { init: mergeTraceparentHeader(start.input, start.init, traceparent) };
|
|
867
787
|
}
|
|
868
|
-
/** Tracing takes the mutation slot, not a plain subscription, because it adds a `traceparent` header. */
|
|
869
788
|
function traceRequests(tracer, urls) {
|
|
870
789
|
return claimRequestMutation((start) => {
|
|
871
790
|
if (!tracer.config.enableTracing) return;
|
|
@@ -941,7 +860,6 @@ var RequestRecorder = class {
|
|
|
941
860
|
|
|
942
861
|
//#endregion
|
|
943
862
|
//#region src/breadcrumbs/index.ts
|
|
944
|
-
/** Starts every recorder, returns one teardown. A recorder that fails to install is skipped. */
|
|
945
863
|
function startBreadcrumbs(host) {
|
|
946
864
|
if (typeof document === "undefined") return () => {};
|
|
947
865
|
const recorders = [
|
|
@@ -975,10 +893,6 @@ var BrowserFlushScheduler = class {
|
|
|
975
893
|
|
|
976
894
|
//#endregion
|
|
977
895
|
//#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
896
|
function cookie(denylist) {
|
|
983
897
|
if (!window.document.cookie) return {};
|
|
984
898
|
const cookies = Object.create(null);
|
|
@@ -1037,7 +951,6 @@ function readTimezone() {
|
|
|
1037
951
|
|
|
1038
952
|
//#endregion
|
|
1039
953
|
//#region src/browser/context/deviceInfo.ts
|
|
1040
|
-
/** Reads what the User-Agent lacks: hardware, screen, network. Static reads cached; screen and network per call. */
|
|
1041
954
|
var BrowserDeviceInfoProvider = class {
|
|
1042
955
|
staticInfo = null;
|
|
1043
956
|
collect() {
|
|
@@ -1072,16 +985,10 @@ var BrowserDeviceInfoProvider = class {
|
|
|
1072
985
|
return this.staticInfo;
|
|
1073
986
|
}
|
|
1074
987
|
};
|
|
1075
|
-
/** Shared singleton so the static read is cached across reports. */
|
|
1076
988
|
const browserDeviceInfoProvider = new BrowserDeviceInfoProvider();
|
|
1077
989
|
|
|
1078
990
|
//#endregion
|
|
1079
991
|
//#region src/browser/context/request.ts
|
|
1080
|
-
/**
|
|
1081
|
-
* @param hrefOverride when set, the `url.*` attributes come from it instead of the live
|
|
1082
|
-
* `window.location.href` (a framework navigation root whose router knows the destination
|
|
1083
|
-
* before the URL commits). The override is pre-validated by the caller.
|
|
1084
|
-
*/
|
|
1085
992
|
function request(urlDenylist, hrefOverride) {
|
|
1086
993
|
return {
|
|
1087
994
|
...urlAttributes(hrefOverride ?? window.location.href, urlDenylist),
|
|
@@ -1137,7 +1044,7 @@ var FetchFileReader = class {
|
|
|
1137
1044
|
|
|
1138
1045
|
//#endregion
|
|
1139
1046
|
//#region src/env/index.ts
|
|
1140
|
-
const CLIENT_VERSION = typeof process !== "undefined" && true ? "2.
|
|
1047
|
+
const CLIENT_VERSION = typeof process !== "undefined" && true ? "2.12.0" : "?";
|
|
1141
1048
|
|
|
1142
1049
|
//#endregion
|
|
1143
1050
|
//#region src/tracing/utils/instrumentationGuard.ts
|
|
@@ -1157,16 +1064,16 @@ function safeInvoke(fn) {
|
|
|
1157
1064
|
}
|
|
1158
1065
|
const instrumented = /* @__PURE__ */ new WeakMap();
|
|
1159
1066
|
/**
|
|
1160
|
-
*
|
|
1161
|
-
* first. Vite HMR re-runs boot code against a router that survives the reload, so without this
|
|
1162
|
-
* cycle
|
|
1067
|
+
* Instruments `target` at most once at a time, tearing down any prior instrumentation of the same
|
|
1068
|
+
* object first. Vite HMR re-runs boot code against a router that survives the reload, so without this
|
|
1069
|
+
* every cycle would add another listener set that is never removed. Keyed on the object, so a new
|
|
1163
1070
|
* router is unaffected.
|
|
1164
1071
|
*
|
|
1165
|
-
* `install` hands each teardown to `track` as it produces it. A router's
|
|
1166
|
-
*
|
|
1167
|
-
*
|
|
1072
|
+
* `install` hands each teardown to `track` as it produces it. A router's `subscribe`/`on`/guard call
|
|
1073
|
+
* can throw during the host's bootstrap, so a throw part-way through unwinds what already succeeded
|
|
1074
|
+
* (newest first) instead of reaching the host.
|
|
1168
1075
|
*
|
|
1169
|
-
* @returns
|
|
1076
|
+
* @returns The cleanup function, or a no-op if install failed and already unwound itself.
|
|
1170
1077
|
*/
|
|
1171
1078
|
function instrumentOnce(target, install) {
|
|
1172
1079
|
instrumented.get(target)?.();
|
|
@@ -1192,13 +1099,6 @@ function instrumentOnce(target, install) {
|
|
|
1192
1099
|
|
|
1193
1100
|
//#endregion
|
|
1194
1101
|
//#region src/browser/context/collectBrowserSpanContext.ts
|
|
1195
|
-
/**
|
|
1196
|
-
* Entry point plus request identity for a pageload/navigation root. Deliberately leaner than the report
|
|
1197
|
-
* context. Captured at span start, so a long-lived root reflects the page it represents rather than the
|
|
1198
|
-
* page at close.
|
|
1199
|
-
*
|
|
1200
|
-
* @param hrefOverride destination href for a router that reports where it is going before the URL commits.
|
|
1201
|
-
*/
|
|
1202
1102
|
function collectBrowserSpanContext(config, hrefOverride) {
|
|
1203
1103
|
if (typeof window === "undefined") return {};
|
|
1204
1104
|
const url = absoluteUrl(hrefOverride);
|
|
@@ -1207,10 +1107,6 @@ function collectBrowserSpanContext(config, hrefOverride) {
|
|
|
1207
1107
|
...request(config.urlDenylist, url?.href)
|
|
1208
1108
|
};
|
|
1209
1109
|
}
|
|
1210
|
-
/**
|
|
1211
|
-
* Updates a root's url after a redirect, or when a newer navigation replaces this one. The root opened
|
|
1212
|
-
* with the first destination, so without this it reports a page the user never reached.
|
|
1213
|
-
*/
|
|
1214
1110
|
function browserSpanUrlAttributes(config, href) {
|
|
1215
1111
|
if (typeof window === "undefined") return {};
|
|
1216
1112
|
const resolved = absoluteUrl(href);
|
|
@@ -2008,28 +1904,12 @@ const onTTFB = (onReport, opts = {}) => {
|
|
|
2008
1904
|
|
|
2009
1905
|
//#endregion
|
|
2010
1906
|
//#region src/tracing/vitals/webVitals.ts
|
|
2011
|
-
/**
|
|
2012
|
-
* Final the moment they first report, so they can ride the pageload span itself. The other three keep
|
|
2013
|
-
* changing until the page goes away, and stamping an early value on the root would leave the root and
|
|
2014
|
-
* the later span disagreeing about the same vital.
|
|
2015
|
-
*/
|
|
2016
1907
|
const EARLY_VITALS = ["ttfb", "fcp"];
|
|
2017
|
-
/** The one place a vital becomes a wire key. Both the pageload stamp and the late span go through it. */
|
|
2018
1908
|
function vitalAttributes(vitals) {
|
|
2019
1909
|
const attributes = {};
|
|
2020
1910
|
for (const [name, value] of Object.entries(vitals)) if (typeof value === "number") attributes[`browser.web_vital.${name}`] = value;
|
|
2021
1911
|
return attributes;
|
|
2022
1912
|
}
|
|
2023
|
-
/**
|
|
2024
|
-
* Turns the leftover values into one zero-duration span. Pure on purpose: the shape is what the backend
|
|
2025
|
-
* groups on, and this way it is testable without a tracer or a clock.
|
|
2026
|
-
*
|
|
2027
|
-
* Both timestamps sit at the pageload root's start. `spans_2` buckets on `start_time_unix_nano`, so
|
|
2028
|
-
* stamping the report moment would drop a tab left open for forty minutes into a minute forty minutes
|
|
2029
|
-
* after the page actually loaded.
|
|
2030
|
-
*
|
|
2031
|
-
* Returns null when nothing is left to report, so the caller emits no span at all.
|
|
2032
|
-
*/
|
|
2033
1913
|
function buildVitalsSpan(input) {
|
|
2034
1914
|
const vitals = vitalAttributes(input.vitals);
|
|
2035
1915
|
if (Object.keys(vitals).length === 0) return null;
|
|
@@ -2059,10 +1939,6 @@ function defaultSubscribers() {
|
|
|
2059
1939
|
onINP: (cb) => onINP(cb, { reportAllChanges: true })
|
|
2060
1940
|
};
|
|
2061
1941
|
}
|
|
2062
|
-
/**
|
|
2063
|
-
* Subscribes at most once per document: upstream's on* functions return no unsubscribe handle, so a
|
|
2064
|
-
* second call would attach a second set of observers with no way to detach either.
|
|
2065
|
-
*/
|
|
2066
1942
|
function startWebVitals(subscribers = defaultSubscribers()) {
|
|
2067
1943
|
recording = true;
|
|
2068
1944
|
if (subscribed) return;
|
|
@@ -2082,21 +1958,10 @@ function record(name, metric) {
|
|
|
2082
1958
|
if (!recording || typeof metric?.value !== "number") return;
|
|
2083
1959
|
collected[name] = metric.value;
|
|
2084
1960
|
}
|
|
2085
|
-
/**
|
|
2086
|
-
* Stops recording and drops what was collected. The observers themselves cannot be detached, and both
|
|
2087
|
-
* the `subscribed` and `taken` latches deliberately survive: clearing `subscribed` would let a re-enable
|
|
2088
|
-
* attach a second set of observers that is just as undetachable as the first, and clearing `taken` would
|
|
2089
|
-
* let those surviving observers refill `collected` and ship a second `browser_web_vital` for the same
|
|
2090
|
-
* document once the page is re-enabled and later hidden.
|
|
2091
|
-
*/
|
|
2092
1961
|
function stopWebVitals() {
|
|
2093
1962
|
recording = false;
|
|
2094
1963
|
collected = {};
|
|
2095
1964
|
}
|
|
2096
|
-
/**
|
|
2097
|
-
* The vitals that are already final when the pageload root closes, removed from `collected` so the late
|
|
2098
|
-
* span cannot report them a second time. Naturally idempotent: a second call finds the keys gone.
|
|
2099
|
-
*/
|
|
2100
1965
|
function takeEarlyVitals() {
|
|
2101
1966
|
if (!recording) return null;
|
|
2102
1967
|
const taking = {};
|
|
@@ -2109,7 +1974,6 @@ function takeEarlyVitals() {
|
|
|
2109
1974
|
}
|
|
2110
1975
|
return Object.keys(taking).length === 0 ? null : taking;
|
|
2111
1976
|
}
|
|
2112
|
-
/** Everything still outstanding, once. Null afterwards, and null when nothing is left. */
|
|
2113
1977
|
function takeWebVitals() {
|
|
2114
1978
|
if (taken || !recording || Object.keys(collected).length === 0) return null;
|
|
2115
1979
|
taken = true;
|
|
@@ -2117,29 +1981,76 @@ function takeWebVitals() {
|
|
|
2117
1981
|
collected = {};
|
|
2118
1982
|
return taking;
|
|
2119
1983
|
}
|
|
2120
|
-
/**
|
|
2121
|
-
* Undoes a take after the emit failed partway through, so the values are not lost and a later trigger
|
|
2122
|
-
* can retry. Safe to assign outright rather than merge: the whole emit runs synchronously between the
|
|
2123
|
-
* take and a catch block calling this, so nothing else can have written to `collected` in between.
|
|
2124
|
-
*/
|
|
2125
1984
|
function restoreWebVitals(vitals) {
|
|
2126
1985
|
collected = vitals;
|
|
2127
1986
|
taken = false;
|
|
2128
1987
|
}
|
|
2129
1988
|
|
|
1989
|
+
//#endregion
|
|
1990
|
+
//#region src/tracing/roots/componentSelfTime.ts
|
|
1991
|
+
/**
|
|
1992
|
+
* Only a parent that never records leaves an entry behind: an async child that registers after its
|
|
1993
|
+
* parent already shipped. The normal path frees every entry the moment the parent records, so this
|
|
1994
|
+
* cap is a backstop, not a working limit.
|
|
1995
|
+
*/
|
|
1996
|
+
const MAX_TRACKED_PARENTS = 256;
|
|
1997
|
+
const childIntervals = /* @__PURE__ */ new Map();
|
|
1998
|
+
/** Files a recorded child under its parent, so the parent can subtract it. */
|
|
1999
|
+
function trackChildInterval(parentSpanId, startTimeUnixNano, endTimeUnixNano) {
|
|
2000
|
+
const existing = childIntervals.get(parentSpanId);
|
|
2001
|
+
if (existing) {
|
|
2002
|
+
existing.push([startTimeUnixNano, endTimeUnixNano]);
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
evictLruIfNew(childIntervals, parentSpanId, MAX_TRACKED_PARENTS);
|
|
2006
|
+
childIntervals.set(parentSpanId, [[startTimeUnixNano, endTimeUnixNano]]);
|
|
2007
|
+
}
|
|
2008
|
+
/**
|
|
2009
|
+
* Duration minus the time the component's own children already account for, and consumes those
|
|
2010
|
+
* children. Grandchildren need no handling: they sit inside a child's interval.
|
|
2011
|
+
*
|
|
2012
|
+
* Children that record after their parent are not subtracted, so the value is self time at commit.
|
|
2013
|
+
* That covers async components and `<Suspense>`, and a vue-router layout too: the initial route
|
|
2014
|
+
* resolves after the layout mounted, so the page component's work falls outside the layout's window
|
|
2015
|
+
* and the layout keeps its full duration.
|
|
2016
|
+
*/
|
|
2017
|
+
function takeSelfTime(spanId, startTimeUnixNano, endTimeUnixNano) {
|
|
2018
|
+
const children = childIntervals.get(spanId);
|
|
2019
|
+
childIntervals.delete(spanId);
|
|
2020
|
+
const duration = endTimeUnixNano - startTimeUnixNano;
|
|
2021
|
+
if (!children) return Math.max(0, duration);
|
|
2022
|
+
return Math.max(0, duration - coveredTime(children, startTimeUnixNano, endTimeUnixNano));
|
|
2023
|
+
}
|
|
2024
|
+
function resetComponentSelfTime() {
|
|
2025
|
+
childIntervals.clear();
|
|
2026
|
+
}
|
|
2027
|
+
/**
|
|
2028
|
+
* The union of the intervals, clamped to the parent window. A sum would double-count: React starts
|
|
2029
|
+
* every component during render and ends them all during commit, so siblings overlap, and summing
|
|
2030
|
+
* their durations exceeds the parent's own.
|
|
2031
|
+
*/
|
|
2032
|
+
function coveredTime(intervals, start, end) {
|
|
2033
|
+
intervals.sort((a, b) => a[0] - b[0]);
|
|
2034
|
+
let covered = 0;
|
|
2035
|
+
let cursor = start;
|
|
2036
|
+
for (const [childStart, childEnd] of intervals) {
|
|
2037
|
+
const from = Math.max(childStart, cursor);
|
|
2038
|
+
const to = Math.min(childEnd, end);
|
|
2039
|
+
if (to > from) {
|
|
2040
|
+
covered += to - from;
|
|
2041
|
+
cursor = to;
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
return covered;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2130
2047
|
//#endregion
|
|
2131
2048
|
//#region src/tracing/roots/IdleRootController.ts
|
|
2132
|
-
/** Browser defaults for the three idle-root timeouts, in ms. Overridable per Config. */
|
|
2133
2049
|
const DEFAULT_IDLE_TIMEOUTS = {
|
|
2134
2050
|
idleTimeout: 1e3,
|
|
2135
2051
|
finalTimeout: 3e4,
|
|
2136
2052
|
childSpanTimeout: 15e3
|
|
2137
2053
|
};
|
|
2138
|
-
/**
|
|
2139
|
-
* Owns one root span's idle lifecycle: open while child spans in its trace are active, closing after
|
|
2140
|
-
* `idleTimeout` with no open children, or on the `finalTimeout` / `childSpanTimeout` backstops. Deps are
|
|
2141
|
-
* injected so this is testable without real timers or a real tracer.
|
|
2142
|
-
*/
|
|
2143
2054
|
var IdleRootController = class {
|
|
2144
2055
|
openChildren = 0;
|
|
2145
2056
|
lastChildEndTime = null;
|
|
@@ -2164,17 +2075,9 @@ var IdleRootController = class {
|
|
|
2164
2075
|
get isEnded() {
|
|
2165
2076
|
return this.ended;
|
|
2166
2077
|
}
|
|
2167
|
-
/** For a route change or pagehide. */
|
|
2168
2078
|
endNow() {
|
|
2169
2079
|
this.finish(this.openChildren > 0 || this.held ? this.deps.now() : this.trimmedEnd());
|
|
2170
2080
|
}
|
|
2171
|
-
/**
|
|
2172
|
-
* Records the settle moment as a close floor and hands the root back to the normal idle lifecycle. It
|
|
2173
|
-
* deliberately does not close here: a router settles before the framework mounts the new route
|
|
2174
|
-
* component (vue-router runs `afterEach` in the route-update tick, Vue mounts on the next flush), so
|
|
2175
|
-
* closing at settle cleared the active root ahead of every post-navigation mount: every component span
|
|
2176
|
-
* read a null root, and a trailing fetch opened a root of its own.
|
|
2177
|
-
*/
|
|
2178
2081
|
releaseHold() {
|
|
2179
2082
|
if (this.ended || !this.held) return;
|
|
2180
2083
|
this.held = false;
|
|
@@ -2211,8 +2114,6 @@ var IdleRootController = class {
|
|
|
2211
2114
|
this.finish(this.trimmedEnd());
|
|
2212
2115
|
}, this.timeouts.idleTimeout);
|
|
2213
2116
|
}
|
|
2214
|
-
/** The latest of the floor, a released hold's settle moment, and the last child's end, so a root
|
|
2215
|
-
* covers its children without ever padding out to `now()`. */
|
|
2216
2117
|
trimmedEnd() {
|
|
2217
2118
|
return Math.max(this.deps.endFloor(), this.settleTime ?? 0, this.lastChildEndTime ?? 0);
|
|
2218
2119
|
}
|
|
@@ -2250,21 +2151,14 @@ var IdleRootController = class {
|
|
|
2250
2151
|
|
|
2251
2152
|
//#endregion
|
|
2252
2153
|
//#region src/tracing/roots/navigationTiming.ts
|
|
2253
|
-
/** Split out so the timestamp maths is testable without a Navigation Timing entry. */
|
|
2254
2154
|
function computePageloadStartNano(timeOriginMs, startTimeMs) {
|
|
2255
2155
|
return Math.round((timeOriginMs + (startTimeMs ?? 0)) * 1e6);
|
|
2256
2156
|
}
|
|
2257
|
-
/**
|
|
2258
|
-
* Choose the pageload root's start time: navigation start while that window is still open,
|
|
2259
|
-
* otherwise `now`. Starting at `now` (when tracing began after the final cap, or the pageload was
|
|
2260
|
-
* already traced) avoids a backdated root reporting a bogus multi-second duration.
|
|
2261
|
-
*/
|
|
2262
2157
|
function resolvePageloadStartNano(backdatedNano, nowNano, finalTimeoutNano, alreadyTraced) {
|
|
2263
2158
|
if (alreadyTraced) return nowNano;
|
|
2264
2159
|
if (nowNano - backdatedNano > finalTimeoutNano) return nowNano;
|
|
2265
2160
|
return backdatedNano;
|
|
2266
2161
|
}
|
|
2267
|
-
/** The Navigation Timing API, or null where it is missing or only partly implemented. */
|
|
2268
2162
|
function navigationTiming() {
|
|
2269
2163
|
const perf = globalThis.performance;
|
|
2270
2164
|
if (!perf || typeof perf.getEntriesByType !== "function" || typeof perf.timeOrigin !== "number") return null;
|
|
@@ -2273,10 +2167,6 @@ function navigationTiming() {
|
|
|
2273
2167
|
function navigationEntry(perf) {
|
|
2274
2168
|
return perf.getEntriesByType("navigation")[0];
|
|
2275
2169
|
}
|
|
2276
|
-
/**
|
|
2277
|
-
* The pageload root's start time in unix nanoseconds, backdated to navigation start via the
|
|
2278
|
-
* Navigation Timing entry. Falls back to the tracer's clock when the API is unavailable.
|
|
2279
|
-
*/
|
|
2280
2170
|
function pageloadStartNano() {
|
|
2281
2171
|
const perf = navigationTiming();
|
|
2282
2172
|
if (!perf) return defaultNowNano();
|
|
@@ -2287,13 +2177,6 @@ function computePageloadEndNano(timeOriginMs, loadEventEndMs, domContentLoadedEv
|
|
|
2287
2177
|
if (!endMs) return nowNano;
|
|
2288
2178
|
return Math.round((timeOriginMs + endMs) * 1e6);
|
|
2289
2179
|
}
|
|
2290
|
-
/**
|
|
2291
|
-
* The pageload root's end time in unix nanoseconds, taken from the Navigation
|
|
2292
|
-
* Timing `loadEventEnd` (the browser's own "page finished loading" mark), falling
|
|
2293
|
-
* back to `domContentLoadedEventEnd`, then the tracer's clock when neither has
|
|
2294
|
-
* fired yet or the API is unavailable. Used as the pageload root's close floor so a
|
|
2295
|
-
* childless pageload reports its real load duration rather than idle-timeout padding.
|
|
2296
|
-
*/
|
|
2297
2180
|
function pageloadEndNano() {
|
|
2298
2181
|
const perf = navigationTiming();
|
|
2299
2182
|
if (!perf) return defaultNowNano();
|
|
@@ -2322,8 +2205,6 @@ function resolveTimeouts(config) {
|
|
|
2322
2205
|
childSpanTimeout: config.childSpanTimeout ?? DEFAULT_IDLE_TIMEOUTS.childSpanTimeout
|
|
2323
2206
|
};
|
|
2324
2207
|
}
|
|
2325
|
-
/** No-ops once the controller has ended: it can close itself asynchronously via a timer, before this
|
|
2326
|
-
* module's `controller` reference is cleared. */
|
|
2327
2208
|
function withLiveController(fn) {
|
|
2328
2209
|
if (!controller || controller.isEnded) return;
|
|
2329
2210
|
try {
|
|
@@ -2332,13 +2213,13 @@ function withLiveController(fn) {
|
|
|
2332
2213
|
if (activeFlare?.config.debug) console.error("Flare: browser tracing controller callback failed", error);
|
|
2333
2214
|
}
|
|
2334
2215
|
}
|
|
2335
|
-
/** Run `fn` only while the current root is still open. Swallows a throw, like `withLiveController`. */
|
|
2336
2216
|
function ifRootLive(fn) {
|
|
2337
2217
|
withLiveController(() => fn());
|
|
2338
2218
|
}
|
|
2339
2219
|
function startRoot(flare, options) {
|
|
2340
2220
|
const { spanType, startTimeUnixNano, name = location.pathname, urlOverride, hold, backdated } = options;
|
|
2341
2221
|
let root;
|
|
2222
|
+
resetComponentSelfTime();
|
|
2342
2223
|
try {
|
|
2343
2224
|
const context = collectBrowserSpanContext(flare.config, urlOverride);
|
|
2344
2225
|
root = flare.startSpan(name, {
|
|
@@ -2400,14 +2281,6 @@ function openNavigationRoot(flare, opts) {
|
|
|
2400
2281
|
hold: opts.hold
|
|
2401
2282
|
});
|
|
2402
2283
|
}
|
|
2403
|
-
/**
|
|
2404
|
-
* Writes the already-final vitals onto the pageload root itself, from `IdleRootController`'s beforeEnd
|
|
2405
|
-
* hook. Whatever has not reported yet stays in `collected` and rides the later `browser_web_vital`
|
|
2406
|
-
* span instead, so no vital is ever sent twice and none is lost.
|
|
2407
|
-
*
|
|
2408
|
-
* Swallows its own failures: this runs inside the root's close path, and a throw here would leave the
|
|
2409
|
-
* root open forever.
|
|
2410
|
-
*/
|
|
2411
2284
|
function stampEarlyVitals(root, flare) {
|
|
2412
2285
|
try {
|
|
2413
2286
|
const early = takeEarlyVitals();
|
|
@@ -2417,19 +2290,6 @@ function stampEarlyVitals(root, flare) {
|
|
|
2417
2290
|
if (flare.config.debug) console.error("Flare: failed to stamp web vitals on the pageload root", error);
|
|
2418
2291
|
}
|
|
2419
2292
|
}
|
|
2420
|
-
/**
|
|
2421
|
-
* Emits whatever the pageload root could not carry as one zero-duration `browser_web_vital` span,
|
|
2422
|
-
* parented to that root. Page hide only, and once per document.
|
|
2423
|
-
*
|
|
2424
|
-
* Deliberately NOT on navigation: LCP, CLS and INP keep moving all document long, so emitting at the
|
|
2425
|
-
* first route change froze them a second after load, and a session whose first action was a nav click
|
|
2426
|
-
* reported no INP at all. One span per document is a backend constraint, so the emit waits for the last
|
|
2427
|
-
* moment we get instead. The cost is a page whose hide event never fires reports no vitals.
|
|
2428
|
-
*
|
|
2429
|
-
* `pageloadRoot` has usually ended by now; reading `traceId` and `spanId` off an ended span is fine,
|
|
2430
|
-
* and passing the `Span` rather than a `{ traceId, spanId }` pair is what makes sampling inherit:
|
|
2431
|
-
* `resolveTrace()` reads `parent.isRecording` instead of re-rolling the sampler.
|
|
2432
|
-
*/
|
|
2433
2293
|
function emitWebVitals(flare) {
|
|
2434
2294
|
const root = pageloadRoot;
|
|
2435
2295
|
const route = pageloadRoute;
|
|
@@ -2457,7 +2317,6 @@ function emitWebVitals(flare) {
|
|
|
2457
2317
|
if (flare.config.debug) console.error("Flare: failed to emit web vitals", error);
|
|
2458
2318
|
}
|
|
2459
2319
|
}
|
|
2460
|
-
/** Opens a backdated pageload root, then a navigation root per History change. No-op outside a browser. Idempotent. */
|
|
2461
2320
|
function startBrowserTracing(flare) {
|
|
2462
2321
|
if (typeof window === "undefined" || typeof history === "undefined" || typeof location === "undefined") return;
|
|
2463
2322
|
if (uninstall) return;
|
|
@@ -2517,7 +2376,6 @@ function startBrowserTracing(flare) {
|
|
|
2517
2376
|
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
2518
2377
|
};
|
|
2519
2378
|
}
|
|
2520
|
-
/** Idempotent. */
|
|
2521
2379
|
function stopBrowserTracing() {
|
|
2522
2380
|
withLiveController((live) => live.endNow());
|
|
2523
2381
|
controller = null;
|
|
@@ -2532,15 +2390,12 @@ function stopBrowserTracing() {
|
|
|
2532
2390
|
pendingRouteName = null;
|
|
2533
2391
|
pendingRouteNameOwner = null;
|
|
2534
2392
|
stopWebVitals();
|
|
2393
|
+
resetComponentSelfTime();
|
|
2535
2394
|
pageloadRoot = null;
|
|
2536
2395
|
pageloadRootStartNano = 0;
|
|
2537
2396
|
pageloadRoute = null;
|
|
2538
2397
|
pageloadContext = {};
|
|
2539
2398
|
}
|
|
2540
|
-
/**
|
|
2541
|
-
* Computes the url attributes a route rename carries, or null when there is nothing to add. Guarded
|
|
2542
|
-
* so the pin below (which runs outside ifRootLive's own try/catch) cannot throw into the host.
|
|
2543
|
-
*/
|
|
2544
2399
|
function urlAttributesFor(route) {
|
|
2545
2400
|
if (route.url === void 0 || !activeFlare) return null;
|
|
2546
2401
|
try {
|
|
@@ -2549,12 +2404,6 @@ function urlAttributesFor(route) {
|
|
|
2549
2404
|
return null;
|
|
2550
2405
|
}
|
|
2551
2406
|
}
|
|
2552
|
-
/**
|
|
2553
|
-
* Rename the current root and update the attributes that go with the name, and pin the pageload's route
|
|
2554
|
-
* for the vitals emit. No-op once it closed; the pin is NOT gated the same way, see below.
|
|
2555
|
-
* With no root yet the name is held for the pageload root that opens next, rather than dropped.
|
|
2556
|
-
* `owner` stamps who is holding it, so a stale or superseded source cannot land its name later.
|
|
2557
|
-
*/
|
|
2558
2407
|
function applyRouteName(route, owner) {
|
|
2559
2408
|
const root = currentRoot;
|
|
2560
2409
|
if (!root) {
|
|
@@ -2590,8 +2439,8 @@ function activeTracingFlare() {
|
|
|
2590
2439
|
/** Unix nanos on the same clock the tracer uses for span timestamps. */
|
|
2591
2440
|
const nowNano = defaultNowNano;
|
|
2592
2441
|
/**
|
|
2593
|
-
* Reserved up front so descendants can point at a span before it is recorded. Null when the trace
|
|
2594
|
-
* its span cap
|
|
2442
|
+
* Reserved up front so descendants can point at a span before it is recorded. Null when the trace
|
|
2443
|
+
* is at its span cap, since descendants record before this span does and would be orphaned.
|
|
2595
2444
|
*/
|
|
2596
2445
|
function reserveSpanId(traceId) {
|
|
2597
2446
|
if (traceId !== void 0 && !activeTracingFlare()?.tracer.claimSpanSlot(traceId)) return null;
|
|
@@ -2611,18 +2460,18 @@ function activeComponentRoot() {
|
|
|
2611
2460
|
}
|
|
2612
2461
|
}
|
|
2613
2462
|
/**
|
|
2614
|
-
* An ancestor's context is only usable while it still belongs to the live trace. A
|
|
2615
|
-
*
|
|
2616
|
-
* trace, and `recordComponentSpan` would drop anything pointing at that closed root.
|
|
2463
|
+
* An ancestor's context is only usable while it still belongs to the live trace. A component that
|
|
2464
|
+
* survives a navigation (a layout around a swapped page body) froze its context under the old
|
|
2465
|
+
* pageload trace, and `recordComponentSpan` would drop anything pointing at that closed root.
|
|
2617
2466
|
*/
|
|
2618
2467
|
function resolveComponentParent(inherited, live) {
|
|
2619
2468
|
if (inherited && live && inherited.traceId === live.traceId) return inherited;
|
|
2620
2469
|
return live;
|
|
2621
2470
|
}
|
|
2622
2471
|
/**
|
|
2623
|
-
* Records only while the reserved root is still the live recording root, and drops the span
|
|
2624
|
-
* Dropping avoids
|
|
2625
|
-
*
|
|
2472
|
+
* Records only while the reserved root is still the live recording root, and drops the span
|
|
2473
|
+
* otherwise. Dropping avoids re-running the sampler for a dead trace and avoids adding a child to
|
|
2474
|
+
* a root that already shipped.
|
|
2626
2475
|
*/
|
|
2627
2476
|
function recordComponentSpan(record) {
|
|
2628
2477
|
try {
|
|
@@ -2630,6 +2479,7 @@ function recordComponentSpan(record) {
|
|
|
2630
2479
|
if (!flare) return;
|
|
2631
2480
|
const root = flare.tracer.getActiveSpan();
|
|
2632
2481
|
if (!root || root.traceId !== record.parent.traceId || !root.isRecording) return;
|
|
2482
|
+
const selfTime = takeSelfTime(record.spanId, record.startTimeUnixNano, record.endTimeUnixNano);
|
|
2633
2483
|
flare.startSpan(record.name, {
|
|
2634
2484
|
spanId: record.spanId,
|
|
2635
2485
|
parent: {
|
|
@@ -2640,19 +2490,17 @@ function recordComponentSpan(record) {
|
|
|
2640
2490
|
startTimeUnixNano: record.startTimeUnixNano,
|
|
2641
2491
|
attributes: {
|
|
2642
2492
|
...record.attributes,
|
|
2643
|
-
"flare.component.name": record.name
|
|
2493
|
+
"flare.component.name": record.name,
|
|
2494
|
+
"flare.component.self_time_ns": selfTime
|
|
2644
2495
|
},
|
|
2645
2496
|
claimed: true
|
|
2646
2497
|
}).end(record.endTimeUnixNano);
|
|
2498
|
+
trackChildInterval(record.parent.parentSpanId, record.startTimeUnixNano, record.endTimeUnixNano);
|
|
2647
2499
|
} catch {}
|
|
2648
2500
|
}
|
|
2649
2501
|
|
|
2650
2502
|
//#endregion
|
|
2651
2503
|
//#region src/createFlareResolver.ts
|
|
2652
|
-
/**
|
|
2653
|
-
* `process.env.NODE_ENV` is replaced inline by bundlers. The try/catch keeps a process-less
|
|
2654
|
-
* environment safe: treat "undetermined" as production (warn, never crash).
|
|
2655
|
-
*/
|
|
2656
2504
|
function isDevMode() {
|
|
2657
2505
|
try {
|
|
2658
2506
|
return process.env.NODE_ENV !== "production";
|
|
@@ -2661,11 +2509,10 @@ function isDevMode() {
|
|
|
2661
2509
|
}
|
|
2662
2510
|
}
|
|
2663
2511
|
/**
|
|
2664
|
-
* Builds a per-package Flare resolver: `registerDefaultFlare` (
|
|
2665
|
-
* `resolveFlare` (called at wiring time). Each call
|
|
2666
|
-
*
|
|
2667
|
-
*
|
|
2668
|
-
* svelte, which points at the preprocessor's importSource).
|
|
2512
|
+
* Builds a per-package Flare resolver: `registerDefaultFlare` (set once by the web entry) and
|
|
2513
|
+
* `resolveFlare` (called at wiring time). Each call keeps its own default-provider state.
|
|
2514
|
+
* `packageName` names the package in the Electron-bridge warning; `injectInstruction` overrides the
|
|
2515
|
+
* closing hint for packages with different advice (for example Svelte's preprocessor importSource).
|
|
2669
2516
|
*/
|
|
2670
2517
|
function createFlareResolver(config) {
|
|
2671
2518
|
const { packageName } = config;
|