@prosdevlab/experience-sdk-plugins 0.1.4 → 0.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/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +30 -0
- package/dist/index.d.ts +608 -1
- package/dist/index.js +692 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/exit-intent/exit-intent.test.ts +423 -0
- package/src/exit-intent/exit-intent.ts +372 -0
- package/src/exit-intent/index.ts +6 -0
- package/src/exit-intent/types.ts +59 -0
- package/src/index.ts +5 -0
- package/src/integration.test.ts +362 -0
- package/src/page-visits/index.ts +6 -0
- package/src/page-visits/page-visits.test.ts +562 -0
- package/src/page-visits/page-visits.ts +314 -0
- package/src/page-visits/types.ts +119 -0
- package/src/scroll-depth/index.ts +6 -0
- package/src/scroll-depth/scroll-depth.test.ts +545 -0
- package/src/scroll-depth/scroll-depth.ts +400 -0
- package/src/scroll-depth/types.ts +122 -0
- package/src/time-delay/index.ts +6 -0
- package/src/time-delay/time-delay.test.ts +477 -0
- package/src/time-delay/time-delay.ts +297 -0
- package/src/time-delay/types.ts +89 -0
- package/src/utils/sanitize.ts +1 -1
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Visits Plugin
|
|
3
|
+
*
|
|
4
|
+
* Generic page visit tracking for any SDK built on sdk-kit.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Session-scoped counter (sessionStorage)
|
|
8
|
+
* - Lifetime counter with timestamps (localStorage)
|
|
9
|
+
* - First-visit detection
|
|
10
|
+
* - DNT (Do Not Track) support
|
|
11
|
+
* - GDPR-compliant expiration
|
|
12
|
+
* - Auto-loads storage plugin if missing
|
|
13
|
+
*
|
|
14
|
+
* Events emitted:
|
|
15
|
+
* - 'pageVisits:incremented' with PageVisitsEvent
|
|
16
|
+
* - 'pageVisits:reset'
|
|
17
|
+
* - 'pageVisits:disabled' with { reason: 'dnt' | 'config' }
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* import { SDK } from '@lytics/sdk-kit';
|
|
22
|
+
* import { storagePlugin, pageVisitsPlugin } from '@lytics/sdk-kit-plugins';
|
|
23
|
+
*
|
|
24
|
+
* const sdk = new SDK({
|
|
25
|
+
* pageVisits: {
|
|
26
|
+
* enabled: true,
|
|
27
|
+
* respectDNT: true,
|
|
28
|
+
* ttl: 31536000 // 1 year
|
|
29
|
+
* }
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* sdk.use(storagePlugin);
|
|
33
|
+
* sdk.use(pageVisitsPlugin);
|
|
34
|
+
*
|
|
35
|
+
* // Listen to visit events
|
|
36
|
+
* sdk.on('pageVisits:incremented', (event) => {
|
|
37
|
+
* console.log('Visit count:', event.totalVisits);
|
|
38
|
+
* if (event.isFirstVisit) {
|
|
39
|
+
* console.log('Welcome, first-time visitor!');
|
|
40
|
+
* }
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* // API methods
|
|
44
|
+
* console.log(sdk.pageVisits.getTotalCount()); // 5
|
|
45
|
+
* console.log(sdk.pageVisits.getSessionCount()); // 2
|
|
46
|
+
* console.log(sdk.pageVisits.isFirstVisit()); // false
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
import type { PluginFunction, SDK } from '@lytics/sdk-kit';
|
|
51
|
+
import { type StoragePlugin, storagePlugin } from '@lytics/sdk-kit-plugins';
|
|
52
|
+
import type { PageVisitsEvent, PageVisitsPlugin } from './types';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Storage data format for lifetime visits
|
|
56
|
+
*/
|
|
57
|
+
interface TotalData {
|
|
58
|
+
count: number;
|
|
59
|
+
first: number; // Timestamp
|
|
60
|
+
last: number; // Timestamp
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Pure function: Check if Do Not Track is enabled
|
|
65
|
+
*/
|
|
66
|
+
export function respectsDNT(): boolean {
|
|
67
|
+
if (typeof navigator === 'undefined') return false;
|
|
68
|
+
return (
|
|
69
|
+
navigator.doNotTrack === '1' ||
|
|
70
|
+
(navigator as any).msDoNotTrack === '1' ||
|
|
71
|
+
(window as any).doNotTrack === '1'
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Pure function: Build storage key with optional prefix
|
|
77
|
+
*/
|
|
78
|
+
export function buildStorageKey(key: string, prefix?: string): string {
|
|
79
|
+
return prefix ? `${prefix}${key}` : key;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Pure function: Create PageVisitsEvent payload
|
|
84
|
+
*/
|
|
85
|
+
export function createVisitsEvent(
|
|
86
|
+
isFirstVisit: boolean,
|
|
87
|
+
totalVisits: number,
|
|
88
|
+
sessionVisits: number,
|
|
89
|
+
firstVisitTime: number | undefined,
|
|
90
|
+
lastVisitTime: number | undefined,
|
|
91
|
+
timestamp: number
|
|
92
|
+
): PageVisitsEvent {
|
|
93
|
+
return {
|
|
94
|
+
isFirstVisit,
|
|
95
|
+
totalVisits,
|
|
96
|
+
sessionVisits,
|
|
97
|
+
firstVisitTime,
|
|
98
|
+
lastVisitTime,
|
|
99
|
+
timestamp,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const pageVisitsPlugin: PluginFunction = (plugin, instance, config) => {
|
|
104
|
+
plugin.ns('pageVisits');
|
|
105
|
+
|
|
106
|
+
// Set defaults
|
|
107
|
+
plugin.defaults({
|
|
108
|
+
pageVisits: {
|
|
109
|
+
enabled: true,
|
|
110
|
+
respectDNT: true,
|
|
111
|
+
sessionKey: 'pageVisits:session',
|
|
112
|
+
totalKey: 'pageVisits:total',
|
|
113
|
+
ttl: undefined,
|
|
114
|
+
autoIncrement: true,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Auto-load storage plugin if not present
|
|
119
|
+
if (!(instance as SDK & { storage?: StoragePlugin }).storage) {
|
|
120
|
+
console.warn('[PageVisits] Storage plugin not found, auto-loading...');
|
|
121
|
+
instance.use(storagePlugin);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Cast instance to include storage
|
|
125
|
+
const sdkInstance = instance as SDK & { storage: StoragePlugin };
|
|
126
|
+
|
|
127
|
+
// Internal state
|
|
128
|
+
let sessionCount = 0;
|
|
129
|
+
let totalCount = 0;
|
|
130
|
+
let firstVisitTime: number | undefined;
|
|
131
|
+
let lastVisitTime: number | undefined;
|
|
132
|
+
let isFirstVisitFlag = false;
|
|
133
|
+
let initialized = false;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Load existing visit data from storage
|
|
137
|
+
*/
|
|
138
|
+
function loadData(): void {
|
|
139
|
+
const sessionKey = config.get('pageVisits.sessionKey') ?? 'pageVisits:session';
|
|
140
|
+
const totalKey = config.get('pageVisits.totalKey') ?? 'pageVisits:total';
|
|
141
|
+
|
|
142
|
+
// Load session count
|
|
143
|
+
const storedSession = sdkInstance.storage.get<number>(sessionKey, {
|
|
144
|
+
backend: 'sessionStorage',
|
|
145
|
+
});
|
|
146
|
+
sessionCount = storedSession ?? 0;
|
|
147
|
+
|
|
148
|
+
// Load total data
|
|
149
|
+
const storedTotal = sdkInstance.storage.get<TotalData>(totalKey, {
|
|
150
|
+
backend: 'localStorage',
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (storedTotal) {
|
|
154
|
+
totalCount = storedTotal.count ?? 0;
|
|
155
|
+
firstVisitTime = storedTotal.first;
|
|
156
|
+
lastVisitTime = storedTotal.last;
|
|
157
|
+
isFirstVisitFlag = false;
|
|
158
|
+
} else {
|
|
159
|
+
totalCount = 0;
|
|
160
|
+
firstVisitTime = undefined;
|
|
161
|
+
lastVisitTime = undefined;
|
|
162
|
+
isFirstVisitFlag = true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Save visit data to storage
|
|
168
|
+
*/
|
|
169
|
+
function saveData(): void {
|
|
170
|
+
const sessionKey = config.get('pageVisits.sessionKey') ?? 'pageVisits:session';
|
|
171
|
+
const totalKey = config.get('pageVisits.totalKey') ?? 'pageVisits:total';
|
|
172
|
+
const ttl = config.get('pageVisits.ttl');
|
|
173
|
+
|
|
174
|
+
// Save session count
|
|
175
|
+
sdkInstance.storage.set(sessionKey, sessionCount, {
|
|
176
|
+
backend: 'sessionStorage',
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Save total data
|
|
180
|
+
const totalData: TotalData = {
|
|
181
|
+
count: totalCount,
|
|
182
|
+
first: firstVisitTime ?? Date.now(),
|
|
183
|
+
last: lastVisitTime ?? Date.now(),
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
sdkInstance.storage.set(totalKey, totalData, {
|
|
187
|
+
backend: 'localStorage',
|
|
188
|
+
...(ttl && { ttl }),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Increment visit counters
|
|
194
|
+
*/
|
|
195
|
+
function increment(): void {
|
|
196
|
+
if (!initialized) {
|
|
197
|
+
loadData();
|
|
198
|
+
initialized = true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Increment counters
|
|
202
|
+
sessionCount += 1;
|
|
203
|
+
totalCount += 1;
|
|
204
|
+
const now = Date.now();
|
|
205
|
+
|
|
206
|
+
// Set first visit time if needed
|
|
207
|
+
if (isFirstVisitFlag) {
|
|
208
|
+
firstVisitTime = now;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Update last visit time
|
|
212
|
+
lastVisitTime = now;
|
|
213
|
+
|
|
214
|
+
// Save to storage
|
|
215
|
+
saveData();
|
|
216
|
+
|
|
217
|
+
// Emit event using pure function
|
|
218
|
+
const event = createVisitsEvent(
|
|
219
|
+
isFirstVisitFlag,
|
|
220
|
+
totalCount,
|
|
221
|
+
sessionCount,
|
|
222
|
+
firstVisitTime,
|
|
223
|
+
lastVisitTime,
|
|
224
|
+
now
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
plugin.emit('pageVisits:incremented', event);
|
|
228
|
+
|
|
229
|
+
// After first increment, no longer first visit
|
|
230
|
+
if (isFirstVisitFlag) {
|
|
231
|
+
isFirstVisitFlag = false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Reset all data
|
|
237
|
+
*/
|
|
238
|
+
function reset(): void {
|
|
239
|
+
const sessionKey = config.get('pageVisits.sessionKey') ?? 'pageVisits:session';
|
|
240
|
+
const totalKey = config.get('pageVisits.totalKey') ?? 'pageVisits:total';
|
|
241
|
+
|
|
242
|
+
// Clear storage
|
|
243
|
+
sdkInstance.storage.remove(sessionKey, { backend: 'sessionStorage' });
|
|
244
|
+
sdkInstance.storage.remove(totalKey, { backend: 'localStorage' });
|
|
245
|
+
|
|
246
|
+
// Reset state
|
|
247
|
+
sessionCount = 0;
|
|
248
|
+
totalCount = 0;
|
|
249
|
+
firstVisitTime = undefined;
|
|
250
|
+
lastVisitTime = undefined;
|
|
251
|
+
isFirstVisitFlag = false;
|
|
252
|
+
initialized = false;
|
|
253
|
+
|
|
254
|
+
// Emit event
|
|
255
|
+
plugin.emit('pageVisits:reset');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Get current state
|
|
260
|
+
*/
|
|
261
|
+
function getState(): PageVisitsEvent {
|
|
262
|
+
return createVisitsEvent(
|
|
263
|
+
isFirstVisitFlag,
|
|
264
|
+
totalCount,
|
|
265
|
+
sessionCount,
|
|
266
|
+
firstVisitTime,
|
|
267
|
+
lastVisitTime,
|
|
268
|
+
Date.now()
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Initialize plugin
|
|
274
|
+
*/
|
|
275
|
+
function initialize(): void {
|
|
276
|
+
const enabled = config.get('pageVisits.enabled') ?? true;
|
|
277
|
+
const respectDNTConfig = config.get('pageVisits.respectDNT') ?? true;
|
|
278
|
+
const autoIncrement = config.get('pageVisits.autoIncrement') ?? true;
|
|
279
|
+
|
|
280
|
+
// Check DNT using pure function
|
|
281
|
+
if (respectDNTConfig && respectsDNT()) {
|
|
282
|
+
plugin.emit('pageVisits:disabled', { reason: 'dnt' });
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Check enabled
|
|
287
|
+
if (!enabled) {
|
|
288
|
+
plugin.emit('pageVisits:disabled', { reason: 'config' });
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Auto-increment on load
|
|
293
|
+
if (autoIncrement) {
|
|
294
|
+
increment();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Initialize on SDK ready
|
|
299
|
+
instance.on('sdk:ready', initialize);
|
|
300
|
+
|
|
301
|
+
// Expose public API
|
|
302
|
+
plugin.expose({
|
|
303
|
+
pageVisits: {
|
|
304
|
+
getTotalCount: () => totalCount,
|
|
305
|
+
getSessionCount: () => sessionCount,
|
|
306
|
+
isFirstVisit: () => isFirstVisitFlag,
|
|
307
|
+
getFirstVisitTime: () => firstVisitTime,
|
|
308
|
+
getLastVisitTime: () => lastVisitTime,
|
|
309
|
+
increment,
|
|
310
|
+
reset,
|
|
311
|
+
getState,
|
|
312
|
+
} satisfies PageVisitsPlugin,
|
|
313
|
+
});
|
|
314
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Visits Plugin Types
|
|
3
|
+
*
|
|
4
|
+
* Generic page visit tracking for any SDK built on sdk-kit.
|
|
5
|
+
* Tracks session and lifetime visit counts with first-visit detection.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Page visits plugin configuration
|
|
10
|
+
*/
|
|
11
|
+
export interface PageVisitsPluginConfig {
|
|
12
|
+
pageVisits?: {
|
|
13
|
+
/**
|
|
14
|
+
* Enable/disable page visit tracking
|
|
15
|
+
* @default true
|
|
16
|
+
*/
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Honor Do Not Track browser setting
|
|
21
|
+
* @default true
|
|
22
|
+
*/
|
|
23
|
+
respectDNT?: boolean;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Storage key for session count
|
|
27
|
+
* @default 'pageVisits:session'
|
|
28
|
+
*/
|
|
29
|
+
sessionKey?: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Storage key for lifetime data
|
|
33
|
+
* @default 'pageVisits:total'
|
|
34
|
+
*/
|
|
35
|
+
totalKey?: string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* TTL for lifetime data in seconds (GDPR compliance)
|
|
39
|
+
* @default undefined (no expiration)
|
|
40
|
+
*/
|
|
41
|
+
ttl?: number;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Automatically increment on plugin load
|
|
45
|
+
* @default true
|
|
46
|
+
*/
|
|
47
|
+
autoIncrement?: boolean;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Page visits event payload
|
|
53
|
+
*/
|
|
54
|
+
export interface PageVisitsEvent {
|
|
55
|
+
/** Whether this is the user's first visit ever */
|
|
56
|
+
isFirstVisit: boolean;
|
|
57
|
+
|
|
58
|
+
/** Total visits across all sessions (lifetime) */
|
|
59
|
+
totalVisits: number;
|
|
60
|
+
|
|
61
|
+
/** Visits in current session */
|
|
62
|
+
sessionVisits: number;
|
|
63
|
+
|
|
64
|
+
/** Timestamp of first visit (unix ms) */
|
|
65
|
+
firstVisitTime?: number;
|
|
66
|
+
|
|
67
|
+
/** Timestamp of last visit (unix ms) */
|
|
68
|
+
lastVisitTime?: number;
|
|
69
|
+
|
|
70
|
+
/** Timestamp of current visit (unix ms) */
|
|
71
|
+
timestamp: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Page visits plugin API
|
|
76
|
+
*/
|
|
77
|
+
export interface PageVisitsPlugin {
|
|
78
|
+
/**
|
|
79
|
+
* Get total visit count (lifetime)
|
|
80
|
+
*/
|
|
81
|
+
getTotalCount(): number;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get session visit count
|
|
85
|
+
*/
|
|
86
|
+
getSessionCount(): number;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check if this is the first visit
|
|
90
|
+
*/
|
|
91
|
+
isFirstVisit(): boolean;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get timestamp of first visit
|
|
95
|
+
*/
|
|
96
|
+
getFirstVisitTime(): number | undefined;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get timestamp of last visit
|
|
100
|
+
*/
|
|
101
|
+
getLastVisitTime(): number | undefined;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Manually increment page visit
|
|
105
|
+
* (useful if autoIncrement is disabled)
|
|
106
|
+
*/
|
|
107
|
+
increment(): void;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Reset all counters and data
|
|
111
|
+
* (useful for testing or user opt-out)
|
|
112
|
+
*/
|
|
113
|
+
reset(): void;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get full page visits state
|
|
117
|
+
*/
|
|
118
|
+
getState(): PageVisitsEvent;
|
|
119
|
+
}
|