@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.js
CHANGED
|
@@ -42,7 +42,7 @@ function sanitizeHTML(html) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
const attrString = attrs.length > 0 ?
|
|
45
|
+
const attrString = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
|
|
46
46
|
let innerHTML = "";
|
|
47
47
|
for (const child of Array.from(element.childNodes)) {
|
|
48
48
|
innerHTML += sanitizeNode(child);
|
|
@@ -115,16 +115,12 @@ var bannerPlugin = (plugin, instance, config) => {
|
|
|
115
115
|
left: 0;
|
|
116
116
|
right: 0;
|
|
117
117
|
width: 100%;
|
|
118
|
-
padding: 16px 20px;
|
|
119
118
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
120
119
|
font-size: 14px;
|
|
121
120
|
line-height: 1.5;
|
|
122
|
-
display: flex;
|
|
123
|
-
align-items: center;
|
|
124
|
-
justify-content: space-between;
|
|
125
121
|
box-sizing: border-box;
|
|
126
122
|
z-index: 10000;
|
|
127
|
-
background: #
|
|
123
|
+
background: #ffffff;
|
|
128
124
|
color: #111827;
|
|
129
125
|
border-bottom: 1px solid #e5e7eb;
|
|
130
126
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.05);
|
|
@@ -144,33 +140,38 @@ var bannerPlugin = (plugin, instance, config) => {
|
|
|
144
140
|
.xp-banner__container {
|
|
145
141
|
display: flex;
|
|
146
142
|
align-items: center;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
143
|
+
gap: 16px;
|
|
144
|
+
max-width: 1280px;
|
|
145
|
+
margin: 0 auto;
|
|
146
|
+
padding: 14px 24px;
|
|
150
147
|
}
|
|
151
148
|
|
|
152
149
|
.xp-banner__content {
|
|
153
150
|
flex: 1;
|
|
154
151
|
min-width: 0;
|
|
152
|
+
display: flex;
|
|
153
|
+
flex-direction: column;
|
|
154
|
+
gap: 4px;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
.xp-banner__title {
|
|
158
158
|
font-weight: 600;
|
|
159
|
-
margin
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
margin: 0;
|
|
160
|
+
font-size: 15px;
|
|
161
|
+
line-height: 1.4;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
.xp-banner__message {
|
|
165
165
|
margin: 0;
|
|
166
166
|
font-size: 14px;
|
|
167
|
+
line-height: 1.5;
|
|
168
|
+
color: #6b7280;
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
.xp-banner__buttons {
|
|
170
172
|
display: flex;
|
|
171
173
|
align-items: center;
|
|
172
|
-
gap:
|
|
173
|
-
flex-wrap: wrap;
|
|
174
|
+
gap: 8px;
|
|
174
175
|
flex-shrink: 0;
|
|
175
176
|
}
|
|
176
177
|
|
|
@@ -183,6 +184,10 @@ var bannerPlugin = (plugin, instance, config) => {
|
|
|
183
184
|
cursor: pointer;
|
|
184
185
|
transition: all 0.2s;
|
|
185
186
|
text-decoration: none;
|
|
187
|
+
display: inline-flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
justify-content: center;
|
|
190
|
+
white-space: nowrap;
|
|
186
191
|
}
|
|
187
192
|
|
|
188
193
|
.xp-banner__button--primary {
|
|
@@ -195,71 +200,93 @@ var bannerPlugin = (plugin, instance, config) => {
|
|
|
195
200
|
}
|
|
196
201
|
|
|
197
202
|
.xp-banner__button--secondary {
|
|
198
|
-
background: #
|
|
203
|
+
background: #f3f4f6;
|
|
199
204
|
color: #374151;
|
|
200
|
-
border: 1px solid #
|
|
205
|
+
border: 1px solid #e5e7eb;
|
|
201
206
|
}
|
|
202
207
|
|
|
203
208
|
.xp-banner__button--secondary:hover {
|
|
204
|
-
background: #
|
|
209
|
+
background: #e5e7eb;
|
|
205
210
|
}
|
|
206
211
|
|
|
207
212
|
.xp-banner__button--link {
|
|
208
213
|
background: transparent;
|
|
209
214
|
color: #2563eb;
|
|
210
|
-
padding:
|
|
215
|
+
padding: 6px 12px;
|
|
211
216
|
font-weight: 400;
|
|
212
|
-
text-decoration: underline;
|
|
213
217
|
}
|
|
214
218
|
|
|
215
219
|
.xp-banner__button--link:hover {
|
|
216
|
-
background:
|
|
220
|
+
background: #f3f4f6;
|
|
221
|
+
text-decoration: underline;
|
|
217
222
|
}
|
|
218
223
|
|
|
219
224
|
.xp-banner__close {
|
|
220
225
|
background: transparent;
|
|
221
226
|
border: none;
|
|
222
|
-
color: #
|
|
223
|
-
font-size:
|
|
227
|
+
color: #9ca3af;
|
|
228
|
+
font-size: 20px;
|
|
224
229
|
line-height: 1;
|
|
225
230
|
cursor: pointer;
|
|
226
|
-
padding:
|
|
231
|
+
padding: 4px;
|
|
227
232
|
margin: 0;
|
|
228
|
-
|
|
229
|
-
transition: opacity 0.2s;
|
|
233
|
+
transition: color 0.2s;
|
|
230
234
|
flex-shrink: 0;
|
|
235
|
+
width: 28px;
|
|
236
|
+
height: 28px;
|
|
237
|
+
display: flex;
|
|
238
|
+
align-items: center;
|
|
239
|
+
justify-content: center;
|
|
240
|
+
border-radius: 4px;
|
|
231
241
|
}
|
|
232
242
|
|
|
233
243
|
.xp-banner__close:hover {
|
|
234
|
-
|
|
244
|
+
color: #111827;
|
|
245
|
+
background: #f3f4f6;
|
|
235
246
|
}
|
|
236
247
|
|
|
237
248
|
@media (max-width: 640px) {
|
|
238
249
|
.xp-banner__container {
|
|
239
|
-
flex-
|
|
240
|
-
|
|
250
|
+
flex-wrap: wrap;
|
|
251
|
+
padding: 14px 16px;
|
|
252
|
+
position: relative;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.xp-banner__content {
|
|
256
|
+
flex: 1 1 100%;
|
|
257
|
+
padding-right: 32px;
|
|
241
258
|
}
|
|
242
259
|
|
|
243
260
|
.xp-banner__buttons {
|
|
261
|
+
flex: 1 1 auto;
|
|
244
262
|
width: 100%;
|
|
245
|
-
flex-direction: column;
|
|
246
263
|
}
|
|
247
264
|
|
|
248
265
|
.xp-banner__button {
|
|
249
|
-
|
|
266
|
+
flex: 1;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.xp-banner__close {
|
|
270
|
+
position: absolute;
|
|
271
|
+
top: 12px;
|
|
272
|
+
right: 12px;
|
|
250
273
|
}
|
|
251
274
|
}
|
|
252
275
|
|
|
253
276
|
/* Dark mode support */
|
|
254
277
|
@media (prefers-color-scheme: dark) {
|
|
255
278
|
.xp-banner {
|
|
256
|
-
background: #
|
|
257
|
-
color: #
|
|
258
|
-
border-bottom-color: #
|
|
279
|
+
background: #111827;
|
|
280
|
+
color: #f9fafb;
|
|
281
|
+
border-bottom-color: #1f2937;
|
|
259
282
|
}
|
|
260
283
|
|
|
261
284
|
.xp-banner--bottom {
|
|
262
|
-
border-top-color: #
|
|
285
|
+
border-top-color: #1f2937;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.xp-banner__message {
|
|
289
|
+
color: #9ca3af;
|
|
263
290
|
}
|
|
264
291
|
|
|
265
292
|
.xp-banner__button--primary {
|
|
@@ -271,21 +298,30 @@ var bannerPlugin = (plugin, instance, config) => {
|
|
|
271
298
|
}
|
|
272
299
|
|
|
273
300
|
.xp-banner__button--secondary {
|
|
274
|
-
background: #
|
|
275
|
-
color: #
|
|
276
|
-
border-color: #
|
|
301
|
+
background: #1f2937;
|
|
302
|
+
color: #f9fafb;
|
|
303
|
+
border-color: #374151;
|
|
277
304
|
}
|
|
278
305
|
|
|
279
306
|
.xp-banner__button--secondary:hover {
|
|
280
|
-
background: #
|
|
307
|
+
background: #374151;
|
|
281
308
|
}
|
|
282
309
|
|
|
283
310
|
.xp-banner__button--link {
|
|
284
|
-
color: #
|
|
311
|
+
color: #60a5fa;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.xp-banner__button--link:hover {
|
|
315
|
+
background: #1f2937;
|
|
285
316
|
}
|
|
286
317
|
|
|
287
318
|
.xp-banner__close {
|
|
288
|
-
color: #
|
|
319
|
+
color: #6b7280;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.xp-banner__close:hover {
|
|
323
|
+
color: #f9fafb;
|
|
324
|
+
background: #1f2937;
|
|
289
325
|
}
|
|
290
326
|
}
|
|
291
327
|
`;
|
|
@@ -326,14 +362,6 @@ var bannerPlugin = (plugin, instance, config) => {
|
|
|
326
362
|
message.innerHTML = sanitizeHTML(content.message);
|
|
327
363
|
contentDiv.appendChild(message);
|
|
328
364
|
container.appendChild(contentDiv);
|
|
329
|
-
banner.appendChild(contentDiv);
|
|
330
|
-
const buttonContainer = document.createElement("div");
|
|
331
|
-
buttonContainer.style.cssText = `
|
|
332
|
-
display: flex;
|
|
333
|
-
align-items: center;
|
|
334
|
-
gap: 12px;
|
|
335
|
-
flex-wrap: wrap;
|
|
336
|
-
`;
|
|
337
365
|
const buttonsDiv = document.createElement("div");
|
|
338
366
|
buttonsDiv.className = "xp-banner__buttons";
|
|
339
367
|
function createButton(buttonConfig) {
|
|
@@ -387,6 +415,31 @@ var bannerPlugin = (plugin, instance, config) => {
|
|
|
387
415
|
container.appendChild(buttonsDiv);
|
|
388
416
|
return banner;
|
|
389
417
|
}
|
|
418
|
+
function applyPushDown(banner, position) {
|
|
419
|
+
const pushDownSelector = config.get("banner.pushDown");
|
|
420
|
+
if (!pushDownSelector || position !== "top") {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const targetElement = document.querySelector(pushDownSelector);
|
|
424
|
+
if (!targetElement || !(targetElement instanceof HTMLElement)) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const height = banner.offsetHeight;
|
|
428
|
+
targetElement.style.transition = "margin-top 0.3s ease";
|
|
429
|
+
targetElement.style.marginTop = `${height}px`;
|
|
430
|
+
}
|
|
431
|
+
function removePushDown() {
|
|
432
|
+
const pushDownSelector = config.get("banner.pushDown");
|
|
433
|
+
if (!pushDownSelector) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const targetElement = document.querySelector(pushDownSelector);
|
|
437
|
+
if (!targetElement || !(targetElement instanceof HTMLElement)) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
targetElement.style.transition = "margin-top 0.3s ease";
|
|
441
|
+
targetElement.style.marginTop = "0";
|
|
442
|
+
}
|
|
390
443
|
function show(experience) {
|
|
391
444
|
if (activeBanners.has(experience.id)) {
|
|
392
445
|
return;
|
|
@@ -397,6 +450,9 @@ var bannerPlugin = (plugin, instance, config) => {
|
|
|
397
450
|
const banner = createBannerElement(experience);
|
|
398
451
|
document.body.appendChild(banner);
|
|
399
452
|
activeBanners.set(experience.id, banner);
|
|
453
|
+
const content = experience.content;
|
|
454
|
+
const position = content.position ?? config.get("banner.position") ?? "top";
|
|
455
|
+
applyPushDown(banner, position);
|
|
400
456
|
instance.emit("experiences:shown", {
|
|
401
457
|
experienceId: experience.id,
|
|
402
458
|
type: "banner",
|
|
@@ -410,6 +466,9 @@ var bannerPlugin = (plugin, instance, config) => {
|
|
|
410
466
|
banner.parentNode.removeChild(banner);
|
|
411
467
|
}
|
|
412
468
|
activeBanners.delete(experienceId);
|
|
469
|
+
if (activeBanners.size === 0) {
|
|
470
|
+
removePushDown();
|
|
471
|
+
}
|
|
413
472
|
} else {
|
|
414
473
|
for (const [id, banner] of activeBanners.entries()) {
|
|
415
474
|
if (banner?.parentNode) {
|
|
@@ -417,6 +476,7 @@ var bannerPlugin = (plugin, instance, config) => {
|
|
|
417
476
|
}
|
|
418
477
|
activeBanners.delete(id);
|
|
419
478
|
}
|
|
479
|
+
removePushDown();
|
|
420
480
|
}
|
|
421
481
|
}
|
|
422
482
|
function isShowing() {
|
|
@@ -501,6 +561,176 @@ var debugPlugin = (plugin, instance, config) => {
|
|
|
501
561
|
});
|
|
502
562
|
}
|
|
503
563
|
};
|
|
564
|
+
|
|
565
|
+
// src/exit-intent/exit-intent.ts
|
|
566
|
+
function isMobileDevice(userAgent) {
|
|
567
|
+
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
|
|
568
|
+
}
|
|
569
|
+
function hasMinTimeElapsed(pageLoadTime, minTime, currentTime) {
|
|
570
|
+
return currentTime - pageLoadTime >= minTime;
|
|
571
|
+
}
|
|
572
|
+
function addPositionToHistory(positions, newPosition, maxSize) {
|
|
573
|
+
const updated = [...positions, newPosition];
|
|
574
|
+
if (updated.length > maxSize) {
|
|
575
|
+
return updated.slice(1);
|
|
576
|
+
}
|
|
577
|
+
return updated;
|
|
578
|
+
}
|
|
579
|
+
function calculateVelocity(lastY, previousY) {
|
|
580
|
+
return Math.abs(lastY - previousY);
|
|
581
|
+
}
|
|
582
|
+
function shouldTriggerExitIntent(positions, sensitivity, relatedTarget) {
|
|
583
|
+
if (positions.length < 2) {
|
|
584
|
+
return { shouldTrigger: false, lastY: 0, previousY: 0, velocity: 0 };
|
|
585
|
+
}
|
|
586
|
+
if (relatedTarget && relatedTarget.nodeName !== "HTML") {
|
|
587
|
+
return { shouldTrigger: false, lastY: 0, previousY: 0, velocity: 0 };
|
|
588
|
+
}
|
|
589
|
+
const lastY = positions[positions.length - 1].y;
|
|
590
|
+
const previousY = positions[positions.length - 2].y;
|
|
591
|
+
const velocity = calculateVelocity(lastY, previousY);
|
|
592
|
+
const isMovingUp = lastY < previousY;
|
|
593
|
+
const isNearTop = lastY - velocity <= sensitivity;
|
|
594
|
+
return {
|
|
595
|
+
shouldTrigger: isMovingUp && isNearTop,
|
|
596
|
+
lastY,
|
|
597
|
+
previousY,
|
|
598
|
+
velocity
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
function createExitIntentEvent(lastY, previousY, velocity, pageLoadTime, timestamp) {
|
|
602
|
+
return {
|
|
603
|
+
timestamp,
|
|
604
|
+
lastY,
|
|
605
|
+
previousY,
|
|
606
|
+
velocity,
|
|
607
|
+
timeOnPage: timestamp - pageLoadTime
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
var exitIntentPlugin = (plugin, instance, config) => {
|
|
611
|
+
plugin.ns("experiences.exitIntent");
|
|
612
|
+
plugin.defaults({
|
|
613
|
+
exitIntent: {
|
|
614
|
+
sensitivity: 50,
|
|
615
|
+
minTimeOnPage: 2e3,
|
|
616
|
+
delay: 0,
|
|
617
|
+
positionHistorySize: 30,
|
|
618
|
+
disableOnMobile: true
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
const exitIntentConfig = config.get("exitIntent");
|
|
622
|
+
if (!exitIntentConfig) {
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
let positions = [];
|
|
626
|
+
let triggered = false;
|
|
627
|
+
const pageLoadTime = Date.now();
|
|
628
|
+
let mouseMoveListener = null;
|
|
629
|
+
let mouseOutListener = null;
|
|
630
|
+
function shouldDisable() {
|
|
631
|
+
if (!exitIntentConfig?.disableOnMobile) {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
return isMobileDevice(navigator.userAgent);
|
|
635
|
+
}
|
|
636
|
+
function trackPosition(e) {
|
|
637
|
+
const newPosition = { x: e.clientX, y: e.clientY };
|
|
638
|
+
const maxSize = exitIntentConfig?.positionHistorySize ?? 30;
|
|
639
|
+
positions = addPositionToHistory(positions, newPosition, maxSize);
|
|
640
|
+
}
|
|
641
|
+
function handleExitIntent(e) {
|
|
642
|
+
if (triggered) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
const minTime = exitIntentConfig?.minTimeOnPage ?? 2e3;
|
|
646
|
+
if (!hasMinTimeElapsed(pageLoadTime, minTime, Date.now())) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const sensitivity = exitIntentConfig?.sensitivity ?? 50;
|
|
650
|
+
const relatedTarget = e.relatedTarget || e.toElement;
|
|
651
|
+
const result = shouldTriggerExitIntent(positions, sensitivity, relatedTarget);
|
|
652
|
+
if (result.shouldTrigger) {
|
|
653
|
+
triggered = true;
|
|
654
|
+
const eventPayload = createExitIntentEvent(
|
|
655
|
+
result.lastY,
|
|
656
|
+
result.previousY,
|
|
657
|
+
result.velocity,
|
|
658
|
+
pageLoadTime,
|
|
659
|
+
Date.now()
|
|
660
|
+
);
|
|
661
|
+
const delay = exitIntentConfig?.delay ?? 0;
|
|
662
|
+
if (delay > 0) {
|
|
663
|
+
setTimeout(() => {
|
|
664
|
+
instance.emit("trigger:exitIntent", eventPayload);
|
|
665
|
+
}, delay);
|
|
666
|
+
} else {
|
|
667
|
+
instance.emit("trigger:exitIntent", eventPayload);
|
|
668
|
+
}
|
|
669
|
+
try {
|
|
670
|
+
sessionStorage.setItem("xp:exitIntent:triggered", Date.now().toString());
|
|
671
|
+
} catch (_e) {
|
|
672
|
+
}
|
|
673
|
+
cleanup();
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function cleanup() {
|
|
677
|
+
if (mouseMoveListener) {
|
|
678
|
+
document.removeEventListener("mousemove", mouseMoveListener);
|
|
679
|
+
mouseMoveListener = null;
|
|
680
|
+
}
|
|
681
|
+
if (mouseOutListener) {
|
|
682
|
+
document.removeEventListener("mouseout", mouseOutListener);
|
|
683
|
+
mouseOutListener = null;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
function initialize() {
|
|
687
|
+
if (shouldDisable()) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
try {
|
|
691
|
+
const storedTrigger = sessionStorage.getItem("xp:exitIntent:triggered");
|
|
692
|
+
if (storedTrigger) {
|
|
693
|
+
triggered = true;
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
} catch (_e) {
|
|
697
|
+
}
|
|
698
|
+
mouseMoveListener = trackPosition;
|
|
699
|
+
mouseOutListener = handleExitIntent;
|
|
700
|
+
document.addEventListener("mousemove", mouseMoveListener);
|
|
701
|
+
document.addEventListener("mouseout", mouseOutListener);
|
|
702
|
+
}
|
|
703
|
+
plugin.expose({
|
|
704
|
+
exitIntent: {
|
|
705
|
+
/**
|
|
706
|
+
* Check if exit intent has been triggered
|
|
707
|
+
*/
|
|
708
|
+
isTriggered: () => triggered,
|
|
709
|
+
/**
|
|
710
|
+
* Reset exit intent state (useful for testing)
|
|
711
|
+
*/
|
|
712
|
+
reset: () => {
|
|
713
|
+
triggered = false;
|
|
714
|
+
positions = [];
|
|
715
|
+
try {
|
|
716
|
+
sessionStorage.removeItem("xp:exitIntent:triggered");
|
|
717
|
+
} catch (_e) {
|
|
718
|
+
}
|
|
719
|
+
cleanup();
|
|
720
|
+
initialize();
|
|
721
|
+
},
|
|
722
|
+
/**
|
|
723
|
+
* Get current position history
|
|
724
|
+
*/
|
|
725
|
+
getPositions: () => [...positions]
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
initialize();
|
|
729
|
+
const destroyHandler = () => {
|
|
730
|
+
cleanup();
|
|
731
|
+
};
|
|
732
|
+
instance.on("destroy", destroyHandler);
|
|
733
|
+
};
|
|
504
734
|
var frequencyPlugin = (plugin, instance, config) => {
|
|
505
735
|
plugin.ns("frequency");
|
|
506
736
|
plugin.defaults({
|
|
@@ -628,7 +858,527 @@ var frequencyPlugin = (plugin, instance, config) => {
|
|
|
628
858
|
});
|
|
629
859
|
}
|
|
630
860
|
};
|
|
861
|
+
function respectsDNT() {
|
|
862
|
+
if (typeof navigator === "undefined") return false;
|
|
863
|
+
return navigator.doNotTrack === "1" || navigator.msDoNotTrack === "1" || window.doNotTrack === "1";
|
|
864
|
+
}
|
|
865
|
+
function createVisitsEvent(isFirstVisit, totalVisits, sessionVisits, firstVisitTime, lastVisitTime, timestamp) {
|
|
866
|
+
return {
|
|
867
|
+
isFirstVisit,
|
|
868
|
+
totalVisits,
|
|
869
|
+
sessionVisits,
|
|
870
|
+
firstVisitTime,
|
|
871
|
+
lastVisitTime,
|
|
872
|
+
timestamp
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
var pageVisitsPlugin = (plugin, instance, config) => {
|
|
876
|
+
plugin.ns("pageVisits");
|
|
877
|
+
plugin.defaults({
|
|
878
|
+
pageVisits: {
|
|
879
|
+
enabled: true,
|
|
880
|
+
respectDNT: true,
|
|
881
|
+
sessionKey: "pageVisits:session",
|
|
882
|
+
totalKey: "pageVisits:total",
|
|
883
|
+
ttl: void 0,
|
|
884
|
+
autoIncrement: true
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
if (!instance.storage) {
|
|
888
|
+
console.warn("[PageVisits] Storage plugin not found, auto-loading...");
|
|
889
|
+
instance.use(storagePlugin);
|
|
890
|
+
}
|
|
891
|
+
const sdkInstance = instance;
|
|
892
|
+
let sessionCount = 0;
|
|
893
|
+
let totalCount = 0;
|
|
894
|
+
let firstVisitTime;
|
|
895
|
+
let lastVisitTime;
|
|
896
|
+
let isFirstVisitFlag = false;
|
|
897
|
+
let initialized = false;
|
|
898
|
+
function loadData() {
|
|
899
|
+
const sessionKey = config.get("pageVisits.sessionKey") ?? "pageVisits:session";
|
|
900
|
+
const totalKey = config.get("pageVisits.totalKey") ?? "pageVisits:total";
|
|
901
|
+
const storedSession = sdkInstance.storage.get(sessionKey, {
|
|
902
|
+
backend: "sessionStorage"
|
|
903
|
+
});
|
|
904
|
+
sessionCount = storedSession ?? 0;
|
|
905
|
+
const storedTotal = sdkInstance.storage.get(totalKey, {
|
|
906
|
+
backend: "localStorage"
|
|
907
|
+
});
|
|
908
|
+
if (storedTotal) {
|
|
909
|
+
totalCount = storedTotal.count ?? 0;
|
|
910
|
+
firstVisitTime = storedTotal.first;
|
|
911
|
+
lastVisitTime = storedTotal.last;
|
|
912
|
+
isFirstVisitFlag = false;
|
|
913
|
+
} else {
|
|
914
|
+
totalCount = 0;
|
|
915
|
+
firstVisitTime = void 0;
|
|
916
|
+
lastVisitTime = void 0;
|
|
917
|
+
isFirstVisitFlag = true;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
function saveData() {
|
|
921
|
+
const sessionKey = config.get("pageVisits.sessionKey") ?? "pageVisits:session";
|
|
922
|
+
const totalKey = config.get("pageVisits.totalKey") ?? "pageVisits:total";
|
|
923
|
+
const ttl = config.get("pageVisits.ttl");
|
|
924
|
+
sdkInstance.storage.set(sessionKey, sessionCount, {
|
|
925
|
+
backend: "sessionStorage"
|
|
926
|
+
});
|
|
927
|
+
const totalData = {
|
|
928
|
+
count: totalCount,
|
|
929
|
+
first: firstVisitTime ?? Date.now(),
|
|
930
|
+
last: lastVisitTime ?? Date.now()
|
|
931
|
+
};
|
|
932
|
+
sdkInstance.storage.set(totalKey, totalData, {
|
|
933
|
+
backend: "localStorage",
|
|
934
|
+
...ttl && { ttl }
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
function increment() {
|
|
938
|
+
if (!initialized) {
|
|
939
|
+
loadData();
|
|
940
|
+
initialized = true;
|
|
941
|
+
}
|
|
942
|
+
sessionCount += 1;
|
|
943
|
+
totalCount += 1;
|
|
944
|
+
const now = Date.now();
|
|
945
|
+
if (isFirstVisitFlag) {
|
|
946
|
+
firstVisitTime = now;
|
|
947
|
+
}
|
|
948
|
+
lastVisitTime = now;
|
|
949
|
+
saveData();
|
|
950
|
+
const event = createVisitsEvent(
|
|
951
|
+
isFirstVisitFlag,
|
|
952
|
+
totalCount,
|
|
953
|
+
sessionCount,
|
|
954
|
+
firstVisitTime,
|
|
955
|
+
lastVisitTime,
|
|
956
|
+
now
|
|
957
|
+
);
|
|
958
|
+
plugin.emit("pageVisits:incremented", event);
|
|
959
|
+
if (isFirstVisitFlag) {
|
|
960
|
+
isFirstVisitFlag = false;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
function reset() {
|
|
964
|
+
const sessionKey = config.get("pageVisits.sessionKey") ?? "pageVisits:session";
|
|
965
|
+
const totalKey = config.get("pageVisits.totalKey") ?? "pageVisits:total";
|
|
966
|
+
sdkInstance.storage.remove(sessionKey, { backend: "sessionStorage" });
|
|
967
|
+
sdkInstance.storage.remove(totalKey, { backend: "localStorage" });
|
|
968
|
+
sessionCount = 0;
|
|
969
|
+
totalCount = 0;
|
|
970
|
+
firstVisitTime = void 0;
|
|
971
|
+
lastVisitTime = void 0;
|
|
972
|
+
isFirstVisitFlag = false;
|
|
973
|
+
initialized = false;
|
|
974
|
+
plugin.emit("pageVisits:reset");
|
|
975
|
+
}
|
|
976
|
+
function getState() {
|
|
977
|
+
return createVisitsEvent(
|
|
978
|
+
isFirstVisitFlag,
|
|
979
|
+
totalCount,
|
|
980
|
+
sessionCount,
|
|
981
|
+
firstVisitTime,
|
|
982
|
+
lastVisitTime,
|
|
983
|
+
Date.now()
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
function initialize() {
|
|
987
|
+
const enabled = config.get("pageVisits.enabled") ?? true;
|
|
988
|
+
const respectDNTConfig = config.get("pageVisits.respectDNT") ?? true;
|
|
989
|
+
const autoIncrement = config.get("pageVisits.autoIncrement") ?? true;
|
|
990
|
+
if (respectDNTConfig && respectsDNT()) {
|
|
991
|
+
plugin.emit("pageVisits:disabled", { reason: "dnt" });
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
if (!enabled) {
|
|
995
|
+
plugin.emit("pageVisits:disabled", { reason: "config" });
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
if (autoIncrement) {
|
|
999
|
+
increment();
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
instance.on("sdk:ready", initialize);
|
|
1003
|
+
plugin.expose({
|
|
1004
|
+
pageVisits: {
|
|
1005
|
+
getTotalCount: () => totalCount,
|
|
1006
|
+
getSessionCount: () => sessionCount,
|
|
1007
|
+
isFirstVisit: () => isFirstVisitFlag,
|
|
1008
|
+
getFirstVisitTime: () => firstVisitTime,
|
|
1009
|
+
getLastVisitTime: () => lastVisitTime,
|
|
1010
|
+
increment,
|
|
1011
|
+
reset,
|
|
1012
|
+
getState
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
// src/scroll-depth/scroll-depth.ts
|
|
1018
|
+
function detectDevice() {
|
|
1019
|
+
if (typeof window === "undefined") return "desktop";
|
|
1020
|
+
const ua = navigator.userAgent;
|
|
1021
|
+
const isMobile = /Android|webOS|iPhone|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua);
|
|
1022
|
+
const isTablet = /iPad|Android(?!.*Mobile)/i.test(ua);
|
|
1023
|
+
const width = window.innerWidth;
|
|
1024
|
+
if (width < 768) return "mobile";
|
|
1025
|
+
if (width < 1024) return "tablet";
|
|
1026
|
+
if (isMobile) return "mobile";
|
|
1027
|
+
if (isTablet) return "tablet";
|
|
1028
|
+
return "desktop";
|
|
1029
|
+
}
|
|
1030
|
+
function throttle(func, wait) {
|
|
1031
|
+
let timeout = null;
|
|
1032
|
+
let previous = 0;
|
|
1033
|
+
return function throttled(...args) {
|
|
1034
|
+
const now = Date.now();
|
|
1035
|
+
const remaining = wait - (now - previous);
|
|
1036
|
+
if (remaining <= 0 || remaining > wait) {
|
|
1037
|
+
if (timeout) {
|
|
1038
|
+
clearTimeout(timeout);
|
|
1039
|
+
timeout = null;
|
|
1040
|
+
}
|
|
1041
|
+
previous = now;
|
|
1042
|
+
func(...args);
|
|
1043
|
+
} else if (!timeout) {
|
|
1044
|
+
timeout = setTimeout(() => {
|
|
1045
|
+
previous = Date.now();
|
|
1046
|
+
timeout = null;
|
|
1047
|
+
func(...args);
|
|
1048
|
+
}, remaining);
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
function calculateScrollPercent(includeViewportHeight) {
|
|
1053
|
+
if (typeof document === "undefined") return 0;
|
|
1054
|
+
const scrollingElement = document.scrollingElement || document.documentElement;
|
|
1055
|
+
const scrollTop = scrollingElement.scrollTop;
|
|
1056
|
+
const scrollHeight = scrollingElement.scrollHeight;
|
|
1057
|
+
const clientHeight = scrollingElement.clientHeight;
|
|
1058
|
+
if (scrollHeight <= clientHeight) {
|
|
1059
|
+
return 100;
|
|
1060
|
+
}
|
|
1061
|
+
if (includeViewportHeight) {
|
|
1062
|
+
return Math.min((scrollTop + clientHeight) / scrollHeight * 100, 100);
|
|
1063
|
+
}
|
|
1064
|
+
return Math.min(scrollTop / (scrollHeight - clientHeight) * 100, 100);
|
|
1065
|
+
}
|
|
1066
|
+
function calculateEngagementScore(velocity, fastScrollThreshold, directionChanges, timeScrollingUp, totalTime) {
|
|
1067
|
+
const velocityScore = Math.min(velocity / fastScrollThreshold * 50, 50);
|
|
1068
|
+
const directionScore = Math.min(directionChanges / 5 * 30, 30);
|
|
1069
|
+
const seekingScore = Math.min(timeScrollingUp / totalTime * 20, 20);
|
|
1070
|
+
return Math.max(0, 100 - (velocityScore + directionScore + seekingScore));
|
|
1071
|
+
}
|
|
1072
|
+
var scrollDepthPlugin = (plugin, instance, config) => {
|
|
1073
|
+
plugin.ns("experiences.scrollDepth");
|
|
1074
|
+
plugin.defaults({
|
|
1075
|
+
scrollDepth: {
|
|
1076
|
+
thresholds: [25, 50, 75, 100],
|
|
1077
|
+
throttle: 100,
|
|
1078
|
+
includeViewportHeight: true,
|
|
1079
|
+
recalculateOnResize: true,
|
|
1080
|
+
trackAdvancedMetrics: false,
|
|
1081
|
+
fastScrollVelocityThreshold: 3,
|
|
1082
|
+
disableOnMobile: false
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
1085
|
+
const scrollConfig = config.get("scrollDepth");
|
|
1086
|
+
if (!scrollConfig) return;
|
|
1087
|
+
const cfg = scrollConfig;
|
|
1088
|
+
const device = detectDevice();
|
|
1089
|
+
if (cfg.disableOnMobile && device === "mobile") {
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
let maxScrollPercent = 0;
|
|
1093
|
+
const triggeredThresholds = /* @__PURE__ */ new Set();
|
|
1094
|
+
const pageLoadTime = Date.now();
|
|
1095
|
+
let lastScrollPosition = 0;
|
|
1096
|
+
let lastScrollTime = Date.now();
|
|
1097
|
+
let lastScrollDirection = null;
|
|
1098
|
+
let directionChangesSinceLastThreshold = 0;
|
|
1099
|
+
let timeScrollingUp = 0;
|
|
1100
|
+
const thresholdTimes = /* @__PURE__ */ new Map();
|
|
1101
|
+
function handleScroll() {
|
|
1102
|
+
const currentPercent = calculateScrollPercent(cfg.includeViewportHeight ?? true);
|
|
1103
|
+
const now = Date.now();
|
|
1104
|
+
const scrollingElement = document.scrollingElement || document.documentElement;
|
|
1105
|
+
const currentPosition = scrollingElement.scrollTop;
|
|
1106
|
+
let velocity = 0;
|
|
1107
|
+
if (cfg.trackAdvancedMetrics) {
|
|
1108
|
+
const timeDelta = now - lastScrollTime;
|
|
1109
|
+
const positionDelta = currentPosition - lastScrollPosition;
|
|
1110
|
+
velocity = timeDelta > 0 ? Math.abs(positionDelta) / timeDelta : 0;
|
|
1111
|
+
const currentDirection = positionDelta > 0 ? "down" : positionDelta < 0 ? "up" : lastScrollDirection;
|
|
1112
|
+
if (currentDirection && lastScrollDirection && currentDirection !== lastScrollDirection) {
|
|
1113
|
+
directionChangesSinceLastThreshold++;
|
|
1114
|
+
}
|
|
1115
|
+
if (currentDirection === "up" && timeDelta > 0) {
|
|
1116
|
+
timeScrollingUp += timeDelta;
|
|
1117
|
+
}
|
|
1118
|
+
lastScrollDirection = currentDirection;
|
|
1119
|
+
lastScrollPosition = currentPosition;
|
|
1120
|
+
lastScrollTime = now;
|
|
1121
|
+
}
|
|
1122
|
+
maxScrollPercent = Math.max(maxScrollPercent, currentPercent);
|
|
1123
|
+
for (const threshold of cfg.thresholds || []) {
|
|
1124
|
+
if (currentPercent >= threshold && !triggeredThresholds.has(threshold)) {
|
|
1125
|
+
triggeredThresholds.add(threshold);
|
|
1126
|
+
if (cfg.trackAdvancedMetrics) {
|
|
1127
|
+
thresholdTimes.set(threshold, now - pageLoadTime);
|
|
1128
|
+
}
|
|
1129
|
+
const eventPayload = {
|
|
1130
|
+
triggered: true,
|
|
1131
|
+
timestamp: now,
|
|
1132
|
+
percent: Math.round(currentPercent * 100) / 100,
|
|
1133
|
+
maxPercent: Math.round(maxScrollPercent * 100) / 100,
|
|
1134
|
+
threshold,
|
|
1135
|
+
thresholdsCrossed: Array.from(triggeredThresholds).sort((a, b) => a - b),
|
|
1136
|
+
device
|
|
1137
|
+
};
|
|
1138
|
+
if (cfg.trackAdvancedMetrics) {
|
|
1139
|
+
const fastScrollThreshold = cfg.fastScrollVelocityThreshold || 3;
|
|
1140
|
+
const isFastScrolling = velocity > fastScrollThreshold;
|
|
1141
|
+
const engagementScore = calculateEngagementScore(
|
|
1142
|
+
velocity,
|
|
1143
|
+
fastScrollThreshold,
|
|
1144
|
+
directionChangesSinceLastThreshold,
|
|
1145
|
+
timeScrollingUp,
|
|
1146
|
+
now - pageLoadTime
|
|
1147
|
+
);
|
|
1148
|
+
eventPayload.advanced = {
|
|
1149
|
+
timeToThreshold: now - pageLoadTime,
|
|
1150
|
+
velocity: Math.round(velocity * 1e3) / 1e3,
|
|
1151
|
+
// Round to 3 decimals
|
|
1152
|
+
isFastScrolling,
|
|
1153
|
+
directionChanges: directionChangesSinceLastThreshold,
|
|
1154
|
+
timeScrollingUp,
|
|
1155
|
+
engagementScore: Math.round(engagementScore)
|
|
1156
|
+
};
|
|
1157
|
+
directionChangesSinceLastThreshold = 0;
|
|
1158
|
+
}
|
|
1159
|
+
instance.emit("trigger:scrollDepth", eventPayload);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
const throttledScrollHandler = throttle(handleScroll, cfg.throttle || 100);
|
|
1164
|
+
const throttledResizeHandler = throttle(handleScroll, cfg.throttle || 100);
|
|
1165
|
+
function initialize() {
|
|
1166
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
window.addEventListener("scroll", throttledScrollHandler, { passive: true });
|
|
1170
|
+
if (cfg.recalculateOnResize) {
|
|
1171
|
+
window.addEventListener("resize", throttledResizeHandler, { passive: true });
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
function cleanup() {
|
|
1175
|
+
window.removeEventListener("scroll", throttledScrollHandler);
|
|
1176
|
+
window.removeEventListener("resize", throttledResizeHandler);
|
|
1177
|
+
}
|
|
1178
|
+
const destroyHandler = () => {
|
|
1179
|
+
cleanup();
|
|
1180
|
+
};
|
|
1181
|
+
instance.on("destroy", destroyHandler);
|
|
1182
|
+
plugin.expose({
|
|
1183
|
+
scrollDepth: {
|
|
1184
|
+
/**
|
|
1185
|
+
* Get the maximum scroll percentage reached during the session
|
|
1186
|
+
*/
|
|
1187
|
+
getMaxPercent: () => maxScrollPercent,
|
|
1188
|
+
/**
|
|
1189
|
+
* Get the current scroll percentage
|
|
1190
|
+
*/
|
|
1191
|
+
getCurrentPercent: () => calculateScrollPercent(cfg.includeViewportHeight ?? true),
|
|
1192
|
+
/**
|
|
1193
|
+
* Get all thresholds that have been crossed
|
|
1194
|
+
*/
|
|
1195
|
+
getThresholdsCrossed: () => Array.from(triggeredThresholds).sort((a, b) => a - b),
|
|
1196
|
+
/**
|
|
1197
|
+
* Get the detected device type
|
|
1198
|
+
*/
|
|
1199
|
+
getDevice: () => device,
|
|
1200
|
+
/**
|
|
1201
|
+
* Get advanced metrics (only available when trackAdvancedMetrics is enabled)
|
|
1202
|
+
*/
|
|
1203
|
+
getAdvancedMetrics: () => {
|
|
1204
|
+
if (!cfg.trackAdvancedMetrics) return null;
|
|
1205
|
+
const now = Date.now();
|
|
1206
|
+
return {
|
|
1207
|
+
timeOnPage: now - pageLoadTime,
|
|
1208
|
+
directionChanges: directionChangesSinceLastThreshold,
|
|
1209
|
+
timeScrollingUp,
|
|
1210
|
+
thresholdTimes: Object.fromEntries(thresholdTimes)
|
|
1211
|
+
};
|
|
1212
|
+
},
|
|
1213
|
+
/**
|
|
1214
|
+
* Reset scroll depth tracking
|
|
1215
|
+
* Clears all triggered thresholds, max scroll, and advanced metrics
|
|
1216
|
+
*/
|
|
1217
|
+
reset: () => {
|
|
1218
|
+
maxScrollPercent = 0;
|
|
1219
|
+
triggeredThresholds.clear();
|
|
1220
|
+
directionChangesSinceLastThreshold = 0;
|
|
1221
|
+
timeScrollingUp = 0;
|
|
1222
|
+
thresholdTimes.clear();
|
|
1223
|
+
lastScrollDirection = null;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
if (typeof window !== "undefined") {
|
|
1228
|
+
setTimeout(initialize, 0);
|
|
1229
|
+
}
|
|
1230
|
+
return () => {
|
|
1231
|
+
cleanup();
|
|
1232
|
+
instance.off("destroy", destroyHandler);
|
|
1233
|
+
};
|
|
1234
|
+
};
|
|
1235
|
+
|
|
1236
|
+
// src/time-delay/time-delay.ts
|
|
1237
|
+
function calculateElapsed(startTime, pausedDuration) {
|
|
1238
|
+
return Date.now() - startTime - pausedDuration;
|
|
1239
|
+
}
|
|
1240
|
+
function isDocumentHidden() {
|
|
1241
|
+
if (typeof document === "undefined") return false;
|
|
1242
|
+
return document.hidden || false;
|
|
1243
|
+
}
|
|
1244
|
+
function createTimeDelayEvent(startTime, pausedDuration, wasPaused, visibilityChanges) {
|
|
1245
|
+
const timestamp = Date.now();
|
|
1246
|
+
const elapsed = timestamp - startTime;
|
|
1247
|
+
const activeElapsed = elapsed - pausedDuration;
|
|
1248
|
+
return {
|
|
1249
|
+
timestamp,
|
|
1250
|
+
elapsed,
|
|
1251
|
+
activeElapsed,
|
|
1252
|
+
wasPaused,
|
|
1253
|
+
visibilityChanges
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
var timeDelayPlugin = (plugin, instance, config) => {
|
|
1257
|
+
plugin.ns("experiences.timeDelay");
|
|
1258
|
+
plugin.defaults({
|
|
1259
|
+
timeDelay: {
|
|
1260
|
+
delay: 0,
|
|
1261
|
+
pauseWhenHidden: true
|
|
1262
|
+
}
|
|
1263
|
+
});
|
|
1264
|
+
const timeDelayConfig = config.get("timeDelay");
|
|
1265
|
+
if (!timeDelayConfig) return;
|
|
1266
|
+
const delay = timeDelayConfig.delay ?? 0;
|
|
1267
|
+
const pauseWhenHidden = timeDelayConfig.pauseWhenHidden ?? true;
|
|
1268
|
+
if (delay <= 0) return;
|
|
1269
|
+
const startTime = Date.now();
|
|
1270
|
+
let triggered = false;
|
|
1271
|
+
let paused = false;
|
|
1272
|
+
let pausedDuration = 0;
|
|
1273
|
+
let lastPauseTime = 0;
|
|
1274
|
+
let visibilityChanges = 0;
|
|
1275
|
+
let timer = null;
|
|
1276
|
+
let visibilityListener = null;
|
|
1277
|
+
function trigger() {
|
|
1278
|
+
if (triggered) return;
|
|
1279
|
+
triggered = true;
|
|
1280
|
+
const eventPayload = createTimeDelayEvent(
|
|
1281
|
+
startTime,
|
|
1282
|
+
pausedDuration,
|
|
1283
|
+
visibilityChanges > 0,
|
|
1284
|
+
visibilityChanges
|
|
1285
|
+
);
|
|
1286
|
+
instance.emit("trigger:timeDelay", eventPayload);
|
|
1287
|
+
cleanup();
|
|
1288
|
+
}
|
|
1289
|
+
function scheduleTimer(remainingDelay) {
|
|
1290
|
+
if (timer) {
|
|
1291
|
+
clearTimeout(timer);
|
|
1292
|
+
}
|
|
1293
|
+
timer = setTimeout(() => {
|
|
1294
|
+
trigger();
|
|
1295
|
+
}, remainingDelay);
|
|
1296
|
+
}
|
|
1297
|
+
function handleVisibilityChange() {
|
|
1298
|
+
const hidden = isDocumentHidden();
|
|
1299
|
+
if (hidden && !paused) {
|
|
1300
|
+
paused = true;
|
|
1301
|
+
lastPauseTime = Date.now();
|
|
1302
|
+
visibilityChanges++;
|
|
1303
|
+
if (timer) {
|
|
1304
|
+
clearTimeout(timer);
|
|
1305
|
+
timer = null;
|
|
1306
|
+
}
|
|
1307
|
+
} else if (!hidden && paused) {
|
|
1308
|
+
paused = false;
|
|
1309
|
+
const pauseDuration = Date.now() - lastPauseTime;
|
|
1310
|
+
pausedDuration += pauseDuration;
|
|
1311
|
+
visibilityChanges++;
|
|
1312
|
+
const elapsed = calculateElapsed(startTime, pausedDuration);
|
|
1313
|
+
const remaining = delay - elapsed;
|
|
1314
|
+
if (remaining > 0) {
|
|
1315
|
+
scheduleTimer(remaining);
|
|
1316
|
+
} else {
|
|
1317
|
+
trigger();
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
function cleanup() {
|
|
1322
|
+
if (timer) {
|
|
1323
|
+
clearTimeout(timer);
|
|
1324
|
+
timer = null;
|
|
1325
|
+
}
|
|
1326
|
+
if (visibilityListener && typeof document !== "undefined") {
|
|
1327
|
+
document.removeEventListener("visibilitychange", visibilityListener);
|
|
1328
|
+
visibilityListener = null;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
function initialize() {
|
|
1332
|
+
if (pauseWhenHidden && isDocumentHidden()) {
|
|
1333
|
+
paused = true;
|
|
1334
|
+
lastPauseTime = Date.now();
|
|
1335
|
+
visibilityChanges++;
|
|
1336
|
+
} else {
|
|
1337
|
+
scheduleTimer(delay);
|
|
1338
|
+
}
|
|
1339
|
+
if (pauseWhenHidden && typeof document !== "undefined") {
|
|
1340
|
+
visibilityListener = handleVisibilityChange;
|
|
1341
|
+
document.addEventListener("visibilitychange", visibilityListener);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
plugin.expose({
|
|
1345
|
+
timeDelay: {
|
|
1346
|
+
getElapsed: () => {
|
|
1347
|
+
return Date.now() - startTime;
|
|
1348
|
+
},
|
|
1349
|
+
getActiveElapsed: () => {
|
|
1350
|
+
let currentPausedDuration = pausedDuration;
|
|
1351
|
+
if (paused) {
|
|
1352
|
+
currentPausedDuration += Date.now() - lastPauseTime;
|
|
1353
|
+
}
|
|
1354
|
+
return calculateElapsed(startTime, currentPausedDuration);
|
|
1355
|
+
},
|
|
1356
|
+
getRemaining: () => {
|
|
1357
|
+
if (triggered) return 0;
|
|
1358
|
+
const elapsed = calculateElapsed(startTime, pausedDuration);
|
|
1359
|
+
const remaining = delay - elapsed;
|
|
1360
|
+
return Math.max(0, remaining);
|
|
1361
|
+
},
|
|
1362
|
+
isPaused: () => paused,
|
|
1363
|
+
isTriggered: () => triggered,
|
|
1364
|
+
reset: () => {
|
|
1365
|
+
triggered = false;
|
|
1366
|
+
paused = false;
|
|
1367
|
+
pausedDuration = 0;
|
|
1368
|
+
lastPauseTime = 0;
|
|
1369
|
+
visibilityChanges = 0;
|
|
1370
|
+
cleanup();
|
|
1371
|
+
initialize();
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
initialize();
|
|
1376
|
+
const destroyHandler = () => {
|
|
1377
|
+
cleanup();
|
|
1378
|
+
};
|
|
1379
|
+
instance.on("destroy", destroyHandler);
|
|
1380
|
+
};
|
|
631
1381
|
|
|
632
|
-
export { bannerPlugin, debugPlugin, frequencyPlugin };
|
|
1382
|
+
export { bannerPlugin, debugPlugin, exitIntentPlugin, frequencyPlugin, pageVisitsPlugin, scrollDepthPlugin, timeDelayPlugin };
|
|
633
1383
|
//# sourceMappingURL=index.js.map
|
|
634
1384
|
//# sourceMappingURL=index.js.map
|