@v-tilt/browser 1.2.0 → 1.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.
- package/dist/all-external-dependencies.js +2 -0
- package/dist/all-external-dependencies.js.map +1 -0
- package/dist/array.full.js +2 -0
- package/dist/array.full.js.map +1 -0
- package/dist/array.js +1 -1
- package/dist/array.js.map +1 -1
- package/dist/array.no-external.js +1 -1
- package/dist/array.no-external.js.map +1 -1
- package/dist/chat.js +2 -0
- package/dist/chat.js.map +1 -0
- package/dist/entrypoints/all-external-dependencies.d.ts +8 -0
- package/dist/entrypoints/array.full.d.ts +17 -0
- package/dist/entrypoints/chat.d.ts +22 -0
- package/dist/entrypoints/web-vitals.d.ts +14 -0
- package/dist/extensions/chat/chat-wrapper.d.ts +172 -0
- package/dist/extensions/chat/chat.d.ts +87 -0
- package/dist/extensions/chat/index.d.ts +10 -0
- package/dist/extensions/chat/types.d.ts +156 -0
- package/dist/external-scripts-loader.js +1 -1
- package/dist/external-scripts-loader.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/module.d.ts +312 -5
- package/dist/module.js +1 -1
- package/dist/module.js.map +1 -1
- package/dist/module.no-external.d.ts +312 -5
- package/dist/module.no-external.js +1 -1
- package/dist/module.no-external.js.map +1 -1
- package/dist/recorder.js.map +1 -1
- package/dist/types.d.ts +59 -2
- package/dist/utils/globals.d.ts +138 -1
- package/dist/vtilt.d.ts +11 -1
- package/dist/web-vitals.d.ts +89 -5
- package/dist/web-vitals.js +2 -0
- package/dist/web-vitals.js.map +1 -0
- package/lib/config.js +5 -3
- package/lib/entrypoints/all-external-dependencies.d.ts +8 -0
- package/lib/entrypoints/all-external-dependencies.js +10 -0
- package/lib/entrypoints/array.full.d.ts +17 -0
- package/lib/entrypoints/array.full.js +19 -0
- package/lib/entrypoints/chat.d.ts +22 -0
- package/lib/entrypoints/chat.js +32 -0
- package/lib/entrypoints/external-scripts-loader.js +1 -1
- package/lib/entrypoints/web-vitals.d.ts +14 -0
- package/lib/entrypoints/web-vitals.js +29 -0
- package/lib/extensions/chat/chat-wrapper.d.ts +172 -0
- package/lib/extensions/chat/chat-wrapper.js +497 -0
- package/lib/extensions/chat/chat.d.ts +87 -0
- package/lib/extensions/chat/chat.js +998 -0
- package/lib/extensions/chat/index.d.ts +10 -0
- package/lib/extensions/chat/index.js +27 -0
- package/lib/extensions/chat/types.d.ts +156 -0
- package/lib/extensions/chat/types.js +22 -0
- package/lib/types.d.ts +59 -2
- package/lib/types.js +16 -0
- package/lib/utils/globals.d.ts +138 -1
- package/lib/vtilt.d.ts +11 -1
- package/lib/vtilt.js +42 -1
- package/lib/web-vitals.d.ts +89 -5
- package/lib/web-vitals.js +354 -46
- package/package.json +66 -65
package/lib/web-vitals.d.ts
CHANGED
|
@@ -1,11 +1,95 @@
|
|
|
1
|
-
import { VTiltConfig } from "./types";
|
|
1
|
+
import { VTiltConfig, SupportedWebVitalsMetric } from "./types";
|
|
2
2
|
import { VTilt } from "./vtilt";
|
|
3
|
+
/**
|
|
4
|
+
* Web Vitals Manager
|
|
5
|
+
*
|
|
6
|
+
* Captures Core Web Vitals (LCP, CLS, FCP, INP, TTFB) and sends them
|
|
7
|
+
* as batched $web_vitals events. Follows PostHog's approach with:
|
|
8
|
+
*
|
|
9
|
+
* 1. Extension pattern - checks for callbacks registered on __VTiltExtensions__
|
|
10
|
+
* 2. Lazy loading - loads web-vitals.js on demand if not bundled
|
|
11
|
+
* 3. Configurable metrics - choose which metrics to capture
|
|
12
|
+
* 4. Smart buffering - collects metrics per page, flushes on navigation or timeout
|
|
13
|
+
* 5. Session context - includes session_id and window_id for correlation
|
|
14
|
+
*
|
|
15
|
+
* Event structure:
|
|
16
|
+
* - event: "$web_vitals"
|
|
17
|
+
* - properties:
|
|
18
|
+
* - $web_vitals_LCP_value: number
|
|
19
|
+
* - $web_vitals_LCP_event: { name, value, delta, rating, ... }
|
|
20
|
+
* - $pathname: string
|
|
21
|
+
* - $current_url: string
|
|
22
|
+
*/
|
|
3
23
|
export declare class WebVitalsManager {
|
|
4
24
|
private instance;
|
|
5
|
-
private
|
|
6
|
-
|
|
25
|
+
private buffer;
|
|
26
|
+
private flushTimer;
|
|
27
|
+
private initialized;
|
|
28
|
+
private config;
|
|
29
|
+
constructor(vtiltConfig: VTiltConfig, instance: VTilt);
|
|
7
30
|
/**
|
|
8
|
-
*
|
|
31
|
+
* Parse capture_performance config (boolean or object)
|
|
9
32
|
*/
|
|
10
|
-
private
|
|
33
|
+
private parseConfig;
|
|
34
|
+
/**
|
|
35
|
+
* Check if web vitals capture is enabled
|
|
36
|
+
*/
|
|
37
|
+
get isEnabled(): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Get the list of metrics to capture
|
|
40
|
+
*/
|
|
41
|
+
get allowedMetrics(): SupportedWebVitalsMetric[];
|
|
42
|
+
/**
|
|
43
|
+
* Get flush timeout in ms
|
|
44
|
+
*/
|
|
45
|
+
get flushTimeoutMs(): number;
|
|
46
|
+
/**
|
|
47
|
+
* Get maximum allowed metric value
|
|
48
|
+
*/
|
|
49
|
+
get maxAllowedValue(): number;
|
|
50
|
+
private createEmptyBuffer;
|
|
51
|
+
/**
|
|
52
|
+
* Check if web vitals callbacks are available
|
|
53
|
+
*/
|
|
54
|
+
private getWebVitalsCallbacks;
|
|
55
|
+
/**
|
|
56
|
+
* Start capturing if enabled and callbacks are available
|
|
57
|
+
*/
|
|
58
|
+
startIfEnabled(): void;
|
|
59
|
+
/**
|
|
60
|
+
* Lazy load web-vitals extension
|
|
61
|
+
*/
|
|
62
|
+
private loadWebVitals;
|
|
63
|
+
/**
|
|
64
|
+
* Start capturing web vitals using the provided callbacks
|
|
65
|
+
*/
|
|
66
|
+
private startCapturing;
|
|
67
|
+
/**
|
|
68
|
+
* Get current URL
|
|
69
|
+
*/
|
|
70
|
+
private getCurrentUrl;
|
|
71
|
+
/**
|
|
72
|
+
* Get current pathname
|
|
73
|
+
*/
|
|
74
|
+
private getCurrentPathname;
|
|
75
|
+
/**
|
|
76
|
+
* Add a metric to the buffer
|
|
77
|
+
*/
|
|
78
|
+
private addToBuffer;
|
|
79
|
+
/**
|
|
80
|
+
* Clean attribution data by removing large elements
|
|
81
|
+
*/
|
|
82
|
+
private cleanAttribution;
|
|
83
|
+
/**
|
|
84
|
+
* Get window ID from instance
|
|
85
|
+
*/
|
|
86
|
+
private getWindowId;
|
|
87
|
+
/**
|
|
88
|
+
* Schedule a delayed flush
|
|
89
|
+
*/
|
|
90
|
+
private scheduleFlush;
|
|
91
|
+
/**
|
|
92
|
+
* Flush buffered metrics as a single event
|
|
93
|
+
*/
|
|
94
|
+
private flush;
|
|
11
95
|
}
|
package/lib/web-vitals.js
CHANGED
|
@@ -1,72 +1,380 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WebVitalsManager = void 0;
|
|
4
|
-
const
|
|
4
|
+
const types_1 = require("./types");
|
|
5
5
|
const globals_1 = require("./utils/globals");
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Constants
|
|
8
|
+
// ============================================================================
|
|
9
|
+
/** Default timeout before flushing metrics (ms) */
|
|
10
|
+
const DEFAULT_FLUSH_TIMEOUT_MS = 5000;
|
|
11
|
+
/** Default maximum metric value (15 minutes in ms) - filters anomalies */
|
|
12
|
+
const DEFAULT_MAX_METRIC_VALUE_MS = 15 * 60 * 1000;
|
|
13
|
+
/** Minimum sensible max value (1 minute) - values below this use default */
|
|
14
|
+
const MIN_SENSIBLE_MAX_VALUE_MS = 60 * 1000;
|
|
15
|
+
const LOGGER_PREFIX = "[WebVitals]";
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// WebVitalsManager
|
|
18
|
+
// ============================================================================
|
|
19
|
+
/**
|
|
20
|
+
* Web Vitals Manager
|
|
21
|
+
*
|
|
22
|
+
* Captures Core Web Vitals (LCP, CLS, FCP, INP, TTFB) and sends them
|
|
23
|
+
* as batched $web_vitals events. Follows PostHog's approach with:
|
|
24
|
+
*
|
|
25
|
+
* 1. Extension pattern - checks for callbacks registered on __VTiltExtensions__
|
|
26
|
+
* 2. Lazy loading - loads web-vitals.js on demand if not bundled
|
|
27
|
+
* 3. Configurable metrics - choose which metrics to capture
|
|
28
|
+
* 4. Smart buffering - collects metrics per page, flushes on navigation or timeout
|
|
29
|
+
* 5. Session context - includes session_id and window_id for correlation
|
|
30
|
+
*
|
|
31
|
+
* Event structure:
|
|
32
|
+
* - event: "$web_vitals"
|
|
33
|
+
* - properties:
|
|
34
|
+
* - $web_vitals_LCP_value: number
|
|
35
|
+
* - $web_vitals_LCP_event: { name, value, delta, rating, ... }
|
|
36
|
+
* - $pathname: string
|
|
37
|
+
* - $current_url: string
|
|
38
|
+
*/
|
|
6
39
|
class WebVitalsManager {
|
|
7
|
-
constructor(
|
|
40
|
+
constructor(vtiltConfig, instance) {
|
|
41
|
+
this.initialized = false;
|
|
8
42
|
this.instance = instance;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
43
|
+
this.buffer = this.createEmptyBuffer();
|
|
44
|
+
this.config = this.parseConfig(vtiltConfig.capture_performance);
|
|
45
|
+
if (this.isEnabled && globals_1.window) {
|
|
46
|
+
this.startIfEnabled();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// ==========================================================================
|
|
50
|
+
// Configuration
|
|
51
|
+
// ==========================================================================
|
|
52
|
+
/**
|
|
53
|
+
* Parse capture_performance config (boolean or object)
|
|
54
|
+
*/
|
|
55
|
+
parseConfig(config) {
|
|
56
|
+
if (typeof config === "boolean") {
|
|
57
|
+
return { web_vitals: config };
|
|
58
|
+
}
|
|
59
|
+
if (typeof config === "object" && config !== null) {
|
|
60
|
+
return config;
|
|
61
|
+
}
|
|
62
|
+
return { web_vitals: false };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Check if web vitals capture is enabled
|
|
66
|
+
*/
|
|
67
|
+
get isEnabled() {
|
|
68
|
+
// Always disable on non-http/https protocols (file://, chrome-extension://, etc.)
|
|
69
|
+
const protocol = globals_1.location === null || globals_1.location === void 0 ? void 0 : globals_1.location.protocol;
|
|
70
|
+
if (protocol !== "http:" && protocol !== "https:") {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
return this.config.web_vitals !== false;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get the list of metrics to capture
|
|
77
|
+
*/
|
|
78
|
+
get allowedMetrics() {
|
|
79
|
+
return this.config.web_vitals_allowed_metrics || types_1.DEFAULT_WEB_VITALS_METRICS;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get flush timeout in ms
|
|
83
|
+
*/
|
|
84
|
+
get flushTimeoutMs() {
|
|
85
|
+
return this.config.web_vitals_delayed_flush_ms || DEFAULT_FLUSH_TIMEOUT_MS;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get maximum allowed metric value
|
|
89
|
+
*/
|
|
90
|
+
get maxAllowedValue() {
|
|
91
|
+
const configured = this.config.__web_vitals_max_value;
|
|
92
|
+
// If not configured, use default
|
|
93
|
+
if (configured === undefined) {
|
|
94
|
+
return DEFAULT_MAX_METRIC_VALUE_MS;
|
|
95
|
+
}
|
|
96
|
+
// 0 disables the check
|
|
97
|
+
if (configured === 0) {
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
// Values below 1 minute are too aggressive, use default instead
|
|
101
|
+
if (configured < MIN_SENSIBLE_MAX_VALUE_MS) {
|
|
102
|
+
return DEFAULT_MAX_METRIC_VALUE_MS;
|
|
103
|
+
}
|
|
104
|
+
return configured;
|
|
105
|
+
}
|
|
106
|
+
// ==========================================================================
|
|
107
|
+
// Buffer Management
|
|
108
|
+
// ==========================================================================
|
|
109
|
+
createEmptyBuffer() {
|
|
110
|
+
return {
|
|
111
|
+
url: undefined,
|
|
112
|
+
pathname: undefined,
|
|
113
|
+
metrics: [],
|
|
114
|
+
firstMetricTimestamp: undefined,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// ==========================================================================
|
|
118
|
+
// Initialization
|
|
119
|
+
// ==========================================================================
|
|
120
|
+
/**
|
|
121
|
+
* Check if web vitals callbacks are available
|
|
122
|
+
*/
|
|
123
|
+
getWebVitalsCallbacks() {
|
|
124
|
+
var _a;
|
|
125
|
+
return (_a = globals_1.assignableWindow.__VTiltExtensions__) === null || _a === void 0 ? void 0 : _a.webVitalsCallbacks;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Start capturing if enabled and callbacks are available
|
|
129
|
+
*/
|
|
130
|
+
startIfEnabled() {
|
|
131
|
+
if (!this.isEnabled || this.initialized) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const callbacks = this.getWebVitalsCallbacks();
|
|
135
|
+
if (callbacks) {
|
|
136
|
+
// Callbacks already loaded (from array.full.ts bundle)
|
|
137
|
+
this.startCapturing(callbacks);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
// Try to lazy load web-vitals
|
|
141
|
+
this.loadWebVitals();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Lazy load web-vitals extension
|
|
146
|
+
*/
|
|
147
|
+
loadWebVitals() {
|
|
148
|
+
var _a;
|
|
149
|
+
const loadExternalDependency = (_a = globals_1.assignableWindow.__VTiltExtensions__) === null || _a === void 0 ? void 0 : _a.loadExternalDependency;
|
|
150
|
+
if (!loadExternalDependency) {
|
|
151
|
+
console.warn(`${LOGGER_PREFIX} External dependency loader not available. ` +
|
|
152
|
+
`Include web-vitals.ts entrypoint or use array.full.js bundle.`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
loadExternalDependency(this.instance, "web-vitals", (error) => {
|
|
156
|
+
if (error) {
|
|
157
|
+
console.error(`${LOGGER_PREFIX} Failed to load web-vitals:`, error);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const callbacks = this.getWebVitalsCallbacks();
|
|
161
|
+
if (callbacks) {
|
|
162
|
+
this.startCapturing(callbacks);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
console.error(`${LOGGER_PREFIX} web-vitals loaded but callbacks not registered`);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Start capturing web vitals using the provided callbacks
|
|
171
|
+
*/
|
|
172
|
+
startCapturing(callbacks) {
|
|
173
|
+
if (this.initialized) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const allowed = this.allowedMetrics;
|
|
177
|
+
const addMetric = this.addToBuffer.bind(this);
|
|
178
|
+
// Register callbacks only for allowed metrics
|
|
179
|
+
// Use reportAllChanges: true for CLS to get accurate final values
|
|
180
|
+
if (allowed.includes("LCP") && callbacks.onLCP) {
|
|
181
|
+
callbacks.onLCP(addMetric);
|
|
182
|
+
}
|
|
183
|
+
if (allowed.includes("CLS") && callbacks.onCLS) {
|
|
184
|
+
// CLS updates over time, so we want all changes
|
|
185
|
+
callbacks.onCLS(addMetric, { reportAllChanges: true });
|
|
186
|
+
}
|
|
187
|
+
if (allowed.includes("FCP") && callbacks.onFCP) {
|
|
188
|
+
callbacks.onFCP(addMetric);
|
|
189
|
+
}
|
|
190
|
+
if (allowed.includes("INP") && callbacks.onINP) {
|
|
191
|
+
callbacks.onINP(addMetric);
|
|
192
|
+
}
|
|
193
|
+
if (allowed.includes("TTFB") && callbacks.onTTFB) {
|
|
194
|
+
callbacks.onTTFB(addMetric);
|
|
195
|
+
}
|
|
196
|
+
this.initialized = true;
|
|
197
|
+
}
|
|
198
|
+
// ==========================================================================
|
|
199
|
+
// Metric Collection
|
|
200
|
+
// ==========================================================================
|
|
201
|
+
/**
|
|
202
|
+
* Get current URL
|
|
203
|
+
*/
|
|
204
|
+
getCurrentUrl() {
|
|
205
|
+
var _a;
|
|
206
|
+
return (_a = globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.location) === null || _a === void 0 ? void 0 : _a.href;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Get current pathname
|
|
210
|
+
*/
|
|
211
|
+
getCurrentPathname() {
|
|
212
|
+
return globals_1.location === null || globals_1.location === void 0 ? void 0 : globals_1.location.pathname;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Add a metric to the buffer
|
|
216
|
+
*/
|
|
217
|
+
addToBuffer(metric) {
|
|
218
|
+
try {
|
|
219
|
+
// Only capture in browser environment
|
|
220
|
+
if (!globals_1.window || !globals_1.document || !globals_1.location) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
// Validate metric
|
|
224
|
+
if (!(metric === null || metric === void 0 ? void 0 : metric.name) || (metric === null || metric === void 0 ? void 0 : metric.value) === undefined) {
|
|
225
|
+
console.warn(`${LOGGER_PREFIX} Invalid metric received`, metric);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
// Filter out anomalously large values (likely errors)
|
|
229
|
+
// CLS is unitless and typically < 1, so skip this check for CLS
|
|
230
|
+
if (this.maxAllowedValue > 0 &&
|
|
231
|
+
metric.value >= this.maxAllowedValue &&
|
|
232
|
+
metric.name !== "CLS") {
|
|
233
|
+
console.warn(`${LOGGER_PREFIX} Ignoring ${metric.name} with value >= ${this.maxAllowedValue}ms`);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const currentUrl = this.getCurrentUrl();
|
|
237
|
+
const currentPathname = this.getCurrentPathname();
|
|
238
|
+
if (!currentUrl) {
|
|
239
|
+
console.warn(`${LOGGER_PREFIX} Could not determine current URL`);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// If URL changed, flush existing buffer first
|
|
243
|
+
const urlChanged = this.buffer.url && this.buffer.url !== currentUrl;
|
|
244
|
+
if (urlChanged) {
|
|
245
|
+
this.flush();
|
|
246
|
+
}
|
|
247
|
+
// Initialize buffer URL if empty
|
|
248
|
+
if (!this.buffer.url) {
|
|
249
|
+
this.buffer.url = currentUrl;
|
|
250
|
+
this.buffer.pathname = currentPathname;
|
|
251
|
+
}
|
|
252
|
+
// Set first metric timestamp
|
|
253
|
+
if (!this.buffer.firstMetricTimestamp) {
|
|
254
|
+
this.buffer.firstMetricTimestamp = Date.now();
|
|
255
|
+
}
|
|
256
|
+
// Clean attribution data - remove large interactionTargetElement
|
|
257
|
+
const cleanedAttribution = this.cleanAttribution(metric.attribution);
|
|
258
|
+
// Get session context
|
|
259
|
+
const sessionId = this.instance.getSessionId();
|
|
260
|
+
const windowId = this.getWindowId();
|
|
261
|
+
// Create buffered metric with session context
|
|
262
|
+
const bufferedMetric = {
|
|
263
|
+
...metric,
|
|
264
|
+
attribution: cleanedAttribution,
|
|
265
|
+
timestamp: Date.now(),
|
|
266
|
+
$current_url: currentUrl,
|
|
267
|
+
$session_id: sessionId,
|
|
268
|
+
$window_id: windowId,
|
|
269
|
+
};
|
|
270
|
+
// Find existing metric with same name and replace (later values are more accurate)
|
|
271
|
+
const existingIndex = this.buffer.metrics.findIndex((m) => m.name === metric.name);
|
|
272
|
+
if (existingIndex >= 0) {
|
|
273
|
+
this.buffer.metrics[existingIndex] = bufferedMetric;
|
|
16
274
|
}
|
|
17
|
-
|
|
18
|
-
|
|
275
|
+
else {
|
|
276
|
+
this.buffer.metrics.push(bufferedMetric);
|
|
19
277
|
}
|
|
278
|
+
// Schedule flush
|
|
279
|
+
this.scheduleFlush();
|
|
280
|
+
// If we have all expected metrics, flush immediately
|
|
281
|
+
if (this.buffer.metrics.length >= this.allowedMetrics.length) {
|
|
282
|
+
this.flush();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
console.error(`${LOGGER_PREFIX} Error adding metric to buffer:`, error);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Clean attribution data by removing large elements
|
|
291
|
+
*/
|
|
292
|
+
cleanAttribution(attribution) {
|
|
293
|
+
if (!attribution) {
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
296
|
+
// Remove interactionTargetElement - it can be very large
|
|
297
|
+
// and isn't useful for analytics
|
|
298
|
+
const cleaned = { ...attribution };
|
|
299
|
+
if ("interactionTargetElement" in cleaned) {
|
|
300
|
+
delete cleaned.interactionTargetElement;
|
|
301
|
+
}
|
|
302
|
+
return cleaned;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Get window ID from instance
|
|
306
|
+
*/
|
|
307
|
+
getWindowId() {
|
|
308
|
+
var _a;
|
|
309
|
+
// Access via internal session manager if available
|
|
310
|
+
try {
|
|
311
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
312
|
+
return ((_a = this.instance.sessionManager) === null || _a === void 0 ? void 0 : _a.getWindowId()) || null;
|
|
20
313
|
}
|
|
314
|
+
catch (_b) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// ==========================================================================
|
|
319
|
+
// Flushing
|
|
320
|
+
// ==========================================================================
|
|
321
|
+
/**
|
|
322
|
+
* Schedule a delayed flush
|
|
323
|
+
*/
|
|
324
|
+
scheduleFlush() {
|
|
325
|
+
// Clear existing timer
|
|
326
|
+
if (this.flushTimer) {
|
|
327
|
+
clearTimeout(this.flushTimer);
|
|
328
|
+
}
|
|
329
|
+
// Schedule new flush
|
|
330
|
+
this.flushTimer = setTimeout(() => {
|
|
331
|
+
this.flush();
|
|
332
|
+
}, this.flushTimeoutMs);
|
|
21
333
|
}
|
|
22
334
|
/**
|
|
23
|
-
*
|
|
335
|
+
* Flush buffered metrics as a single event
|
|
24
336
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
337
|
+
flush() {
|
|
338
|
+
// Clear timer
|
|
339
|
+
if (this.flushTimer) {
|
|
340
|
+
clearTimeout(this.flushTimer);
|
|
341
|
+
this.flushTimer = undefined;
|
|
342
|
+
}
|
|
343
|
+
// Check if we have any metrics to flush
|
|
344
|
+
if (this.buffer.metrics.length === 0) {
|
|
27
345
|
return;
|
|
28
346
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
347
|
+
try {
|
|
348
|
+
// Build event properties following PostHog's format
|
|
349
|
+
const properties = {
|
|
350
|
+
$pathname: this.buffer.pathname,
|
|
351
|
+
$current_url: this.buffer.url,
|
|
352
|
+
};
|
|
353
|
+
// Add each metric's value and full event object
|
|
354
|
+
for (const metric of this.buffer.metrics) {
|
|
355
|
+
// Add the numeric value (for easy percentile calculations)
|
|
356
|
+
properties[`$web_vitals_${metric.name}_value`] = metric.value;
|
|
357
|
+
// Add the full metric object (includes rating, delta, attribution, etc.)
|
|
358
|
+
properties[`$web_vitals_${metric.name}_event`] = {
|
|
37
359
|
name: metric.name,
|
|
38
360
|
value: metric.value,
|
|
39
361
|
delta: metric.delta,
|
|
40
362
|
rating: metric.rating,
|
|
41
363
|
id: metric.id,
|
|
42
364
|
navigationType: metric.navigationType,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
location: country,
|
|
48
|
-
referrer: globals_1.document.referrer,
|
|
49
|
-
});
|
|
365
|
+
attribution: metric.attribution,
|
|
366
|
+
$session_id: metric.$session_id,
|
|
367
|
+
$window_id: metric.$window_id,
|
|
368
|
+
};
|
|
50
369
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
if (this.webVitals.onCLS) {
|
|
56
|
-
this.webVitals.onCLS(sendMetric);
|
|
57
|
-
}
|
|
58
|
-
if (this.webVitals.onFCP) {
|
|
59
|
-
this.webVitals.onFCP(sendMetric);
|
|
60
|
-
}
|
|
61
|
-
if (this.webVitals.onLCP) {
|
|
62
|
-
this.webVitals.onLCP(sendMetric);
|
|
63
|
-
}
|
|
64
|
-
if (this.webVitals.onTTFB) {
|
|
65
|
-
this.webVitals.onTTFB(sendMetric);
|
|
370
|
+
// Send the event
|
|
371
|
+
this.instance.capture("$web_vitals", properties);
|
|
66
372
|
}
|
|
67
|
-
|
|
68
|
-
|
|
373
|
+
catch (error) {
|
|
374
|
+
console.error(`${LOGGER_PREFIX} Error flushing metrics:`, error);
|
|
69
375
|
}
|
|
376
|
+
// Reset buffer
|
|
377
|
+
this.buffer = this.createEmptyBuffer();
|
|
70
378
|
}
|
|
71
379
|
}
|
|
72
380
|
exports.WebVitalsManager = WebVitalsManager;
|
package/package.json
CHANGED
|
@@ -1,65 +1,66 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@v-tilt/browser",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "vTilt browser tracking library",
|
|
5
|
-
"main": "dist/main.js",
|
|
6
|
-
"module": "dist/module.js",
|
|
7
|
-
"types": "dist/module.d.ts",
|
|
8
|
-
"files": [
|
|
9
|
-
"lib/*",
|
|
10
|
-
"dist/*"
|
|
11
|
-
],
|
|
12
|
-
"publishConfig": {
|
|
13
|
-
"access": "public"
|
|
14
|
-
},
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "tsc -b && rollup -c",
|
|
17
|
-
"dev": "rollup -c -w",
|
|
18
|
-
"type-check": "tsc --noEmit",
|
|
19
|
-
"lint": "eslint src --ext .ts",
|
|
20
|
-
"lint:fix": "eslint src --ext .ts --fix",
|
|
21
|
-
"clean": "rimraf lib dist",
|
|
22
|
-
"prepublishOnly": "pnpm run build"
|
|
23
|
-
},
|
|
24
|
-
"keywords": [
|
|
25
|
-
"analytics",
|
|
26
|
-
"tracking",
|
|
27
|
-
"web-vitals",
|
|
28
|
-
"performance"
|
|
29
|
-
],
|
|
30
|
-
"author": "vTilt",
|
|
31
|
-
"license": "MIT",
|
|
32
|
-
"devDependencies": {
|
|
33
|
-
"@babel/preset-env": "^7.28.3",
|
|
34
|
-
"@rollup/plugin-babel": "^6.0.4",
|
|
35
|
-
"@rollup/plugin-commonjs": "^25.0.8",
|
|
36
|
-
"@rollup/plugin-json": "^6.1.0",
|
|
37
|
-
"@rollup/plugin-node-resolve": "^15.3.1",
|
|
38
|
-
"@rollup/plugin-terser": "^0.4.4",
|
|
39
|
-
"@rollup/plugin-typescript": "^11.1.6",
|
|
40
|
-
"@types/node": "^20.10.5",
|
|
41
|
-
"@v-tilt/eslint-config": "workspace:*",
|
|
42
|
-
"eslint": "^9.0.0",
|
|
43
|
-
"rimraf": "^5.0.5",
|
|
44
|
-
"rollup": "^4.9.1",
|
|
45
|
-
"rollup-plugin-dts": "^6.2.3",
|
|
46
|
-
"rollup-plugin-terser": "^7.0.2",
|
|
47
|
-
"rollup-plugin-visualizer": "^6.0.3",
|
|
48
|
-
"typescript": "^5.3.3"
|
|
49
|
-
},
|
|
50
|
-
"dependencies": {
|
|
51
|
-
"@rrweb/record": "2.0.0-alpha.17",
|
|
52
|
-
"@rrweb/rrweb-plugin-console-record": "2.0.0-alpha.17",
|
|
53
|
-
"@rrweb/types": "2.0.0-alpha.17",
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@v-tilt/browser",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "vTilt browser tracking library",
|
|
5
|
+
"main": "dist/main.js",
|
|
6
|
+
"module": "dist/module.js",
|
|
7
|
+
"types": "dist/module.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"lib/*",
|
|
10
|
+
"dist/*"
|
|
11
|
+
],
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -b && rollup -c",
|
|
17
|
+
"dev": "rollup -c -w",
|
|
18
|
+
"type-check": "tsc --noEmit",
|
|
19
|
+
"lint": "eslint src --ext .ts",
|
|
20
|
+
"lint:fix": "eslint src --ext .ts --fix",
|
|
21
|
+
"clean": "rimraf lib dist",
|
|
22
|
+
"prepublishOnly": "pnpm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"analytics",
|
|
26
|
+
"tracking",
|
|
27
|
+
"web-vitals",
|
|
28
|
+
"performance"
|
|
29
|
+
],
|
|
30
|
+
"author": "vTilt",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@babel/preset-env": "^7.28.3",
|
|
34
|
+
"@rollup/plugin-babel": "^6.0.4",
|
|
35
|
+
"@rollup/plugin-commonjs": "^25.0.8",
|
|
36
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
37
|
+
"@rollup/plugin-node-resolve": "^15.3.1",
|
|
38
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
39
|
+
"@rollup/plugin-typescript": "^11.1.6",
|
|
40
|
+
"@types/node": "^20.10.5",
|
|
41
|
+
"@v-tilt/eslint-config": "workspace:*",
|
|
42
|
+
"eslint": "^9.0.0",
|
|
43
|
+
"rimraf": "^5.0.5",
|
|
44
|
+
"rollup": "^4.9.1",
|
|
45
|
+
"rollup-plugin-dts": "^6.2.3",
|
|
46
|
+
"rollup-plugin-terser": "^7.0.2",
|
|
47
|
+
"rollup-plugin-visualizer": "^6.0.3",
|
|
48
|
+
"typescript": "^5.3.3"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@rrweb/record": "2.0.0-alpha.17",
|
|
52
|
+
"@rrweb/rrweb-plugin-console-record": "2.0.0-alpha.17",
|
|
53
|
+
"@rrweb/types": "2.0.0-alpha.17",
|
|
54
|
+
"ably": "^2.4.0",
|
|
55
|
+
"fflate": "^0.8.2",
|
|
56
|
+
"web-vitals": "^3.5.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"web-vitals": "^3.0.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"web-vitals": {
|
|
63
|
+
"optional": true
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|