@prosdevlab/experience-sdk-plugins 0.1.3 → 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 +56 -0
- package/dist/index.d.ts +626 -2
- package/dist/index.js +799 -49
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/banner/banner.ts +149 -51
- 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
package/dist/index.d.ts
CHANGED
|
@@ -117,6 +117,7 @@ interface BannerPluginConfig {
|
|
|
117
117
|
position?: 'top' | 'bottom';
|
|
118
118
|
dismissable?: boolean;
|
|
119
119
|
zIndex?: number;
|
|
120
|
+
pushDown?: string;
|
|
120
121
|
};
|
|
121
122
|
}
|
|
122
123
|
interface BannerPlugin {
|
|
@@ -134,7 +135,23 @@ interface BannerPlugin {
|
|
|
134
135
|
* import { createInstance } from '@prosdevlab/experience-sdk';
|
|
135
136
|
* import { bannerPlugin } from '@prosdevlab/experience-sdk-plugins';
|
|
136
137
|
*
|
|
137
|
-
*
|
|
138
|
+
* // Basic usage (banner overlays at top)
|
|
139
|
+
* const sdk = createInstance({
|
|
140
|
+
* banner: {
|
|
141
|
+
* position: 'top',
|
|
142
|
+
* dismissable: true
|
|
143
|
+
* }
|
|
144
|
+
* });
|
|
145
|
+
* sdk.use(bannerPlugin);
|
|
146
|
+
*
|
|
147
|
+
* // With pushDown (pushes navigation down instead of overlaying)
|
|
148
|
+
* const sdk = createInstance({
|
|
149
|
+
* banner: {
|
|
150
|
+
* position: 'top',
|
|
151
|
+
* dismissable: true,
|
|
152
|
+
* pushDown: 'header' // CSS selector of element to push down
|
|
153
|
+
* }
|
|
154
|
+
* });
|
|
138
155
|
* sdk.use(bannerPlugin);
|
|
139
156
|
* ```
|
|
140
157
|
*/
|
|
@@ -175,6 +192,140 @@ interface DebugPlugin {
|
|
|
175
192
|
*/
|
|
176
193
|
declare const debugPlugin: PluginFunction;
|
|
177
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Exit Intent Plugin Configuration
|
|
197
|
+
*/
|
|
198
|
+
interface ExitIntentPluginConfig {
|
|
199
|
+
exitIntent?: {
|
|
200
|
+
/**
|
|
201
|
+
* Maximum Y position (px) where exit intent can trigger
|
|
202
|
+
* @default 50
|
|
203
|
+
*/
|
|
204
|
+
sensitivity?: number;
|
|
205
|
+
/**
|
|
206
|
+
* Minimum time on page (ms) before exit intent is active
|
|
207
|
+
* Prevents immediate triggers on page load
|
|
208
|
+
* @default 2000
|
|
209
|
+
*/
|
|
210
|
+
minTimeOnPage?: number;
|
|
211
|
+
/**
|
|
212
|
+
* Delay (ms) between detection and trigger
|
|
213
|
+
* @default 0
|
|
214
|
+
*/
|
|
215
|
+
delay?: number;
|
|
216
|
+
/**
|
|
217
|
+
* Number of mouse positions to track for velocity calculation
|
|
218
|
+
* @default 30
|
|
219
|
+
*/
|
|
220
|
+
positionHistorySize?: number;
|
|
221
|
+
/**
|
|
222
|
+
* Disable exit intent on mobile devices
|
|
223
|
+
* @default true
|
|
224
|
+
*/
|
|
225
|
+
disableOnMobile?: boolean;
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Exit Intent Event Payload
|
|
230
|
+
*/
|
|
231
|
+
interface ExitIntentEvent {
|
|
232
|
+
timestamp: number;
|
|
233
|
+
lastY: number;
|
|
234
|
+
previousY: number;
|
|
235
|
+
velocity: number;
|
|
236
|
+
timeOnPage: number;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Exit Intent Plugin API
|
|
240
|
+
*/
|
|
241
|
+
interface ExitIntentPlugin {
|
|
242
|
+
isTriggered(): boolean;
|
|
243
|
+
reset(): void;
|
|
244
|
+
getPositions(): Array<{
|
|
245
|
+
x: number;
|
|
246
|
+
y: number;
|
|
247
|
+
}>;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Exit Intent Plugin
|
|
252
|
+
*
|
|
253
|
+
* Detects when users are about to leave the page by tracking upward mouse movement
|
|
254
|
+
* near the top of the viewport. Inspired by Pathfora's showOnExitIntent.
|
|
255
|
+
*
|
|
256
|
+
* **Event-Driven Architecture:**
|
|
257
|
+
* This plugin emits `trigger:exitIntent` events when exit intent is detected.
|
|
258
|
+
* The core runtime listens for these events and automatically re-evaluates experiences.
|
|
259
|
+
*
|
|
260
|
+
* **Usage Pattern:**
|
|
261
|
+
* Use `targeting.custom` to check if exit intent has triggered:
|
|
262
|
+
*
|
|
263
|
+
* @example Basic usage
|
|
264
|
+
* ```typescript
|
|
265
|
+
* import { init, register } from '@prosdevlab/experience-sdk';
|
|
266
|
+
* import { exitIntentPlugin } from '@prosdevlab/experience-sdk-plugins';
|
|
267
|
+
*
|
|
268
|
+
* init({
|
|
269
|
+
* plugins: [exitIntentPlugin],
|
|
270
|
+
* exitIntent: {
|
|
271
|
+
* sensitivity: 20, // Trigger within 20px of top (default: 50)
|
|
272
|
+
* minTimeOnPage: 2000, // Wait 2s before enabling (default: 2000)
|
|
273
|
+
* delay: 0, // Delay after trigger (default: 0)
|
|
274
|
+
* disableOnMobile: true // Disable on mobile (default: true)
|
|
275
|
+
* }
|
|
276
|
+
* });
|
|
277
|
+
*
|
|
278
|
+
* // Show banner only when exit intent is detected
|
|
279
|
+
* register('exit-offer', {
|
|
280
|
+
* type: 'banner',
|
|
281
|
+
* content: {
|
|
282
|
+
* title: 'Wait! Don't leave yet!',
|
|
283
|
+
* message: 'Get 15% off your first order',
|
|
284
|
+
* buttons: [{ text: 'Claim Offer', variant: 'primary' }]
|
|
285
|
+
* },
|
|
286
|
+
* targeting: {
|
|
287
|
+
* custom: (context) => context.triggers?.exitIntent?.triggered === true
|
|
288
|
+
* },
|
|
289
|
+
* frequency: { max: 1, per: 'session' } // Only show once per session
|
|
290
|
+
* });
|
|
291
|
+
* ```
|
|
292
|
+
*
|
|
293
|
+
* @example Combining with other conditions
|
|
294
|
+
* ```typescript
|
|
295
|
+
* // Show exit offer only on shop pages with items in cart
|
|
296
|
+
* register('cart-recovery', {
|
|
297
|
+
* type: 'banner',
|
|
298
|
+
* content: { message: 'Complete your purchase and save!' },
|
|
299
|
+
* targeting: {
|
|
300
|
+
* url: { contains: '/shop' },
|
|
301
|
+
* custom: (context) => {
|
|
302
|
+
* return (
|
|
303
|
+
* context.triggers?.exitIntent?.triggered === true &&
|
|
304
|
+
* getCart().items.length > 0
|
|
305
|
+
* );
|
|
306
|
+
* }
|
|
307
|
+
* }
|
|
308
|
+
* });
|
|
309
|
+
* ```
|
|
310
|
+
*
|
|
311
|
+
* @example Combining multiple triggers (exit intent + scroll depth)
|
|
312
|
+
* ```typescript
|
|
313
|
+
* // Show offer on exit intent OR after 70% scroll
|
|
314
|
+
* register('engaged-exit', {
|
|
315
|
+
* type: 'banner',
|
|
316
|
+
* content: { message: 'You're almost there!' },
|
|
317
|
+
* targeting: {
|
|
318
|
+
* custom: (context) => {
|
|
319
|
+
* const exitIntent = context.triggers?.exitIntent?.triggered;
|
|
320
|
+
* const scrolled = (context.triggers?.scrollDepth?.percent || 0) >= 70;
|
|
321
|
+
* return exitIntent || scrolled;
|
|
322
|
+
* }
|
|
323
|
+
* }
|
|
324
|
+
* });
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
declare const exitIntentPlugin: PluginFunction;
|
|
328
|
+
|
|
178
329
|
/**
|
|
179
330
|
* Frequency Capping Plugin
|
|
180
331
|
*
|
|
@@ -210,4 +361,477 @@ interface FrequencyPlugin {
|
|
|
210
361
|
*/
|
|
211
362
|
declare const frequencyPlugin: PluginFunction;
|
|
212
363
|
|
|
213
|
-
|
|
364
|
+
/**
|
|
365
|
+
* Page Visits Plugin Types
|
|
366
|
+
*
|
|
367
|
+
* Generic page visit tracking for any SDK built on sdk-kit.
|
|
368
|
+
* Tracks session and lifetime visit counts with first-visit detection.
|
|
369
|
+
*/
|
|
370
|
+
/**
|
|
371
|
+
* Page visits plugin configuration
|
|
372
|
+
*/
|
|
373
|
+
interface PageVisitsPluginConfig {
|
|
374
|
+
pageVisits?: {
|
|
375
|
+
/**
|
|
376
|
+
* Enable/disable page visit tracking
|
|
377
|
+
* @default true
|
|
378
|
+
*/
|
|
379
|
+
enabled?: boolean;
|
|
380
|
+
/**
|
|
381
|
+
* Honor Do Not Track browser setting
|
|
382
|
+
* @default true
|
|
383
|
+
*/
|
|
384
|
+
respectDNT?: boolean;
|
|
385
|
+
/**
|
|
386
|
+
* Storage key for session count
|
|
387
|
+
* @default 'pageVisits:session'
|
|
388
|
+
*/
|
|
389
|
+
sessionKey?: string;
|
|
390
|
+
/**
|
|
391
|
+
* Storage key for lifetime data
|
|
392
|
+
* @default 'pageVisits:total'
|
|
393
|
+
*/
|
|
394
|
+
totalKey?: string;
|
|
395
|
+
/**
|
|
396
|
+
* TTL for lifetime data in seconds (GDPR compliance)
|
|
397
|
+
* @default undefined (no expiration)
|
|
398
|
+
*/
|
|
399
|
+
ttl?: number;
|
|
400
|
+
/**
|
|
401
|
+
* Automatically increment on plugin load
|
|
402
|
+
* @default true
|
|
403
|
+
*/
|
|
404
|
+
autoIncrement?: boolean;
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Page visits event payload
|
|
409
|
+
*/
|
|
410
|
+
interface PageVisitsEvent {
|
|
411
|
+
/** Whether this is the user's first visit ever */
|
|
412
|
+
isFirstVisit: boolean;
|
|
413
|
+
/** Total visits across all sessions (lifetime) */
|
|
414
|
+
totalVisits: number;
|
|
415
|
+
/** Visits in current session */
|
|
416
|
+
sessionVisits: number;
|
|
417
|
+
/** Timestamp of first visit (unix ms) */
|
|
418
|
+
firstVisitTime?: number;
|
|
419
|
+
/** Timestamp of last visit (unix ms) */
|
|
420
|
+
lastVisitTime?: number;
|
|
421
|
+
/** Timestamp of current visit (unix ms) */
|
|
422
|
+
timestamp: number;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Page visits plugin API
|
|
426
|
+
*/
|
|
427
|
+
interface PageVisitsPlugin {
|
|
428
|
+
/**
|
|
429
|
+
* Get total visit count (lifetime)
|
|
430
|
+
*/
|
|
431
|
+
getTotalCount(): number;
|
|
432
|
+
/**
|
|
433
|
+
* Get session visit count
|
|
434
|
+
*/
|
|
435
|
+
getSessionCount(): number;
|
|
436
|
+
/**
|
|
437
|
+
* Check if this is the first visit
|
|
438
|
+
*/
|
|
439
|
+
isFirstVisit(): boolean;
|
|
440
|
+
/**
|
|
441
|
+
* Get timestamp of first visit
|
|
442
|
+
*/
|
|
443
|
+
getFirstVisitTime(): number | undefined;
|
|
444
|
+
/**
|
|
445
|
+
* Get timestamp of last visit
|
|
446
|
+
*/
|
|
447
|
+
getLastVisitTime(): number | undefined;
|
|
448
|
+
/**
|
|
449
|
+
* Manually increment page visit
|
|
450
|
+
* (useful if autoIncrement is disabled)
|
|
451
|
+
*/
|
|
452
|
+
increment(): void;
|
|
453
|
+
/**
|
|
454
|
+
* Reset all counters and data
|
|
455
|
+
* (useful for testing or user opt-out)
|
|
456
|
+
*/
|
|
457
|
+
reset(): void;
|
|
458
|
+
/**
|
|
459
|
+
* Get full page visits state
|
|
460
|
+
*/
|
|
461
|
+
getState(): PageVisitsEvent;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Page Visits Plugin
|
|
466
|
+
*
|
|
467
|
+
* Generic page visit tracking for any SDK built on sdk-kit.
|
|
468
|
+
*
|
|
469
|
+
* Features:
|
|
470
|
+
* - Session-scoped counter (sessionStorage)
|
|
471
|
+
* - Lifetime counter with timestamps (localStorage)
|
|
472
|
+
* - First-visit detection
|
|
473
|
+
* - DNT (Do Not Track) support
|
|
474
|
+
* - GDPR-compliant expiration
|
|
475
|
+
* - Auto-loads storage plugin if missing
|
|
476
|
+
*
|
|
477
|
+
* Events emitted:
|
|
478
|
+
* - 'pageVisits:incremented' with PageVisitsEvent
|
|
479
|
+
* - 'pageVisits:reset'
|
|
480
|
+
* - 'pageVisits:disabled' with { reason: 'dnt' | 'config' }
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* ```typescript
|
|
484
|
+
* import { SDK } from '@lytics/sdk-kit';
|
|
485
|
+
* import { storagePlugin, pageVisitsPlugin } from '@lytics/sdk-kit-plugins';
|
|
486
|
+
*
|
|
487
|
+
* const sdk = new SDK({
|
|
488
|
+
* pageVisits: {
|
|
489
|
+
* enabled: true,
|
|
490
|
+
* respectDNT: true,
|
|
491
|
+
* ttl: 31536000 // 1 year
|
|
492
|
+
* }
|
|
493
|
+
* });
|
|
494
|
+
*
|
|
495
|
+
* sdk.use(storagePlugin);
|
|
496
|
+
* sdk.use(pageVisitsPlugin);
|
|
497
|
+
*
|
|
498
|
+
* // Listen to visit events
|
|
499
|
+
* sdk.on('pageVisits:incremented', (event) => {
|
|
500
|
+
* console.log('Visit count:', event.totalVisits);
|
|
501
|
+
* if (event.isFirstVisit) {
|
|
502
|
+
* console.log('Welcome, first-time visitor!');
|
|
503
|
+
* }
|
|
504
|
+
* });
|
|
505
|
+
*
|
|
506
|
+
* // API methods
|
|
507
|
+
* console.log(sdk.pageVisits.getTotalCount()); // 5
|
|
508
|
+
* console.log(sdk.pageVisits.getSessionCount()); // 2
|
|
509
|
+
* console.log(sdk.pageVisits.isFirstVisit()); // false
|
|
510
|
+
* ```
|
|
511
|
+
*/
|
|
512
|
+
|
|
513
|
+
declare const pageVisitsPlugin: PluginFunction;
|
|
514
|
+
|
|
515
|
+
/** @module scrollDepthPlugin */
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Scroll Depth Plugin
|
|
519
|
+
*
|
|
520
|
+
* Tracks scroll depth and emits `trigger:scrollDepth` events when thresholds are crossed.
|
|
521
|
+
*
|
|
522
|
+
* ## How It Works
|
|
523
|
+
*
|
|
524
|
+
* 1. **Detection**: Listens to `scroll` events (throttled)
|
|
525
|
+
* 2. **Calculation**: Calculates current scroll percentage
|
|
526
|
+
* 3. **Tracking**: Tracks maximum scroll depth and threshold crossings
|
|
527
|
+
* 4. **Emission**: Emits `trigger:scrollDepth` events when thresholds are crossed
|
|
528
|
+
*
|
|
529
|
+
* ## Configuration
|
|
530
|
+
*
|
|
531
|
+
* ```typescript
|
|
532
|
+
* init({
|
|
533
|
+
* scrollDepth: {
|
|
534
|
+
* thresholds: [25, 50, 75, 100], // Percentages to track
|
|
535
|
+
* throttle: 100, // Throttle interval (ms)
|
|
536
|
+
* includeViewportHeight: true, // Calculation method
|
|
537
|
+
* recalculateOnResize: true // Recalculate on resize
|
|
538
|
+
* }
|
|
539
|
+
* });
|
|
540
|
+
* ```
|
|
541
|
+
*
|
|
542
|
+
* ## Experience Targeting
|
|
543
|
+
*
|
|
544
|
+
* ```typescript
|
|
545
|
+
* register('mid-article-cta', {
|
|
546
|
+
* type: 'banner',
|
|
547
|
+
* content: { message: 'Enjoying the article?' },
|
|
548
|
+
* targeting: {
|
|
549
|
+
* custom: (ctx) => (ctx.triggers?.scrollDepth?.percent || 0) >= 50
|
|
550
|
+
* }
|
|
551
|
+
* });
|
|
552
|
+
* ```
|
|
553
|
+
*
|
|
554
|
+
* ## API Methods
|
|
555
|
+
*
|
|
556
|
+
* ```typescript
|
|
557
|
+
* // Get maximum scroll percentage reached
|
|
558
|
+
* instance.scrollDepth.getMaxPercent(); // 73
|
|
559
|
+
*
|
|
560
|
+
* // Get current scroll percentage
|
|
561
|
+
* instance.scrollDepth.getCurrentPercent(); // 50
|
|
562
|
+
*
|
|
563
|
+
* // Get all crossed thresholds
|
|
564
|
+
* instance.scrollDepth.getThresholdsCrossed(); // [25, 50]
|
|
565
|
+
*
|
|
566
|
+
* // Reset tracking (useful for testing)
|
|
567
|
+
* instance.scrollDepth.reset();
|
|
568
|
+
* ```
|
|
569
|
+
*
|
|
570
|
+
* @param plugin Plugin interface from sdk-kit
|
|
571
|
+
* @param instance SDK instance
|
|
572
|
+
* @param config SDK configuration
|
|
573
|
+
*/
|
|
574
|
+
declare const scrollDepthPlugin: PluginFunction;
|
|
575
|
+
|
|
576
|
+
/** @module scrollDepthPlugin */
|
|
577
|
+
/**
|
|
578
|
+
* Scroll Depth Plugin API
|
|
579
|
+
*/
|
|
580
|
+
interface ScrollDepthPlugin {
|
|
581
|
+
getMaxPercent(): number;
|
|
582
|
+
getCurrentPercent(): number;
|
|
583
|
+
getThresholdsCrossed(): number[];
|
|
584
|
+
getDevice(): 'mobile' | 'tablet' | 'desktop';
|
|
585
|
+
getAdvancedMetrics(): {
|
|
586
|
+
timeOnPage: number;
|
|
587
|
+
directionChanges: number;
|
|
588
|
+
timeScrollingUp: number;
|
|
589
|
+
thresholdTimes: Record<number, number>;
|
|
590
|
+
} | null;
|
|
591
|
+
reset(): void;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Scroll Depth Plugin Configuration
|
|
595
|
+
*
|
|
596
|
+
* Tracks scroll depth and emits trigger:scrollDepth events when thresholds are crossed.
|
|
597
|
+
*/
|
|
598
|
+
interface ScrollDepthPluginConfig {
|
|
599
|
+
scrollDepth?: {
|
|
600
|
+
/**
|
|
601
|
+
* Array of scroll percentage thresholds to track (0-100).
|
|
602
|
+
* When user scrolls past a threshold, a trigger:scrollDepth event is emitted.
|
|
603
|
+
* @default [25, 50, 75, 100]
|
|
604
|
+
* @example [50, 100]
|
|
605
|
+
*/
|
|
606
|
+
thresholds?: number[];
|
|
607
|
+
/**
|
|
608
|
+
* Throttle interval in milliseconds for scroll event handler.
|
|
609
|
+
* Lower values are more responsive but impact performance.
|
|
610
|
+
* @default 100
|
|
611
|
+
* @example 200
|
|
612
|
+
*/
|
|
613
|
+
throttle?: number;
|
|
614
|
+
/**
|
|
615
|
+
* Include viewport height in scroll percentage calculation.
|
|
616
|
+
*
|
|
617
|
+
* - true: (scrollTop + viewportHeight) / totalHeight
|
|
618
|
+
* More intuitive: 100% when bottom of viewport reaches end
|
|
619
|
+
* - false: scrollTop / (totalHeight - viewportHeight)
|
|
620
|
+
* Pathfora's method: 100% when top of viewport reaches end
|
|
621
|
+
*
|
|
622
|
+
* @default true
|
|
623
|
+
*/
|
|
624
|
+
includeViewportHeight?: boolean;
|
|
625
|
+
/**
|
|
626
|
+
* Recalculate scroll on window resize.
|
|
627
|
+
* Useful for responsive layouts where content height changes.
|
|
628
|
+
* @default true
|
|
629
|
+
*/
|
|
630
|
+
recalculateOnResize?: boolean;
|
|
631
|
+
/**
|
|
632
|
+
* Track advanced metrics (velocity, direction, time-to-threshold).
|
|
633
|
+
* Enables advanced engagement quality analysis.
|
|
634
|
+
* Slight performance overhead but provides rich insights.
|
|
635
|
+
* @default false
|
|
636
|
+
*/
|
|
637
|
+
trackAdvancedMetrics?: boolean;
|
|
638
|
+
/**
|
|
639
|
+
* Velocity threshold (px/ms) to consider "fast scrolling".
|
|
640
|
+
* Fast scrolling often indicates skimming rather than reading.
|
|
641
|
+
* Only used when trackAdvancedMetrics is true.
|
|
642
|
+
* @default 3
|
|
643
|
+
*/
|
|
644
|
+
fastScrollVelocityThreshold?: number;
|
|
645
|
+
/**
|
|
646
|
+
* Disable scroll tracking on mobile devices.
|
|
647
|
+
* Useful since mobile scroll behavior differs significantly from desktop.
|
|
648
|
+
* @default false
|
|
649
|
+
*/
|
|
650
|
+
disableOnMobile?: boolean;
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Scroll Depth Event Payload
|
|
655
|
+
*
|
|
656
|
+
* Emitted as `trigger:scrollDepth` when a threshold is crossed.
|
|
657
|
+
*/
|
|
658
|
+
interface ScrollDepthEvent {
|
|
659
|
+
/** Whether the trigger has fired */
|
|
660
|
+
triggered: boolean;
|
|
661
|
+
/** Timestamp when the event was emitted */
|
|
662
|
+
timestamp: number;
|
|
663
|
+
/** Current scroll percentage (0-100) */
|
|
664
|
+
percent: number;
|
|
665
|
+
/** Maximum scroll percentage reached during session */
|
|
666
|
+
maxPercent: number;
|
|
667
|
+
/** The threshold that was just crossed */
|
|
668
|
+
threshold: number;
|
|
669
|
+
/** All thresholds that have been triggered */
|
|
670
|
+
thresholdsCrossed: number[];
|
|
671
|
+
/** Device type (mobile, tablet, desktop) */
|
|
672
|
+
device: 'mobile' | 'tablet' | 'desktop';
|
|
673
|
+
/** Advanced metrics (only present when trackAdvancedMetrics is enabled) */
|
|
674
|
+
advanced?: {
|
|
675
|
+
/** Time in milliseconds to reach this threshold from page load */
|
|
676
|
+
timeToThreshold: number;
|
|
677
|
+
/** Current scroll velocity in pixels per millisecond */
|
|
678
|
+
velocity: number;
|
|
679
|
+
/** Whether user is scrolling fast (indicates skimming) */
|
|
680
|
+
isFastScrolling: boolean;
|
|
681
|
+
/** Number of direction changes (up/down) since last threshold */
|
|
682
|
+
directionChanges: number;
|
|
683
|
+
/** Total time spent scrolling up (indicates seeking behavior) */
|
|
684
|
+
timeScrollingUp: number;
|
|
685
|
+
/** Scroll quality score (0-100, higher = more engaged) */
|
|
686
|
+
engagementScore: number;
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/** @module timeDelayPlugin */
|
|
691
|
+
/**
|
|
692
|
+
* Time Delay Plugin Configuration
|
|
693
|
+
*
|
|
694
|
+
* Tracks time elapsed since SDK initialization and emits trigger:timeDelay events.
|
|
695
|
+
*/
|
|
696
|
+
interface TimeDelayPluginConfig {
|
|
697
|
+
timeDelay?: {
|
|
698
|
+
/**
|
|
699
|
+
* Delay before emitting trigger event (milliseconds).
|
|
700
|
+
* Set to 0 to disable (immediate trigger on init).
|
|
701
|
+
* @default 0
|
|
702
|
+
* @example 5000 // 5 seconds
|
|
703
|
+
*/
|
|
704
|
+
delay?: number;
|
|
705
|
+
/**
|
|
706
|
+
* Pause timer when tab is hidden (Page Visibility API).
|
|
707
|
+
* When true, only counts "active viewing time".
|
|
708
|
+
* When false, timer runs even when tab is hidden.
|
|
709
|
+
* @default true
|
|
710
|
+
*/
|
|
711
|
+
pauseWhenHidden?: boolean;
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Time Delay Event Payload
|
|
716
|
+
*
|
|
717
|
+
* Emitted via 'trigger:timeDelay' when the configured delay is reached.
|
|
718
|
+
*/
|
|
719
|
+
interface TimeDelayEvent {
|
|
720
|
+
/** Timestamp when the trigger event was emitted */
|
|
721
|
+
timestamp: number;
|
|
722
|
+
/** Total elapsed time since init (milliseconds, includes paused time) */
|
|
723
|
+
elapsed: number;
|
|
724
|
+
/** Active elapsed time (milliseconds, excludes time when tab was hidden) */
|
|
725
|
+
activeElapsed: number;
|
|
726
|
+
/** Whether the timer was paused at any point */
|
|
727
|
+
wasPaused: boolean;
|
|
728
|
+
/** Number of times visibility changed (hidden/visible) */
|
|
729
|
+
visibilityChanges: number;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Time Delay Plugin API
|
|
733
|
+
*/
|
|
734
|
+
interface TimeDelayPlugin {
|
|
735
|
+
/**
|
|
736
|
+
* Get total elapsed time since init (includes paused time)
|
|
737
|
+
* @returns Time in milliseconds
|
|
738
|
+
*/
|
|
739
|
+
getElapsed(): number;
|
|
740
|
+
/**
|
|
741
|
+
* Get active elapsed time (excludes paused time)
|
|
742
|
+
* @returns Time in milliseconds
|
|
743
|
+
*/
|
|
744
|
+
getActiveElapsed(): number;
|
|
745
|
+
/**
|
|
746
|
+
* Get remaining time until trigger
|
|
747
|
+
* @returns Time in milliseconds, or 0 if already triggered
|
|
748
|
+
*/
|
|
749
|
+
getRemaining(): number;
|
|
750
|
+
/**
|
|
751
|
+
* Check if timer is currently paused (tab hidden)
|
|
752
|
+
* @returns True if paused
|
|
753
|
+
*/
|
|
754
|
+
isPaused(): boolean;
|
|
755
|
+
/**
|
|
756
|
+
* Check if trigger has fired
|
|
757
|
+
* @returns True if triggered
|
|
758
|
+
*/
|
|
759
|
+
isTriggered(): boolean;
|
|
760
|
+
/**
|
|
761
|
+
* Reset timer to initial state
|
|
762
|
+
* Clears trigger flag and restarts timing
|
|
763
|
+
*/
|
|
764
|
+
reset(): void;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/** @module timeDelayPlugin */
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Time Delay Plugin
|
|
771
|
+
*
|
|
772
|
+
* Tracks time elapsed since SDK initialization and emits trigger:timeDelay events
|
|
773
|
+
* when the configured delay is reached.
|
|
774
|
+
*
|
|
775
|
+
* **Features:**
|
|
776
|
+
* - Millisecond precision timing
|
|
777
|
+
* - Pause/resume on tab visibility change (optional)
|
|
778
|
+
* - Tracks active vs total elapsed time
|
|
779
|
+
* - Full timer lifecycle management
|
|
780
|
+
*
|
|
781
|
+
* **Event-Driven Architecture:**
|
|
782
|
+
* This plugin emits `trigger:timeDelay` events when the delay threshold is reached.
|
|
783
|
+
* The core runtime listens for these events and automatically re-evaluates experiences.
|
|
784
|
+
*
|
|
785
|
+
* **Usage Pattern:**
|
|
786
|
+
* Use `targeting.custom` to check if time delay has triggered:
|
|
787
|
+
*
|
|
788
|
+
* @example Basic usage
|
|
789
|
+
* ```typescript
|
|
790
|
+
* import { init, register } from '@prosdevlab/experience-sdk';
|
|
791
|
+
*
|
|
792
|
+
* init({
|
|
793
|
+
* timeDelay: {
|
|
794
|
+
* delay: 5000, // 5 seconds
|
|
795
|
+
* pauseWhenHidden: true // Pause when tab hidden (default)
|
|
796
|
+
* }
|
|
797
|
+
* });
|
|
798
|
+
*
|
|
799
|
+
* // Show banner after 5 seconds of active viewing time
|
|
800
|
+
* register('timed-offer', {
|
|
801
|
+
* type: 'banner',
|
|
802
|
+
* content: {
|
|
803
|
+
* message: 'Limited time offer!',
|
|
804
|
+
* buttons: [{ text: 'Claim Now', variant: 'primary' }]
|
|
805
|
+
* },
|
|
806
|
+
* targeting: {
|
|
807
|
+
* custom: (context) => {
|
|
808
|
+
* const active = context.triggers?.timeDelay?.activeElapsed || 0;
|
|
809
|
+
* return active >= 5000;
|
|
810
|
+
* }
|
|
811
|
+
* }
|
|
812
|
+
* });
|
|
813
|
+
* ```
|
|
814
|
+
*
|
|
815
|
+
* @example Combining with other triggers
|
|
816
|
+
* ```typescript
|
|
817
|
+
* // Show after 10s OR on exit intent (whichever comes first)
|
|
818
|
+
* register('engaged-offer', {
|
|
819
|
+
* type: 'banner',
|
|
820
|
+
* content: { message: 'Special offer for engaged users!' },
|
|
821
|
+
* targeting: {
|
|
822
|
+
* custom: (context) => {
|
|
823
|
+
* const timeElapsed = (context.triggers?.timeDelay?.activeElapsed || 0) >= 10000;
|
|
824
|
+
* const exitIntent = context.triggers?.exitIntent?.triggered;
|
|
825
|
+
* return timeElapsed || exitIntent;
|
|
826
|
+
* }
|
|
827
|
+
* }
|
|
828
|
+
* });
|
|
829
|
+
* ```
|
|
830
|
+
*
|
|
831
|
+
* @param plugin Plugin interface from sdk-kit
|
|
832
|
+
* @param instance SDK instance
|
|
833
|
+
* @param config SDK configuration
|
|
834
|
+
*/
|
|
835
|
+
declare const timeDelayPlugin: PluginFunction;
|
|
836
|
+
|
|
837
|
+
export { type BannerContent, type BannerPlugin, type BannerPluginConfig, type DebugPlugin, type DebugPluginConfig, type Decision, type DecisionMetadata, type ExitIntentEvent, type ExitIntentPlugin, type ExitIntentPluginConfig, type Experience, type ExperienceContent, type FrequencyPlugin, type FrequencyPluginConfig, type ModalContent, type PageVisitsEvent, type PageVisitsPlugin, type PageVisitsPluginConfig, type ScrollDepthEvent, type ScrollDepthPlugin, type ScrollDepthPluginConfig, type TimeDelayEvent, type TimeDelayPlugin, type TimeDelayPluginConfig, type TooltipContent, type TraceStep, bannerPlugin, debugPlugin, exitIntentPlugin, frequencyPlugin, pageVisitsPlugin, scrollDepthPlugin, timeDelayPlugin };
|