@magicfeedback/popup-sdk 1.13.0 → 1.15.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.
@@ -217,6 +217,15 @@ interface DeepdotsInitParams {
217
217
  * en vez de cambiar los defaults que comparten todos los clientes.
218
218
  */
219
219
  surveyCss?: string;
220
+ /**
221
+ * Nonce de la Content-Security-Policy de la página, para hosts con una CSP estricta
222
+ * (`style-src 'nonce-…'` sin `'unsafe-inline'`). El popup lo pone en cada `<style>` que
223
+ * inyecta en el `<head>` (hoja del survey, spinner, responsive, `surveyCss`, `@font-face`);
224
+ * sin él el navegador las bloquea y el survey sale sin estilos. Debe ser el MISMO nonce de
225
+ * la cabecera CSP de esa respuesta (en Angular, el token `CSP_NONCE`). Solo web: en React
226
+ * Native el survey vive en su propio WebView y no le afecta la CSP del host.
227
+ */
228
+ cspNonce?: string;
220
229
  }
221
230
  /**
222
231
  * Options for configuring survey triggers
@@ -314,6 +323,12 @@ interface PopupRenderOptions {
314
323
  * (`formData.lang[0]`) en cuanto este carga, y ambos los pisa `actions.*.label` de la API.
315
324
  */
316
325
  language?: string;
326
+ /**
327
+ * Nonce de la Content-Security-Policy del host (`DeepdotsInitParams.cspNonce`). Se pone en
328
+ * cada `<style>` que inyecta el popup DOM; sin él, una CSP con `style-src 'nonce-…'` bloquea
329
+ * la hoja del survey. No aplica a React Native (el survey vive en su propio WebView).
330
+ */
331
+ cspNonce?: string;
317
332
  }
318
333
  interface PopupRenderer {
319
334
  /** Preparar recursos si aplica */
@@ -393,7 +408,6 @@ declare class DeepdotsPopups {
393
408
  private renderer;
394
409
  /** Logger para el output de debug. Default `console`; el host puede inyectar el suyo en init(). */
395
410
  private logger;
396
- private popupContainer;
397
411
  private popupDefinitions;
398
412
  private popupsLoaded;
399
413
  /**
@@ -418,6 +432,7 @@ declare class DeepdotsPopups {
418
432
  private showProgressBar;
419
433
  /** CSS del host inyectado al final de los estilos del popup. */
420
434
  private surveyCss;
435
+ private cspNonce;
421
436
  /** Identidad + sesión (Fase 1 tracking). Null hasta init(). */
422
437
  private tracking;
423
438
  /** Capa de analytics (canal separado del feedback). Null hasta init(). */
@@ -730,8 +745,6 @@ declare class DeepdotsPopups {
730
745
  on(eventType: DeepdotsEventType, listener: EventListener): void;
731
746
  /** Remove an event listener */
732
747
  off(eventType: DeepdotsEventType, listener: EventListener): void;
733
- /** Setup popup container element */
734
- private setupPopupContainer;
735
748
  /** Render the popup UI */
736
749
  private renderPopup;
737
750
  /** Hide the popup */
@@ -217,6 +217,15 @@ interface DeepdotsInitParams {
217
217
  * en vez de cambiar los defaults que comparten todos los clientes.
218
218
  */
219
219
  surveyCss?: string;
220
+ /**
221
+ * Nonce de la Content-Security-Policy de la página, para hosts con una CSP estricta
222
+ * (`style-src 'nonce-…'` sin `'unsafe-inline'`). El popup lo pone en cada `<style>` que
223
+ * inyecta en el `<head>` (hoja del survey, spinner, responsive, `surveyCss`, `@font-face`);
224
+ * sin él el navegador las bloquea y el survey sale sin estilos. Debe ser el MISMO nonce de
225
+ * la cabecera CSP de esa respuesta (en Angular, el token `CSP_NONCE`). Solo web: en React
226
+ * Native el survey vive en su propio WebView y no le afecta la CSP del host.
227
+ */
228
+ cspNonce?: string;
220
229
  }
221
230
  /**
222
231
  * Options for configuring survey triggers
@@ -314,6 +323,12 @@ interface PopupRenderOptions {
314
323
  * (`formData.lang[0]`) en cuanto este carga, y ambos los pisa `actions.*.label` de la API.
315
324
  */
316
325
  language?: string;
326
+ /**
327
+ * Nonce de la Content-Security-Policy del host (`DeepdotsInitParams.cspNonce`). Se pone en
328
+ * cada `<style>` que inyecta el popup DOM; sin él, una CSP con `style-src 'nonce-…'` bloquea
329
+ * la hoja del survey. No aplica a React Native (el survey vive en su propio WebView).
330
+ */
331
+ cspNonce?: string;
317
332
  }
318
333
  interface PopupRenderer {
319
334
  /** Preparar recursos si aplica */
@@ -393,7 +408,6 @@ declare class DeepdotsPopups {
393
408
  private renderer;
394
409
  /** Logger para el output de debug. Default `console`; el host puede inyectar el suyo en init(). */
395
410
  private logger;
396
- private popupContainer;
397
411
  private popupDefinitions;
398
412
  private popupsLoaded;
399
413
  /**
@@ -418,6 +432,7 @@ declare class DeepdotsPopups {
418
432
  private showProgressBar;
419
433
  /** CSS del host inyectado al final de los estilos del popup. */
420
434
  private surveyCss;
435
+ private cspNonce;
421
436
  /** Identidad + sesión (Fase 1 tracking). Null hasta init(). */
422
437
  private tracking;
423
438
  /** Capa de analytics (canal separado del feedback). Null hasta init(). */
@@ -730,8 +745,6 @@ declare class DeepdotsPopups {
730
745
  on(eventType: DeepdotsEventType, listener: EventListener): void;
731
746
  /** Remove an event listener */
732
747
  off(eventType: DeepdotsEventType, listener: EventListener): void;
733
- /** Setup popup container element */
734
- private setupPopupContainer;
735
748
  /** Render the popup UI */
736
749
  private renderPopup;
737
750
  /** Hide the popup */
@@ -552,7 +552,7 @@ var init_style = __esm({
552
552
  style_default = `/**
553
553
  * NO EDITAR A MANO \u2014 generado por scripts/vendor-survey-css.mjs
554
554
  *
555
- * Copia de @magicfeedback/native@2.2.31 \u2192 dist/styles/magicfeedback-default.css
555
+ * Copia de @magicfeedback/native@2.2.33 \u2192 dist/styles/magicfeedback-default.css
556
556
  * (la hoja can\xF3nica del paquete, la que apunta su campo "style"), con estos cambios locales:
557
557
  *
558
558
  * 1. Sin el @import de Google Fonts (Nunito): no queremos una petici\xF3n externa desde la
@@ -2796,30 +2796,35 @@ __export(renderPopup_exports, {
2796
2796
  SURVEY_ALIGNMENT_STYLE_ID: () => SURVEY_ALIGNMENT_STYLE_ID,
2797
2797
  renderPopup: () => renderPopup
2798
2798
  });
2799
- function ensureMagicFeedbackStyles(_popup) {
2800
- const STYLE_ID = "magicfeedback-sdk-styles";
2801
- if (document.getElementById(STYLE_ID)) return;
2802
- const style = document.createElement("style");
2803
- style.id = STYLE_ID;
2804
- style.textContent = style_default;
2805
- document.head.appendChild(style);
2806
- }
2807
- function ensureSurveyAlignmentStyles() {
2808
- if (document.getElementById(SURVEY_ALIGNMENT_STYLE_ID)) return;
2809
- const style = document.createElement("style");
2810
- style.id = SURVEY_ALIGNMENT_STYLE_ID;
2811
- style.textContent = `
2799
+ function ensureStyle(id, css, nonce) {
2800
+ let el = document.getElementById(id);
2801
+ if (el && nonce && el.getAttribute("nonce") !== nonce) {
2802
+ el.remove();
2803
+ el = null;
2804
+ }
2805
+ if (!el) {
2806
+ el = document.createElement("style");
2807
+ el.id = id;
2808
+ if (nonce) el.setAttribute("nonce", nonce);
2809
+ el.textContent = css;
2810
+ document.head.appendChild(el);
2811
+ } else if (el.textContent !== css) {
2812
+ el.textContent = css;
2813
+ }
2814
+ return el;
2815
+ }
2816
+ function ensureMagicFeedbackStyles(nonce) {
2817
+ ensureStyle("magicfeedback-sdk-styles", style_default, nonce);
2818
+ }
2819
+ function ensureSurveyAlignmentStyles(nonce) {
2820
+ ensureStyle(SURVEY_ALIGNMENT_STYLE_ID, `
2812
2821
  .deepdots-popup .magicfeedback-container,
2813
2822
  .deepdots-popup .magicfeedback-form,
2814
2823
  .deepdots-popup .magicfeedback-div { padding-left: 0; padding-right: 0; }
2815
- `;
2816
- document.head.appendChild(style);
2817
- }
2818
- function ensureSpinnerStyles(_popup) {
2819
- if (document.getElementById("deepdots-spinner-styles")) return;
2820
- const style = document.createElement("style");
2821
- style.id = "deepdots-spinner-styles";
2822
- style.textContent = `
2824
+ `, nonce);
2825
+ }
2826
+ function ensureSpinnerStyles(nonce) {
2827
+ ensureStyle("deepdots-spinner-styles", `
2823
2828
  /* El enunciado trae margin-top:20px del CSS de surveys, que sumaba al hueco de la barra
2824
2829
  de progreso y descompensaba la simetr\xEDa. Solo se anula en el primero: entre preguntas
2825
2830
  de la misma p\xE1gina sigue separando. */
@@ -2827,14 +2832,10 @@ function ensureSpinnerStyles(_popup) {
2827
2832
  @keyframes ddspin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
2828
2833
  .mf-spinner { display:flex; justify-content:center; align-items:center; padding:8px 0; }
2829
2834
  .mf-spinner-circle { width:28px; height:28px; border:3px solid #e0e6ed; border-top-color:#1E293B; border-radius:50%; animation: ddspin 0.9s linear infinite; }
2830
- `;
2831
- document.head.appendChild(style);
2832
- }
2833
- function ensureResponsiveStyles(_popup) {
2834
- if (document.getElementById("deepdots-responsive-styles")) return;
2835
- const style = document.createElement("style");
2836
- style.id = "deepdots-responsive-styles";
2837
- style.textContent = `
2835
+ `, nonce);
2836
+ }
2837
+ function ensureResponsiveStyles(nonce) {
2838
+ ensureStyle("deepdots-responsive-styles", `
2838
2839
  /* Responsive adjustments */
2839
2840
  @media (max-width: 640px) {
2840
2841
  .deepdots-popup {
@@ -2867,32 +2868,20 @@ function ensureResponsiveStyles(_popup) {
2867
2868
  overflow-y: auto !important;
2868
2869
  }
2869
2870
  }
2870
- `;
2871
- document.head.appendChild(style);
2871
+ `, nonce);
2872
2872
  }
2873
- function ensureCustomCss(css) {
2873
+ function ensureCustomCss(css, nonce) {
2874
2874
  const STYLE_ID = "deepdots-custom-css";
2875
- const existing = document.getElementById(STYLE_ID);
2876
2875
  if (!css) {
2877
- existing?.remove();
2876
+ document.getElementById(STYLE_ID)?.remove();
2878
2877
  return;
2879
2878
  }
2880
- const el = existing ?? document.createElement("style");
2881
- el.id = STYLE_ID;
2882
- if (el.textContent !== css) el.textContent = css;
2883
- document.head.appendChild(el);
2879
+ document.head.appendChild(ensureStyle(STYLE_ID, css, nonce));
2884
2880
  }
2885
- function ensureFontFace(family, url) {
2881
+ function ensureFontFace(family, url, nonce) {
2886
2882
  const css = buildFontFaceCss(family, url);
2887
2883
  if (!css) return;
2888
- const STYLE_ID = "deepdots-font-face";
2889
- let el = document.getElementById(STYLE_ID);
2890
- if (!el) {
2891
- el = document.createElement("style");
2892
- el.id = STYLE_ID;
2893
- document.head.appendChild(el);
2894
- }
2895
- if (el.textContent !== css) el.textContent = css;
2884
+ ensureStyle("deepdots-font-face", css, nonce);
2896
2885
  }
2897
2886
  function hasFinePointer() {
2898
2887
  return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(pointer: fine)").matches;
@@ -2902,6 +2891,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2902
2891
  let stylesInjected = false;
2903
2892
  let pageDepth = 0;
2904
2893
  let onStartPage = false;
2894
+ const cspNonce = options?.cspNonce?.trim() || void 0;
2905
2895
  let labels = getLabels(options?.language);
2906
2896
  let actionLabels = resolveActionLabels(actions, options?.language);
2907
2897
  let direction = directionFor(options?.language);
@@ -2953,7 +2943,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2953
2943
  popup.style.fontFamily = familyValue;
2954
2944
  popup.style.setProperty("--deepdots-font", familyValue);
2955
2945
  popup.classList.add("deepdots-has-font");
2956
- if (font.url) ensureFontFace(font.family, font.url);
2946
+ if (font.url) ensureFontFace(font.family, font.url, cspNonce);
2957
2947
  }
2958
2948
  const header = document.createElement("div");
2959
2949
  header.className = "deepdots-popup-header";
@@ -3069,11 +3059,11 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
3069
3059
  }
3070
3060
  progressFollowUp.style.display = p.followup ? "inline-block" : "none";
3071
3061
  }
3072
- ensureMagicFeedbackStyles(popup);
3073
- ensureSurveyAlignmentStyles();
3074
- ensureSpinnerStyles(popup);
3075
- ensureResponsiveStyles(popup);
3076
- ensureCustomCss(options?.surveyCss);
3062
+ ensureMagicFeedbackStyles(cspNonce);
3063
+ ensureSurveyAlignmentStyles(cspNonce);
3064
+ ensureSpinnerStyles(cspNonce);
3065
+ ensureResponsiveStyles(cspNonce);
3066
+ ensureCustomCss(options?.surveyCss, cspNonce);
3077
3067
  const containerContent = document.createElement("div");
3078
3068
  containerContent.id = "dd-content";
3079
3069
  containerContent.className = "deepdots-popup-container-content";
@@ -3781,7 +3771,7 @@ function trimmedOrNull(value) {
3781
3771
  }
3782
3772
  function buildSurveyHtml(opts) {
3783
3773
  const env = opts.env === "development" ? "dev" : "prod";
3784
- const version = opts.version ?? "2.2.31";
3774
+ const version = opts.version ?? "2.2.33";
3785
3775
  const cdn = `https://cdn.jsdelivr.net/npm/@magicfeedback/native@${version}/dist/magicfeedback-sdk.browser.js`;
3786
3776
  const sid = jsonForScript(opts.surveyId);
3787
3777
  const pid = jsonForScript(opts.productId);
@@ -5538,8 +5528,6 @@ var DeepdotsPopups = class {
5538
5528
  this.renderer = createDefaultRenderer();
5539
5529
  /** Logger para el output de debug. Default `console`; el host puede inyectar el suyo en init(). */
5540
5530
  this.logger = console;
5541
- this.popupContainer = null;
5542
- // deprecated: mantenido para compatibilidad interna
5543
5531
  this.popupDefinitions = [];
5544
5532
  this.popupsLoaded = false;
5545
5533
  /**
@@ -5570,6 +5558,7 @@ var DeepdotsPopups = class {
5570
5558
  this.showProgressBar = void 0;
5571
5559
  /** CSS del host inyectado al final de los estilos del popup. */
5572
5560
  this.surveyCss = void 0;
5561
+ this.cspNonce = void 0;
5573
5562
  /** Identidad + sesión (Fase 1 tracking). Null hasta init(). */
5574
5563
  this.tracking = null;
5575
5564
  /** Capa de analytics (canal separado del feedback). Null hasta init(). */
@@ -5642,6 +5631,7 @@ var DeepdotsPopups = class {
5642
5631
  this.renderChrome = config.renderChrome ?? true;
5643
5632
  this.showProgressBar = config.showProgressBar;
5644
5633
  this.surveyCss = config.surveyCss;
5634
+ this.cspNonce = config.cspNonce;
5645
5635
  this.onFeedbackSession = config.onFeedbackSession;
5646
5636
  this.config = {
5647
5637
  apiKey: config.apiKey || void 0,
@@ -5742,7 +5732,6 @@ var DeepdotsPopups = class {
5742
5732
  this.initialized = true;
5743
5733
  this.log("SDK initialized", this.config);
5744
5734
  if (this.renderer.init) this.renderer.init();
5745
- this.setupPopupContainer();
5746
5735
  this.awaitingPopups = Boolean(this.config.apiKey && this.baseUrl);
5747
5736
  this.fetchPopupsFromServer().then((defs) => {
5748
5737
  if (!defs) this.dropPendingTriggers();
@@ -6713,27 +6702,6 @@ var DeepdotsPopups = class {
6713
6702
  listeners.delete(listener);
6714
6703
  }
6715
6704
  }
6716
- /** Setup popup container element */
6717
- setupPopupContainer() {
6718
- if (typeof document === "undefined") {
6719
- return;
6720
- }
6721
- this.popupContainer = document.createElement("div");
6722
- this.popupContainer.id = "deepdots-popup-container";
6723
- this.popupContainer.style.cssText = `
6724
- position: fixed;
6725
- top: 0;
6726
- left: 0;
6727
- width: 100%;
6728
- height: 100%;
6729
- display: none;
6730
- z-index: 999999;
6731
- background: rgba(0, 0, 0, 0.5);
6732
- justify-content: center;
6733
- align-items: center;
6734
- `;
6735
- document.body.appendChild(this.popupContainer);
6736
- }
6737
6705
  /** Render the popup UI */
6738
6706
  renderPopup(surveyId, productId, actions, style, title) {
6739
6707
  const userId = this.tracking?.getUserId() ?? this.config?.userId;
@@ -6741,54 +6709,25 @@ var DeepdotsPopups = class {
6741
6709
  const miniService = this.analytics?.getMiniService() ?? void 0;
6742
6710
  const analyticsFeedbackSessionId = this.analyticsFeedbackSessionId;
6743
6711
  this.log("tracking \xB7 survey identity \u2192", { userId: userId ?? null, sessionId: sessionId ?? null, miniService: miniService ?? null, analyticsFeedbackSessionId: analyticsFeedbackSessionId ?? null });
6744
- if (this.renderer) {
6745
- this.renderer.show(
6746
- surveyId,
6747
- productId,
6748
- actions,
6749
- (type, id, payload) => this.emitEvent(type, id, payload),
6750
- () => this.hidePopup(),
6751
- this.env,
6752
- userId,
6753
- style,
6754
- sessionId,
6755
- miniService,
6756
- analyticsFeedbackSessionId,
6757
- this.renderChrome,
6758
- { title, showProgressBar: this.showProgressBar, surveyCss: this.surveyCss, language: this.language }
6759
- );
6760
- return;
6761
- }
6762
- if (!this.popupContainer) return;
6763
- const container = this.popupContainer;
6764
- void Promise.resolve().then(() => (init_renderPopup(), renderPopup_exports)).then(({ renderPopup: renderPopup2 }) => {
6765
- renderPopup2(
6766
- container,
6767
- surveyId,
6768
- productId,
6769
- actions,
6770
- (type, id, payload) => this.emitEvent(type, id, payload),
6771
- () => this.hidePopup(),
6772
- this.env,
6773
- userId,
6774
- style,
6775
- sessionId,
6776
- miniService,
6777
- analyticsFeedbackSessionId,
6778
- { title, showProgressBar: this.showProgressBar, surveyCss: this.surveyCss, language: this.language }
6779
- );
6780
- });
6712
+ this.renderer.show(
6713
+ surveyId,
6714
+ productId,
6715
+ actions,
6716
+ (type, id, payload) => this.emitEvent(type, id, payload),
6717
+ () => this.hidePopup(),
6718
+ this.env,
6719
+ userId,
6720
+ style,
6721
+ sessionId,
6722
+ miniService,
6723
+ analyticsFeedbackSessionId,
6724
+ this.renderChrome,
6725
+ { title, showProgressBar: this.showProgressBar, surveyCss: this.surveyCss, language: this.language, cspNonce: this.cspNonce }
6726
+ );
6781
6727
  }
6782
6728
  /** Hide the popup */
6783
6729
  hidePopup() {
6784
- if (this.renderer) {
6785
- this.renderer.hide();
6786
- return;
6787
- }
6788
- if (this.popupContainer) {
6789
- this.popupContainer.style.display = "none";
6790
- this.popupContainer.innerHTML = "";
6791
- }
6730
+ this.renderer.hide();
6792
6731
  }
6793
6732
  /** Emit an event */
6794
6733
  emitEvent(type, surveyId, data) {