@v-tilt/browser 1.0.11 → 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/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/constants.d.ts +172 -10
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/module.d.ts +230 -46
- package/dist/module.js +1 -1
- package/dist/module.js.map +1 -1
- package/dist/module.no-external.d.ts +230 -46
- package/dist/module.no-external.js +1 -1
- package/dist/module.no-external.js.map +1 -1
- package/dist/rate-limiter.d.ts +52 -0
- package/dist/request-queue.d.ts +78 -0
- package/dist/request.d.ts +54 -0
- package/dist/retry-queue.d.ts +64 -0
- package/dist/session.d.ts +2 -2
- package/dist/types.d.ts +154 -37
- package/dist/user-manager.d.ts +2 -2
- package/dist/vtilt.d.ts +51 -12
- package/lib/config.js +6 -13
- package/lib/constants.d.ts +172 -10
- package/lib/constants.js +644 -439
- package/lib/rate-limiter.d.ts +52 -0
- package/lib/rate-limiter.js +80 -0
- package/lib/request-queue.d.ts +78 -0
- package/lib/request-queue.js +156 -0
- package/lib/request.d.ts +54 -0
- package/lib/request.js +265 -0
- package/lib/retry-queue.d.ts +64 -0
- package/lib/retry-queue.js +182 -0
- package/lib/session.d.ts +2 -2
- package/lib/session.js +3 -3
- package/lib/types.d.ts +154 -37
- package/lib/types.js +6 -0
- package/lib/user-manager.d.ts +2 -2
- package/lib/user-manager.js +38 -11
- package/lib/utils/event-utils.js +88 -82
- package/lib/utils/index.js +2 -2
- package/lib/utils/request-utils.js +21 -19
- package/lib/vtilt.d.ts +51 -12
- package/lib/vtilt.js +199 -40
- package/lib/web-vitals.js +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry Queue - Exponential Backoff (PostHog-style)
|
|
3
|
+
*
|
|
4
|
+
* Retries failed requests with jittered exponential backoff.
|
|
5
|
+
* Detects online/offline status and pauses retries when offline.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Exponential backoff: 3s, 6s, 12s, 24s... up to 30 minutes
|
|
9
|
+
* - Jitter: +/- 50% to prevent thundering herd
|
|
10
|
+
* - Online/offline detection
|
|
11
|
+
* - Max 10 retries before giving up
|
|
12
|
+
* - Uses sendBeacon on page unload for final attempt
|
|
13
|
+
*/
|
|
14
|
+
import type { BatchedRequest } from "./request-queue";
|
|
15
|
+
/**
|
|
16
|
+
* Generates a jittered exponential backoff delay in milliseconds
|
|
17
|
+
*
|
|
18
|
+
* Base value is 3 seconds, doubled with each retry up to 30 minutes max.
|
|
19
|
+
* Each value has +/- 50% jitter.
|
|
20
|
+
*
|
|
21
|
+
* @param retriesPerformedSoFar - Number of retries already attempted
|
|
22
|
+
* @returns Delay in milliseconds
|
|
23
|
+
*/
|
|
24
|
+
export declare function pickNextRetryDelay(retriesPerformedSoFar: number): number;
|
|
25
|
+
export interface RetryQueueConfig {
|
|
26
|
+
sendRequest: (req: BatchedRequest) => Promise<{
|
|
27
|
+
statusCode: number;
|
|
28
|
+
}>;
|
|
29
|
+
sendBeacon: (req: BatchedRequest) => void;
|
|
30
|
+
}
|
|
31
|
+
export declare class RetryQueue {
|
|
32
|
+
private _isPolling;
|
|
33
|
+
private _poller?;
|
|
34
|
+
private _pollIntervalMs;
|
|
35
|
+
private _queue;
|
|
36
|
+
private _areWeOnline;
|
|
37
|
+
private _sendRequest;
|
|
38
|
+
private _sendBeacon;
|
|
39
|
+
constructor(config: RetryQueueConfig);
|
|
40
|
+
/**
|
|
41
|
+
* Get current queue length
|
|
42
|
+
*/
|
|
43
|
+
get length(): number;
|
|
44
|
+
/**
|
|
45
|
+
* Enqueue a failed request for retry
|
|
46
|
+
*/
|
|
47
|
+
enqueue(request: BatchedRequest, retriesPerformedSoFar?: number): void;
|
|
48
|
+
/**
|
|
49
|
+
* Attempt to send a request with retry on failure
|
|
50
|
+
*/
|
|
51
|
+
retriableRequest(request: BatchedRequest): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Start polling for retries
|
|
54
|
+
*/
|
|
55
|
+
private _poll;
|
|
56
|
+
/**
|
|
57
|
+
* Flush ready items from the queue
|
|
58
|
+
*/
|
|
59
|
+
private _flush;
|
|
60
|
+
/**
|
|
61
|
+
* Flush all queued requests using sendBeacon on page unload
|
|
62
|
+
*/
|
|
63
|
+
unload(): void;
|
|
64
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Retry Queue - Exponential Backoff (PostHog-style)
|
|
4
|
+
*
|
|
5
|
+
* Retries failed requests with jittered exponential backoff.
|
|
6
|
+
* Detects online/offline status and pauses retries when offline.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Exponential backoff: 3s, 6s, 12s, 24s... up to 30 minutes
|
|
10
|
+
* - Jitter: +/- 50% to prevent thundering herd
|
|
11
|
+
* - Online/offline detection
|
|
12
|
+
* - Max 10 retries before giving up
|
|
13
|
+
* - Uses sendBeacon on page unload for final attempt
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.RetryQueue = void 0;
|
|
17
|
+
exports.pickNextRetryDelay = pickNextRetryDelay;
|
|
18
|
+
const utils_1 = require("./utils");
|
|
19
|
+
const globals_1 = require("./utils/globals");
|
|
20
|
+
const THIRTY_MINUTES = 30 * 60 * 1000;
|
|
21
|
+
const MAX_RETRIES = 10;
|
|
22
|
+
/**
|
|
23
|
+
* Generates a jittered exponential backoff delay in milliseconds
|
|
24
|
+
*
|
|
25
|
+
* Base value is 3 seconds, doubled with each retry up to 30 minutes max.
|
|
26
|
+
* Each value has +/- 50% jitter.
|
|
27
|
+
*
|
|
28
|
+
* @param retriesPerformedSoFar - Number of retries already attempted
|
|
29
|
+
* @returns Delay in milliseconds
|
|
30
|
+
*/
|
|
31
|
+
function pickNextRetryDelay(retriesPerformedSoFar) {
|
|
32
|
+
const rawBackoffTime = 3000 * 2 ** retriesPerformedSoFar;
|
|
33
|
+
const minBackoff = rawBackoffTime / 2;
|
|
34
|
+
const cappedBackoffTime = Math.min(THIRTY_MINUTES, rawBackoffTime);
|
|
35
|
+
const jitterFraction = Math.random() - 0.5; // Random between -0.5 and 0.5
|
|
36
|
+
const jitter = jitterFraction * (cappedBackoffTime - minBackoff);
|
|
37
|
+
return Math.ceil(cappedBackoffTime + jitter);
|
|
38
|
+
}
|
|
39
|
+
class RetryQueue {
|
|
40
|
+
constructor(config) {
|
|
41
|
+
this._isPolling = false;
|
|
42
|
+
this._pollIntervalMs = 3000;
|
|
43
|
+
this._queue = [];
|
|
44
|
+
this._areWeOnline = true;
|
|
45
|
+
this._sendRequest = config.sendRequest;
|
|
46
|
+
this._sendBeacon = config.sendBeacon;
|
|
47
|
+
// Set up online/offline detection
|
|
48
|
+
if (globals_1.window && typeof globals_1.navigator !== "undefined" && "onLine" in globals_1.navigator) {
|
|
49
|
+
this._areWeOnline = globals_1.navigator.onLine;
|
|
50
|
+
(0, utils_1.addEventListener)(globals_1.window, "online", () => {
|
|
51
|
+
this._areWeOnline = true;
|
|
52
|
+
this._flush();
|
|
53
|
+
});
|
|
54
|
+
(0, utils_1.addEventListener)(globals_1.window, "offline", () => {
|
|
55
|
+
this._areWeOnline = false;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get current queue length
|
|
61
|
+
*/
|
|
62
|
+
get length() {
|
|
63
|
+
return this._queue.length;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Enqueue a failed request for retry
|
|
67
|
+
*/
|
|
68
|
+
enqueue(request, retriesPerformedSoFar = 0) {
|
|
69
|
+
// Don't retry if we've exceeded max retries
|
|
70
|
+
if (retriesPerformedSoFar >= MAX_RETRIES) {
|
|
71
|
+
console.warn(`VTilt: Request failed after ${MAX_RETRIES} retries, giving up`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const msToNextRetry = pickNextRetryDelay(retriesPerformedSoFar);
|
|
75
|
+
const retryAt = Date.now() + msToNextRetry;
|
|
76
|
+
this._queue.push({
|
|
77
|
+
retryAt,
|
|
78
|
+
request,
|
|
79
|
+
retriesPerformedSoFar: retriesPerformedSoFar + 1,
|
|
80
|
+
});
|
|
81
|
+
let logMessage = `VTilt: Enqueued failed request for retry in ${Math.round(msToNextRetry / 1000)}s`;
|
|
82
|
+
if (!this._areWeOnline) {
|
|
83
|
+
logMessage += " (Browser is offline)";
|
|
84
|
+
}
|
|
85
|
+
console.warn(logMessage);
|
|
86
|
+
// Start polling if not already
|
|
87
|
+
if (!this._isPolling) {
|
|
88
|
+
this._isPolling = true;
|
|
89
|
+
this._poll();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Attempt to send a request with retry on failure
|
|
94
|
+
*/
|
|
95
|
+
async retriableRequest(request) {
|
|
96
|
+
try {
|
|
97
|
+
const response = await this._sendRequest(request);
|
|
98
|
+
// Retry on server errors (5xx) or network errors (0)
|
|
99
|
+
// Don't retry on client errors (4xx)
|
|
100
|
+
if (response.statusCode !== 200 &&
|
|
101
|
+
(response.statusCode < 400 || response.statusCode >= 500)) {
|
|
102
|
+
this.enqueue(request, 0);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (_a) {
|
|
106
|
+
// Network error - enqueue for retry
|
|
107
|
+
this.enqueue(request, 0);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Start polling for retries
|
|
112
|
+
*/
|
|
113
|
+
_poll() {
|
|
114
|
+
if (this._poller) {
|
|
115
|
+
clearTimeout(this._poller);
|
|
116
|
+
}
|
|
117
|
+
this._poller = setTimeout(() => {
|
|
118
|
+
if (this._areWeOnline && this._queue.length > 0) {
|
|
119
|
+
this._flush();
|
|
120
|
+
}
|
|
121
|
+
// Continue polling if there are items in queue
|
|
122
|
+
if (this._queue.length > 0) {
|
|
123
|
+
this._poll();
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
this._isPolling = false;
|
|
127
|
+
}
|
|
128
|
+
}, this._pollIntervalMs);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Flush ready items from the queue
|
|
132
|
+
*/
|
|
133
|
+
_flush() {
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
const notReady = [];
|
|
136
|
+
const ready = [];
|
|
137
|
+
this._queue.forEach((item) => {
|
|
138
|
+
if (item.retryAt < now) {
|
|
139
|
+
ready.push(item);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
notReady.push(item);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
this._queue = notReady;
|
|
146
|
+
// Retry ready items
|
|
147
|
+
ready.forEach(async ({ request, retriesPerformedSoFar }) => {
|
|
148
|
+
try {
|
|
149
|
+
const response = await this._sendRequest(request);
|
|
150
|
+
// If still failing, re-enqueue
|
|
151
|
+
if (response.statusCode !== 200 &&
|
|
152
|
+
(response.statusCode < 400 || response.statusCode >= 500)) {
|
|
153
|
+
this.enqueue(request, retriesPerformedSoFar);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (_a) {
|
|
157
|
+
// Network error - re-enqueue
|
|
158
|
+
this.enqueue(request, retriesPerformedSoFar);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Flush all queued requests using sendBeacon on page unload
|
|
164
|
+
*/
|
|
165
|
+
unload() {
|
|
166
|
+
if (this._poller) {
|
|
167
|
+
clearTimeout(this._poller);
|
|
168
|
+
this._poller = undefined;
|
|
169
|
+
}
|
|
170
|
+
// Attempt final send of all queued requests
|
|
171
|
+
this._queue.forEach(({ request }) => {
|
|
172
|
+
try {
|
|
173
|
+
this._sendBeacon(request);
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
console.error("VTilt: Failed to send beacon on unload", e);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
this._queue = [];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.RetryQueue = RetryQueue;
|
package/lib/session.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
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?:
|
|
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.
|
|
20
|
-
this.storageMethod === constants_1.
|
|
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.
|
|
29
|
+
return this.storageMethod === constants_1.PERSISTENCE_METHODS.localStorage
|
|
30
30
|
? localStorage
|
|
31
31
|
: sessionStorage;
|
|
32
32
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -1,68 +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
|
-
|
|
9
|
+
/** Project identifier (required) */
|
|
3
10
|
token: string;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
delta: number;
|
|
25
|
-
rating: string;
|
|
26
|
-
id: string;
|
|
27
|
-
navigationType: string;
|
|
28
|
-
}
|
|
29
|
-
export interface GeolocationData {
|
|
30
|
-
country?: string;
|
|
31
|
-
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;
|
|
32
74
|
}
|
|
33
75
|
export interface EventPayload {
|
|
34
76
|
[key: string]: any;
|
|
35
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
|
+
}
|
|
36
96
|
export interface TrackingEvent {
|
|
37
97
|
timestamp: string;
|
|
38
98
|
event: string;
|
|
39
99
|
project_id: string;
|
|
40
100
|
domain: string;
|
|
41
|
-
|
|
42
|
-
distinct_id?: string;
|
|
101
|
+
distinct_id: string;
|
|
43
102
|
anonymous_id?: string;
|
|
103
|
+
payload: EventPayload;
|
|
44
104
|
}
|
|
45
|
-
export type
|
|
46
|
-
export interface
|
|
47
|
-
|
|
48
|
-
localStorage: "localStorage";
|
|
49
|
-
sessionStorage: "sessionStorage";
|
|
105
|
+
export type Property = string | number | boolean | null | undefined | Date | any[] | Record<string, any>;
|
|
106
|
+
export interface Properties {
|
|
107
|
+
[key: string]: Property;
|
|
50
108
|
}
|
|
109
|
+
export interface PropertyOperations {
|
|
110
|
+
$set?: Properties;
|
|
111
|
+
$set_once?: Properties;
|
|
112
|
+
$unset?: string[];
|
|
113
|
+
}
|
|
114
|
+
export interface SessionData {
|
|
115
|
+
value: string;
|
|
116
|
+
expiry: number;
|
|
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 */
|
|
51
129
|
export interface UserIdentity {
|
|
130
|
+
/** Current distinct ID (null if anonymous) */
|
|
52
131
|
distinct_id: string | null;
|
|
132
|
+
/** Anonymous ID (always present) */
|
|
53
133
|
anonymous_id: string;
|
|
134
|
+
/** Device ID (persists across sessions) */
|
|
54
135
|
device_id: string;
|
|
55
|
-
properties
|
|
56
|
-
|
|
136
|
+
/** User properties */
|
|
137
|
+
properties: Properties;
|
|
138
|
+
/** Identity state */
|
|
139
|
+
user_state: 'anonymous' | 'identified';
|
|
57
140
|
}
|
|
58
141
|
export interface UserProperties {
|
|
59
142
|
[key: string]: any;
|
|
60
143
|
}
|
|
61
|
-
export interface PropertyOperations {
|
|
62
|
-
$set?: Record<string, any>;
|
|
63
|
-
$set_once?: Record<string, any>;
|
|
64
|
-
}
|
|
65
144
|
export interface AliasEvent {
|
|
66
145
|
distinct_id: string;
|
|
67
146
|
original: string;
|
|
68
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
package/lib/user-manager.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { UserIdentity, AliasEvent,
|
|
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?:
|
|
7
|
+
constructor(storageMethod?: PersistenceMethod, domain?: string);
|
|
8
8
|
/**
|
|
9
9
|
* Get current user identity
|
|
10
10
|
*/
|
package/lib/user-manager.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
471
|
-
|
|
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
|
-
|
|
474
|
-
|
|
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
|
-
|
|
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.
|
|
495
|
-
|
|
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
|
-
|
|
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.
|
|
514
|
-
|
|
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
|
}
|