midline-agent 0.3.0 → 0.4.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.
Files changed (41) hide show
  1. package/README.md +89 -4
  2. package/browser/package.json +8 -0
  3. package/dist/browser/client.d.ts +59 -0
  4. package/dist/browser/client.js +608 -0
  5. package/dist/browser/index.d.ts +34 -0
  6. package/dist/browser/index.js +65 -0
  7. package/dist/browser/instrument.d.ts +39 -0
  8. package/dist/browser/instrument.js +217 -0
  9. package/dist/browser/transport.d.ts +43 -0
  10. package/dist/browser/transport.js +168 -0
  11. package/dist/browser/types.d.ts +94 -0
  12. package/dist/browser/types.js +2 -0
  13. package/dist/browser/version.d.ts +2 -0
  14. package/dist/browser/version.js +5 -0
  15. package/dist/browser/vitals.d.ts +16 -0
  16. package/dist/browser/vitals.js +135 -0
  17. package/dist/cli.js +0 -0
  18. package/dist/esm/browser/client.js +601 -0
  19. package/dist/esm/browser/index.js +52 -0
  20. package/dist/esm/browser/instrument.js +210 -0
  21. package/dist/esm/browser/transport.js +164 -0
  22. package/dist/esm/browser/types.js +1 -0
  23. package/dist/esm/browser/version.js +2 -0
  24. package/dist/esm/browser/vitals.js +132 -0
  25. package/dist/esm/package.json +1 -0
  26. package/dist/esm/redact.js +224 -0
  27. package/dist/esm/types.js +1 -0
  28. package/dist/redact.d.ts +3 -0
  29. package/dist/redact.js +12 -6
  30. package/package.json +27 -4
  31. package/scripts/mark-esm.js +6 -0
  32. package/src/browser/client.ts +686 -0
  33. package/src/browser/index.ts +74 -0
  34. package/src/browser/instrument.ts +275 -0
  35. package/src/browser/transport.ts +184 -0
  36. package/src/browser/types.ts +105 -0
  37. package/src/browser/version.ts +2 -0
  38. package/src/browser/vitals.ts +149 -0
  39. package/src/redact.ts +12 -6
  40. package/test/browser.test.js +328 -0
  41. package/tsconfig.esm.json +14 -0
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.observeVitals = observeVitals;
4
+ /** web.dev thresholds: at or under the first is good, over the second is poor. */
5
+ const THRESHOLDS = {
6
+ LCP: [2500, 4000],
7
+ INP: [200, 500],
8
+ CLS: [0.1, 0.25],
9
+ FCP: [1800, 3000],
10
+ TTFB: [800, 1800],
11
+ };
12
+ const rate = (name, value) => value <= THRESHOLDS[name][0] ? "good" : value <= THRESHOLDS[name][1] ? "needs-improvement" : "poor";
13
+ /**
14
+ * Core Web Vitals without a dependency, measured the way the web-vitals library
15
+ * does and reported once, when the page is first hidden (the last moment a
16
+ * report reliably gets out). Browsers that don't expose an entry type simply
17
+ * omit that metric: Safari has no LCP, INP or CLS.
18
+ */
19
+ function observeVitals(win, onReport) {
20
+ const Observer = win.PerformanceObserver;
21
+ const supported = Observer?.supportedEntryTypes ?? [];
22
+ const perf = win.performance;
23
+ const doc = win.document;
24
+ const values = {};
25
+ const observers = [];
26
+ let activationStart = 0;
27
+ let navigationType;
28
+ try {
29
+ const navigation = perf?.getEntriesByType?.("navigation")?.[0];
30
+ if (navigation) {
31
+ activationStart = navigation.activationStart ?? 0;
32
+ navigationType = navigation.type;
33
+ if (typeof navigation.responseStart === "number" && navigation.responseStart > 0) {
34
+ values.TTFB = Math.max(0, navigation.responseStart - activationStart);
35
+ }
36
+ }
37
+ }
38
+ catch {
39
+ // no navigation timing
40
+ }
41
+ const observe = (type, handle, extra = {}) => {
42
+ if (!Observer || !supported.includes(type))
43
+ return;
44
+ try {
45
+ const observer = new Observer((list) => handle(list.getEntries()));
46
+ observer.observe({ type, buffered: true, ...extra });
47
+ observers.push({ observer, handle });
48
+ }
49
+ catch {
50
+ // unsupported option in this browser
51
+ }
52
+ };
53
+ observe("paint", (entries) => {
54
+ for (const entry of entries) {
55
+ if (entry.name === "first-contentful-paint")
56
+ values.FCP = Math.max(0, entry.startTime - activationStart);
57
+ }
58
+ });
59
+ observe("largest-contentful-paint", (entries) => {
60
+ const last = entries[entries.length - 1];
61
+ if (last)
62
+ values.LCP = Math.max(0, last.startTime - activationStart);
63
+ });
64
+ // CLS is the largest session window: shifts less than 1s apart, at most 5s long.
65
+ let windowValue = 0;
66
+ let windowStart = 0;
67
+ let windowLast = 0;
68
+ observe("layout-shift", (entries) => {
69
+ for (const entry of entries) {
70
+ if (entry.hadRecentInput || typeof entry.value !== "number")
71
+ continue;
72
+ if (windowValue && entry.startTime - windowLast < 1000 && entry.startTime - windowStart < 5000) {
73
+ windowValue += entry.value;
74
+ }
75
+ else {
76
+ windowValue = entry.value;
77
+ windowStart = entry.startTime;
78
+ }
79
+ windowLast = entry.startTime;
80
+ values.CLS = Math.max(values.CLS ?? 0, windowValue);
81
+ }
82
+ });
83
+ // INP: the slowest interaction, or near the 98th percentile on busy pages.
84
+ const interactions = new Map();
85
+ const recordInteractions = (entries) => {
86
+ for (const entry of entries) {
87
+ if (!entry.interactionId)
88
+ continue;
89
+ interactions.set(entry.interactionId, Math.max(interactions.get(entry.interactionId) ?? 0, entry.duration));
90
+ }
91
+ };
92
+ observe("event", recordInteractions, { durationThreshold: 40 });
93
+ observe("first-input", recordInteractions);
94
+ let reported = false;
95
+ const report = () => {
96
+ if (reported)
97
+ return;
98
+ reported = true;
99
+ for (const { observer, handle } of observers) {
100
+ try {
101
+ handle(observer.takeRecords());
102
+ observer.disconnect();
103
+ }
104
+ catch {
105
+ // already disconnected
106
+ }
107
+ }
108
+ if (interactions.size) {
109
+ const durations = [...interactions.values()].sort((a, b) => b - a);
110
+ values.INP = durations[Math.min(durations.length - 1, Math.floor(durations.length / 50))];
111
+ }
112
+ const vitals = {};
113
+ for (const name of Object.keys(values)) {
114
+ const raw = values[name];
115
+ if (typeof raw !== "number" || !Number.isFinite(raw))
116
+ continue;
117
+ const value = name === "CLS" ? Math.round(raw * 10000) / 10000 : Math.round(raw);
118
+ vitals[name] = { value, rating: rate(name, value) };
119
+ }
120
+ if (Object.keys(vitals).length)
121
+ onReport({ vitals, navigationType });
122
+ };
123
+ const onVisibility = () => {
124
+ if (doc?.visibilityState === "hidden")
125
+ report();
126
+ };
127
+ doc?.addEventListener("visibilitychange", onVisibility, true);
128
+ win.addEventListener("pagehide", report, true);
129
+ return () => {
130
+ doc?.removeEventListener("visibilitychange", onVisibility, true);
131
+ win.removeEventListener("pagehide", report, true);
132
+ for (const { observer } of observers)
133
+ observer.disconnect();
134
+ };
135
+ }
package/dist/cli.js CHANGED
File without changes