@obsrviq/tracker 0.4.0 → 0.4.2
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/index.js +178 -62
- package/dist/index.js.map +1 -1
- package/dist/obsrviq.global.js +40 -40
- package/dist/obsrviq.global.js.map +1 -1
- package/package.json +1 -1
- package/src/global.ts +21 -12
- package/src/network.ts +240 -63
package/dist/index.js
CHANGED
|
@@ -506,36 +506,144 @@ function bodyPreview(text, contentType) {
|
|
|
506
506
|
redacted: masked !== text
|
|
507
507
|
};
|
|
508
508
|
}
|
|
509
|
-
function
|
|
509
|
+
function perfNow() {
|
|
510
|
+
return typeof performance !== "undefined" ? performance.now() : 0;
|
|
511
|
+
}
|
|
512
|
+
function absUrl(raw) {
|
|
510
513
|
try {
|
|
511
|
-
|
|
512
|
-
const match = entries.filter((e) => e.name === url).pop();
|
|
513
|
-
if (!match) return base;
|
|
514
|
-
const t = { ...base };
|
|
515
|
-
if (match.domainLookupEnd && match.domainLookupStart)
|
|
516
|
-
t.dns = Math.max(0, match.domainLookupEnd - match.domainLookupStart);
|
|
517
|
-
if (match.connectEnd && match.connectStart)
|
|
518
|
-
t.tcp = Math.max(0, match.connectEnd - match.connectStart);
|
|
519
|
-
if (match.secureConnectionStart)
|
|
520
|
-
t.tls = Math.max(0, match.connectEnd - match.secureConnectionStart);
|
|
521
|
-
if (match.responseStart && match.requestStart)
|
|
522
|
-
t.ttfb = Math.max(0, match.responseStart - match.requestStart);
|
|
523
|
-
if (match.responseEnd && match.responseStart)
|
|
524
|
-
t.download = Math.max(0, match.responseEnd - match.responseStart);
|
|
525
|
-
return t;
|
|
514
|
+
return new URL(raw, typeof location !== "undefined" ? location.href : void 0).href;
|
|
526
515
|
} catch {
|
|
527
|
-
return
|
|
516
|
+
return raw;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function phasesFrom(m, base) {
|
|
520
|
+
const t = { ...base };
|
|
521
|
+
const r = (n) => Math.round(n);
|
|
522
|
+
if (m.domainLookupStart > 0 && m.domainLookupEnd > m.domainLookupStart)
|
|
523
|
+
t.dns = r(m.domainLookupEnd - m.domainLookupStart);
|
|
524
|
+
if (m.connectStart > 0 && m.connectEnd > m.connectStart) t.tcp = r(m.connectEnd - m.connectStart);
|
|
525
|
+
if (m.secureConnectionStart > 0 && m.connectEnd > m.secureConnectionStart)
|
|
526
|
+
t.tls = r(m.connectEnd - m.secureConnectionStart);
|
|
527
|
+
if (m.requestStart > 0 && m.responseStart > m.requestStart)
|
|
528
|
+
t.ttfb = r(m.responseStart - m.requestStart);
|
|
529
|
+
if (m.responseStart > 0 && m.responseEnd > m.responseStart)
|
|
530
|
+
t.download = r(m.responseEnd - m.responseStart);
|
|
531
|
+
return t;
|
|
532
|
+
}
|
|
533
|
+
function matchEntry(name, perfStart, entries) {
|
|
534
|
+
let best;
|
|
535
|
+
let bestDelta = Infinity;
|
|
536
|
+
for (const e of entries) {
|
|
537
|
+
if (e.name !== name) continue;
|
|
538
|
+
if (e.startTime < perfStart - 4) continue;
|
|
539
|
+
const d = e.startTime - perfStart;
|
|
540
|
+
if (d < bestDelta) {
|
|
541
|
+
bestDelta = d;
|
|
542
|
+
best = e;
|
|
543
|
+
}
|
|
528
544
|
}
|
|
545
|
+
return best;
|
|
546
|
+
}
|
|
547
|
+
var TIMING_WAIT_MS = 300;
|
|
548
|
+
function createTimingResolver() {
|
|
549
|
+
const pending = [];
|
|
550
|
+
let obs;
|
|
551
|
+
const stopObs = () => {
|
|
552
|
+
if (obs) {
|
|
553
|
+
try {
|
|
554
|
+
obs.disconnect();
|
|
555
|
+
} catch {
|
|
556
|
+
}
|
|
557
|
+
obs = void 0;
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
const settle = (p, t) => {
|
|
561
|
+
const i = pending.indexOf(p);
|
|
562
|
+
if (i === -1) return;
|
|
563
|
+
pending.splice(i, 1);
|
|
564
|
+
clearTimeout(p.timer);
|
|
565
|
+
p.resolve(t);
|
|
566
|
+
if (pending.length === 0) stopObs();
|
|
567
|
+
};
|
|
568
|
+
const consume = (entries) => {
|
|
569
|
+
for (const e of entries) {
|
|
570
|
+
let best;
|
|
571
|
+
let bestDelta = Infinity;
|
|
572
|
+
for (const p of pending) {
|
|
573
|
+
if (e.name !== p.name || e.startTime < p.perfStart - 4) continue;
|
|
574
|
+
const d = e.startTime - p.perfStart;
|
|
575
|
+
if (d < bestDelta) {
|
|
576
|
+
bestDelta = d;
|
|
577
|
+
best = p;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (best) settle(best, phasesFrom(e, best.base));
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
const ensureObserver = () => {
|
|
584
|
+
if (obs || typeof PerformanceObserver === "undefined") return;
|
|
585
|
+
try {
|
|
586
|
+
obs = new PerformanceObserver((list) => consume(list.getEntries()));
|
|
587
|
+
obs.observe({ type: "resource", buffered: false });
|
|
588
|
+
} catch {
|
|
589
|
+
obs = void 0;
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
return {
|
|
593
|
+
resolve(rawUrl, perfStart, base) {
|
|
594
|
+
if (typeof performance === "undefined") return Promise.resolve(base);
|
|
595
|
+
const name = absUrl(rawUrl);
|
|
596
|
+
try {
|
|
597
|
+
const now = performance.getEntriesByType("resource");
|
|
598
|
+
const immediate = matchEntry(name, perfStart, now);
|
|
599
|
+
if (immediate) return Promise.resolve(phasesFrom(immediate, base));
|
|
600
|
+
} catch {
|
|
601
|
+
}
|
|
602
|
+
if (typeof PerformanceObserver === "undefined") return Promise.resolve(base);
|
|
603
|
+
return new Promise((resolve) => {
|
|
604
|
+
const p = {
|
|
605
|
+
name,
|
|
606
|
+
perfStart,
|
|
607
|
+
base,
|
|
608
|
+
resolve,
|
|
609
|
+
timer: setTimeout(() => settle(p, base), TIMING_WAIT_MS)
|
|
610
|
+
};
|
|
611
|
+
pending.push(p);
|
|
612
|
+
ensureObserver();
|
|
613
|
+
});
|
|
614
|
+
},
|
|
615
|
+
// Force-settle everything still in flight (with duration-only timing) so their
|
|
616
|
+
// events are emitted into the transport buffer before an unload beacon fires.
|
|
617
|
+
flushPending() {
|
|
618
|
+
for (const p of [...pending]) settle(p, p.base);
|
|
619
|
+
},
|
|
620
|
+
teardown() {
|
|
621
|
+
this.flushPending();
|
|
622
|
+
stopObs();
|
|
623
|
+
}
|
|
624
|
+
};
|
|
529
625
|
}
|
|
530
626
|
function instrumentNetwork(ctx) {
|
|
627
|
+
const timing = createTimingResolver();
|
|
628
|
+
const onPageHide = () => timing.flushPending();
|
|
629
|
+
const onVisibility = () => {
|
|
630
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") timing.flushPending();
|
|
631
|
+
};
|
|
632
|
+
if (typeof window !== "undefined") window.addEventListener("pagehide", onPageHide);
|
|
633
|
+
if (typeof document !== "undefined") document.addEventListener("visibilitychange", onVisibility);
|
|
531
634
|
const teardowns = [];
|
|
532
|
-
teardowns.push(wrapFetch(ctx));
|
|
533
|
-
teardowns.push(wrapXhr(ctx));
|
|
635
|
+
teardowns.push(wrapFetch(ctx, timing));
|
|
636
|
+
teardowns.push(wrapXhr(ctx, timing));
|
|
534
637
|
teardowns.push(wrapBeacon(ctx));
|
|
535
638
|
teardowns.push(observeResources(ctx));
|
|
639
|
+
teardowns.push(() => {
|
|
640
|
+
if (typeof window !== "undefined") window.removeEventListener("pagehide", onPageHide);
|
|
641
|
+
if (typeof document !== "undefined") document.removeEventListener("visibilitychange", onVisibility);
|
|
642
|
+
timing.teardown();
|
|
643
|
+
});
|
|
536
644
|
return () => teardowns.forEach((t) => t());
|
|
537
645
|
}
|
|
538
|
-
function wrapFetch(ctx) {
|
|
646
|
+
function wrapFetch(ctx, tr) {
|
|
539
647
|
const orig = window.fetch;
|
|
540
648
|
if (!orig) return () => {
|
|
541
649
|
};
|
|
@@ -543,6 +651,7 @@ function wrapFetch(ctx) {
|
|
|
543
651
|
const rawUrl = input instanceof Request ? input.url : String(input);
|
|
544
652
|
if (rawUrl.includes("/v1/batch")) return orig.call(window, input, init);
|
|
545
653
|
const startT = ctx.clock.now();
|
|
654
|
+
const perfStart = perfNow();
|
|
546
655
|
const method = (init?.method || (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
547
656
|
const url = redactUrl(rawUrl);
|
|
548
657
|
const reqHeaders = pickHeaders(
|
|
@@ -551,25 +660,27 @@ function wrapFetch(ctx) {
|
|
|
551
660
|
);
|
|
552
661
|
const emit = (partial) => {
|
|
553
662
|
const endT = ctx.clock.now();
|
|
554
|
-
const
|
|
555
|
-
const ev = {
|
|
556
|
-
id: uuid(),
|
|
557
|
-
sessionId: ctx.sessionId,
|
|
558
|
-
type: "network",
|
|
559
|
-
t: startT,
|
|
560
|
-
ts: ctx.clock.wall(),
|
|
561
|
-
initiator: "fetch",
|
|
562
|
-
method,
|
|
563
|
-
url,
|
|
564
|
-
status: 0,
|
|
565
|
-
ok: false,
|
|
566
|
-
resourceType: "fetch",
|
|
567
|
-
requestHeaders: reqHeaders,
|
|
568
|
-
timing,
|
|
569
|
-
...partial
|
|
570
|
-
};
|
|
571
|
-
ctx.emit(ev);
|
|
663
|
+
const base = { startT, endT, duration: endT - startT };
|
|
572
664
|
ctx.markActivity();
|
|
665
|
+
void tr.resolve(rawUrl, perfStart, base).then((timing) => {
|
|
666
|
+
const ev = {
|
|
667
|
+
id: uuid(),
|
|
668
|
+
sessionId: ctx.sessionId,
|
|
669
|
+
type: "network",
|
|
670
|
+
t: startT,
|
|
671
|
+
ts: ctx.clock.wall(),
|
|
672
|
+
initiator: "fetch",
|
|
673
|
+
method,
|
|
674
|
+
url,
|
|
675
|
+
status: 0,
|
|
676
|
+
ok: false,
|
|
677
|
+
resourceType: "fetch",
|
|
678
|
+
requestHeaders: reqHeaders,
|
|
679
|
+
timing,
|
|
680
|
+
...partial
|
|
681
|
+
};
|
|
682
|
+
ctx.emit(ev);
|
|
683
|
+
});
|
|
573
684
|
};
|
|
574
685
|
try {
|
|
575
686
|
const res = await orig.call(window, input, init);
|
|
@@ -611,7 +722,7 @@ function classifyFetchError(err) {
|
|
|
611
722
|
if (msg.includes("timeout")) return "timeout";
|
|
612
723
|
return "network";
|
|
613
724
|
}
|
|
614
|
-
function wrapXhr(ctx) {
|
|
725
|
+
function wrapXhr(ctx, tr) {
|
|
615
726
|
const XHR = XMLHttpRequest.prototype;
|
|
616
727
|
const origOpen = XHR.open;
|
|
617
728
|
const origSend = XHR.send;
|
|
@@ -624,6 +735,7 @@ function wrapXhr(ctx) {
|
|
|
624
735
|
rawUrl,
|
|
625
736
|
url: redactUrl(rawUrl),
|
|
626
737
|
startT: 0,
|
|
738
|
+
perfStart: 0,
|
|
627
739
|
reqHeaders: {}
|
|
628
740
|
});
|
|
629
741
|
return origOpen.apply(this, [method, url, ...rest]);
|
|
@@ -642,14 +754,15 @@ function wrapXhr(ctx) {
|
|
|
642
754
|
const meta = META.get(this);
|
|
643
755
|
if (meta) {
|
|
644
756
|
meta.startT = ctx.clock.now();
|
|
757
|
+
meta.perfStart = perfNow();
|
|
645
758
|
if (ctx.config.captureNetworkBodies) meta.body = stringifyBody(body);
|
|
646
759
|
const finish = (error) => {
|
|
647
760
|
const endT = ctx.clock.now();
|
|
648
|
-
const
|
|
761
|
+
const base = {
|
|
649
762
|
startT: meta.startT,
|
|
650
763
|
endT,
|
|
651
764
|
duration: endT - meta.startT
|
|
652
|
-
}
|
|
765
|
+
};
|
|
653
766
|
const status = this.status;
|
|
654
767
|
const respHeaders = parseRawHeaders(ctx, this.getAllResponseHeaders());
|
|
655
768
|
let responseBody;
|
|
@@ -661,28 +774,31 @@ function wrapXhr(ctx) {
|
|
|
661
774
|
} catch {
|
|
662
775
|
}
|
|
663
776
|
}
|
|
664
|
-
const
|
|
665
|
-
id: uuid(),
|
|
666
|
-
sessionId: ctx.sessionId,
|
|
667
|
-
type: "network",
|
|
668
|
-
t: meta.startT,
|
|
669
|
-
ts: ctx.clock.wall(),
|
|
670
|
-
initiator: "xhr",
|
|
671
|
-
method: meta.method,
|
|
672
|
-
url: meta.url,
|
|
673
|
-
status,
|
|
674
|
-
ok: status >= 200 && status < 400,
|
|
675
|
-
resourceType: "xhr",
|
|
676
|
-
requestHeaders: meta.reqHeaders,
|
|
677
|
-
responseHeaders: respHeaders,
|
|
678
|
-
responseSize: safeLen(this),
|
|
679
|
-
requestBody: ctx.config.captureNetworkBodies ? bodyPreview(meta.body, void 0) : void 0,
|
|
680
|
-
responseBody,
|
|
681
|
-
timing,
|
|
682
|
-
error: error || classifyHttp(status)
|
|
683
|
-
};
|
|
684
|
-
ctx.emit(ev);
|
|
777
|
+
const responseSize = safeLen(this);
|
|
685
778
|
ctx.markActivity();
|
|
779
|
+
void tr.resolve(meta.rawUrl, meta.perfStart, base).then((timing) => {
|
|
780
|
+
const ev = {
|
|
781
|
+
id: uuid(),
|
|
782
|
+
sessionId: ctx.sessionId,
|
|
783
|
+
type: "network",
|
|
784
|
+
t: meta.startT,
|
|
785
|
+
ts: ctx.clock.wall(),
|
|
786
|
+
initiator: "xhr",
|
|
787
|
+
method: meta.method,
|
|
788
|
+
url: meta.url,
|
|
789
|
+
status,
|
|
790
|
+
ok: status >= 200 && status < 400,
|
|
791
|
+
resourceType: "xhr",
|
|
792
|
+
requestHeaders: meta.reqHeaders,
|
|
793
|
+
responseHeaders: respHeaders,
|
|
794
|
+
responseSize,
|
|
795
|
+
requestBody: ctx.config.captureNetworkBodies ? bodyPreview(meta.body, void 0) : void 0,
|
|
796
|
+
responseBody,
|
|
797
|
+
timing,
|
|
798
|
+
error: error || classifyHttp(status)
|
|
799
|
+
};
|
|
800
|
+
ctx.emit(ev);
|
|
801
|
+
});
|
|
686
802
|
};
|
|
687
803
|
this.addEventListener("loadend", () => finish());
|
|
688
804
|
this.addEventListener("error", () => finish("network"));
|