@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/request.js CHANGED
@@ -72,13 +72,18 @@ const encodePostData = (options) => {
72
72
  * Send request using XMLHttpRequest
73
73
  */
74
74
  const xhrRequest = (options) => {
75
- const req = new XMLHttpRequest();
76
- req.open(options.method || "POST", options.url, true);
77
75
  const encoded = encodePostData(options);
78
76
  if (!encoded) {
79
77
  return;
80
78
  }
81
79
  const { contentType, body } = encoded;
80
+ // Add compression query param for server to detect (consistent with sendBeacon)
81
+ const url = options.compression === Compression.GZipJS &&
82
+ contentType === CONTENT_TYPE_PLAIN
83
+ ? `${options.url}${options.url.includes("?") ? "&" : "?"}compression=gzip-js`
84
+ : options.url;
85
+ const req = new XMLHttpRequest();
86
+ req.open(options.method || "POST", url, true);
82
87
  // Set headers
83
88
  if (options.headers) {
84
89
  Object.entries(options.headers).forEach(([key, value]) => {
@@ -86,11 +91,6 @@ const xhrRequest = (options) => {
86
91
  });
87
92
  }
88
93
  req.setRequestHeader("Content-Type", contentType);
89
- // Add compression indicator for gzip
90
- if (contentType === CONTENT_TYPE_PLAIN &&
91
- options.compression === Compression.GZipJS) {
92
- req.setRequestHeader("Content-Encoding", "gzip");
93
- }
94
94
  if (options.timeout) {
95
95
  req.timeout = options.timeout;
96
96
  }
@@ -127,20 +127,20 @@ const fetchRequest = (options) => {
127
127
  return;
128
128
  }
129
129
  const { contentType, body, estimatedSize } = encoded;
130
+ // Add compression query param for server to detect (consistent with sendBeacon)
131
+ const url = options.compression === Compression.GZipJS &&
132
+ contentType === CONTENT_TYPE_PLAIN
133
+ ? `${options.url}${options.url.includes("?") ? "&" : "?"}compression=gzip-js`
134
+ : options.url;
130
135
  const headers = {
131
136
  "Content-Type": contentType,
132
137
  ...(options.headers || {}),
133
138
  };
134
- // Add compression indicator for gzip
135
- if (contentType === CONTENT_TYPE_PLAIN &&
136
- options.compression === Compression.GZipJS) {
137
- headers["Content-Encoding"] = "gzip";
138
- }
139
139
  const controller = new AbortController();
140
140
  const timeoutId = options.timeout
141
141
  ? setTimeout(() => controller.abort(), options.timeout)
142
142
  : null;
143
- fetch(options.url, {
143
+ fetch(url, {
144
144
  method: options.method || "POST",
145
145
  headers,
146
146
  body,
package/lib/session.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { StorageMethod } from "./types";
1
+ import { PersistenceMethod } from "./types";
2
2
  export declare class SessionManager {
3
3
  private storageMethod;
4
4
  private domain?;
5
5
  private _windowId;
6
- constructor(storageMethod?: StorageMethod, domain?: string);
6
+ constructor(storageMethod?: PersistenceMethod, domain?: string);
7
7
  /**
8
8
  * Check if using web storage (localStorage or sessionStorage)
9
9
  */
package/lib/session.js CHANGED
@@ -16,8 +16,8 @@ class SessionManager {
16
16
  * Check if using web storage (localStorage or sessionStorage)
17
17
  */
18
18
  _isWebStorage() {
19
- return (this.storageMethod === constants_1.STORAGE_METHODS.localStorage ||
20
- this.storageMethod === constants_1.STORAGE_METHODS.sessionStorage);
19
+ return (this.storageMethod === constants_1.PERSISTENCE_METHODS.localStorage ||
20
+ this.storageMethod === constants_1.PERSISTENCE_METHODS.sessionStorage);
21
21
  }
22
22
  /**
23
23
  * Get storage object (localStorage or sessionStorage)
@@ -26,7 +26,7 @@ class SessionManager {
26
26
  if (!this._isWebStorage()) {
27
27
  return null;
28
28
  }
29
- return this.storageMethod === constants_1.STORAGE_METHODS.localStorage
29
+ return this.storageMethod === constants_1.PERSISTENCE_METHODS.localStorage
30
30
  ? localStorage
31
31
  : sessionStorage;
32
32
  }
package/lib/types.d.ts CHANGED
@@ -1,69 +1,185 @@
1
+ /**
2
+ * VTilt Types
3
+ *
4
+ * Type definitions for the VTilt tracking SDK.
5
+ * Following PostHog's patterns where applicable.
6
+ */
7
+ import type { PersonProfilesMode } from './constants';
1
8
  export interface VTiltConfig {
2
- projectId: string;
9
+ /** Project identifier (required) */
3
10
  token: string;
4
- name?: string;
5
- host?: string;
6
- scriptHost?: string;
11
+ /** API host for tracking (default: https://api.vtilt.io) */
12
+ api_host?: string;
13
+ /** UI host for dashboard links */
14
+ ui_host?: string | null;
15
+ /** Proxy domain for tracking requests */
7
16
  proxy?: string;
17
+ /** Full proxy URL for tracking requests */
8
18
  proxyUrl?: string;
19
+ /** Instance name (for multiple instances) */
20
+ name?: string;
21
+ /** Project ID (set via init() first argument) */
22
+ projectId?: string;
23
+ /** Domain to track (auto-detected if not provided) */
9
24
  domain?: string;
10
- storage?: "cookie" | "localStorage" | "sessionStorage";
25
+ /** Storage method for session data */
26
+ storage?: PersistenceMethod;
27
+ /** Persistence method for user data */
28
+ persistence?: PersistenceMethod;
29
+ /** Persistence name prefix */
30
+ persistence_name?: string;
31
+ /** Enable cross-subdomain cookies */
32
+ cross_subdomain_cookie?: boolean;
33
+ /**
34
+ * Person profiles mode:
35
+ * - 'always': Always create person profiles (default)
36
+ * - 'identified_only': Only create when user is identified
37
+ * - 'never': Never create person profiles
38
+ */
39
+ person_profiles?: PersonProfilesMode;
40
+ /** Enable autocapture */
41
+ autocapture?: boolean;
42
+ /** Enable web vitals tracking */
43
+ capture_performance?: boolean;
44
+ /** Enable page view tracking */
45
+ capture_pageview?: boolean | 'auto';
46
+ /** Enable page leave tracking */
47
+ capture_pageleave?: boolean | 'if_capture_pageview';
48
+ /** Disable compression */
49
+ disable_compression?: boolean;
50
+ /** Whether to stringify payload before sending */
11
51
  stringifyPayload?: boolean;
12
- webVitals?: boolean;
52
+ /** Properties to exclude from events */
53
+ property_denylist?: string[];
54
+ /** Mask text in autocapture */
55
+ mask_all_text?: boolean;
56
+ /** Mask all element attributes */
57
+ mask_all_element_attributes?: boolean;
58
+ /** Respect Do Not Track browser setting */
59
+ respect_dnt?: boolean;
60
+ /** Opt users out by default */
61
+ opt_out_capturing_by_default?: boolean;
62
+ /** Global attributes added to all events */
13
63
  globalAttributes?: Record<string, string>;
14
- persistence?: "localStorage" | "cookie";
15
- crossSubdomainCookie?: boolean;
16
- disable_compression?: boolean;
17
- }
18
- export interface SessionData {
19
- value: string;
20
- expiry: number;
21
- }
22
- export interface WebVitalMetric {
23
- name: string;
24
- value: number;
25
- delta: number;
26
- rating: string;
27
- id: string;
28
- navigationType: string;
29
- }
30
- export interface GeolocationData {
31
- country?: string;
32
- locale?: string;
64
+ /** Bootstrap data for initialization */
65
+ bootstrap?: {
66
+ distinctID?: string;
67
+ isIdentifiedID?: boolean;
68
+ featureFlags?: Record<string, boolean | string>;
69
+ };
70
+ /** Before send hook for modifying events */
71
+ before_send?: (event: CaptureResult) => CaptureResult | null;
72
+ /** Loaded callback */
73
+ loaded?: (vtilt: any) => void;
33
74
  }
34
75
  export interface EventPayload {
35
76
  [key: string]: any;
36
77
  }
78
+ export interface CaptureResult {
79
+ uuid: string;
80
+ event: string;
81
+ properties: Properties;
82
+ $set?: Properties;
83
+ $set_once?: Properties;
84
+ timestamp?: string;
85
+ }
86
+ export interface CaptureOptions {
87
+ /** Override timestamp */
88
+ timestamp?: Date;
89
+ /** Properties to $set on person */
90
+ $set?: Properties;
91
+ /** Properties to $set_once on person */
92
+ $set_once?: Properties;
93
+ /** Send immediately (skip batching) */
94
+ send_instantly?: boolean;
95
+ }
37
96
  export interface TrackingEvent {
38
97
  timestamp: string;
39
98
  event: string;
40
99
  project_id: string;
41
100
  domain: string;
42
- payload: EventPayload;
43
- distinct_id?: string;
101
+ distinct_id: string;
44
102
  anonymous_id?: string;
103
+ payload: EventPayload;
104
+ }
105
+ export type Property = string | number | boolean | null | undefined | Date | any[] | Record<string, any>;
106
+ export interface Properties {
107
+ [key: string]: Property;
108
+ }
109
+ export interface PropertyOperations {
110
+ $set?: Properties;
111
+ $set_once?: Properties;
112
+ $unset?: string[];
45
113
  }
46
- export type StorageMethod = "cookie" | "localStorage" | "sessionStorage";
47
- export interface StorageMethods {
48
- cookie: "cookie";
49
- localStorage: "localStorage";
50
- sessionStorage: "sessionStorage";
114
+ export interface SessionData {
115
+ value: string;
116
+ expiry: number;
51
117
  }
118
+ /**
119
+ * Persistence method for user/session data
120
+ * Following PostHog's approach:
121
+ * - 'localStorage+cookie': Stores limited data in cookies, rest in localStorage (default)
122
+ * - 'cookie': Stores all data in cookies
123
+ * - 'localStorage': Stores all data in localStorage
124
+ * - 'sessionStorage': Stores all data in sessionStorage
125
+ * - 'memory': Stores all data in memory only (no persistence)
126
+ */
127
+ export type PersistenceMethod = 'localStorage+cookie' | 'cookie' | 'localStorage' | 'sessionStorage' | 'memory';
128
+ /** User identity state */
52
129
  export interface UserIdentity {
130
+ /** Current distinct ID (null if anonymous) */
53
131
  distinct_id: string | null;
132
+ /** Anonymous ID (always present) */
54
133
  anonymous_id: string;
134
+ /** Device ID (persists across sessions) */
55
135
  device_id: string;
56
- properties: Record<string, any>;
57
- user_state: "anonymous" | "identified";
136
+ /** User properties */
137
+ properties: Properties;
138
+ /** Identity state */
139
+ user_state: 'anonymous' | 'identified';
58
140
  }
59
141
  export interface UserProperties {
60
142
  [key: string]: any;
61
143
  }
62
- export interface PropertyOperations {
63
- $set?: Record<string, any>;
64
- $set_once?: Record<string, any>;
65
- }
66
144
  export interface AliasEvent {
67
145
  distinct_id: string;
68
146
  original: string;
69
147
  }
148
+ export interface WebVitalMetric {
149
+ name: string;
150
+ value: number;
151
+ delta: number;
152
+ rating: 'good' | 'needs-improvement' | 'poor';
153
+ id: string;
154
+ navigationType: string;
155
+ }
156
+ export interface GeolocationData {
157
+ country?: string;
158
+ locale?: string;
159
+ }
160
+ export interface GroupsConfig {
161
+ [groupType: string]: string;
162
+ }
163
+ export interface FeatureFlagsConfig {
164
+ [flagKey: string]: boolean | string;
165
+ }
166
+ export type SessionIdChangedCallback = (newSessionId: string, previousSessionId: string | null, changeInfo: {
167
+ reason: 'timeout' | 'new_session' | 'reset';
168
+ }) => void;
169
+ export interface RequestOptions {
170
+ method?: 'POST' | 'GET';
171
+ headers?: Record<string, string>;
172
+ timeout?: number;
173
+ retry?: boolean;
174
+ }
175
+ export interface RemoteConfig {
176
+ /** Default to identified_only mode */
177
+ defaultIdentifiedOnly?: boolean;
178
+ /** Feature flags */
179
+ featureFlags?: FeatureFlagsConfig;
180
+ /** Session recording config */
181
+ sessionRecording?: {
182
+ enabled?: boolean;
183
+ sampleRate?: number;
184
+ };
185
+ }
package/lib/types.js CHANGED
@@ -1,2 +1,8 @@
1
1
  "use strict";
2
+ /**
3
+ * VTilt Types
4
+ *
5
+ * Type definitions for the VTilt tracking SDK.
6
+ * Following PostHog's patterns where applicable.
7
+ */
2
8
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,10 +1,10 @@
1
- import { UserIdentity, AliasEvent, StorageMethod } from "./types";
1
+ import { UserIdentity, AliasEvent, PersistenceMethod } from "./types";
2
2
  export declare class UserManager {
3
3
  private storageMethod;
4
4
  private domain?;
5
5
  private userIdentity;
6
6
  private _cachedPersonProperties;
7
- constructor(storageMethod?: StorageMethod, domain?: string);
7
+ constructor(storageMethod?: PersistenceMethod, domain?: string);
8
8
  /**
9
9
  * Get current user identity
10
10
  */
@@ -30,7 +30,8 @@ class UserManager {
30
30
  if (!this.userIdentity.anonymous_id) {
31
31
  // Regenerate if somehow undefined
32
32
  this.userIdentity.anonymous_id = this.generateAnonymousId();
33
- this.saveUserIdentity();
33
+ // Save to storage
34
+ this.setStoredValue(constants_1.ANONYMOUS_ID_KEY, this.userIdentity.anonymous_id);
34
35
  }
35
36
  return this.userIdentity.anonymous_id;
36
37
  }
@@ -467,13 +468,25 @@ class UserManager {
467
468
  */
468
469
  getStoredValue(key) {
469
470
  try {
470
- if (this.storageMethod === constants_1.STORAGE_METHODS.localStorage) {
471
- return localStorage.getItem(key);
471
+ // Memory mode doesn't persist - return null (will use in-memory userIdentity)
472
+ if (this.storageMethod === constants_1.PERSISTENCE_METHODS.memory) {
473
+ return null;
472
474
  }
473
- else if (this.storageMethod === constants_1.STORAGE_METHODS.sessionStorage) {
474
- return sessionStorage.getItem(key);
475
+ if (this.storageMethod === constants_1.PERSISTENCE_METHODS.localStorage ||
476
+ this.storageMethod === constants_1.PERSISTENCE_METHODS.localStoragePlusCookie) {
477
+ // Try localStorage first for localStorage and localStorage+cookie modes
478
+ const value = localStorage.getItem(key);
479
+ if (value !== null) {
480
+ return value;
481
+ }
482
+ // Fall back to cookie for localStorage+cookie mode
483
+ if (this.storageMethod === constants_1.PERSISTENCE_METHODS.localStoragePlusCookie) {
484
+ return this.getCookieValue(key);
485
+ }
486
+ return null;
475
487
  }
476
488
  else {
489
+ // Cookie-only mode
477
490
  return this.getCookieValue(key);
478
491
  }
479
492
  }
@@ -488,13 +501,20 @@ class UserManager {
488
501
  */
489
502
  setStoredValue(key, value) {
490
503
  try {
491
- if (this.storageMethod === constants_1.STORAGE_METHODS.localStorage) {
504
+ // Memory mode doesn't persist
505
+ if (this.storageMethod === constants_1.PERSISTENCE_METHODS.memory) {
506
+ return;
507
+ }
508
+ if (this.storageMethod === constants_1.PERSISTENCE_METHODS.localStorage) {
492
509
  localStorage.setItem(key, value);
493
510
  }
494
- else if (this.storageMethod === constants_1.STORAGE_METHODS.sessionStorage) {
495
- sessionStorage.setItem(key, value);
511
+ else if (this.storageMethod === constants_1.PERSISTENCE_METHODS.localStoragePlusCookie) {
512
+ // Store in both localStorage and cookie
513
+ localStorage.setItem(key, value);
514
+ this.setCookieValue(key, value);
496
515
  }
497
516
  else {
517
+ // Cookie-only mode
498
518
  this.setCookieValue(key, value);
499
519
  }
500
520
  }
@@ -507,13 +527,20 @@ class UserManager {
507
527
  * Remove stored value from storage
508
528
  */
509
529
  removeStoredValue(key) {
510
- if (this.storageMethod === constants_1.STORAGE_METHODS.localStorage) {
530
+ // Memory mode doesn't persist
531
+ if (this.storageMethod === constants_1.PERSISTENCE_METHODS.memory) {
532
+ return;
533
+ }
534
+ if (this.storageMethod === constants_1.PERSISTENCE_METHODS.localStorage) {
511
535
  localStorage.removeItem(key);
512
536
  }
513
- else if (this.storageMethod === constants_1.STORAGE_METHODS.sessionStorage) {
514
- sessionStorage.removeItem(key);
537
+ else if (this.storageMethod === constants_1.PERSISTENCE_METHODS.localStoragePlusCookie) {
538
+ // Remove from both localStorage and cookie
539
+ localStorage.removeItem(key);
540
+ this.removeCookieValue(key);
515
541
  }
516
542
  else {
543
+ // Cookie-only mode
517
544
  this.removeCookieValue(key);
518
545
  }
519
546
  }
package/lib/vtilt.d.ts CHANGED
@@ -11,11 +11,11 @@ export declare class VTilt {
11
11
  private rateLimiter;
12
12
  historyAutocapture?: HistoryAutocapture;
13
13
  __loaded: boolean;
14
- private _initialPageviewCaptured;
15
- private _visibilityStateListener;
14
+ private _initial_pageview_captured;
15
+ private _visibility_state_listener;
16
16
  __request_queue: QueuedRequest[];
17
- private _hasWarnedAboutConfig;
18
- private _setOncePropertiesSent;
17
+ private _has_warned_about_config;
18
+ private _set_once_properties_sent;
19
19
  constructor(config?: Partial<VTiltConfig>);
20
20
  /**
21
21
  * Initializes a new instance of the VTilt tracking object.
@@ -53,11 +53,17 @@ export declare class VTilt {
53
53
  * This internal method should only be called by `init()`.
54
54
  */
55
55
  private _init;
56
+ /**
57
+ * Start the request queue if user hasn't opted out
58
+ * Following PostHog's pattern - called from both _init() and _dom_loaded()
59
+ * Safe to call multiple times as enable() is idempotent
60
+ */
61
+ private _start_queue_if_opted_in;
56
62
  /**
57
63
  * Set up handler to flush event queue on page unload
58
64
  * Uses both beforeunload and pagehide for maximum compatibility
59
65
  */
60
- private _setupUnloadHandler;
66
+ private _setup_unload_handler;
61
67
  /**
62
68
  * Returns a string representation of the instance name
63
69
  * Used for debugging and logging
@@ -75,7 +81,7 @@ export declare class VTilt {
75
81
  * Returns true if projectId and token are present, false otherwise
76
82
  * Logs a warning only once per instance if not configured
77
83
  */
78
- private _isConfigured;
84
+ private _is_configured;
79
85
  /**
80
86
  * Build the tracking URL with token in query parameters
81
87
  */
@@ -91,18 +97,18 @@ export declare class VTilt {
91
97
  * Called by RequestQueue when flushing
92
98
  * Uses RetryQueue for automatic retry on failure
93
99
  */
94
- private _sendBatchedRequest;
100
+ private _send_batched_request;
95
101
  /**
96
102
  * Send HTTP request and return status code
97
103
  * Uses GZip compression for payloads > 1KB
98
104
  * Used by RetryQueue for retryable requests
99
105
  */
100
- private _sendHttpRequest;
106
+ private _send_http_request;
101
107
  /**
102
108
  * Send request using sendBeacon for reliable delivery on page unload
103
109
  * Uses GZip compression for payloads > 1KB
104
110
  */
105
- private _sendBeaconRequest;
111
+ private _send_beacon_request;
106
112
  /**
107
113
  * Send a queued request (called after DOM is loaded)
108
114
  */
@@ -125,7 +131,7 @@ export declare class VTilt {
125
131
  * Internal capture method that bypasses rate limiting
126
132
  * Used for system events like rate limit warnings
127
133
  */
128
- private _captureInternal;
134
+ private _capture_internal;
129
135
  /**
130
136
  * Track a custom event (alias for capture)
131
137
  */
@@ -219,8 +225,9 @@ export declare class VTilt {
219
225
  createAlias(alias: string, original?: string): void;
220
226
  /**
221
227
  * Capture initial pageview with visibility check
228
+ * Note: The capture_pageview config check happens at the call site (in _init)
222
229
  */
223
- private _captureInitialPageview;
230
+ private _capture_initial_pageview;
224
231
  /**
225
232
  * Get current configuration
226
233
  */
@@ -247,6 +254,7 @@ export declare class VTilt {
247
254
  _execute_array(array: any[]): void;
248
255
  /**
249
256
  * Called when DOM is loaded - processes queued requests and enables batching
257
+ * Following PostHog's pattern in _dom_loaded()
250
258
  */
251
259
  _dom_loaded(): void;
252
260
  }