@v-tilt/browser 1.0.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/dist/array.js +2 -0
- package/dist/array.js.map +1 -0
- package/dist/array.no-external.js +2 -0
- package/dist/array.no-external.js.map +1 -0
- package/dist/config.d.ts +17 -0
- package/dist/constants.d.ts +10 -0
- package/dist/entrypoints/array.d.ts +1 -0
- package/dist/entrypoints/array.no-external.d.ts +1 -0
- package/dist/entrypoints/main.cjs.d.ts +4 -0
- package/dist/entrypoints/module.es.d.ts +3 -0
- package/dist/entrypoints/module.no-external.es.d.ts +4 -0
- package/dist/geolocation.d.ts +5 -0
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -0
- package/dist/module.d.ts +214 -0
- package/dist/module.js +2 -0
- package/dist/module.js.map +1 -0
- package/dist/module.no-external.d.ts +214 -0
- package/dist/module.no-external.js +2 -0
- package/dist/module.no-external.js.map +1 -0
- package/dist/session.d.ts +26 -0
- package/dist/tracking.d.ts +112 -0
- package/dist/types.d.ts +67 -0
- package/dist/user-manager.d.ts +152 -0
- package/dist/utils/globals.d.ts +4 -0
- package/dist/utils/index.d.ts +30 -0
- package/dist/utils.d.ts +21 -0
- package/dist/vtilt.d.ts +178 -0
- package/dist/web-vitals.d.ts +11 -0
- package/lib/config.d.ts +17 -0
- package/lib/config.js +76 -0
- package/lib/constants.d.ts +10 -0
- package/lib/constants.js +463 -0
- package/lib/entrypoints/array.d.ts +1 -0
- package/lib/entrypoints/array.js +3 -0
- package/lib/entrypoints/array.no-external.d.ts +1 -0
- package/lib/entrypoints/array.no-external.js +4 -0
- package/lib/entrypoints/main.cjs.d.ts +4 -0
- package/lib/entrypoints/main.cjs.js +29 -0
- package/lib/entrypoints/module.es.d.ts +3 -0
- package/lib/entrypoints/module.es.js +22 -0
- package/lib/entrypoints/module.no-external.es.d.ts +4 -0
- package/lib/entrypoints/module.no-external.es.js +23 -0
- package/lib/geolocation.d.ts +5 -0
- package/lib/geolocation.js +26 -0
- package/lib/session.d.ts +26 -0
- package/lib/session.js +115 -0
- package/lib/tracking.d.ts +112 -0
- package/lib/tracking.js +326 -0
- package/lib/types.d.ts +67 -0
- package/lib/types.js +2 -0
- package/lib/user-manager.d.ts +152 -0
- package/lib/user-manager.js +565 -0
- package/lib/utils/globals.d.ts +4 -0
- package/lib/utils/globals.js +5 -0
- package/lib/utils/index.d.ts +30 -0
- package/lib/utils/index.js +89 -0
- package/lib/utils.d.ts +21 -0
- package/lib/utils.js +57 -0
- package/lib/vtilt.d.ts +178 -0
- package/lib/vtilt.js +403 -0
- package/lib/web-vitals.d.ts +11 -0
- package/lib/web-vitals.js +67 -0
- package/package.json +61 -0
package/lib/session.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SessionManager = void 0;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
const utils_1 = require("./utils");
|
|
6
|
+
class SessionManager {
|
|
7
|
+
constructor(storageMethod = "cookie", domain) {
|
|
8
|
+
this.storageMethod = storageMethod;
|
|
9
|
+
this.domain = domain;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Get session ID from cookie
|
|
13
|
+
*/
|
|
14
|
+
getSessionIdFromCookie() {
|
|
15
|
+
const cookie = {};
|
|
16
|
+
document.cookie.split(";").forEach(function (el) {
|
|
17
|
+
const [key, value] = el.split("=");
|
|
18
|
+
cookie[key.trim()] = value;
|
|
19
|
+
});
|
|
20
|
+
return cookie[constants_1.STORAGE_KEY] || null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get session ID from storage
|
|
24
|
+
*/
|
|
25
|
+
getSessionId() {
|
|
26
|
+
if (this.storageMethod === constants_1.STORAGE_METHODS.localStorage ||
|
|
27
|
+
this.storageMethod === constants_1.STORAGE_METHODS.sessionStorage) {
|
|
28
|
+
const storage = this.storageMethod === constants_1.STORAGE_METHODS.localStorage
|
|
29
|
+
? localStorage
|
|
30
|
+
: sessionStorage;
|
|
31
|
+
const serializedItem = storage.getItem(constants_1.STORAGE_KEY);
|
|
32
|
+
if (!serializedItem) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
let item = null;
|
|
36
|
+
try {
|
|
37
|
+
item = JSON.parse(serializedItem);
|
|
38
|
+
}
|
|
39
|
+
catch (_a) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (typeof item !== "object" || item === null) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const now = new Date();
|
|
46
|
+
if (now.getTime() > item.expiry) {
|
|
47
|
+
storage.removeItem(constants_1.STORAGE_KEY);
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return item.value;
|
|
51
|
+
}
|
|
52
|
+
return this.getSessionIdFromCookie();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Set session ID in cookie
|
|
56
|
+
*/
|
|
57
|
+
setSessionIdFromCookie(sessionId) {
|
|
58
|
+
let cookieValue = `${constants_1.STORAGE_KEY}=${sessionId}; Max-Age=1800; path=/; secure`;
|
|
59
|
+
if (this.domain) {
|
|
60
|
+
cookieValue += `; domain=${this.domain}`;
|
|
61
|
+
}
|
|
62
|
+
document.cookie = cookieValue;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Set session ID in storage
|
|
66
|
+
*/
|
|
67
|
+
setSessionId() {
|
|
68
|
+
/**
|
|
69
|
+
* Try to keep same session id if it exists, generate a new one otherwise.
|
|
70
|
+
* - First request in a session will generate a new session id
|
|
71
|
+
* - The next request will keep the same session id and extend the TTL for 30 more minutes
|
|
72
|
+
*/
|
|
73
|
+
const sessionId = this.getSessionId() || (0, utils_1.uuidv4)();
|
|
74
|
+
if (this.storageMethod === constants_1.STORAGE_METHODS.localStorage ||
|
|
75
|
+
this.storageMethod === constants_1.STORAGE_METHODS.sessionStorage) {
|
|
76
|
+
const now = new Date();
|
|
77
|
+
const item = {
|
|
78
|
+
value: sessionId,
|
|
79
|
+
expiry: now.getTime() + 1800 * 1000,
|
|
80
|
+
};
|
|
81
|
+
const value = JSON.stringify(item);
|
|
82
|
+
const storage = this.storageMethod === constants_1.STORAGE_METHODS.localStorage
|
|
83
|
+
? localStorage
|
|
84
|
+
: sessionStorage;
|
|
85
|
+
storage.setItem(constants_1.STORAGE_KEY, value);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
this.setSessionIdFromCookie(sessionId);
|
|
89
|
+
}
|
|
90
|
+
return sessionId;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Reset session ID (PostHog behavior: generates new session on reset)
|
|
94
|
+
*/
|
|
95
|
+
resetSessionId() {
|
|
96
|
+
// Clear existing session
|
|
97
|
+
if (this.storageMethod === constants_1.STORAGE_METHODS.localStorage ||
|
|
98
|
+
this.storageMethod === constants_1.STORAGE_METHODS.sessionStorage) {
|
|
99
|
+
const storage = this.storageMethod === constants_1.STORAGE_METHODS.localStorage
|
|
100
|
+
? localStorage
|
|
101
|
+
: sessionStorage;
|
|
102
|
+
storage.removeItem(constants_1.STORAGE_KEY);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// Clear cookie
|
|
106
|
+
document.cookie = `${constants_1.STORAGE_KEY}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
|
107
|
+
if (this.domain) {
|
|
108
|
+
document.cookie = `${constants_1.STORAGE_KEY}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${this.domain};`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Generate new session ID
|
|
112
|
+
this.setSessionId();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.SessionManager = SessionManager;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { VTiltConfig, EventPayload } from "./types";
|
|
2
|
+
interface QueuedRequest {
|
|
3
|
+
url: string;
|
|
4
|
+
event: any;
|
|
5
|
+
}
|
|
6
|
+
export declare class TrackingManager {
|
|
7
|
+
private config;
|
|
8
|
+
private sessionManager;
|
|
9
|
+
private userManager;
|
|
10
|
+
__request_queue: QueuedRequest[];
|
|
11
|
+
private _hasWarnedAboutConfig;
|
|
12
|
+
constructor(config: VTiltConfig);
|
|
13
|
+
/**
|
|
14
|
+
* Get current domain from window location
|
|
15
|
+
* Returns full URL format for consistency with dashboard
|
|
16
|
+
*/
|
|
17
|
+
private getCurrentDomain;
|
|
18
|
+
/**
|
|
19
|
+
* Check if tracking is properly configured
|
|
20
|
+
* Returns true if projectId and token are present, false otherwise
|
|
21
|
+
* Logs a warning only once per instance if not configured
|
|
22
|
+
*/
|
|
23
|
+
private _isConfigured;
|
|
24
|
+
/**
|
|
25
|
+
* Send event to endpoint
|
|
26
|
+
*/
|
|
27
|
+
sendEvent(name: string, payload: EventPayload): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Build the tracking URL with token in query parameters (PostHog style)
|
|
30
|
+
*/
|
|
31
|
+
private buildUrl;
|
|
32
|
+
/**
|
|
33
|
+
* Send HTTP request
|
|
34
|
+
* This is the central entry point for all tracking requests
|
|
35
|
+
*/
|
|
36
|
+
private sendRequest;
|
|
37
|
+
/**
|
|
38
|
+
* Send a queued request (called after DOM is loaded)
|
|
39
|
+
*/
|
|
40
|
+
_send_retriable_request(item: QueuedRequest): void;
|
|
41
|
+
/**
|
|
42
|
+
* Track page hit
|
|
43
|
+
*/
|
|
44
|
+
trackPageHit(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Get current session ID
|
|
47
|
+
*/
|
|
48
|
+
getSessionId(): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* Identify a user with PostHog-style property operations
|
|
51
|
+
* Copied from PostHog's identify method signature
|
|
52
|
+
*/
|
|
53
|
+
identify(distinctId?: string, userPropertiesToSet?: Record<string, any>, userPropertiesToSetOnce?: Record<string, any>): void;
|
|
54
|
+
/**
|
|
55
|
+
* Set user properties (PostHog-style)
|
|
56
|
+
*/
|
|
57
|
+
setUserProperties(userPropertiesToSet?: Record<string, any>, userPropertiesToSetOnce?: Record<string, any>): void;
|
|
58
|
+
/**
|
|
59
|
+
* Reset user identity (logout)
|
|
60
|
+
* PostHog behavior: Clears all user data, generates new anonymous ID, resets session
|
|
61
|
+
*
|
|
62
|
+
* @param reset_device_id - If true, also resets device_id. Default: false
|
|
63
|
+
*/
|
|
64
|
+
resetUser(reset_device_id?: boolean): void;
|
|
65
|
+
/**
|
|
66
|
+
* Get current user identity
|
|
67
|
+
*/
|
|
68
|
+
getUserIdentity(): import("./types").UserIdentity;
|
|
69
|
+
/**
|
|
70
|
+
* Get current device ID
|
|
71
|
+
*/
|
|
72
|
+
getDeviceId(): string;
|
|
73
|
+
/**
|
|
74
|
+
* Get current user state
|
|
75
|
+
*/
|
|
76
|
+
getUserState(): "anonymous" | "identified";
|
|
77
|
+
/**
|
|
78
|
+
* Create an alias to link two distinct IDs
|
|
79
|
+
* PostHog behavior: Links anonymous session to account on signup
|
|
80
|
+
*
|
|
81
|
+
* @param alias - A unique identifier that you want to use for this user in the future
|
|
82
|
+
* @param original - The current identifier being used for this user (optional, defaults to current distinct_id)
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* // Link anonymous user to account on signup
|
|
86
|
+
* vtilt.createAlias('user_12345')
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* // Explicit alias with original ID
|
|
90
|
+
* vtilt.createAlias('user_12345', 'anonymous_abc123')
|
|
91
|
+
*/
|
|
92
|
+
createAlias(alias: string, original?: string): void;
|
|
93
|
+
/**
|
|
94
|
+
* Setup listener for identify, set, and alias events
|
|
95
|
+
*/
|
|
96
|
+
private setupIdentifyListener;
|
|
97
|
+
/**
|
|
98
|
+
* Send identify event for session merging
|
|
99
|
+
*/
|
|
100
|
+
private sendIdentifyEvent;
|
|
101
|
+
/**
|
|
102
|
+
* Send $set event for property updates (PostHog behavior)
|
|
103
|
+
* This notifies Tinybird when user properties are updated
|
|
104
|
+
*/
|
|
105
|
+
private sendSetEvent;
|
|
106
|
+
/**
|
|
107
|
+
* Send alias event for identity linking
|
|
108
|
+
* PostHog format: { alias: alias, distinct_id: original }
|
|
109
|
+
*/
|
|
110
|
+
private sendAliasEvent;
|
|
111
|
+
}
|
|
112
|
+
export {};
|
package/lib/tracking.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TrackingManager = void 0;
|
|
4
|
+
const session_1 = require("./session");
|
|
5
|
+
const user_manager_1 = require("./user-manager");
|
|
6
|
+
const utils_1 = require("./utils");
|
|
7
|
+
const geolocation_1 = require("./geolocation");
|
|
8
|
+
class TrackingManager {
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.__request_queue = [];
|
|
11
|
+
this._hasWarnedAboutConfig = false; // Track if we've already warned about missing config
|
|
12
|
+
this.config = config;
|
|
13
|
+
// Auto-detect domain from window.location if not provided
|
|
14
|
+
if (!this.config.domain && typeof window !== "undefined") {
|
|
15
|
+
this.config.domain = this.getCurrentDomain();
|
|
16
|
+
}
|
|
17
|
+
this.sessionManager = new session_1.SessionManager(config.storage || "cookie", this.config.domain);
|
|
18
|
+
this.userManager = new user_manager_1.UserManager(config.persistence || "localStorage", this.config.domain);
|
|
19
|
+
// Listen for identify events
|
|
20
|
+
this.setupIdentifyListener();
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get current domain from window location
|
|
24
|
+
* Returns full URL format for consistency with dashboard
|
|
25
|
+
*/
|
|
26
|
+
getCurrentDomain() {
|
|
27
|
+
if (typeof window === "undefined") {
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
const protocol = window.location.protocol;
|
|
31
|
+
const hostname = window.location.hostname;
|
|
32
|
+
const port = window.location.port;
|
|
33
|
+
const portSuffix = port ? `:${port}` : "";
|
|
34
|
+
return `${protocol}//${hostname}${portSuffix}`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Check if tracking is properly configured
|
|
38
|
+
* Returns true if projectId and token are present, false otherwise
|
|
39
|
+
* Logs a warning only once per instance if not configured
|
|
40
|
+
*/
|
|
41
|
+
_isConfigured() {
|
|
42
|
+
if (this.config.projectId && this.config.token) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
// Only warn once to avoid console spam
|
|
46
|
+
if (!this._hasWarnedAboutConfig) {
|
|
47
|
+
console.warn("VTilt: projectId and token are required for tracking. " +
|
|
48
|
+
"Events will be skipped until init() or updateConfig() is called with these fields.");
|
|
49
|
+
this._hasWarnedAboutConfig = true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Send event to endpoint
|
|
55
|
+
*/
|
|
56
|
+
async sendEvent(name, payload) {
|
|
57
|
+
this.sessionManager.setSessionId();
|
|
58
|
+
if (!(0, utils_1.isValidUserAgent)(window.navigator.userAgent)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const url = this.buildUrl();
|
|
62
|
+
let processedPayload;
|
|
63
|
+
if (this.config.stringifyPayload !== false) {
|
|
64
|
+
processedPayload = (0, utils_1.maskSuspiciousAttributes)(payload);
|
|
65
|
+
processedPayload = Object.assign({}, JSON.parse(processedPayload), this.config.globalAttributes);
|
|
66
|
+
processedPayload = JSON.stringify(processedPayload);
|
|
67
|
+
if (!(0, utils_1.isValidPayload)(processedPayload)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
processedPayload = Object.assign({}, payload, this.config.globalAttributes);
|
|
73
|
+
const maskedStr = (0, utils_1.maskSuspiciousAttributes)(processedPayload);
|
|
74
|
+
if (!(0, utils_1.isValidPayload)(maskedStr)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
processedPayload = JSON.parse(maskedStr);
|
|
78
|
+
}
|
|
79
|
+
const session_id = this.sessionManager.getSessionId() || (0, utils_1.uuidv4)();
|
|
80
|
+
const distinct_id = this.userManager.getDistinctId();
|
|
81
|
+
const anonymous_id = this.userManager.getAnonymousId();
|
|
82
|
+
const trackingEvent = {
|
|
83
|
+
timestamp: new Date().toISOString(),
|
|
84
|
+
event: name,
|
|
85
|
+
session_id,
|
|
86
|
+
tenant_id: this.config.projectId || "",
|
|
87
|
+
domain: this.config.domain || this.getCurrentDomain(), // Use config domain or current domain
|
|
88
|
+
payload: processedPayload,
|
|
89
|
+
distinct_id: distinct_id || anonymous_id,
|
|
90
|
+
};
|
|
91
|
+
this.sendRequest(url, trackingEvent, true);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Build the tracking URL with token in query parameters (PostHog style)
|
|
95
|
+
*/
|
|
96
|
+
buildUrl() {
|
|
97
|
+
const { proxyUrl, proxy, host, token } = this.config;
|
|
98
|
+
// Use proxy endpoint to handle Tinybird authentication
|
|
99
|
+
if (proxyUrl) {
|
|
100
|
+
// Use the full proxy URL as provided
|
|
101
|
+
return proxyUrl;
|
|
102
|
+
}
|
|
103
|
+
else if (proxy) {
|
|
104
|
+
// Construct the proxy URL from the proxy domain with token
|
|
105
|
+
return `${proxy}/api/tracking?token=${token}`;
|
|
106
|
+
}
|
|
107
|
+
else if (host) {
|
|
108
|
+
const cleanHost = host.replace(/\/+$/gm, "");
|
|
109
|
+
return `${cleanHost}/api/tracking?token=${token}`;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// Use relative path to our tracking proxy endpoint
|
|
113
|
+
return `/api/tracking?token=${token}`;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Send HTTP request
|
|
118
|
+
* This is the central entry point for all tracking requests
|
|
119
|
+
*/
|
|
120
|
+
sendRequest(url, event, shouldEnqueue) {
|
|
121
|
+
// Validate configuration (only warns once per instance)
|
|
122
|
+
// This is the single place where validation happens for all sending methods
|
|
123
|
+
if (!this._isConfigured()) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Check if we should queue requests (for older browsers before DOM is loaded)
|
|
127
|
+
if (shouldEnqueue && typeof window !== "undefined") {
|
|
128
|
+
// ENQUEUE_REQUESTS is defined in vtilt.ts as a module-level variable
|
|
129
|
+
const ENQUEUE_REQUESTS = window.__VTILT_ENQUEUE_REQUESTS;
|
|
130
|
+
if (ENQUEUE_REQUESTS) {
|
|
131
|
+
this.__request_queue.push({ url, event });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const request = new XMLHttpRequest();
|
|
136
|
+
request.open("POST", url, true);
|
|
137
|
+
request.setRequestHeader("Content-Type", "application/json");
|
|
138
|
+
request.send(JSON.stringify(event));
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Send a queued request (called after DOM is loaded)
|
|
142
|
+
*/
|
|
143
|
+
_send_retriable_request(item) {
|
|
144
|
+
this.sendRequest(item.url, item.event, false);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Track page hit
|
|
148
|
+
*/
|
|
149
|
+
trackPageHit() {
|
|
150
|
+
// If test environment
|
|
151
|
+
if ((0, utils_1.isTestEnvironment)()) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const { country, locale } = (0, geolocation_1.getCountryAndLocale)();
|
|
155
|
+
// Wait a bit for SPA routers
|
|
156
|
+
setTimeout(() => {
|
|
157
|
+
this.sendEvent("page_hit", {
|
|
158
|
+
"user-agent": window.navigator.userAgent,
|
|
159
|
+
locale,
|
|
160
|
+
location: country,
|
|
161
|
+
referrer: document.referrer,
|
|
162
|
+
pathname: window.location.pathname,
|
|
163
|
+
href: window.location.href,
|
|
164
|
+
});
|
|
165
|
+
}, 300);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get current session ID
|
|
169
|
+
*/
|
|
170
|
+
getSessionId() {
|
|
171
|
+
return this.sessionManager.getSessionId();
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Identify a user with PostHog-style property operations
|
|
175
|
+
* Copied from PostHog's identify method signature
|
|
176
|
+
*/
|
|
177
|
+
identify(distinctId, userPropertiesToSet, userPropertiesToSetOnce) {
|
|
178
|
+
this.userManager.identify(distinctId, userPropertiesToSet, userPropertiesToSetOnce);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Set user properties (PostHog-style)
|
|
182
|
+
*/
|
|
183
|
+
setUserProperties(userPropertiesToSet, userPropertiesToSetOnce) {
|
|
184
|
+
this.userManager.setUserProperties(userPropertiesToSet, userPropertiesToSetOnce);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Reset user identity (logout)
|
|
188
|
+
* PostHog behavior: Clears all user data, generates new anonymous ID, resets session
|
|
189
|
+
*
|
|
190
|
+
* @param reset_device_id - If true, also resets device_id. Default: false
|
|
191
|
+
*/
|
|
192
|
+
resetUser(reset_device_id) {
|
|
193
|
+
// PostHog behavior: Reset session ID
|
|
194
|
+
this.sessionManager.resetSessionId();
|
|
195
|
+
this.userManager.reset(reset_device_id);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get current user identity
|
|
199
|
+
*/
|
|
200
|
+
getUserIdentity() {
|
|
201
|
+
return this.userManager.getUserIdentity();
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Get current device ID
|
|
205
|
+
*/
|
|
206
|
+
getDeviceId() {
|
|
207
|
+
return this.userManager.getDeviceId();
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Get current user state
|
|
211
|
+
*/
|
|
212
|
+
getUserState() {
|
|
213
|
+
return this.userManager.getUserState();
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Create an alias to link two distinct IDs
|
|
217
|
+
* PostHog behavior: Links anonymous session to account on signup
|
|
218
|
+
*
|
|
219
|
+
* @param alias - A unique identifier that you want to use for this user in the future
|
|
220
|
+
* @param original - The current identifier being used for this user (optional, defaults to current distinct_id)
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* // Link anonymous user to account on signup
|
|
224
|
+
* vtilt.createAlias('user_12345')
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* // Explicit alias with original ID
|
|
228
|
+
* vtilt.createAlias('user_12345', 'anonymous_abc123')
|
|
229
|
+
*/
|
|
230
|
+
createAlias(alias, original) {
|
|
231
|
+
this.userManager.createAlias(alias, original);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Setup listener for identify, set, and alias events
|
|
235
|
+
*/
|
|
236
|
+
setupIdentifyListener() {
|
|
237
|
+
// Listen for identify events
|
|
238
|
+
window.addEventListener("vtilt:identify", (event) => {
|
|
239
|
+
const customEvent = event;
|
|
240
|
+
const { distinct_id, anonymous_id, device_id, properties } = customEvent.detail;
|
|
241
|
+
this.sendIdentifyEvent(distinct_id, anonymous_id, device_id, properties);
|
|
242
|
+
});
|
|
243
|
+
// Listen for set events (property updates)
|
|
244
|
+
window.addEventListener("vtilt:set", (event) => {
|
|
245
|
+
const customEvent = event;
|
|
246
|
+
const { $set, $set_once } = customEvent.detail;
|
|
247
|
+
this.sendSetEvent($set || {}, $set_once || {});
|
|
248
|
+
});
|
|
249
|
+
// Listen for alias events
|
|
250
|
+
window.addEventListener("vtilt:alias", (event) => {
|
|
251
|
+
const customEvent = event;
|
|
252
|
+
const aliasEvent = customEvent.detail;
|
|
253
|
+
this.sendAliasEvent(aliasEvent);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Send identify event for session merging
|
|
258
|
+
*/
|
|
259
|
+
sendIdentifyEvent(distinctId, anonymousId, deviceId, properties) {
|
|
260
|
+
const url = this.buildUrl();
|
|
261
|
+
const session_id = this.sessionManager.getSessionId() || (0, utils_1.uuidv4)();
|
|
262
|
+
// Payload contains only properties, not event or distinct_id (those are in trackingEvent)
|
|
263
|
+
const identifyPayload = {
|
|
264
|
+
$anon_distinct_id: anonymousId,
|
|
265
|
+
$device_id: deviceId,
|
|
266
|
+
...properties,
|
|
267
|
+
};
|
|
268
|
+
const trackingEvent = {
|
|
269
|
+
timestamp: new Date().toISOString(),
|
|
270
|
+
event: "$identify",
|
|
271
|
+
session_id,
|
|
272
|
+
tenant_id: this.config.projectId || "",
|
|
273
|
+
domain: this.config.domain || this.getCurrentDomain(),
|
|
274
|
+
payload: identifyPayload,
|
|
275
|
+
distinct_id: distinctId,
|
|
276
|
+
};
|
|
277
|
+
this.sendRequest(url, trackingEvent, true);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Send $set event for property updates (PostHog behavior)
|
|
281
|
+
* This notifies Tinybird when user properties are updated
|
|
282
|
+
*/
|
|
283
|
+
sendSetEvent(userPropertiesToSet, userPropertiesToSetOnce) {
|
|
284
|
+
const url = this.buildUrl();
|
|
285
|
+
const session_id = this.sessionManager.getSessionId() || (0, utils_1.uuidv4)();
|
|
286
|
+
const distinct_id = this.userManager.getDistinctId();
|
|
287
|
+
const anonymous_id = this.userManager.getAnonymousId();
|
|
288
|
+
const setEventPayload = {
|
|
289
|
+
$set: userPropertiesToSet || {},
|
|
290
|
+
$set_once: userPropertiesToSetOnce || {},
|
|
291
|
+
};
|
|
292
|
+
const trackingEvent = {
|
|
293
|
+
timestamp: new Date().toISOString(),
|
|
294
|
+
event: "$set",
|
|
295
|
+
session_id,
|
|
296
|
+
tenant_id: this.config.projectId || "",
|
|
297
|
+
domain: this.config.domain || this.getCurrentDomain(),
|
|
298
|
+
payload: setEventPayload,
|
|
299
|
+
distinct_id: distinct_id || anonymous_id,
|
|
300
|
+
};
|
|
301
|
+
this.sendRequest(url, trackingEvent, true);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Send alias event for identity linking
|
|
305
|
+
* PostHog format: { alias: alias, distinct_id: original }
|
|
306
|
+
*/
|
|
307
|
+
sendAliasEvent(aliasEvent) {
|
|
308
|
+
const url = this.buildUrl();
|
|
309
|
+
const session_id = this.sessionManager.getSessionId() || (0, utils_1.uuidv4)();
|
|
310
|
+
const aliasPayload = {
|
|
311
|
+
$original_id: aliasEvent.original,
|
|
312
|
+
$alias_id: aliasEvent.distinct_id,
|
|
313
|
+
};
|
|
314
|
+
const trackingEvent = {
|
|
315
|
+
timestamp: new Date().toISOString(),
|
|
316
|
+
event: "$alias",
|
|
317
|
+
session_id,
|
|
318
|
+
tenant_id: this.config.projectId || "",
|
|
319
|
+
domain: this.config.domain || this.getCurrentDomain(),
|
|
320
|
+
payload: aliasPayload,
|
|
321
|
+
distinct_id: aliasEvent.distinct_id,
|
|
322
|
+
};
|
|
323
|
+
this.sendRequest(url, trackingEvent, true);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
exports.TrackingManager = TrackingManager;
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export interface VTiltConfig {
|
|
2
|
+
projectId: string;
|
|
3
|
+
token: string;
|
|
4
|
+
host?: string;
|
|
5
|
+
scriptHost?: string;
|
|
6
|
+
proxy?: string;
|
|
7
|
+
proxyUrl?: string;
|
|
8
|
+
domain?: string;
|
|
9
|
+
storage?: "cookie" | "localStorage" | "sessionStorage";
|
|
10
|
+
stringifyPayload?: boolean;
|
|
11
|
+
webVitals?: boolean;
|
|
12
|
+
globalAttributes?: Record<string, string>;
|
|
13
|
+
persistence?: "localStorage" | "cookie";
|
|
14
|
+
crossSubdomainCookie?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface SessionData {
|
|
17
|
+
value: string;
|
|
18
|
+
expiry: number;
|
|
19
|
+
}
|
|
20
|
+
export interface WebVitalMetric {
|
|
21
|
+
name: string;
|
|
22
|
+
value: number;
|
|
23
|
+
delta: number;
|
|
24
|
+
rating: string;
|
|
25
|
+
id: string;
|
|
26
|
+
navigationType: string;
|
|
27
|
+
}
|
|
28
|
+
export interface GeolocationData {
|
|
29
|
+
country?: string;
|
|
30
|
+
locale?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface EventPayload {
|
|
33
|
+
[key: string]: any;
|
|
34
|
+
}
|
|
35
|
+
export interface TrackingEvent {
|
|
36
|
+
timestamp: string;
|
|
37
|
+
event: string;
|
|
38
|
+
session_id: string;
|
|
39
|
+
tenant_id: string;
|
|
40
|
+
domain: string;
|
|
41
|
+
payload: EventPayload;
|
|
42
|
+
distinct_id?: string;
|
|
43
|
+
}
|
|
44
|
+
export type StorageMethod = "cookie" | "localStorage" | "sessionStorage";
|
|
45
|
+
export interface StorageMethods {
|
|
46
|
+
cookie: "cookie";
|
|
47
|
+
localStorage: "localStorage";
|
|
48
|
+
sessionStorage: "sessionStorage";
|
|
49
|
+
}
|
|
50
|
+
export interface UserIdentity {
|
|
51
|
+
distinct_id: string | null;
|
|
52
|
+
anonymous_id: string;
|
|
53
|
+
device_id: string;
|
|
54
|
+
properties: Record<string, any>;
|
|
55
|
+
user_state: "anonymous" | "identified";
|
|
56
|
+
}
|
|
57
|
+
export interface UserProperties {
|
|
58
|
+
[key: string]: any;
|
|
59
|
+
}
|
|
60
|
+
export interface PropertyOperations {
|
|
61
|
+
$set?: Record<string, any>;
|
|
62
|
+
$set_once?: Record<string, any>;
|
|
63
|
+
}
|
|
64
|
+
export interface AliasEvent {
|
|
65
|
+
distinct_id: string;
|
|
66
|
+
original: string;
|
|
67
|
+
}
|
package/lib/types.js
ADDED