@magicfeedback/popup-sdk 1.0.10 → 1.0.11

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/dist/index.js CHANGED
@@ -1787,7 +1787,7 @@ function ensureResponsiveStyles(popup) {
1787
1787
  `;
1788
1788
  popup.appendChild(style);
1789
1789
  }
1790
- async function renderPopup(container, surveyId, productId, actions, emit, onClose) {
1790
+ async function renderPopup(container, surveyId, productId, actions, emit, onClose, env = "production") {
1791
1791
  let surveyCompletedEmitted = false;
1792
1792
  let stylesInjected = false;
1793
1793
  const popup = document.createElement("div");
@@ -2071,7 +2071,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2071
2071
  setLoading(false);
2072
2072
  return;
2073
2073
  }
2074
- import_native.default.init({ debug: true, env: "prod" });
2074
+ import_native.default.init({ debug: true, env: env === "production" ? "prod" : "dev" });
2075
2075
  formInstance = import_native.default.form(surveyId, productId);
2076
2076
  const generateOptions = {
2077
2077
  addButton: false,
@@ -2222,6 +2222,9 @@ function setupTrigger(popups, trigger) {
2222
2222
  case "exit":
2223
2223
  setupExitTrigger(popups, trigger);
2224
2224
  break;
2225
+ case "click":
2226
+ setupClickTrigger(popups, trigger);
2227
+ break;
2225
2228
  }
2226
2229
  }
2227
2230
  function setupTimeTrigger(popups, trigger) {
@@ -2244,23 +2247,120 @@ function setupScrollTrigger(popups, trigger) {
2244
2247
  popups.debug(`Scroll trigger set for ${threshold}%`);
2245
2248
  }
2246
2249
  function setupExitTrigger(popups, trigger) {
2247
- const handler = (e) => {
2248
- if (e.clientY <= 0) {
2249
- popups.triggerSurvey(trigger.surveyId);
2250
- document.removeEventListener("mouseout", handler);
2250
+ let fired = false;
2251
+ let lastUrl = typeof window !== "undefined" ? window.location.href : "";
2252
+ const historyRef = typeof window !== "undefined" ? window.history : null;
2253
+ const originalPushState = historyRef ? historyRef.pushState.bind(historyRef) : null;
2254
+ const originalReplaceState = historyRef ? historyRef.replaceState.bind(historyRef) : null;
2255
+ const cleanup = () => {
2256
+ document.removeEventListener("click", handleNavClick, true);
2257
+ window.removeEventListener("popstate", handlePopState);
2258
+ if (historyRef && originalPushState) historyRef.pushState = originalPushState;
2259
+ if (historyRef && originalReplaceState) historyRef.replaceState = originalReplaceState;
2260
+ };
2261
+ const fireOnce = () => {
2262
+ if (fired) return;
2263
+ fired = true;
2264
+ popups.triggerSurvey(trigger.surveyId);
2265
+ cleanup();
2266
+ };
2267
+ const handleNavClick = (e) => {
2268
+ if (fired) return;
2269
+ if (e.defaultPrevented) return;
2270
+ if (e.button !== 0) return;
2271
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
2272
+ const target = e.target?.closest?.("a");
2273
+ if (!target) return;
2274
+ const href = target.getAttribute("href");
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 {
2282
+ return;
2283
+ }
2284
+ if (url.origin !== window.location.origin) return;
2285
+ e.preventDefault();
2286
+ fireOnce();
2287
+ };
2288
+ const shouldBlockUrl = (rawUrl) => {
2289
+ if (!rawUrl) return false;
2290
+ let url;
2291
+ try {
2292
+ url = rawUrl instanceof URL ? rawUrl : new URL(rawUrl, window.location.href);
2293
+ } catch {
2294
+ return false;
2251
2295
  }
2296
+ if (url.origin !== window.location.origin) return false;
2297
+ if (url.href === window.location.href) return false;
2298
+ return true;
2252
2299
  };
2253
- document.addEventListener("mouseout", handler);
2254
- popups.debug("Exit intent trigger set");
2300
+ const handlePopState = () => {
2301
+ if (fired) return;
2302
+ fireOnce();
2303
+ if (historyRef && originalPushState && lastUrl) {
2304
+ originalPushState(null, document.title, lastUrl);
2305
+ }
2306
+ };
2307
+ if (historyRef && originalPushState && originalReplaceState) {
2308
+ historyRef.pushState = function(data, unused, url) {
2309
+ if (!fired && shouldBlockUrl(url)) {
2310
+ fireOnce();
2311
+ return;
2312
+ }
2313
+ const result = originalPushState(data, unused, url);
2314
+ lastUrl = window.location.href;
2315
+ return result;
2316
+ };
2317
+ historyRef.replaceState = function(data, unused, url) {
2318
+ if (!fired && shouldBlockUrl(url)) {
2319
+ fireOnce();
2320
+ return;
2321
+ }
2322
+ const result = originalReplaceState(data, unused, url);
2323
+ lastUrl = window.location.href;
2324
+ return result;
2325
+ };
2326
+ }
2327
+ document.addEventListener("click", handleNavClick, true);
2328
+ window.addEventListener("popstate", handlePopState);
2329
+ popups.debug("Exit intent trigger set (navigation/spa)");
2330
+ }
2331
+ function setupClickTrigger(popups, trigger) {
2332
+ const targetId = typeof trigger.value === "string" ? trigger.value : "";
2333
+ if (!targetId) {
2334
+ popups.debug("Click trigger missing element id", trigger);
2335
+ return;
2336
+ }
2337
+ const attach = () => {
2338
+ const el = document.getElementById(targetId);
2339
+ if (!el) {
2340
+ popups.debug("Click trigger target not found", targetId);
2341
+ return false;
2342
+ }
2343
+ const handler = (e) => {
2344
+ if (e.defaultPrevented) return;
2345
+ popups.triggerSurvey(trigger.surveyId);
2346
+ el.removeEventListener("click", handler);
2347
+ };
2348
+ el.addEventListener("click", handler);
2349
+ popups.debug(`Click trigger set for #${targetId}`);
2350
+ return true;
2351
+ };
2352
+ if (typeof document === "undefined") return;
2353
+ if (attach()) return;
2354
+ window.addEventListener("DOMContentLoaded", () => attach(), { once: true });
2255
2355
  }
2256
2356
 
2257
2357
  // src/config/env.ts
2258
2358
  var ENVIRONMENT_MAP = {
2259
2359
  development: {
2260
- apiBaseUrl: "https://api-dev.magicfeedback.com"
2360
+ apiBaseUrl: "https://api-dev.deepdots.com"
2261
2361
  },
2262
2362
  production: {
2263
- apiBaseUrl: "https://api.magicfeedback.com"
2363
+ apiBaseUrl: "https://api.deepdots.com"
2264
2364
  }
2265
2365
  };
2266
2366
  function resolveEnvironment(nodeEnv) {
@@ -2342,10 +2442,10 @@ var BrowserPopupRenderer = class {
2342
2442
  document.body.appendChild(this.container);
2343
2443
  }
2344
2444
  }
2345
- show(surveyId, productId, actions, emit, onClose) {
2445
+ show(surveyId, productId, actions, emit, onClose, env = "production") {
2346
2446
  if (!this.container) this.init();
2347
2447
  if (!this.container) return;
2348
- renderPopup(this.container, surveyId, productId, actions, emit, onClose);
2448
+ renderPopup(this.container, surveyId, productId, actions, emit, onClose, env);
2349
2449
  }
2350
2450
  hide() {
2351
2451
  if (this.container) {
@@ -2378,8 +2478,12 @@ var DeepdotsPopups = class {
2378
2478
  this.pendingAutoLaunch = false;
2379
2479
  this.answeredSurveys = /* @__PURE__ */ new Set();
2380
2480
  this.lastShown = /* @__PURE__ */ new Map();
2481
+ // popupId -> timestamp mostrado
2482
+ this.surveyToPopupId = /* @__PURE__ */ new Map();
2483
+ // surveyId -> popupId
2484
+ this.baseUrl = "";
2485
+ this.env = "production";
2381
2486
  }
2382
- // popupId -> timestamp mostrado
2383
2487
  /** Initialize the SDK with configuration */
2384
2488
  init(config) {
2385
2489
  if (this.initialized) {
@@ -2387,12 +2491,14 @@ var DeepdotsPopups = class {
2387
2491
  return;
2388
2492
  }
2389
2493
  const env = resolveEnvironment(config.nodeEnv);
2494
+ this.baseUrl = env.apiBaseUrl;
2495
+ this.env = config.nodeEnv || "production";
2390
2496
  this.config = {
2391
2497
  apiKey: config.apiKey || void 0,
2392
- baseUrl: env.apiBaseUrl,
2393
2498
  mode: config.mode || "client",
2394
2499
  debug: config.debug || false,
2395
- popups: config.popups || []
2500
+ popups: config.popups || [],
2501
+ userId: config.userId || void 0
2396
2502
  };
2397
2503
  this.initialized = true;
2398
2504
  this.log("SDK initialized", this.config);
@@ -2441,8 +2547,9 @@ var DeepdotsPopups = class {
2441
2547
  if (isDefinition) {
2442
2548
  const def = options;
2443
2549
  this.log("Showing popup (definition)", def);
2550
+ this.surveyToPopupId.set(def.surveyId, def.id);
2444
2551
  this.renderPopup(def.surveyId, def.productId, def.actions);
2445
- this.emitEvent("popup_shown", def.surveyId);
2552
+ this.emitEvent("popup_shown", def.surveyId, { popupId: def.id });
2446
2553
  } else {
2447
2554
  const opt = options;
2448
2555
  this.log("Showing popup (legacy options)", opt);
@@ -2471,9 +2578,10 @@ var DeepdotsPopups = class {
2471
2578
  configureTriggersFromDefinitions() {
2472
2579
  const derived = [];
2473
2580
  const validDefs = this.validatePopupDefinitions(this.popupDefinitions);
2581
+ console.log("validDefs", validDefs);
2474
2582
  validDefs.forEach((def) => {
2475
- if (!def.trigger) return;
2476
- const t = def.trigger;
2583
+ if (!def.triggers) return;
2584
+ const t = def.triggers;
2477
2585
  let type;
2478
2586
  switch (t.type) {
2479
2587
  case "time_on_page":
@@ -2485,11 +2593,14 @@ var DeepdotsPopups = class {
2485
2593
  case "exit":
2486
2594
  type = "exit";
2487
2595
  break;
2596
+ case "click":
2597
+ type = "click";
2598
+ break;
2488
2599
  default:
2489
2600
  this.debug("Unsupported trigger type", t.type);
2490
2601
  return;
2491
2602
  }
2492
- const value = type === "time" ? t.value * 1e3 : t.value;
2603
+ const value = type === "time" && typeof t.value === "number" ? t.value * 1e3 : t.value;
2493
2604
  derived.push({ type, value, surveyId: def.surveyId });
2494
2605
  });
2495
2606
  if (derived.length) {
@@ -2511,10 +2622,52 @@ var DeepdotsPopups = class {
2511
2622
  this.lastShown.set(def.id, Date.now());
2512
2623
  }
2513
2624
  shouldShow(def) {
2514
- const conditions = def.trigger.condition || [];
2625
+ if (!this.matchesSegmentsPath(def)) {
2626
+ return false;
2627
+ }
2628
+ const conditions = def.triggers.condition || [];
2515
2629
  if (!conditions.length) return true;
2516
2630
  return conditions.every((c) => this.evaluateCondition(def, c));
2517
2631
  }
2632
+ matchesSegmentsPath(def) {
2633
+ const paths = def.segments?.path;
2634
+ if (!paths || paths.length === 0) return true;
2635
+ if (typeof window === "undefined" || !window.location) {
2636
+ this.debug("No window.location available for path comparison", { popupId: def.id, paths });
2637
+ return true;
2638
+ }
2639
+ const currentPath = window.location.pathname || "";
2640
+ const currentHref = window.location.href || "";
2641
+ const normalize = (value) => {
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);
2647
+ const matches = paths.some((rawCandidate) => {
2648
+ const candidate = normalize(rawCandidate);
2649
+ let match = false;
2650
+ if (candidate.startsWith("http://") || candidate.startsWith("https://")) {
2651
+ match = normalizedHref === candidate;
2652
+ } else if (candidate.startsWith("/")) {
2653
+ match = normalizedHref.includes(candidate);
2654
+ } else {
2655
+ match = normalizedPath === candidate;
2656
+ }
2657
+ this.debug("Path comparison", {
2658
+ popupId: def.id,
2659
+ candidate,
2660
+ currentPath: normalizedPath,
2661
+ currentHref: normalizedHref,
2662
+ match
2663
+ });
2664
+ return match;
2665
+ });
2666
+ if (!matches) {
2667
+ this.debug("No path match for popup", { popupId: def.id, paths, currentPath: normalizedPath });
2668
+ }
2669
+ return matches;
2670
+ }
2518
2671
  evaluateCondition(def, condition) {
2519
2672
  if (!condition.answered && this.answeredSurveys.has(def.surveyId)) {
2520
2673
  return false;
@@ -2533,29 +2686,59 @@ var DeepdotsPopups = class {
2533
2686
  markSurveyAnswered(surveyId) {
2534
2687
  this.answeredSurveys.add(surveyId);
2535
2688
  }
2536
- /** Simula fetch al servidor para obtener popups */
2689
+ /** Fetch al servidor para obtener popups */
2537
2690
  async fetchPopupsFromServer() {
2538
- await new Promise((res) => setTimeout(res, 50));
2539
- return this.generateFakePopups();
2691
+ const apiKey = this.config?.apiKey;
2692
+ const baseUrl = this.baseUrl;
2693
+ if (!apiKey || !baseUrl) {
2694
+ this.log("Missing apiKey or baseUrl. Skipping popups fetch.");
2695
+ return [];
2696
+ }
2697
+ const endpoint = `${baseUrl}/sdk/${encodeURIComponent(apiKey)}/popups`;
2698
+ try {
2699
+ const response = await fetch(endpoint);
2700
+ if (!response.ok) {
2701
+ this.log("Failed to fetch popups", response.status, response.statusText);
2702
+ return [];
2703
+ }
2704
+ const raw = await response.text();
2705
+ const parsed = JSON.parse(raw);
2706
+ if (!Array.isArray(parsed)) {
2707
+ this.log("Unexpected popups payload", parsed);
2708
+ return [];
2709
+ }
2710
+ console.log("res: ", parsed, this.validatePopupDefinitions(parsed));
2711
+ return parsed;
2712
+ } catch (error) {
2713
+ this.log("Error fetching popups", error);
2714
+ return [];
2715
+ }
2540
2716
  }
2541
- /** Genera datos fake para pruebas en modo server */
2542
- generateFakePopups() {
2543
- return [
2544
- {
2545
- id: "popup-123",
2546
- title: "Would you like to help us improve?",
2547
- message: "<p>Take a quick survey and share your thoughts.</p>",
2548
- trigger: { type: "time_on_page", value: 1, condition: [{ answered: false, cooldownDays: 7 }] },
2549
- actions: {
2550
- accept: { label: "Take Survey", surveyId: "survey-abc" },
2551
- decline: { label: "Not Now", cooldownDays: 7 }
2552
- },
2553
- surveyId: "survey-abc",
2554
- productId: "product-xyz",
2555
- style: { theme: "light", position: "bottom-right", imageUrl: null },
2556
- segments: { lang: ["en"], path: ["/checkout"] }
2717
+ /** Notifica eventos de popup a la API */
2718
+ async postPopupEvent(status, popupId, userId) {
2719
+ const apiKey = this.config?.apiKey;
2720
+ const baseUrl = this.baseUrl;
2721
+ if (!apiKey || !baseUrl) {
2722
+ return;
2723
+ }
2724
+ const endpoint = `${baseUrl}/sdk/popups`;
2725
+ try {
2726
+ const response = await fetch(endpoint, {
2727
+ method: "POST",
2728
+ headers: { "Content-Type": "application/json" },
2729
+ body: JSON.stringify({
2730
+ publicKey: apiKey,
2731
+ status,
2732
+ popupId,
2733
+ userId: userId || void 0
2734
+ })
2735
+ });
2736
+ if (!response.ok) {
2737
+ this.log("Failed to post popup event", response.status, response.statusText);
2557
2738
  }
2558
- ];
2739
+ } catch (error) {
2740
+ this.log("Error posting popup event", error);
2741
+ }
2559
2742
  }
2560
2743
  /** Add an event listener */
2561
2744
  on(eventType, listener) {
@@ -2595,11 +2778,26 @@ var DeepdotsPopups = class {
2595
2778
  /** Render the popup UI */
2596
2779
  renderPopup(surveyId, productId, actions) {
2597
2780
  if (this.renderer) {
2598
- this.renderer.show(surveyId, productId, actions, (type, id, payload) => this.emitEvent(type, id, payload), () => this.hidePopup());
2781
+ this.renderer.show(
2782
+ surveyId,
2783
+ productId,
2784
+ actions,
2785
+ (type, id, payload) => this.emitEvent(type, id, payload),
2786
+ () => this.hidePopup(),
2787
+ this.env
2788
+ );
2599
2789
  return;
2600
2790
  }
2601
2791
  if (!this.popupContainer) return;
2602
- renderPopup(this.popupContainer, surveyId, productId, actions, (type, id, payload) => this.emitEvent(type, id, payload), () => this.hidePopup());
2792
+ renderPopup(
2793
+ this.popupContainer,
2794
+ surveyId,
2795
+ productId,
2796
+ actions,
2797
+ (type, id, payload) => this.emitEvent(type, id, payload),
2798
+ () => this.hidePopup(),
2799
+ this.env
2800
+ );
2603
2801
  }
2604
2802
  /** Hide the popup */
2605
2803
  hidePopup() {
@@ -2619,6 +2817,16 @@ var DeepdotsPopups = class {
2619
2817
  if (type === "survey_completed") {
2620
2818
  this.markSurveyAnswered(surveyId);
2621
2819
  }
2820
+ if (type === "popup_shown" || type === "survey_completed") {
2821
+ const popupIdFromData = data?.popupId;
2822
+ const popupId = popupIdFromData || this.surveyToPopupId.get(surveyId);
2823
+ if (popupId) {
2824
+ const userIdFromData = data?.userId;
2825
+ void this.postPopupEvent(type === "popup_shown" ? "opened" : "completed", popupId, userIdFromData || this.config?.userId);
2826
+ } else {
2827
+ this.debug("No popupId available to post event", { type, surveyId });
2828
+ }
2829
+ }
2622
2830
  const listeners = this.listeners.get(type);
2623
2831
  if (listeners) {
2624
2832
  listeners.forEach((listener) => {
@@ -2651,10 +2859,26 @@ var DeepdotsPopups = class {
2651
2859
  isPopupDefinition(value) {
2652
2860
  if (typeof value !== "object" || value === null) return false;
2653
2861
  const v = value;
2654
- return typeof v.id === "string" && typeof v.title === "string" && typeof v.message === "string" && typeof v.surveyId === "string" && typeof v.productId === "string" && !!v.trigger;
2862
+ const trigger = v.triggers || v.triggers;
2863
+ if (!trigger) return false;
2864
+ return typeof v.id === "string" && typeof v.title === "string" && typeof v.message === "string" && typeof v.surveyId === "string" && typeof v.productId === "string";
2865
+ }
2866
+ normalizePopupDefinition(def) {
2867
+ if (typeof def !== "object" || def === null) return null;
2868
+ const raw = def;
2869
+ const trigger = raw.triggers || raw.triggers;
2870
+ if (!trigger) return null;
2871
+ const condition = trigger.condition ?? raw.conditions ?? [];
2872
+ return {
2873
+ ...raw,
2874
+ triggers: {
2875
+ ...trigger,
2876
+ condition
2877
+ }
2878
+ };
2655
2879
  }
2656
2880
  validatePopupDefinitions(defs) {
2657
- return defs.filter((d) => this.isPopupDefinition(d));
2881
+ return defs.map((def) => this.normalizePopupDefinition(def)).filter((def) => !!def && this.isPopupDefinition(def));
2658
2882
  }
2659
2883
  };
2660
2884
  //# sourceMappingURL=index.js.map