@v-tilt/browser 1.1.4 → 1.2.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/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/entrypoints/array.d.ts +1 -0
- package/dist/entrypoints/external-scripts-loader.d.ts +24 -0
- package/dist/entrypoints/module.es.d.ts +1 -0
- package/dist/entrypoints/recorder.d.ts +23 -0
- package/dist/extensions/replay/index.d.ts +13 -0
- package/dist/extensions/replay/session-recording-utils.d.ts +92 -0
- package/dist/extensions/replay/session-recording-wrapper.d.ts +61 -0
- package/dist/extensions/replay/session-recording.d.ts +95 -0
- package/dist/extensions/replay/types.d.ts +211 -0
- package/dist/external-scripts-loader.js +2 -0
- package/dist/external-scripts-loader.js.map +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/module.d.ts +271 -8
- package/dist/module.js +1 -1
- package/dist/module.js.map +1 -1
- package/dist/module.no-external.d.ts +271 -8
- package/dist/module.no-external.js +1 -1
- package/dist/module.no-external.js.map +1 -1
- package/dist/recorder.js +2 -0
- package/dist/recorder.js.map +1 -0
- package/dist/session.d.ts +4 -2
- package/dist/storage.d.ts +8 -3
- package/dist/types.d.ts +91 -7
- package/dist/user-manager.d.ts +2 -2
- package/dist/utils/globals.d.ts +42 -0
- package/dist/vtilt.d.ts +36 -0
- package/lib/config.js +2 -0
- package/lib/entrypoints/array.d.ts +1 -0
- package/lib/entrypoints/array.js +1 -0
- package/lib/entrypoints/external-scripts-loader.d.ts +24 -0
- package/lib/entrypoints/external-scripts-loader.js +107 -0
- package/lib/entrypoints/module.es.d.ts +1 -0
- package/lib/entrypoints/module.es.js +1 -0
- package/lib/entrypoints/recorder.d.ts +23 -0
- package/lib/entrypoints/recorder.js +42 -0
- package/lib/extensions/replay/index.d.ts +13 -0
- package/lib/extensions/replay/index.js +31 -0
- package/lib/extensions/replay/session-recording-utils.d.ts +92 -0
- package/lib/extensions/replay/session-recording-utils.js +212 -0
- package/lib/extensions/replay/session-recording-wrapper.d.ts +61 -0
- package/lib/extensions/replay/session-recording-wrapper.js +149 -0
- package/lib/extensions/replay/session-recording.d.ts +95 -0
- package/lib/extensions/replay/session-recording.js +700 -0
- package/lib/extensions/replay/types.d.ts +211 -0
- package/lib/extensions/replay/types.js +8 -0
- package/lib/session.d.ts +4 -2
- package/lib/session.js +7 -41
- package/lib/storage.d.ts +8 -3
- package/lib/storage.js +62 -9
- package/lib/types.d.ts +91 -7
- package/lib/user-manager.d.ts +2 -2
- package/lib/user-manager.js +4 -4
- package/lib/utils/globals.d.ts +42 -0
- package/lib/utils/globals.js +2 -0
- package/lib/vtilt.d.ts +36 -0
- package/lib/vtilt.js +110 -14
- package/package.json +4 -1
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Recording Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for rrweb session recording.
|
|
5
|
+
* Based on PostHog's implementation.
|
|
6
|
+
*/
|
|
7
|
+
import type { blockClass, eventWithTime, hooksParam, KeepIframeSrcFn, maskTextClass, PackFn, RecordPlugin, SamplingStrategy } from "@rrweb/types";
|
|
8
|
+
/** Mask options for input elements */
|
|
9
|
+
export type MaskInputOptions = Partial<{
|
|
10
|
+
color: boolean;
|
|
11
|
+
date: boolean;
|
|
12
|
+
"datetime-local": boolean;
|
|
13
|
+
email: boolean;
|
|
14
|
+
month: boolean;
|
|
15
|
+
number: boolean;
|
|
16
|
+
range: boolean;
|
|
17
|
+
search: boolean;
|
|
18
|
+
tel: boolean;
|
|
19
|
+
text: boolean;
|
|
20
|
+
time: boolean;
|
|
21
|
+
url: boolean;
|
|
22
|
+
week: boolean;
|
|
23
|
+
textarea: boolean;
|
|
24
|
+
select: boolean;
|
|
25
|
+
password: boolean;
|
|
26
|
+
}>;
|
|
27
|
+
/** Function to mask input values */
|
|
28
|
+
export type MaskInputFn = (text: string, element: HTMLElement) => string;
|
|
29
|
+
/** Function to mask text content */
|
|
30
|
+
export type MaskTextFn = (text: string, element: HTMLElement | null) => string;
|
|
31
|
+
/** Options for slim DOM mode */
|
|
32
|
+
export type SlimDOMOptions = Partial<{
|
|
33
|
+
script: boolean;
|
|
34
|
+
comment: boolean;
|
|
35
|
+
headFavicon: boolean;
|
|
36
|
+
headWhitespace: boolean;
|
|
37
|
+
headMetaDescKeywords: boolean;
|
|
38
|
+
headMetaSocial: boolean;
|
|
39
|
+
headMetaRobots: boolean;
|
|
40
|
+
headMetaHttpEquiv: boolean;
|
|
41
|
+
headMetaAuthorship: boolean;
|
|
42
|
+
headMetaVerification: boolean;
|
|
43
|
+
headTitleMutations: boolean;
|
|
44
|
+
}>;
|
|
45
|
+
/** Options for data URL generation */
|
|
46
|
+
export type DataURLOptions = Partial<{
|
|
47
|
+
type: string;
|
|
48
|
+
quality: number;
|
|
49
|
+
}>;
|
|
50
|
+
/** Error handler type */
|
|
51
|
+
export type ErrorHandler = (error: unknown) => void | boolean;
|
|
52
|
+
/** rrweb record options */
|
|
53
|
+
export interface RecordOptions {
|
|
54
|
+
emit?: (e: eventWithTime, isCheckout?: boolean) => void;
|
|
55
|
+
checkoutEveryNth?: number;
|
|
56
|
+
checkoutEveryNms?: number;
|
|
57
|
+
blockClass?: blockClass;
|
|
58
|
+
blockSelector?: string;
|
|
59
|
+
ignoreClass?: string;
|
|
60
|
+
ignoreSelector?: string;
|
|
61
|
+
maskTextClass?: maskTextClass;
|
|
62
|
+
maskTextSelector?: string;
|
|
63
|
+
maskAllInputs?: boolean;
|
|
64
|
+
maskInputOptions?: MaskInputOptions;
|
|
65
|
+
maskInputFn?: MaskInputFn;
|
|
66
|
+
maskTextFn?: MaskTextFn;
|
|
67
|
+
slimDOMOptions?: SlimDOMOptions | "all" | true;
|
|
68
|
+
ignoreCSSAttributes?: Set<string>;
|
|
69
|
+
inlineStylesheet?: boolean;
|
|
70
|
+
hooks?: hooksParam;
|
|
71
|
+
packFn?: PackFn;
|
|
72
|
+
sampling?: SamplingStrategy;
|
|
73
|
+
dataURLOptions?: DataURLOptions;
|
|
74
|
+
recordDOM?: boolean;
|
|
75
|
+
recordCanvas?: boolean;
|
|
76
|
+
recordCrossOriginIframes?: boolean;
|
|
77
|
+
recordAfter?: "DOMContentLoaded" | "load";
|
|
78
|
+
userTriggeredOnInput?: boolean;
|
|
79
|
+
collectFonts?: boolean;
|
|
80
|
+
inlineImages?: boolean;
|
|
81
|
+
plugins?: RecordPlugin[];
|
|
82
|
+
mousemoveWait?: number;
|
|
83
|
+
keepIframeSrcFn?: KeepIframeSrcFn;
|
|
84
|
+
errorHandler?: ErrorHandler;
|
|
85
|
+
}
|
|
86
|
+
/** rrweb record function type */
|
|
87
|
+
export interface RRWebRecord {
|
|
88
|
+
(options: RecordOptions): () => void;
|
|
89
|
+
addCustomEvent: (tag: string, payload: unknown) => void;
|
|
90
|
+
takeFullSnapshot: () => void;
|
|
91
|
+
mirror: {
|
|
92
|
+
getId(n: Node | undefined | null): number;
|
|
93
|
+
getNode(id: number): Node | null;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** Canvas recording configuration */
|
|
97
|
+
export interface CanvasRecordingConfig {
|
|
98
|
+
enabled: boolean;
|
|
99
|
+
fps: number;
|
|
100
|
+
quality: number;
|
|
101
|
+
}
|
|
102
|
+
/** Network payload capture configuration */
|
|
103
|
+
export interface NetworkPayloadCaptureConfig {
|
|
104
|
+
recordHeaders?: boolean;
|
|
105
|
+
recordBody?: boolean;
|
|
106
|
+
recordPerformance?: boolean;
|
|
107
|
+
}
|
|
108
|
+
/** Masking configuration */
|
|
109
|
+
export interface MaskingConfig {
|
|
110
|
+
maskAllInputs?: boolean;
|
|
111
|
+
maskTextSelector?: string;
|
|
112
|
+
blockSelector?: string;
|
|
113
|
+
}
|
|
114
|
+
/** Session recording configuration */
|
|
115
|
+
export interface SessionRecordingConfig {
|
|
116
|
+
/** Enable session recording */
|
|
117
|
+
enabled?: boolean;
|
|
118
|
+
/** Sample rate (0-1, where 1 = 100%) */
|
|
119
|
+
sampleRate?: number;
|
|
120
|
+
/** Minimum session duration in ms before sending */
|
|
121
|
+
minimumDurationMs?: number;
|
|
122
|
+
/** Session idle threshold in ms */
|
|
123
|
+
sessionIdleThresholdMs?: number;
|
|
124
|
+
/** Full snapshot interval in ms */
|
|
125
|
+
fullSnapshotIntervalMs?: number;
|
|
126
|
+
/** Enable console log capture */
|
|
127
|
+
captureConsole?: boolean;
|
|
128
|
+
/** Enable network request capture */
|
|
129
|
+
captureNetwork?: boolean;
|
|
130
|
+
/** Canvas recording settings */
|
|
131
|
+
captureCanvas?: {
|
|
132
|
+
recordCanvas?: boolean;
|
|
133
|
+
canvasFps?: number;
|
|
134
|
+
canvasQuality?: number;
|
|
135
|
+
};
|
|
136
|
+
/** Masking settings */
|
|
137
|
+
masking?: MaskingConfig;
|
|
138
|
+
/** Block class for elements to hide */
|
|
139
|
+
blockClass?: string;
|
|
140
|
+
/** Block selector for elements to hide */
|
|
141
|
+
blockSelector?: string;
|
|
142
|
+
/** Ignore class for input masking */
|
|
143
|
+
ignoreClass?: string;
|
|
144
|
+
/** Mask text class */
|
|
145
|
+
maskTextClass?: string;
|
|
146
|
+
/** Mask text selector */
|
|
147
|
+
maskTextSelector?: string;
|
|
148
|
+
/** Mask all inputs */
|
|
149
|
+
maskAllInputs?: boolean;
|
|
150
|
+
/** Mask input options */
|
|
151
|
+
maskInputOptions?: MaskInputOptions;
|
|
152
|
+
/** Record headers in network requests */
|
|
153
|
+
recordHeaders?: boolean;
|
|
154
|
+
/** Record body in network requests */
|
|
155
|
+
recordBody?: boolean;
|
|
156
|
+
/** Compress events before sending */
|
|
157
|
+
compressEvents?: boolean;
|
|
158
|
+
/** Internal: Mutation throttler refill rate */
|
|
159
|
+
__mutationThrottlerRefillRate?: number;
|
|
160
|
+
/** Internal: Mutation throttler bucket size */
|
|
161
|
+
__mutationThrottlerBucketSize?: number;
|
|
162
|
+
}
|
|
163
|
+
/** Recording status values */
|
|
164
|
+
export type SessionRecordingStatus = "disabled" | "buffering" | "active" | "paused" | "sampled" | "trigger_pending";
|
|
165
|
+
/** Recording start reason */
|
|
166
|
+
export type SessionStartReason = "recording_initialized" | "session_id_changed" | "linked_flag_matched" | "linked_flag_overridden" | "sampling_overridden" | "url_trigger_matched" | "event_trigger_matched" | "sampled";
|
|
167
|
+
/** Trigger type for starting recording */
|
|
168
|
+
export type TriggerType = "url" | "event";
|
|
169
|
+
/** Buffer for snapshot events */
|
|
170
|
+
export interface SnapshotBuffer {
|
|
171
|
+
size: number;
|
|
172
|
+
data: eventWithTime[];
|
|
173
|
+
sessionId: string;
|
|
174
|
+
windowId: string;
|
|
175
|
+
}
|
|
176
|
+
/** Remote configuration for session recording */
|
|
177
|
+
export interface SessionRecordingRemoteConfig {
|
|
178
|
+
enabled?: boolean;
|
|
179
|
+
endpoint?: string;
|
|
180
|
+
sampleRate?: string;
|
|
181
|
+
minimumDurationMilliseconds?: number;
|
|
182
|
+
consoleLogRecordingEnabled?: boolean;
|
|
183
|
+
networkPayloadCapture?: {
|
|
184
|
+
recordHeaders?: boolean;
|
|
185
|
+
recordBody?: boolean;
|
|
186
|
+
capturePerformance?: boolean;
|
|
187
|
+
};
|
|
188
|
+
masking?: MaskingConfig;
|
|
189
|
+
recordCanvas?: boolean;
|
|
190
|
+
canvasFps?: number;
|
|
191
|
+
canvasQuality?: number;
|
|
192
|
+
scriptConfig?: {
|
|
193
|
+
script?: string;
|
|
194
|
+
};
|
|
195
|
+
triggerMatchType?: "any" | "all";
|
|
196
|
+
urlTriggers?: Array<{
|
|
197
|
+
url: string;
|
|
198
|
+
matching: "regex" | "exact" | "contains";
|
|
199
|
+
}>;
|
|
200
|
+
urlBlocklist?: Array<{
|
|
201
|
+
url: string;
|
|
202
|
+
matching: "regex" | "exact" | "contains";
|
|
203
|
+
}>;
|
|
204
|
+
eventTriggers?: string[];
|
|
205
|
+
}
|
|
206
|
+
/** Queued rrweb method call */
|
|
207
|
+
export interface QueuedRRWebEvent {
|
|
208
|
+
rrwebMethod: () => void;
|
|
209
|
+
attempt: number;
|
|
210
|
+
enqueuedAt: number;
|
|
211
|
+
}
|
package/lib/session.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { PersistenceMethod } from "./types";
|
|
|
10
10
|
export declare class SessionManager {
|
|
11
11
|
private storage;
|
|
12
12
|
private _windowId;
|
|
13
|
-
constructor(storageMethod?: PersistenceMethod,
|
|
13
|
+
constructor(storageMethod?: PersistenceMethod, cross_subdomain?: boolean);
|
|
14
14
|
/**
|
|
15
15
|
* Get session ID (always returns a value, generates if needed)
|
|
16
16
|
*/
|
|
@@ -26,10 +26,12 @@ export declare class SessionManager {
|
|
|
26
26
|
resetSessionId(): void;
|
|
27
27
|
/**
|
|
28
28
|
* Get session ID from storage (raw, can return null)
|
|
29
|
+
* Cookie Max-Age handles expiration automatically
|
|
29
30
|
*/
|
|
30
31
|
private _getSessionIdRaw;
|
|
31
32
|
/**
|
|
32
33
|
* Store session ID
|
|
34
|
+
* Uses plain string format - cookie Max-Age handles expiration
|
|
33
35
|
*/
|
|
34
36
|
private _storeSessionId;
|
|
35
37
|
/**
|
|
@@ -60,5 +62,5 @@ export declare class SessionManager {
|
|
|
60
62
|
/**
|
|
61
63
|
* Update storage method at runtime
|
|
62
64
|
*/
|
|
63
|
-
updateStorageMethod(method: PersistenceMethod,
|
|
65
|
+
updateStorageMethod(method: PersistenceMethod, cross_subdomain?: boolean): void;
|
|
64
66
|
}
|
package/lib/session.js
CHANGED
|
@@ -13,13 +13,11 @@ const constants_1 = require("./constants");
|
|
|
13
13
|
const utils_1 = require("./utils");
|
|
14
14
|
const globals_1 = require("./utils/globals");
|
|
15
15
|
const storage_1 = require("./storage");
|
|
16
|
-
// Session TTL in milliseconds (30 minutes)
|
|
17
|
-
const SESSION_TTL_MS = 30 * 60 * 1000;
|
|
18
16
|
class SessionManager {
|
|
19
|
-
constructor(storageMethod = "cookie",
|
|
17
|
+
constructor(storageMethod = "cookie", cross_subdomain) {
|
|
20
18
|
this.storage = new storage_1.StorageManager({
|
|
21
19
|
method: storageMethod,
|
|
22
|
-
|
|
20
|
+
cross_subdomain,
|
|
23
21
|
sameSite: "Lax",
|
|
24
22
|
});
|
|
25
23
|
this._windowId = undefined;
|
|
@@ -66,48 +64,16 @@ class SessionManager {
|
|
|
66
64
|
}
|
|
67
65
|
/**
|
|
68
66
|
* Get session ID from storage (raw, can return null)
|
|
67
|
+
* Cookie Max-Age handles expiration automatically
|
|
69
68
|
*/
|
|
70
69
|
_getSessionIdRaw() {
|
|
71
|
-
|
|
72
|
-
const sessionData = this.storage.getWithExpiry(constants_1.STORAGE_KEY);
|
|
73
|
-
if (sessionData) {
|
|
74
|
-
return sessionData;
|
|
75
|
-
}
|
|
76
|
-
// For cookie mode, the cookie itself handles expiry via Max-Age
|
|
77
|
-
// Just get the raw value
|
|
78
|
-
const rawValue = this.storage.get(constants_1.STORAGE_KEY);
|
|
79
|
-
if (rawValue) {
|
|
80
|
-
// Check if it's JSON format (from web storage) or plain string (from cookie)
|
|
81
|
-
try {
|
|
82
|
-
const parsed = JSON.parse(rawValue);
|
|
83
|
-
// If it's a StorageItem, extract value and check expiry
|
|
84
|
-
if (typeof parsed === "object" &&
|
|
85
|
-
parsed !== null &&
|
|
86
|
-
"value" in parsed) {
|
|
87
|
-
if (parsed.expiry && Date.now() > parsed.expiry) {
|
|
88
|
-
this.storage.remove(constants_1.STORAGE_KEY);
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
return parsed.value;
|
|
92
|
-
}
|
|
93
|
-
return rawValue;
|
|
94
|
-
}
|
|
95
|
-
catch (_a) {
|
|
96
|
-
// Plain string value (from cookie)
|
|
97
|
-
return rawValue;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return null;
|
|
70
|
+
return this.storage.get(constants_1.STORAGE_KEY);
|
|
101
71
|
}
|
|
102
72
|
/**
|
|
103
73
|
* Store session ID
|
|
74
|
+
* Uses plain string format - cookie Max-Age handles expiration
|
|
104
75
|
*/
|
|
105
76
|
_storeSessionId(sessionId) {
|
|
106
|
-
// Use setWithExpiry for localStorage/sessionStorage
|
|
107
|
-
// For cookies, the StorageManager handles Max-Age
|
|
108
|
-
this.storage.setWithExpiry(constants_1.STORAGE_KEY, sessionId, SESSION_TTL_MS);
|
|
109
|
-
// Also set as plain cookie for cookie mode (overwrites JSON format)
|
|
110
|
-
// This ensures cookies work properly with Max-Age
|
|
111
77
|
this.storage.set(constants_1.STORAGE_KEY, sessionId, storage_1.SESSION_COOKIE_MAX_AGE);
|
|
112
78
|
}
|
|
113
79
|
/**
|
|
@@ -214,10 +180,10 @@ class SessionManager {
|
|
|
214
180
|
/**
|
|
215
181
|
* Update storage method at runtime
|
|
216
182
|
*/
|
|
217
|
-
updateStorageMethod(method,
|
|
183
|
+
updateStorageMethod(method, cross_subdomain) {
|
|
218
184
|
this.storage = new storage_1.StorageManager({
|
|
219
185
|
method,
|
|
220
|
-
|
|
186
|
+
cross_subdomain,
|
|
221
187
|
sameSite: "Lax",
|
|
222
188
|
});
|
|
223
189
|
}
|
package/lib/storage.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare const SESSION_COOKIE_MAX_AGE = 1800;
|
|
|
16
16
|
export declare const USER_COOKIE_MAX_AGE = 31536000;
|
|
17
17
|
export interface StorageOptions {
|
|
18
18
|
method: PersistenceMethod;
|
|
19
|
-
|
|
19
|
+
cross_subdomain?: boolean;
|
|
20
20
|
secure?: boolean;
|
|
21
21
|
sameSite?: "Strict" | "Lax" | "None";
|
|
22
22
|
}
|
|
@@ -24,13 +24,18 @@ export interface StorageItem<T = string> {
|
|
|
24
24
|
value: T;
|
|
25
25
|
expiry?: number;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Auto-detect if cross-subdomain cookies should be enabled
|
|
29
|
+
* Returns false for platforms like herokuapp.com, vercel.app, netlify.app
|
|
30
|
+
*/
|
|
31
|
+
export declare function shouldUseCrossSubdomainCookie(): boolean;
|
|
27
32
|
/**
|
|
28
33
|
* Unified Storage Manager
|
|
29
34
|
* Provides consistent storage operations across all persistence methods
|
|
30
35
|
*/
|
|
31
36
|
export declare class StorageManager {
|
|
32
37
|
private method;
|
|
33
|
-
private
|
|
38
|
+
private cross_subdomain;
|
|
34
39
|
private secure;
|
|
35
40
|
private sameSite;
|
|
36
41
|
private memoryStorage;
|
|
@@ -92,4 +97,4 @@ export declare class StorageManager {
|
|
|
92
97
|
* Create a shared storage instance
|
|
93
98
|
* Use this for creating storage managers with consistent settings
|
|
94
99
|
*/
|
|
95
|
-
export declare function createStorageManager(method: PersistenceMethod,
|
|
100
|
+
export declare function createStorageManager(method: PersistenceMethod, cross_subdomain?: boolean): StorageManager;
|
package/lib/storage.js
CHANGED
|
@@ -14,26 +14,75 @@
|
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
16
|
exports.StorageManager = exports.USER_COOKIE_MAX_AGE = exports.SESSION_COOKIE_MAX_AGE = void 0;
|
|
17
|
+
exports.shouldUseCrossSubdomainCookie = shouldUseCrossSubdomainCookie;
|
|
17
18
|
exports.createStorageManager = createStorageManager;
|
|
18
19
|
const constants_1 = require("./constants");
|
|
19
20
|
const globals_1 = require("./utils/globals");
|
|
20
21
|
// Default cookie TTLs
|
|
21
22
|
exports.SESSION_COOKIE_MAX_AGE = 1800; // 30 minutes
|
|
22
23
|
exports.USER_COOKIE_MAX_AGE = 31536000; // 1 year
|
|
24
|
+
// Platforms excluded from cross-subdomain cookies (following PostHog)
|
|
25
|
+
const EXCLUDED_FROM_CROSS_SUBDOMAIN = [
|
|
26
|
+
"herokuapp.com",
|
|
27
|
+
"vercel.app",
|
|
28
|
+
"netlify.app",
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* Auto-detect if cross-subdomain cookies should be enabled
|
|
32
|
+
* Returns false for platforms like herokuapp.com, vercel.app, netlify.app
|
|
33
|
+
*/
|
|
34
|
+
function shouldUseCrossSubdomainCookie() {
|
|
35
|
+
var _a;
|
|
36
|
+
if (typeof document === "undefined") {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const hostname = (_a = document.location) === null || _a === void 0 ? void 0 : _a.hostname;
|
|
40
|
+
if (!hostname) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
const lastTwoParts = hostname.split(".").slice(-2).join(".");
|
|
44
|
+
for (const excluded of EXCLUDED_FROM_CROSS_SUBDOMAIN) {
|
|
45
|
+
if (lastTwoParts === excluded) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get the cookie domain for cross-subdomain cookies
|
|
53
|
+
* Returns domain like ".example.com" or empty string for same-origin
|
|
54
|
+
*/
|
|
55
|
+
function getCookieDomain(cross_subdomain) {
|
|
56
|
+
var _a;
|
|
57
|
+
if (!cross_subdomain) {
|
|
58
|
+
return "";
|
|
59
|
+
}
|
|
60
|
+
if (typeof document === "undefined") {
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
const hostname = (_a = document.location) === null || _a === void 0 ? void 0 : _a.hostname;
|
|
64
|
+
if (!hostname) {
|
|
65
|
+
return "";
|
|
66
|
+
}
|
|
67
|
+
// Match domain pattern like "example.com" from "sub.example.com"
|
|
68
|
+
const matches = hostname.match(/[a-z0-9][a-z0-9-]+\.[a-z]{2,}$/i);
|
|
69
|
+
return matches ? `.${matches[0]}` : "";
|
|
70
|
+
}
|
|
23
71
|
/**
|
|
24
72
|
* Unified Storage Manager
|
|
25
73
|
* Provides consistent storage operations across all persistence methods
|
|
26
74
|
*/
|
|
27
75
|
class StorageManager {
|
|
28
76
|
constructor(options) {
|
|
29
|
-
var _a;
|
|
77
|
+
var _a, _b;
|
|
30
78
|
this.memoryStorage = new Map();
|
|
31
79
|
this.method = options.method;
|
|
32
|
-
this.
|
|
80
|
+
this.cross_subdomain =
|
|
81
|
+
(_a = options.cross_subdomain) !== null && _a !== void 0 ? _a : shouldUseCrossSubdomainCookie();
|
|
33
82
|
this.sameSite = options.sameSite || "Lax";
|
|
34
83
|
// Auto-detect secure flag from protocol
|
|
35
84
|
this.secure =
|
|
36
|
-
(
|
|
85
|
+
(_b = options.secure) !== null && _b !== void 0 ? _b : (typeof location !== "undefined" && location.protocol === "https:");
|
|
37
86
|
}
|
|
38
87
|
// ============================================================================
|
|
39
88
|
// Public API - Simple key-value operations
|
|
@@ -216,8 +265,10 @@ class StorageManager {
|
|
|
216
265
|
if (this.secure) {
|
|
217
266
|
cookieString += `; Secure`;
|
|
218
267
|
}
|
|
219
|
-
|
|
220
|
-
|
|
268
|
+
// Auto-detect domain for cross-subdomain cookies
|
|
269
|
+
const domain = getCookieDomain(this.cross_subdomain);
|
|
270
|
+
if (domain) {
|
|
271
|
+
cookieString += `; domain=${domain}`;
|
|
221
272
|
}
|
|
222
273
|
document.cookie = cookieString;
|
|
223
274
|
}
|
|
@@ -225,10 +276,12 @@ class StorageManager {
|
|
|
225
276
|
if (typeof document === "undefined") {
|
|
226
277
|
return;
|
|
227
278
|
}
|
|
279
|
+
// Auto-detect domain for cross-subdomain cookies
|
|
280
|
+
const domain = getCookieDomain(this.cross_subdomain);
|
|
228
281
|
// Set cookie with expired date
|
|
229
282
|
let cookieString = `${name}=; Max-Age=0; path=/`;
|
|
230
|
-
if (
|
|
231
|
-
cookieString += `; domain=${
|
|
283
|
+
if (domain) {
|
|
284
|
+
cookieString += `; domain=${domain}`;
|
|
232
285
|
}
|
|
233
286
|
document.cookie = cookieString;
|
|
234
287
|
// Also try without domain (in case it was set without)
|
|
@@ -289,10 +342,10 @@ exports.StorageManager = StorageManager;
|
|
|
289
342
|
* Create a shared storage instance
|
|
290
343
|
* Use this for creating storage managers with consistent settings
|
|
291
344
|
*/
|
|
292
|
-
function createStorageManager(method,
|
|
345
|
+
function createStorageManager(method, cross_subdomain) {
|
|
293
346
|
return new StorageManager({
|
|
294
347
|
method,
|
|
295
|
-
|
|
348
|
+
cross_subdomain,
|
|
296
349
|
sameSite: "Lax",
|
|
297
350
|
});
|
|
298
351
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -12,6 +12,13 @@ export interface VTiltConfig {
|
|
|
12
12
|
api_host?: string;
|
|
13
13
|
/** UI host for dashboard links */
|
|
14
14
|
ui_host?: string | null;
|
|
15
|
+
/**
|
|
16
|
+
* Host for loading extension scripts (recorder.js, etc.)
|
|
17
|
+
* Defaults to unpkg CDN: https://unpkg.com/@v-tilt/browser@{version}/dist
|
|
18
|
+
* Can also use jsdelivr: https://cdn.jsdelivr.net/npm/@v-tilt/browser@{version}/dist
|
|
19
|
+
* Or self-hosted: https://your-domain.com/static
|
|
20
|
+
*/
|
|
21
|
+
script_host?: string;
|
|
15
22
|
/** Proxy domain for tracking requests */
|
|
16
23
|
proxy?: string;
|
|
17
24
|
/** Full proxy URL for tracking requests */
|
|
@@ -20,7 +27,7 @@ export interface VTiltConfig {
|
|
|
20
27
|
name?: string;
|
|
21
28
|
/** Project ID (set via init() first argument) */
|
|
22
29
|
projectId?: string;
|
|
23
|
-
/** Domain to track (auto-detected if not provided) */
|
|
30
|
+
/** Domain to track (auto-detected if not provided) - used in event properties */
|
|
24
31
|
domain?: string;
|
|
25
32
|
/** Storage method for session data */
|
|
26
33
|
storage?: PersistenceMethod;
|
|
@@ -28,7 +35,12 @@ export interface VTiltConfig {
|
|
|
28
35
|
persistence?: PersistenceMethod;
|
|
29
36
|
/** Persistence name prefix */
|
|
30
37
|
persistence_name?: string;
|
|
31
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Enable cross-subdomain cookies.
|
|
40
|
+
* When true, cookies are shared across subdomains (e.g., app.example.com and www.example.com).
|
|
41
|
+
* Auto-detects false for platforms like herokuapp.com, vercel.app, netlify.app.
|
|
42
|
+
* @default true (except for excluded platforms)
|
|
43
|
+
*/
|
|
32
44
|
cross_subdomain_cookie?: boolean;
|
|
33
45
|
/**
|
|
34
46
|
* Person profiles mode:
|
|
@@ -59,6 +71,10 @@ export interface VTiltConfig {
|
|
|
59
71
|
respect_dnt?: boolean;
|
|
60
72
|
/** Opt users out by default */
|
|
61
73
|
opt_out_capturing_by_default?: boolean;
|
|
74
|
+
/** Session recording configuration */
|
|
75
|
+
session_recording?: SessionRecordingOptions;
|
|
76
|
+
/** Disable session recording (convenience flag) */
|
|
77
|
+
disable_session_recording?: boolean;
|
|
62
78
|
/** Global attributes added to all events */
|
|
63
79
|
globalAttributes?: Record<string, string>;
|
|
64
80
|
/** Bootstrap data for initialization */
|
|
@@ -97,7 +113,6 @@ export interface TrackingEvent {
|
|
|
97
113
|
timestamp: string;
|
|
98
114
|
event: string;
|
|
99
115
|
project_id: string;
|
|
100
|
-
domain: string;
|
|
101
116
|
distinct_id: string;
|
|
102
117
|
anonymous_id?: string;
|
|
103
118
|
payload: EventPayload;
|
|
@@ -172,14 +187,83 @@ export interface RequestOptions {
|
|
|
172
187
|
timeout?: number;
|
|
173
188
|
retry?: boolean;
|
|
174
189
|
}
|
|
190
|
+
/** Mask options for input elements in session recording */
|
|
191
|
+
export interface SessionRecordingMaskInputOptions {
|
|
192
|
+
color?: boolean;
|
|
193
|
+
date?: boolean;
|
|
194
|
+
"datetime-local"?: boolean;
|
|
195
|
+
email?: boolean;
|
|
196
|
+
month?: boolean;
|
|
197
|
+
number?: boolean;
|
|
198
|
+
range?: boolean;
|
|
199
|
+
search?: boolean;
|
|
200
|
+
tel?: boolean;
|
|
201
|
+
text?: boolean;
|
|
202
|
+
time?: boolean;
|
|
203
|
+
url?: boolean;
|
|
204
|
+
week?: boolean;
|
|
205
|
+
textarea?: boolean;
|
|
206
|
+
select?: boolean;
|
|
207
|
+
password?: boolean;
|
|
208
|
+
}
|
|
209
|
+
/** Session recording configuration */
|
|
210
|
+
export interface SessionRecordingOptions {
|
|
211
|
+
/** Enable session recording */
|
|
212
|
+
enabled?: boolean;
|
|
213
|
+
/** Sample rate (0-1, where 1 = 100%) */
|
|
214
|
+
sampleRate?: number;
|
|
215
|
+
/** Minimum session duration in ms before sending */
|
|
216
|
+
minimumDurationMs?: number;
|
|
217
|
+
/** Session idle threshold in ms (default: 5 minutes) */
|
|
218
|
+
sessionIdleThresholdMs?: number;
|
|
219
|
+
/** Full snapshot interval in ms (default: 5 minutes) */
|
|
220
|
+
fullSnapshotIntervalMs?: number;
|
|
221
|
+
/** Enable console log capture */
|
|
222
|
+
captureConsole?: boolean;
|
|
223
|
+
/** Enable network request capture */
|
|
224
|
+
captureNetwork?: boolean;
|
|
225
|
+
/** Canvas recording settings */
|
|
226
|
+
captureCanvas?: {
|
|
227
|
+
recordCanvas?: boolean;
|
|
228
|
+
canvasFps?: number;
|
|
229
|
+
canvasQuality?: number;
|
|
230
|
+
};
|
|
231
|
+
/** Block class for elements to hide (default: 'vt-no-capture') */
|
|
232
|
+
blockClass?: string;
|
|
233
|
+
/** Block selector for elements to hide */
|
|
234
|
+
blockSelector?: string;
|
|
235
|
+
/** Ignore class for input masking (default: 'vt-ignore-input') */
|
|
236
|
+
ignoreClass?: string;
|
|
237
|
+
/** Mask text class (default: 'vt-mask') */
|
|
238
|
+
maskTextClass?: string;
|
|
239
|
+
/** Mask text selector */
|
|
240
|
+
maskTextSelector?: string;
|
|
241
|
+
/** Mask all inputs (default: true) */
|
|
242
|
+
maskAllInputs?: boolean;
|
|
243
|
+
/** Mask input options */
|
|
244
|
+
maskInputOptions?: SessionRecordingMaskInputOptions;
|
|
245
|
+
/** Masking configuration */
|
|
246
|
+
masking?: {
|
|
247
|
+
maskAllInputs?: boolean;
|
|
248
|
+
maskTextSelector?: string;
|
|
249
|
+
blockSelector?: string;
|
|
250
|
+
};
|
|
251
|
+
/** Record headers in network requests */
|
|
252
|
+
recordHeaders?: boolean;
|
|
253
|
+
/** Record body in network requests */
|
|
254
|
+
recordBody?: boolean;
|
|
255
|
+
/** Compress events before sending (default: true) */
|
|
256
|
+
compressEvents?: boolean;
|
|
257
|
+
/** Internal: Mutation throttler refill rate */
|
|
258
|
+
__mutationThrottlerRefillRate?: number;
|
|
259
|
+
/** Internal: Mutation throttler bucket size */
|
|
260
|
+
__mutationThrottlerBucketSize?: number;
|
|
261
|
+
}
|
|
175
262
|
export interface RemoteConfig {
|
|
176
263
|
/** Default to identified_only mode */
|
|
177
264
|
defaultIdentifiedOnly?: boolean;
|
|
178
265
|
/** Feature flags */
|
|
179
266
|
featureFlags?: FeatureFlagsConfig;
|
|
180
267
|
/** Session recording config */
|
|
181
|
-
sessionRecording?:
|
|
182
|
-
enabled?: boolean;
|
|
183
|
-
sampleRate?: number;
|
|
184
|
-
};
|
|
268
|
+
sessionRecording?: SessionRecordingOptions;
|
|
185
269
|
}
|
package/lib/user-manager.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export declare class UserManager {
|
|
|
15
15
|
private storage;
|
|
16
16
|
private userIdentity;
|
|
17
17
|
private _cachedPersonProperties;
|
|
18
|
-
constructor(storageMethod?: PersistenceMethod,
|
|
18
|
+
constructor(storageMethod?: PersistenceMethod, cross_subdomain?: boolean);
|
|
19
19
|
/**
|
|
20
20
|
* Get current user identity
|
|
21
21
|
*/
|
|
@@ -136,5 +136,5 @@ export declare class UserManager {
|
|
|
136
136
|
/**
|
|
137
137
|
* Update storage method at runtime
|
|
138
138
|
*/
|
|
139
|
-
updateStorageMethod(method: PersistenceMethod,
|
|
139
|
+
updateStorageMethod(method: PersistenceMethod, cross_subdomain?: boolean): void;
|
|
140
140
|
}
|
package/lib/user-manager.js
CHANGED
|
@@ -18,11 +18,11 @@ const utils_1 = require("./utils");
|
|
|
18
18
|
const event_utils_1 = require("./utils/event-utils");
|
|
19
19
|
const storage_1 = require("./storage");
|
|
20
20
|
class UserManager {
|
|
21
|
-
constructor(storageMethod = "localStorage",
|
|
21
|
+
constructor(storageMethod = "localStorage", cross_subdomain) {
|
|
22
22
|
this._cachedPersonProperties = null; // Cache for deduplication
|
|
23
23
|
this.storage = new storage_1.StorageManager({
|
|
24
24
|
method: storageMethod,
|
|
25
|
-
|
|
25
|
+
cross_subdomain,
|
|
26
26
|
sameSite: "Lax",
|
|
27
27
|
});
|
|
28
28
|
this.userIdentity = this.loadUserIdentity();
|
|
@@ -544,10 +544,10 @@ class UserManager {
|
|
|
544
544
|
/**
|
|
545
545
|
* Update storage method at runtime
|
|
546
546
|
*/
|
|
547
|
-
updateStorageMethod(method,
|
|
547
|
+
updateStorageMethod(method, cross_subdomain) {
|
|
548
548
|
this.storage = new storage_1.StorageManager({
|
|
549
549
|
method,
|
|
550
|
-
|
|
550
|
+
cross_subdomain,
|
|
551
551
|
sameSite: "Lax",
|
|
552
552
|
});
|
|
553
553
|
// Reload identity from new storage
|