@v-tilt/browser 1.1.0 → 1.1.1

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/lib/vtilt.js CHANGED
@@ -15,16 +15,28 @@ const rate_limiter_1 = require("./rate-limiter");
15
15
  const utils_1 = require("./utils");
16
16
  const event_utils_1 = require("./utils/event-utils");
17
17
  const globals_1 = require("./utils/globals");
18
+ /*
19
+ STYLE GUIDE:
20
+
21
+ Naming conventions:
22
+ - snake_case: Config options and public API methods (e.g., capture_pageview, get_distinct_id)
23
+ - camelCase: Internal variables and class properties (e.g., sessionManager, requestQueue)
24
+ - _snake_case: Private methods that can be mangled/minified (e.g., _init, _dom_loaded)
25
+ - __snake_case: Internal but not minified, signals internal use (e.g., __loaded, __request_queue)
26
+ - UPPER_CASE: Constants and globals (e.g., PRIMARY_INSTANCE_NAME, ENQUEUE_REQUESTS)
27
+
28
+ Use TypeScript accessibility modifiers (private/protected) for non-public members.
29
+ */
18
30
  // Helper to check if value is an array
19
31
  const isArray = Array.isArray;
20
32
  class VTilt {
21
33
  constructor(config = {}) {
22
34
  this.__loaded = false; // Matches snippet's window.vt.__loaded check
23
- this._initialPageviewCaptured = false;
24
- this._visibilityStateListener = null;
35
+ this._initial_pageview_captured = false;
36
+ this._visibility_state_listener = null;
25
37
  this.__request_queue = []; // Legacy queue for DOM loaded handler (pre-init)
26
- this._hasWarnedAboutConfig = false; // Track if we've already warned about missing config
27
- this._setOncePropertiesSent = false; // Track if $set_once with initial props has been sent (only send once per page load)
38
+ this._has_warned_about_config = false; // Track if we've already warned about missing config
39
+ this._set_once_properties_sent = false; // Track if $set_once with initial props has been sent (only send once per page load)
28
40
  this.configManager = new config_1.ConfigManager(config);
29
41
  const fullConfig = this.configManager.getConfig();
30
42
  // Auto-detect domain from location if not provided
@@ -42,7 +54,7 @@ class VTilt {
42
54
  eventsBurstLimit: 100,
43
55
  captureWarning: (message) => {
44
56
  // Send rate limit warning event (bypasses rate limiting)
45
- this._captureInternal(rate_limiter_1.RATE_LIMIT_WARNING_EVENT, {
57
+ this._capture_internal(rate_limiter_1.RATE_LIMIT_WARNING_EVENT, {
46
58
  $$client_ingestion_warning_message: message,
47
59
  });
48
60
  },
@@ -50,12 +62,12 @@ class VTilt {
50
62
  // Initialize retry queue for failed requests
51
63
  // Retries with exponential backoff: 3s, 6s, 12s... up to 30 minutes
52
64
  this.retryQueue = new retry_queue_1.RetryQueue({
53
- sendRequest: (req) => this._sendHttpRequest(req),
54
- sendBeacon: (req) => this._sendBeaconRequest(req),
65
+ sendRequest: (req) => this._send_http_request(req),
66
+ sendBeacon: (req) => this._send_beacon_request(req),
55
67
  });
56
68
  // Initialize request queue for event batching
57
69
  // Events are batched and sent every 3 seconds (configurable)
58
- this.requestQueue = new request_queue_1.RequestQueue((req) => this._sendBatchedRequest(req), { flush_interval_ms: 3000 });
70
+ this.requestQueue = new request_queue_1.RequestQueue((req) => this._send_batched_request(req), { flush_interval_ms: 3000 });
59
71
  }
60
72
  /**
61
73
  * Initializes a new instance of the VTilt tracking object.
@@ -126,16 +138,32 @@ class VTilt {
126
138
  this.historyAutocapture = new history_autocapture_1.HistoryAutocapture(this);
127
139
  this.historyAutocapture.startIfEnabled();
128
140
  // Set up page unload handler to flush queued events
129
- this._setupUnloadHandler();
141
+ this._setup_unload_handler();
142
+ // Enable the request queue for batched sending (PostHog pattern)
143
+ this._start_queue_if_opted_in();
130
144
  // Capture initial pageview (with visibility check)
131
- this._captureInitialPageview();
145
+ // Only if capture_pageview is enabled (default: true)
146
+ if (fullConfig.capture_pageview !== false) {
147
+ this._capture_initial_pageview();
148
+ }
132
149
  return this;
133
150
  }
151
+ /**
152
+ * Start the request queue if user hasn't opted out
153
+ * Following PostHog's pattern - called from both _init() and _dom_loaded()
154
+ * Safe to call multiple times as enable() is idempotent
155
+ */
156
+ _start_queue_if_opted_in() {
157
+ // TODO: Add opt-out check when consent management is implemented
158
+ // if (!this.is_capturing()) return;
159
+ // Enable batched sending
160
+ this.requestQueue.enable();
161
+ }
134
162
  /**
135
163
  * Set up handler to flush event queue on page unload
136
164
  * Uses both beforeunload and pagehide for maximum compatibility
137
165
  */
138
- _setupUnloadHandler() {
166
+ _setup_unload_handler() {
139
167
  if (!globals_1.window) {
140
168
  return;
141
169
  }
@@ -183,16 +211,16 @@ class VTilt {
183
211
  * Returns true if projectId and token are present, false otherwise
184
212
  * Logs a warning only once per instance if not configured
185
213
  */
186
- _isConfigured() {
214
+ _is_configured() {
187
215
  const config = this.configManager.getConfig();
188
216
  if (config.projectId && config.token) {
189
217
  return true;
190
218
  }
191
219
  // Only warn once to avoid console spam
192
- if (!this._hasWarnedAboutConfig) {
220
+ if (!this._has_warned_about_config) {
193
221
  console.warn("VTilt: projectId and token are required for tracking. " +
194
222
  "Events will be skipped until init() or updateConfig() is called with these fields.");
195
- this._hasWarnedAboutConfig = true;
223
+ this._has_warned_about_config = true;
196
224
  }
197
225
  return false;
198
226
  }
@@ -201,8 +229,8 @@ class VTilt {
201
229
  */
202
230
  buildUrl() {
203
231
  const config = this.configManager.getConfig();
204
- const { proxyUrl, proxy, host, token } = config;
205
- // Use proxy endpoint to handle Tinybird authentication
232
+ const { proxyUrl, proxy, api_host, token } = config;
233
+ // Use proxy endpoint to handle backend authentication
206
234
  if (proxyUrl) {
207
235
  // Use the full proxy URL as provided
208
236
  return proxyUrl;
@@ -211,8 +239,8 @@ class VTilt {
211
239
  // Construct the proxy URL from the proxy domain with token
212
240
  return `${proxy}/api/tracking?token=${token}`;
213
241
  }
214
- else if (host) {
215
- const cleanHost = host.replace(/\/+$/gm, "");
242
+ else if (api_host) {
243
+ const cleanHost = api_host.replace(/\/+$/gm, "");
216
244
  return `${cleanHost}/api/tracking?token=${token}`;
217
245
  }
218
246
  else {
@@ -228,7 +256,7 @@ class VTilt {
228
256
  sendRequest(url, event, shouldEnqueue) {
229
257
  // Validate configuration (only warns once per instance)
230
258
  // This is the single place where validation happens for all sending methods
231
- if (!this._isConfigured()) {
259
+ if (!this._is_configured()) {
232
260
  return;
233
261
  }
234
262
  // Check if we should queue requests (for older browsers before DOM is loaded)
@@ -248,11 +276,11 @@ class VTilt {
248
276
  * Called by RequestQueue when flushing
249
277
  * Uses RetryQueue for automatic retry on failure
250
278
  */
251
- _sendBatchedRequest(req) {
279
+ _send_batched_request(req) {
252
280
  const { transport } = req;
253
281
  // Use sendBeacon for reliable delivery on page unload
254
282
  if (transport === "sendBeacon") {
255
- this._sendBeaconRequest(req);
283
+ this._send_beacon_request(req);
256
284
  return;
257
285
  }
258
286
  // Use retry queue for normal requests (handles failures automatically)
@@ -263,7 +291,7 @@ class VTilt {
263
291
  * Uses GZip compression for payloads > 1KB
264
292
  * Used by RetryQueue for retryable requests
265
293
  */
266
- _sendHttpRequest(req) {
294
+ _send_http_request(req) {
267
295
  return new Promise((resolve) => {
268
296
  const { url, events } = req;
269
297
  // Prepare payload (single event or batch)
@@ -288,7 +316,7 @@ class VTilt {
288
316
  * Send request using sendBeacon for reliable delivery on page unload
289
317
  * Uses GZip compression for payloads > 1KB
290
318
  */
291
- _sendBeaconRequest(req) {
319
+ _send_beacon_request(req) {
292
320
  const { url, events } = req;
293
321
  // Prepare payload (single event or batch)
294
322
  const payload = events.length === 1 ? events[0] : { events };
@@ -360,7 +388,7 @@ class VTilt {
360
388
  // and cause unnecessary server processing for identity creation/updates.
361
389
  const setOnce = {};
362
390
  // Only include initial props if we haven't sent them yet this page load
363
- if (!this._setOncePropertiesSent && Object.keys(initialProps).length > 0) {
391
+ if (!this._set_once_properties_sent && Object.keys(initialProps).length > 0) {
364
392
  Object.assign(setOnce, initialProps);
365
393
  }
366
394
  // Always merge with user-provided $set_once if present
@@ -382,8 +410,8 @@ class VTilt {
382
410
  ...payload, // User-provided payload (can override base and person properties)
383
411
  };
384
412
  // Mark that $set_once with initial props has been sent (only do this once per page load)
385
- if (!this._setOncePropertiesSent && Object.keys(initialProps).length > 0) {
386
- this._setOncePropertiesSent = true;
413
+ if (!this._set_once_properties_sent && Object.keys(initialProps).length > 0) {
414
+ this._set_once_properties_sent = true;
387
415
  }
388
416
  // Add title only to $pageview events
389
417
  if (name === "$pageview" && globals_1.document) {
@@ -436,7 +464,7 @@ class VTilt {
436
464
  * Internal capture method that bypasses rate limiting
437
465
  * Used for system events like rate limit warnings
438
466
  */
439
- _captureInternal(name, payload) {
467
+ _capture_internal(name, payload) {
440
468
  this.capture(name, payload, { skip_client_rate_limiting: true });
441
469
  }
442
470
  /**
@@ -506,7 +534,7 @@ class VTilt {
506
534
  // New distinct_id and properties go in payload
507
535
  this.capture("$identify", {
508
536
  distinct_id: newDistinctId, // New distinct_id in payload
509
- $anon_distinct_id: previousDistinctId || anonymousId, // Previous ID in payload
537
+ $anon_distinct_id: anonymousId, // Previous ID in payload
510
538
  $set: userPropertiesToSet || {},
511
539
  $set_once: userPropertiesToSetOnce || {},
512
540
  });
@@ -641,8 +669,9 @@ class VTilt {
641
669
  }
642
670
  /**
643
671
  * Capture initial pageview with visibility check
672
+ * Note: The capture_pageview config check happens at the call site (in _init)
644
673
  */
645
- _captureInitialPageview() {
674
+ _capture_initial_pageview() {
646
675
  if (!globals_1.document) {
647
676
  return;
648
677
  }
@@ -651,17 +680,17 @@ class VTilt {
651
680
  // This is useful to avoid `prerender` calls from Chrome/Wordpress/SPAs
652
681
  // that are not visible to the user
653
682
  if (globals_1.document.visibilityState !== "visible") {
654
- if (!this._visibilityStateListener) {
655
- this._visibilityStateListener = () => {
656
- this._captureInitialPageview();
683
+ if (!this._visibility_state_listener) {
684
+ this._visibility_state_listener = () => {
685
+ this._capture_initial_pageview();
657
686
  };
658
- (0, utils_1.addEventListener)(globals_1.document, "visibilitychange", this._visibilityStateListener);
687
+ (0, utils_1.addEventListener)(globals_1.document, "visibilitychange", this._visibility_state_listener);
659
688
  }
660
689
  return;
661
690
  }
662
691
  // Extra check here to guarantee we only ever trigger a single initial pageview event
663
- if (!this._initialPageviewCaptured) {
664
- this._initialPageviewCaptured = true;
692
+ if (!this._initial_pageview_captured) {
693
+ this._initial_pageview_captured = true;
665
694
  // Wait a bit for SPA routers
666
695
  setTimeout(() => {
667
696
  // Double-check we're still in browser environment (defensive check)
@@ -676,9 +705,9 @@ class VTilt {
676
705
  this.capture("$pageview", payload);
677
706
  }, 300);
678
707
  // After we've captured the initial pageview, we can remove the listener
679
- if (this._visibilityStateListener) {
680
- globals_1.document.removeEventListener("visibilitychange", this._visibilityStateListener);
681
- this._visibilityStateListener = null;
708
+ if (this._visibility_state_listener) {
709
+ globals_1.document.removeEventListener("visibilitychange", this._visibility_state_listener);
710
+ this._visibility_state_listener = null;
682
711
  }
683
712
  }
684
713
  }
@@ -741,16 +770,17 @@ class VTilt {
741
770
  }
742
771
  /**
743
772
  * Called when DOM is loaded - processes queued requests and enables batching
773
+ * Following PostHog's pattern in _dom_loaded()
744
774
  */
745
775
  _dom_loaded() {
746
- // Enable the request queue for batched sending
747
- this.requestQueue.enable();
748
776
  // Process all pre-init queued requests
749
777
  this.__request_queue.forEach((item) => {
750
778
  this._send_retriable_request(item);
751
779
  });
752
780
  // Clear the legacy queue
753
781
  this.__request_queue = [];
782
+ // Enable the request queue for batched sending (PostHog pattern)
783
+ this._start_queue_if_opted_in();
754
784
  }
755
785
  }
756
786
  exports.VTilt = VTilt;
@@ -765,7 +795,8 @@ let ENQUEUE_REQUESTS = !SUPPORTS_REQUEST &&
765
795
  (globals_1.userAgent === null || globals_1.userAgent === void 0 ? void 0 : globals_1.userAgent.indexOf("MSIE")) === -1 &&
766
796
  (globals_1.userAgent === null || globals_1.userAgent === void 0 ? void 0 : globals_1.userAgent.indexOf("Mozilla")) === -1;
767
797
  // Expose ENQUEUE_REQUESTS to window for request queuing
768
- if (globals_1.window) {
798
+ // Use typeof check to safely access global window object
799
+ if (typeof globals_1.window !== "undefined" && globals_1.window !== null) {
769
800
  globals_1.window.__VTILT_ENQUEUE_REQUESTS = ENQUEUE_REQUESTS;
770
801
  }
771
802
  /**
@@ -781,7 +812,7 @@ const add_dom_loaded_handler = function () {
781
812
  dom_loaded_handler.done = true;
782
813
  // Disable request queuing now that DOM is loaded
783
814
  ENQUEUE_REQUESTS = false;
784
- if (typeof globals_1.window !== "undefined") {
815
+ if (typeof globals_1.window !== "undefined" && globals_1.window !== null) {
785
816
  globals_1.window.__VTILT_ENQUEUE_REQUESTS = false;
786
817
  }
787
818
  // Process queued requests for all instances
package/lib/web-vitals.js CHANGED
@@ -7,7 +7,7 @@ class WebVitalsManager {
7
7
  constructor(config, instance) {
8
8
  this.instance = instance;
9
9
  // Load web-vitals if enabled and in browser environment (not SSR)
10
- if (config.webVitals && globals_1.window) {
10
+ if (config.capture_performance && globals_1.window) {
11
11
  try {
12
12
  // Dynamically require web-vitals, ignore type error since it's a runtime import
13
13
  // eslint-disable-next-line
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v-tilt/browser",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "vTilt browser tracking library",
5
5
  "main": "dist/main.js",
6
6
  "module": "dist/module.js",
@@ -12,6 +12,15 @@
12
12
  "publishConfig": {
13
13
  "access": "public"
14
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
+ },
15
24
  "keywords": [
16
25
  "analytics",
17
26
  "tracking",
@@ -29,14 +38,14 @@
29
38
  "@rollup/plugin-terser": "^0.4.4",
30
39
  "@rollup/plugin-typescript": "^11.1.6",
31
40
  "@types/node": "^20.10.5",
41
+ "@v-tilt/eslint-config": "workspace:*",
32
42
  "eslint": "^9.0.0",
33
43
  "rimraf": "^5.0.5",
34
44
  "rollup": "^4.9.1",
35
45
  "rollup-plugin-dts": "^6.2.3",
36
46
  "rollup-plugin-terser": "^7.0.2",
37
47
  "rollup-plugin-visualizer": "^6.0.3",
38
- "typescript": "^5.3.3",
39
- "@v-tilt/eslint-config": "1.0.0"
48
+ "typescript": "^5.3.3"
40
49
  },
41
50
  "dependencies": {
42
51
  "fflate": "^0.8.2",
@@ -49,13 +58,5 @@
49
58
  "web-vitals": {
50
59
  "optional": true
51
60
  }
52
- },
53
- "scripts": {
54
- "build": "tsc -b && rollup -c",
55
- "dev": "rollup -c -w",
56
- "type-check": "tsc --noEmit",
57
- "lint": "eslint src --ext .ts",
58
- "lint:fix": "eslint src --ext .ts --fix",
59
- "clean": "rimraf lib dist"
60
61
  }
61
- }
62
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2022 Tinybird.co
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.