@magicfeedback/popup-sdk 1.0.11 → 1.0.12
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/README.md +269 -207
- package/dist/index.d.mts +41 -10
- package/dist/index.d.ts +41 -10
- package/dist/index.js +406 -144
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +406 -144
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1721,10 +1721,18 @@ var require_magicfeedback_sdk_browser = __commonJS({
|
|
|
1721
1721
|
// src/index.ts
|
|
1722
1722
|
var index_exports = {};
|
|
1723
1723
|
__export(index_exports, {
|
|
1724
|
-
DeepdotsPopups: () => DeepdotsPopups
|
|
1724
|
+
DeepdotsPopups: () => DeepdotsPopups,
|
|
1725
|
+
POPUP_TRIGGER_CONDITION_STATUSES: () => POPUP_TRIGGER_CONDITION_STATUSES
|
|
1725
1726
|
});
|
|
1726
1727
|
module.exports = __toCommonJS(index_exports);
|
|
1727
1728
|
|
|
1729
|
+
// src/types/index.ts
|
|
1730
|
+
var POPUP_TRIGGER_CONDITION_STATUSES = [
|
|
1731
|
+
"SHOWED",
|
|
1732
|
+
"PARTIAL",
|
|
1733
|
+
"COMPLETED"
|
|
1734
|
+
];
|
|
1735
|
+
|
|
1728
1736
|
// src/ui/renderPopup.ts
|
|
1729
1737
|
var import_native = __toESM(require_magicfeedback_sdk_browser());
|
|
1730
1738
|
function ensureMagicFeedbackStyles(popup) {
|
|
@@ -2186,6 +2194,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
|
|
|
2186
2194
|
updateButtons("completed");
|
|
2187
2195
|
return;
|
|
2188
2196
|
}
|
|
2197
|
+
emit("popup_clicked", surveyId, { action: "partial" });
|
|
2189
2198
|
if (total > 1 && progress > 0 && progress < total) {
|
|
2190
2199
|
updateButtons("in_progress_next");
|
|
2191
2200
|
} else {
|
|
@@ -2222,6 +2231,9 @@ function setupTrigger(popups, trigger) {
|
|
|
2222
2231
|
case "exit":
|
|
2223
2232
|
setupExitTrigger(popups, trigger);
|
|
2224
2233
|
break;
|
|
2234
|
+
case "event":
|
|
2235
|
+
setupEventTrigger(popups, trigger);
|
|
2236
|
+
break;
|
|
2225
2237
|
case "click":
|
|
2226
2238
|
setupClickTrigger(popups, trigger);
|
|
2227
2239
|
break;
|
|
@@ -2230,7 +2242,7 @@ function setupTrigger(popups, trigger) {
|
|
|
2230
2242
|
function setupTimeTrigger(popups, trigger) {
|
|
2231
2243
|
const delay = trigger.value || 5e3;
|
|
2232
2244
|
setTimeout(() => {
|
|
2233
|
-
popups.triggerSurvey(trigger.surveyId);
|
|
2245
|
+
popups.triggerSurvey(trigger.surveyId, trigger.popupId);
|
|
2234
2246
|
}, delay);
|
|
2235
2247
|
popups.debug(`Time trigger set for ${delay}ms`);
|
|
2236
2248
|
}
|
|
@@ -2239,7 +2251,7 @@ function setupScrollTrigger(popups, trigger) {
|
|
|
2239
2251
|
const handler = () => {
|
|
2240
2252
|
const scrollPercentage = window.scrollY / (document.documentElement.scrollHeight - window.innerHeight) * 100;
|
|
2241
2253
|
if (scrollPercentage >= threshold) {
|
|
2242
|
-
popups.triggerSurvey(trigger.surveyId);
|
|
2254
|
+
popups.triggerSurvey(trigger.surveyId, trigger.popupId);
|
|
2243
2255
|
window.removeEventListener("scroll", handler);
|
|
2244
2256
|
}
|
|
2245
2257
|
};
|
|
@@ -2247,86 +2259,103 @@ function setupScrollTrigger(popups, trigger) {
|
|
|
2247
2259
|
popups.debug(`Scroll trigger set for ${threshold}%`);
|
|
2248
2260
|
}
|
|
2249
2261
|
function setupExitTrigger(popups, trigger) {
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
const historyRef =
|
|
2253
|
-
const originalPushState = historyRef
|
|
2254
|
-
const originalReplaceState = historyRef
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2262
|
+
if (typeof window === "undefined") return;
|
|
2263
|
+
const delaySeconds = parseExitDelaySeconds(trigger.value);
|
|
2264
|
+
const historyRef = window.history;
|
|
2265
|
+
const originalPushState = historyRef.pushState.bind(historyRef);
|
|
2266
|
+
const originalReplaceState = historyRef.replaceState.bind(historyRef);
|
|
2267
|
+
let lastUrl = normalizeUrl(window.location.href);
|
|
2268
|
+
let lastQueuedKey = "";
|
|
2269
|
+
let lastQueuedAt = 0;
|
|
2270
|
+
const queueLeave = (fromRaw, toRaw) => {
|
|
2271
|
+
const from = normalizeUrl(fromRaw);
|
|
2272
|
+
const to = normalizeUrl(toRaw);
|
|
2273
|
+
if (!from || !to || from === to) return;
|
|
2274
|
+
const key = `${from}=>${to}`;
|
|
2275
|
+
const now = Date.now();
|
|
2276
|
+
if (key === lastQueuedKey && now - lastQueuedAt < 150) return;
|
|
2277
|
+
lastQueuedKey = key;
|
|
2278
|
+
lastQueuedAt = now;
|
|
2279
|
+
popups.queueExitPopup(trigger.surveyId, delaySeconds, from, trigger.popupId);
|
|
2260
2280
|
};
|
|
2261
|
-
const
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
cleanup();
|
|
2281
|
+
const handleNavigation = (to) => {
|
|
2282
|
+
const from = lastUrl;
|
|
2283
|
+
queueLeave(from, to);
|
|
2284
|
+
lastUrl = normalizeUrl(to);
|
|
2266
2285
|
};
|
|
2267
|
-
const
|
|
2268
|
-
if (
|
|
2269
|
-
if (
|
|
2270
|
-
if (
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
if (!href || href.startsWith("#")) return;
|
|
2276
|
-
const targetAttr = target.getAttribute("target");
|
|
2277
|
-
if (targetAttr && targetAttr !== "_self") return;
|
|
2278
|
-
let url;
|
|
2279
|
-
try {
|
|
2280
|
-
url = new URL(href, window.location.href);
|
|
2281
|
-
} catch {
|
|
2286
|
+
const handleDocumentClick = (event) => {
|
|
2287
|
+
if (event.defaultPrevented) return;
|
|
2288
|
+
if (event.button !== 0) return;
|
|
2289
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
2290
|
+
const anchor = event.target?.closest?.("a");
|
|
2291
|
+
if (!(anchor instanceof HTMLAnchorElement)) return;
|
|
2292
|
+
const href = anchor.getAttribute("href");
|
|
2293
|
+
if (!href || href.startsWith("javascript:") || href.startsWith("mailto:") || href.startsWith("tel:")) {
|
|
2282
2294
|
return;
|
|
2283
2295
|
}
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
};
|
|
2288
|
-
const shouldBlockUrl = (rawUrl) => {
|
|
2289
|
-
if (!rawUrl) return false;
|
|
2290
|
-
let url;
|
|
2296
|
+
const target = anchor.getAttribute("target");
|
|
2297
|
+
if (target && target !== "_self") return;
|
|
2298
|
+
let destination;
|
|
2291
2299
|
try {
|
|
2292
|
-
|
|
2300
|
+
destination = new URL(href, window.location.href);
|
|
2293
2301
|
} catch {
|
|
2294
|
-
return
|
|
2302
|
+
return;
|
|
2295
2303
|
}
|
|
2296
|
-
if (
|
|
2297
|
-
|
|
2298
|
-
|
|
2304
|
+
if (destination.origin !== window.location.origin) return;
|
|
2305
|
+
queueLeave(window.location.href, destination.href);
|
|
2306
|
+
};
|
|
2307
|
+
const handleHashChange = (event) => {
|
|
2308
|
+
const to = event.newURL || window.location.href;
|
|
2309
|
+
const from = event.oldURL || lastUrl;
|
|
2310
|
+
queueLeave(from, to);
|
|
2311
|
+
lastUrl = normalizeUrl(to);
|
|
2299
2312
|
};
|
|
2300
2313
|
const handlePopState = () => {
|
|
2301
|
-
|
|
2302
|
-
fireOnce();
|
|
2303
|
-
if (historyRef && originalPushState && lastUrl) {
|
|
2304
|
-
originalPushState(null, document.title, lastUrl);
|
|
2305
|
-
}
|
|
2314
|
+
handleNavigation(window.location.href);
|
|
2306
2315
|
};
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
lastUrl = window.location.href;
|
|
2324
|
-
return result;
|
|
2325
|
-
};
|
|
2326
|
-
}
|
|
2327
|
-
document.addEventListener("click", handleNavClick, true);
|
|
2316
|
+
historyRef.pushState = function(data, unused, url) {
|
|
2317
|
+
const from = window.location.href;
|
|
2318
|
+
const result = originalPushState(data, unused, url);
|
|
2319
|
+
queueLeave(from, window.location.href);
|
|
2320
|
+
lastUrl = normalizeUrl(window.location.href);
|
|
2321
|
+
return result;
|
|
2322
|
+
};
|
|
2323
|
+
historyRef.replaceState = function(data, unused, url) {
|
|
2324
|
+
const from = window.location.href;
|
|
2325
|
+
const result = originalReplaceState(data, unused, url);
|
|
2326
|
+
queueLeave(from, window.location.href);
|
|
2327
|
+
lastUrl = normalizeUrl(window.location.href);
|
|
2328
|
+
return result;
|
|
2329
|
+
};
|
|
2330
|
+
document.addEventListener("click", handleDocumentClick, true);
|
|
2331
|
+
window.addEventListener("hashchange", handleHashChange);
|
|
2328
2332
|
window.addEventListener("popstate", handlePopState);
|
|
2329
|
-
popups.debug("Exit
|
|
2333
|
+
popups.debug("Exit trigger set (deferred to next route)", {
|
|
2334
|
+
surveyId: trigger.surveyId,
|
|
2335
|
+
delaySeconds
|
|
2336
|
+
});
|
|
2337
|
+
}
|
|
2338
|
+
function setupEventTrigger(popups, trigger) {
|
|
2339
|
+
popups.debug("Event trigger registered", {
|
|
2340
|
+
popupId: trigger.popupId,
|
|
2341
|
+
surveyId: trigger.surveyId,
|
|
2342
|
+
eventName: String(trigger.value || "")
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
function parseExitDelaySeconds(value) {
|
|
2346
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2347
|
+
return Math.max(0, value);
|
|
2348
|
+
}
|
|
2349
|
+
if (typeof value === "string" && value.trim()) {
|
|
2350
|
+
const parsed = Number(value);
|
|
2351
|
+
if (Number.isFinite(parsed)) return Math.max(0, parsed);
|
|
2352
|
+
}
|
|
2353
|
+
return 0;
|
|
2354
|
+
}
|
|
2355
|
+
function normalizeUrl(value) {
|
|
2356
|
+
if (!value) return "";
|
|
2357
|
+
const withoutIndex = value.replace(/\/index\.html(?=($|[?#]))/i, "");
|
|
2358
|
+
return withoutIndex.length > 1 && withoutIndex.endsWith("/") ? withoutIndex.slice(0, -1) : withoutIndex;
|
|
2330
2359
|
}
|
|
2331
2360
|
function setupClickTrigger(popups, trigger) {
|
|
2332
2361
|
const targetId = typeof trigger.value === "string" ? trigger.value : "";
|
|
@@ -2342,7 +2371,7 @@ function setupClickTrigger(popups, trigger) {
|
|
|
2342
2371
|
}
|
|
2343
2372
|
const handler = (e) => {
|
|
2344
2373
|
if (e.defaultPrevented) return;
|
|
2345
|
-
popups.triggerSurvey(trigger.surveyId);
|
|
2374
|
+
popups.triggerSurvey(trigger.surveyId, trigger.popupId);
|
|
2346
2375
|
el.removeEventListener("click", handler);
|
|
2347
2376
|
};
|
|
2348
2377
|
el.addEventListener("click", handler);
|
|
@@ -2464,6 +2493,8 @@ function createDefaultRenderer() {
|
|
|
2464
2493
|
}
|
|
2465
2494
|
|
|
2466
2495
|
// src/core/deepdots-popups.ts
|
|
2496
|
+
var EXIT_QUEUE_STORAGE_KEY = "__deepdots_exit_popup_queue__";
|
|
2497
|
+
var DAY_IN_MS = 24 * 60 * 60 * 1e3;
|
|
2467
2498
|
var DeepdotsPopups = class {
|
|
2468
2499
|
constructor() {
|
|
2469
2500
|
this.config = null;
|
|
@@ -2477,10 +2508,12 @@ var DeepdotsPopups = class {
|
|
|
2477
2508
|
this.popupsLoaded = false;
|
|
2478
2509
|
this.pendingAutoLaunch = false;
|
|
2479
2510
|
this.answeredSurveys = /* @__PURE__ */ new Set();
|
|
2511
|
+
this.surveyProgress = /* @__PURE__ */ new Map();
|
|
2480
2512
|
this.lastShown = /* @__PURE__ */ new Map();
|
|
2481
2513
|
// popupId -> timestamp mostrado
|
|
2482
2514
|
this.surveyToPopupId = /* @__PURE__ */ new Map();
|
|
2483
2515
|
// surveyId -> popupId
|
|
2516
|
+
this.deferredExitTimers = [];
|
|
2484
2517
|
this.baseUrl = "";
|
|
2485
2518
|
this.env = "production";
|
|
2486
2519
|
}
|
|
@@ -2505,15 +2538,17 @@ var DeepdotsPopups = class {
|
|
|
2505
2538
|
if (this.renderer.init) this.renderer.init();
|
|
2506
2539
|
this.setupPopupContainer();
|
|
2507
2540
|
if (this.config.mode === "client") {
|
|
2508
|
-
this.popupDefinitions = this.config.popups || [];
|
|
2541
|
+
this.popupDefinitions = this.validatePopupDefinitions(this.config.popups || []);
|
|
2509
2542
|
this.popupsLoaded = true;
|
|
2510
2543
|
this.configureTriggersFromDefinitions();
|
|
2544
|
+
this.processDeferredExitQueue();
|
|
2511
2545
|
} else {
|
|
2512
2546
|
this.fetchPopupsFromServer().then((defs) => {
|
|
2513
|
-
this.popupDefinitions = defs;
|
|
2547
|
+
this.popupDefinitions = this.validatePopupDefinitions(defs);
|
|
2514
2548
|
this.popupsLoaded = true;
|
|
2515
|
-
this.log("Popups loaded from server (fake)",
|
|
2549
|
+
this.log("Popups loaded from server (fake)", this.popupDefinitions);
|
|
2516
2550
|
this.configureTriggersFromDefinitions();
|
|
2551
|
+
this.processDeferredExitQueue();
|
|
2517
2552
|
if (this.pendingAutoLaunch) {
|
|
2518
2553
|
this.startTriggers();
|
|
2519
2554
|
}
|
|
@@ -2539,7 +2574,6 @@ var DeepdotsPopups = class {
|
|
|
2539
2574
|
}
|
|
2540
2575
|
/** Show a popup immediately (supports legacy ShowOptions and new PopupDefinition) */
|
|
2541
2576
|
show(options) {
|
|
2542
|
-
console.log("Show popup called with options:", options);
|
|
2543
2577
|
if (!this.initialized) {
|
|
2544
2578
|
throw new Error("SDK not initialized. Call init() first.");
|
|
2545
2579
|
}
|
|
@@ -2548,6 +2582,7 @@ var DeepdotsPopups = class {
|
|
|
2548
2582
|
const def = options;
|
|
2549
2583
|
this.log("Showing popup (definition)", def);
|
|
2550
2584
|
this.surveyToPopupId.set(def.surveyId, def.id);
|
|
2585
|
+
this.lastShown.set(def.id, Date.now());
|
|
2551
2586
|
this.renderPopup(def.surveyId, def.productId, def.actions);
|
|
2552
2587
|
this.emitEvent("popup_shown", def.surveyId, { popupId: def.id });
|
|
2553
2588
|
} else {
|
|
@@ -2559,7 +2594,7 @@ var DeepdotsPopups = class {
|
|
|
2559
2594
|
}
|
|
2560
2595
|
/** Mostrar popup por id de definición (usa surveyId interno) */
|
|
2561
2596
|
showByPopupId(popupId) {
|
|
2562
|
-
const def = this.popupDefinitions.find((
|
|
2597
|
+
const def = this.popupDefinitions.find((popup) => popup.id === popupId);
|
|
2563
2598
|
if (!def) {
|
|
2564
2599
|
this.log("Popup definition not found", popupId);
|
|
2565
2600
|
return;
|
|
@@ -2577,75 +2612,89 @@ var DeepdotsPopups = class {
|
|
|
2577
2612
|
/** Deriva triggers desde las definiciones de popup */
|
|
2578
2613
|
configureTriggersFromDefinitions() {
|
|
2579
2614
|
const derived = [];
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
switch (t.type) {
|
|
2587
|
-
case "time_on_page":
|
|
2588
|
-
type = "time";
|
|
2589
|
-
break;
|
|
2590
|
-
case "scroll":
|
|
2591
|
-
type = "scroll";
|
|
2592
|
-
break;
|
|
2593
|
-
case "exit":
|
|
2594
|
-
type = "exit";
|
|
2595
|
-
break;
|
|
2596
|
-
case "click":
|
|
2597
|
-
type = "click";
|
|
2598
|
-
break;
|
|
2599
|
-
default:
|
|
2600
|
-
this.debug("Unsupported trigger type", t.type);
|
|
2615
|
+
this.debug("Validated popup definitions", this.popupDefinitions);
|
|
2616
|
+
this.popupDefinitions.forEach((def) => {
|
|
2617
|
+
def.triggers.forEach((trigger) => {
|
|
2618
|
+
const type = this.mapPopupTriggerType(trigger.type);
|
|
2619
|
+
if (!type) {
|
|
2620
|
+
this.debug("Unsupported trigger type", trigger.type);
|
|
2601
2621
|
return;
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2622
|
+
}
|
|
2623
|
+
const value = type === "time" && typeof trigger.value === "number" ? trigger.value * 1e3 : trigger.value;
|
|
2624
|
+
derived.push({ type, value, surveyId: def.surveyId, popupId: def.id });
|
|
2625
|
+
});
|
|
2605
2626
|
});
|
|
2606
2627
|
if (derived.length) {
|
|
2607
2628
|
this.configureTriggers(derived);
|
|
2608
2629
|
}
|
|
2609
2630
|
}
|
|
2610
2631
|
/** Lógica para evaluar condiciones antes de mostrar una encuesta */
|
|
2611
|
-
triggerSurvey(surveyId) {
|
|
2612
|
-
const def = this.
|
|
2632
|
+
triggerSurvey(surveyId, popupId) {
|
|
2633
|
+
const def = this.findPopupDefinition(surveyId, popupId);
|
|
2613
2634
|
if (!def) {
|
|
2614
|
-
this.debug("No popup definition for
|
|
2635
|
+
this.debug("No popup definition for trigger", { surveyId, popupId });
|
|
2615
2636
|
return;
|
|
2616
2637
|
}
|
|
2617
2638
|
if (!this.shouldShow(def)) {
|
|
2618
2639
|
this.debug("Conditions prevented showing popup", def.id);
|
|
2619
2640
|
return;
|
|
2620
2641
|
}
|
|
2642
|
+
this.showDefinition(def);
|
|
2643
|
+
}
|
|
2644
|
+
/** Trigger popups configured with trigger.type = 'event' and matching trigger.value */
|
|
2645
|
+
triggerEvent(eventName) {
|
|
2646
|
+
if (!this.initialized) {
|
|
2647
|
+
throw new Error("SDK not initialized. Call init() first.");
|
|
2648
|
+
}
|
|
2649
|
+
const normalized = String(eventName || "").trim();
|
|
2650
|
+
if (!normalized) {
|
|
2651
|
+
this.debug("Ignoring empty event trigger name");
|
|
2652
|
+
return;
|
|
2653
|
+
}
|
|
2654
|
+
const candidates = this.popupDefinitions.filter((def) => {
|
|
2655
|
+
return def.triggers.some((trigger) => {
|
|
2656
|
+
const triggerValue = String(trigger.value ?? "").trim();
|
|
2657
|
+
return trigger.type === "event" && triggerValue === normalized;
|
|
2658
|
+
});
|
|
2659
|
+
});
|
|
2660
|
+
if (!candidates.length) {
|
|
2661
|
+
this.debug("No event popup definitions found", { eventName: normalized });
|
|
2662
|
+
return;
|
|
2663
|
+
}
|
|
2664
|
+
const matched = candidates.find((def) => this.shouldShow(def));
|
|
2665
|
+
if (!matched) {
|
|
2666
|
+
this.debug("Event popup blocked by conditions/segments", { eventName: normalized });
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2669
|
+
this.showDefinition(matched);
|
|
2670
|
+
}
|
|
2671
|
+
showDefinition(def) {
|
|
2621
2672
|
this.show(def);
|
|
2622
|
-
this.lastShown.set(def.id, Date.now());
|
|
2623
2673
|
}
|
|
2624
|
-
shouldShow(def) {
|
|
2625
|
-
if (!this.matchesSegmentsPath(def)) {
|
|
2674
|
+
shouldShow(def, pathUrl, skipPathCheck = false) {
|
|
2675
|
+
if (!skipPathCheck && !this.matchesSegmentsPath(def, pathUrl)) {
|
|
2676
|
+
return false;
|
|
2677
|
+
}
|
|
2678
|
+
const cooldowns = def.cooldown || [];
|
|
2679
|
+
if (cooldowns.length && !cooldowns.every((cooldown) => this.evaluateCooldown(def, cooldown))) {
|
|
2626
2680
|
return false;
|
|
2627
2681
|
}
|
|
2628
|
-
const
|
|
2629
|
-
if (!
|
|
2630
|
-
return
|
|
2682
|
+
const legacyConditions = def.legacyConditions || [];
|
|
2683
|
+
if (!legacyConditions.length) return true;
|
|
2684
|
+
return legacyConditions.every((condition) => this.evaluateLegacyCondition(def, condition));
|
|
2631
2685
|
}
|
|
2632
|
-
matchesSegmentsPath(def) {
|
|
2686
|
+
matchesSegmentsPath(def, pathUrl) {
|
|
2633
2687
|
const paths = def.segments?.path;
|
|
2634
2688
|
if (!paths || paths.length === 0) return true;
|
|
2635
2689
|
if (typeof window === "undefined" || !window.location) {
|
|
2636
2690
|
this.debug("No window.location available for path comparison", { popupId: def.id, paths });
|
|
2637
2691
|
return true;
|
|
2638
2692
|
}
|
|
2639
|
-
const
|
|
2640
|
-
const
|
|
2641
|
-
const
|
|
2642
|
-
if (value.length > 1 && value.endsWith("/")) return value.slice(0, -1);
|
|
2643
|
-
return value;
|
|
2644
|
-
};
|
|
2645
|
-
const normalizedPath = normalize(currentPath);
|
|
2646
|
-
const normalizedHref = normalize(currentHref);
|
|
2693
|
+
const normalizedHref = this.normalizeUrl(pathUrl || window.location.href || "");
|
|
2694
|
+
const currentUrl = this.safeParseUrl(normalizedHref);
|
|
2695
|
+
const normalizedPath = this.normalizeUrl(currentUrl?.pathname || window.location.pathname || "");
|
|
2647
2696
|
const matches = paths.some((rawCandidate) => {
|
|
2648
|
-
const candidate =
|
|
2697
|
+
const candidate = this.normalizeUrl(rawCandidate);
|
|
2649
2698
|
let match = false;
|
|
2650
2699
|
if (candidate.startsWith("http://") || candidate.startsWith("https://")) {
|
|
2651
2700
|
match = normalizedHref === candidate;
|
|
@@ -2668,33 +2717,166 @@ var DeepdotsPopups = class {
|
|
|
2668
2717
|
}
|
|
2669
2718
|
return matches;
|
|
2670
2719
|
}
|
|
2671
|
-
|
|
2720
|
+
evaluateCooldown(def, condition) {
|
|
2721
|
+
switch (condition.answered) {
|
|
2722
|
+
case "SHOWED":
|
|
2723
|
+
return this.hasCooldownElapsed(this.lastShown.get(def.id), condition.cooldownDays);
|
|
2724
|
+
case "PARTIAL":
|
|
2725
|
+
case "COMPLETED": {
|
|
2726
|
+
const progress = this.surveyProgress.get(def.surveyId);
|
|
2727
|
+
if (!progress || progress.status !== condition.answered) {
|
|
2728
|
+
return true;
|
|
2729
|
+
}
|
|
2730
|
+
return this.hasCooldownElapsed(progress.timestamp, condition.cooldownDays);
|
|
2731
|
+
}
|
|
2732
|
+
default:
|
|
2733
|
+
return true;
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
evaluateLegacyCondition(def, condition) {
|
|
2672
2737
|
if (!condition.answered && this.answeredSurveys.has(def.surveyId)) {
|
|
2673
2738
|
return false;
|
|
2674
2739
|
}
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
if (msSince < required) return false;
|
|
2681
|
-
}
|
|
2740
|
+
return this.hasCooldownElapsed(this.lastShown.get(def.id), condition.cooldownDays);
|
|
2741
|
+
}
|
|
2742
|
+
hasCooldownElapsed(timestamp, cooldownDays) {
|
|
2743
|
+
if (!timestamp || !cooldownDays) {
|
|
2744
|
+
return true;
|
|
2682
2745
|
}
|
|
2683
|
-
return
|
|
2746
|
+
return Date.now() - timestamp >= cooldownDays * DAY_IN_MS;
|
|
2684
2747
|
}
|
|
2685
2748
|
/** Marcar encuesta como contestada externamente */
|
|
2686
2749
|
markSurveyAnswered(surveyId) {
|
|
2687
2750
|
this.answeredSurveys.add(surveyId);
|
|
2751
|
+
this.markSurveyProgress(surveyId, "COMPLETED");
|
|
2752
|
+
}
|
|
2753
|
+
/** Queue an exit popup so it can render after navigation */
|
|
2754
|
+
queueExitPopup(surveyId, delaySeconds, sourceUrl, popupId) {
|
|
2755
|
+
const def = this.findPopupDefinition(surveyId, popupId);
|
|
2756
|
+
if (!def) {
|
|
2757
|
+
this.debug("No popup definition for exit trigger", { surveyId, popupId });
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
const originUrl = sourceUrl || (typeof window !== "undefined" ? window.location.href : "");
|
|
2761
|
+
if (!originUrl) {
|
|
2762
|
+
this.debug("Exit popup skipped: missing source URL", { surveyId });
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
if (!this.shouldShow(def, originUrl)) {
|
|
2766
|
+
this.debug("Exit popup skipped by conditions/path", { popupId: def.id, sourceUrl: originUrl });
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2769
|
+
const safeDelayMs = Number.isFinite(delaySeconds) ? Math.max(0, delaySeconds * 1e3) : 0;
|
|
2770
|
+
const deferred = {
|
|
2771
|
+
id: def.id,
|
|
2772
|
+
surveyId: def.surveyId,
|
|
2773
|
+
sourceUrl: this.normalizeUrl(originUrl),
|
|
2774
|
+
dueAt: Date.now() + safeDelayMs
|
|
2775
|
+
};
|
|
2776
|
+
const queue = this.getDeferredExitQueue().filter((item) => !(item.id === deferred.id && item.sourceUrl === deferred.sourceUrl));
|
|
2777
|
+
queue.push(deferred);
|
|
2778
|
+
this.setDeferredExitQueue(queue);
|
|
2779
|
+
this.scheduleDeferredExitPopup(deferred);
|
|
2780
|
+
this.debug("Exit popup queued", deferred);
|
|
2781
|
+
}
|
|
2782
|
+
processDeferredExitQueue() {
|
|
2783
|
+
const queue = this.getDeferredExitQueue();
|
|
2784
|
+
if (!queue.length) return;
|
|
2785
|
+
const pending = [];
|
|
2786
|
+
const now = Date.now();
|
|
2787
|
+
queue.forEach((item) => {
|
|
2788
|
+
const def = this.popupDefinitions.find((popup) => popup.id === item.id && popup.surveyId === item.surveyId);
|
|
2789
|
+
if (!def) {
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
if (item.dueAt <= now) {
|
|
2793
|
+
this.tryShowDeferredExitPopup(item);
|
|
2794
|
+
return;
|
|
2795
|
+
}
|
|
2796
|
+
pending.push(item);
|
|
2797
|
+
this.scheduleDeferredExitPopup(item);
|
|
2798
|
+
});
|
|
2799
|
+
this.setDeferredExitQueue(pending);
|
|
2800
|
+
}
|
|
2801
|
+
scheduleDeferredExitPopup(item) {
|
|
2802
|
+
if (typeof window === "undefined") return;
|
|
2803
|
+
const delay = Math.max(0, item.dueAt - Date.now());
|
|
2804
|
+
const timer = window.setTimeout(() => this.tryShowDeferredExitPopup(item), delay);
|
|
2805
|
+
this.deferredExitTimers.push(timer);
|
|
2806
|
+
}
|
|
2807
|
+
tryShowDeferredExitPopup(item) {
|
|
2808
|
+
const def = this.popupDefinitions.find((popup) => popup.id === item.id && popup.surveyId === item.surveyId);
|
|
2809
|
+
if (!def) {
|
|
2810
|
+
this.removeDeferredExitPopup(item);
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
const currentUrl = typeof window !== "undefined" ? this.normalizeUrl(window.location.href || "") : "";
|
|
2814
|
+
if (!currentUrl || currentUrl === this.normalizeUrl(item.sourceUrl)) {
|
|
2815
|
+
this.removeDeferredExitPopup(item);
|
|
2816
|
+
this.debug("Exit popup dropped because route did not change", item);
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
if (!this.shouldShow(def, void 0, true)) {
|
|
2820
|
+
this.removeDeferredExitPopup(item);
|
|
2821
|
+
this.debug("Exit popup skipped at render-time conditions", item);
|
|
2822
|
+
return;
|
|
2823
|
+
}
|
|
2824
|
+
this.showDefinition(def);
|
|
2825
|
+
this.removeDeferredExitPopup(item);
|
|
2826
|
+
}
|
|
2827
|
+
findPopupDefinition(surveyId, popupId) {
|
|
2828
|
+
if (popupId) {
|
|
2829
|
+
const byId = this.popupDefinitions.find((popup) => popup.id === popupId);
|
|
2830
|
+
if (byId) return byId;
|
|
2831
|
+
}
|
|
2832
|
+
return this.popupDefinitions.find((popup) => popup.surveyId === surveyId);
|
|
2833
|
+
}
|
|
2834
|
+
removeDeferredExitPopup(item) {
|
|
2835
|
+
const queue = this.getDeferredExitQueue().filter((entry) => !(entry.id === item.id && entry.sourceUrl === item.sourceUrl));
|
|
2836
|
+
this.setDeferredExitQueue(queue);
|
|
2837
|
+
}
|
|
2838
|
+
getDeferredExitQueue() {
|
|
2839
|
+
if (typeof window === "undefined") return [];
|
|
2840
|
+
try {
|
|
2841
|
+
const raw = window.sessionStorage.getItem(EXIT_QUEUE_STORAGE_KEY);
|
|
2842
|
+
if (!raw) return [];
|
|
2843
|
+
const parsed = JSON.parse(raw);
|
|
2844
|
+
if (!Array.isArray(parsed)) return [];
|
|
2845
|
+
return parsed.filter((entry) => {
|
|
2846
|
+
if (typeof entry !== "object" || entry === null) return false;
|
|
2847
|
+
const item = entry;
|
|
2848
|
+
return typeof item.id === "string" && typeof item.surveyId === "string" && typeof item.sourceUrl === "string" && typeof item.dueAt === "number" && Number.isFinite(item.dueAt);
|
|
2849
|
+
}).map((item) => ({
|
|
2850
|
+
...item,
|
|
2851
|
+
sourceUrl: this.normalizeUrl(item.sourceUrl)
|
|
2852
|
+
}));
|
|
2853
|
+
} catch {
|
|
2854
|
+
return [];
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
setDeferredExitQueue(queue) {
|
|
2858
|
+
if (typeof window === "undefined") return;
|
|
2859
|
+
try {
|
|
2860
|
+
if (!queue.length) {
|
|
2861
|
+
window.sessionStorage.removeItem(EXIT_QUEUE_STORAGE_KEY);
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
window.sessionStorage.setItem(EXIT_QUEUE_STORAGE_KEY, JSON.stringify(queue));
|
|
2865
|
+
} catch {
|
|
2866
|
+
}
|
|
2688
2867
|
}
|
|
2689
2868
|
/** Fetch al servidor para obtener popups */
|
|
2690
2869
|
async fetchPopupsFromServer() {
|
|
2691
2870
|
const apiKey = this.config?.apiKey;
|
|
2692
2871
|
const baseUrl = this.baseUrl;
|
|
2872
|
+
const userId = this.config?.userId;
|
|
2693
2873
|
if (!apiKey || !baseUrl) {
|
|
2694
2874
|
this.log("Missing apiKey or baseUrl. Skipping popups fetch.");
|
|
2695
2875
|
return [];
|
|
2696
2876
|
}
|
|
2697
|
-
const
|
|
2877
|
+
const filter = userId ? { where: { userId } } : null;
|
|
2878
|
+
const query = filter ? `?filter=${encodeURIComponent(JSON.stringify(filter))}` : "";
|
|
2879
|
+
const endpoint = `${baseUrl}/sdk/${encodeURIComponent(apiKey)}/popups${query}`;
|
|
2698
2880
|
try {
|
|
2699
2881
|
const response = await fetch(endpoint);
|
|
2700
2882
|
if (!response.ok) {
|
|
@@ -2707,7 +2889,7 @@ var DeepdotsPopups = class {
|
|
|
2707
2889
|
this.log("Unexpected popups payload", parsed);
|
|
2708
2890
|
return [];
|
|
2709
2891
|
}
|
|
2710
|
-
|
|
2892
|
+
this.debug("Fetched popups payload", parsed);
|
|
2711
2893
|
return parsed;
|
|
2712
2894
|
} catch (error) {
|
|
2713
2895
|
this.log("Error fetching popups", error);
|
|
@@ -2814,6 +2996,9 @@ var DeepdotsPopups = class {
|
|
|
2814
2996
|
emitEvent(type, surveyId, data) {
|
|
2815
2997
|
const event = { type, surveyId, timestamp: Date.now(), data };
|
|
2816
2998
|
this.log("Event emitted", event);
|
|
2999
|
+
if (type === "popup_clicked" && data?.action === "partial") {
|
|
3000
|
+
this.markSurveyProgress(surveyId, "PARTIAL");
|
|
3001
|
+
}
|
|
2817
3002
|
if (type === "survey_completed") {
|
|
2818
3003
|
this.markSurveyAnswered(surveyId);
|
|
2819
3004
|
}
|
|
@@ -2855,26 +3040,103 @@ var DeepdotsPopups = class {
|
|
|
2855
3040
|
this.renderer.init();
|
|
2856
3041
|
}
|
|
2857
3042
|
}
|
|
2858
|
-
|
|
3043
|
+
normalizeUrl(value) {
|
|
3044
|
+
if (!value) return "";
|
|
3045
|
+
const withoutIndex = value.replace(/\/index\.html(?=($|[?#]))/i, "");
|
|
3046
|
+
return withoutIndex.length > 1 && withoutIndex.endsWith("/") ? withoutIndex.slice(0, -1) : withoutIndex;
|
|
3047
|
+
}
|
|
3048
|
+
safeParseUrl(url) {
|
|
3049
|
+
if (!url) return null;
|
|
3050
|
+
try {
|
|
3051
|
+
return new URL(url, typeof window !== "undefined" ? window.location.href : void 0);
|
|
3052
|
+
} catch {
|
|
3053
|
+
return null;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
mapPopupTriggerType(type) {
|
|
3057
|
+
switch (type) {
|
|
3058
|
+
case "time_on_page":
|
|
3059
|
+
return "time";
|
|
3060
|
+
case "scroll":
|
|
3061
|
+
return "scroll";
|
|
3062
|
+
case "exit":
|
|
3063
|
+
return "exit";
|
|
3064
|
+
case "click":
|
|
3065
|
+
return "click";
|
|
3066
|
+
case "event":
|
|
3067
|
+
return "event";
|
|
3068
|
+
default:
|
|
3069
|
+
return null;
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
markSurveyProgress(surveyId, status) {
|
|
3073
|
+
const current = this.surveyProgress.get(surveyId);
|
|
3074
|
+
if (current?.status === "COMPLETED" && status !== "COMPLETED") {
|
|
3075
|
+
return;
|
|
3076
|
+
}
|
|
3077
|
+
this.surveyProgress.set(surveyId, { status, timestamp: Date.now() });
|
|
3078
|
+
}
|
|
2859
3079
|
isPopupDefinition(value) {
|
|
2860
3080
|
if (typeof value !== "object" || value === null) return false;
|
|
2861
|
-
const
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
3081
|
+
const popup = value;
|
|
3082
|
+
return typeof popup.id === "string" && typeof popup.title === "string" && typeof popup.message === "string" && typeof popup.surveyId === "string" && typeof popup.productId === "string" && Array.isArray(popup.triggers) && popup.triggers.length > 0 && popup.triggers.every((trigger) => this.isPopupTrigger(trigger)) && (!popup.cooldown || popup.cooldown.every((condition) => this.isPopupTriggerCondition(condition)));
|
|
3083
|
+
}
|
|
3084
|
+
isPopupTrigger(value) {
|
|
3085
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3086
|
+
const trigger = value;
|
|
3087
|
+
return this.isPopupTriggerType(trigger.type) && (typeof trigger.value === "number" || typeof trigger.value === "string");
|
|
3088
|
+
}
|
|
3089
|
+
isPopupTriggerType(value) {
|
|
3090
|
+
return value === "time_on_page" || value === "scroll" || value === "exit" || value === "click" || value === "event";
|
|
3091
|
+
}
|
|
3092
|
+
isPopupTriggerCondition(value) {
|
|
3093
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3094
|
+
const condition = value;
|
|
3095
|
+
return POPUP_TRIGGER_CONDITION_STATUSES.includes(condition.answered) && typeof condition.cooldownDays === "number" && Number.isFinite(condition.cooldownDays) && condition.cooldownDays >= 0;
|
|
3096
|
+
}
|
|
3097
|
+
isLegacyPopupTriggerCondition(value) {
|
|
3098
|
+
if (typeof value !== "object" || value === null) return false;
|
|
3099
|
+
const condition = value;
|
|
3100
|
+
if (condition.answered !== void 0 && typeof condition.answered !== "boolean") {
|
|
3101
|
+
return false;
|
|
3102
|
+
}
|
|
3103
|
+
if (condition.cooldownDays !== void 0) {
|
|
3104
|
+
return typeof condition.cooldownDays === "number" && Number.isFinite(condition.cooldownDays) && condition.cooldownDays >= 0;
|
|
3105
|
+
}
|
|
3106
|
+
return true;
|
|
3107
|
+
}
|
|
3108
|
+
normalizeTriggers(value) {
|
|
3109
|
+
const items = Array.isArray(value) ? value : value ? [value] : [];
|
|
3110
|
+
return items.filter((trigger) => this.isPopupTrigger(trigger)).map((trigger) => ({
|
|
3111
|
+
type: trigger.type,
|
|
3112
|
+
value: trigger.value
|
|
3113
|
+
}));
|
|
3114
|
+
}
|
|
3115
|
+
normalizeCooldown(value) {
|
|
3116
|
+
if (!Array.isArray(value)) return [];
|
|
3117
|
+
return value.filter((condition) => this.isPopupTriggerCondition(condition));
|
|
3118
|
+
}
|
|
3119
|
+
normalizeLegacyConditions(value) {
|
|
3120
|
+
if (!Array.isArray(value)) return [];
|
|
3121
|
+
return value.filter((condition) => this.isLegacyPopupTriggerCondition(condition));
|
|
2865
3122
|
}
|
|
2866
3123
|
normalizePopupDefinition(def) {
|
|
2867
3124
|
if (typeof def !== "object" || def === null) return null;
|
|
2868
3125
|
const raw = def;
|
|
2869
|
-
const
|
|
2870
|
-
|
|
2871
|
-
|
|
3126
|
+
const rawTriggers = raw.triggers ?? raw.trigger;
|
|
3127
|
+
const triggers = this.normalizeTriggers(rawTriggers);
|
|
3128
|
+
if (!triggers.length) return null;
|
|
3129
|
+
const rawTriggerList = Array.isArray(rawTriggers) ? rawTriggers : rawTriggers ? [rawTriggers] : [];
|
|
3130
|
+
const legacyConditions = [
|
|
3131
|
+
...this.normalizeLegacyConditions(raw.conditions),
|
|
3132
|
+
...rawTriggerList.flatMap((trigger) => this.normalizeLegacyConditions(trigger.condition))
|
|
3133
|
+
];
|
|
3134
|
+
const cooldown = this.normalizeCooldown(raw.cooldown);
|
|
2872
3135
|
return {
|
|
2873
3136
|
...raw,
|
|
2874
|
-
triggers
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
}
|
|
3137
|
+
triggers,
|
|
3138
|
+
cooldown,
|
|
3139
|
+
legacyConditions
|
|
2878
3140
|
};
|
|
2879
3141
|
}
|
|
2880
3142
|
validatePopupDefinitions(defs) {
|