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