@magicfeedback/popup-sdk 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -7,8 +7,9 @@ import {
7
7
  buildSurveyIdentity,
8
8
  createDefaultStorage,
9
9
  sdkWarn,
10
- setLogger
11
- } from "./chunk-XOTM5CHX.mjs";
10
+ setLogger,
11
+ style_default
12
+ } from "./chunk-XFA2Y6CK.mjs";
12
13
 
13
14
  // src/types/index.ts
14
15
  var POPUP_TRIGGER_CONDITION_STATUSES = [
@@ -18,6 +19,10 @@ var POPUP_TRIGGER_CONDITION_STATUSES = [
18
19
  ];
19
20
 
20
21
  // src/triggers/index.ts
22
+ function toNumericValue(value, fallback) {
23
+ const n = Number(value);
24
+ return Number.isFinite(n) ? n : fallback;
25
+ }
21
26
  function setupTrigger(popups, trigger) {
22
27
  switch (trigger.type) {
23
28
  case "time":
@@ -38,14 +43,14 @@ function setupTrigger(popups, trigger) {
38
43
  }
39
44
  }
40
45
  function setupTimeTrigger(popups, trigger) {
41
- const delay = trigger.value || 5e3;
46
+ const delay = toNumericValue(trigger.value, 5e3);
42
47
  setTimeout(() => {
43
48
  popups.triggerSurvey(trigger.surveyId, trigger.popupId);
44
49
  }, delay);
45
50
  popups.debug(`Time trigger set for ${delay}ms`);
46
51
  }
47
52
  function setupScrollTrigger(popups, trigger) {
48
- const threshold = trigger.value || 50;
53
+ const threshold = toNumericValue(trigger.value, 50);
49
54
  const handler = () => {
50
55
  const scrollPercentage = window.scrollY / (document.documentElement.scrollHeight - window.innerHeight) * 100;
51
56
  if (scrollPercentage >= threshold) {
@@ -195,6 +200,15 @@ function resolveEnvironment(nodeEnv) {
195
200
  }
196
201
 
197
202
  // src/ui/surveyHtml.ts
203
+ var POSITION_MAP = {
204
+ center: { justifyContent: "center", alignItems: "center", padding: "0", background: "rgba(0,0,0,0.5)" },
205
+ bottom: { justifyContent: "center", alignItems: "flex-end", padding: "16px", background: "transparent" },
206
+ "bottom-right": { justifyContent: "flex-end", alignItems: "flex-end", padding: "16px", background: "transparent" },
207
+ "bottom-left": { justifyContent: "flex-start", alignItems: "flex-end", padding: "16px", background: "transparent" },
208
+ top: { justifyContent: "center", alignItems: "flex-start", padding: "16px", background: "transparent" },
209
+ "top-right": { justifyContent: "flex-end", alignItems: "flex-start", padding: "16px", background: "transparent" },
210
+ "top-left": { justifyContent: "flex-start", alignItems: "flex-start", padding: "16px", background: "transparent" }
211
+ };
198
212
  function buildSurveyHtml(opts) {
199
213
  const env = opts.env === "development" ? "dev" : "prod";
200
214
  const version = opts.version ?? "2.2.4";
@@ -205,17 +219,152 @@ function buildSurveyHtml(opts) {
205
219
  const metaJson = JSON.stringify(opts.metadata ?? []);
206
220
  const fontFaceCss = opts.font ? buildFontFaceCss(opts.font.family, opts.font.url) : "";
207
221
  const fontFamilyValue = opts.font ? buildFontFamilyValue(opts.font.family) : "-apple-system,system-ui,sans-serif";
222
+ const popupClass = opts.font?.family ? "deepdots-popup deepdots-has-font" : "deepdots-popup";
223
+ const isDark = opts.theme === "dark";
224
+ const popupBg = isDark ? "#1e1e1e" : "#fff";
225
+ const colorScheme = isDark ? "dark" : "light";
226
+ const textPrimary = isDark ? "#f0f0f0" : "#111";
227
+ const pos = POSITION_MAP[opts.position ?? "center"] ?? POSITION_MAP.center;
228
+ const chrome = opts.chrome !== false;
229
+ const bodyBg = chrome ? pos.background : "transparent";
230
+ const bodyPadding = chrome ? pos.padding : "0";
231
+ const bodyDisplay = chrome ? "flex" : "block";
232
+ const cardBg = chrome ? popupBg : "transparent";
233
+ const cardShadow = chrome ? "0 4px 6px rgba(0,0,0,0.1)" : "none";
234
+ const cardRadius = chrome ? "8px" : "0";
235
+ const cardMaxWidth = chrome ? "600px" : "none";
236
+ const cardWidth = chrome ? "90%" : "100%";
237
+ const cardMinHeight = chrome ? "200px" : "0";
238
+ const backLabel = JSON.stringify(opts.actions?.back?.label ?? "Back");
239
+ const startLabel = JSON.stringify(opts.actions?.start?.label ?? "Start survey");
240
+ const completeLabel = JSON.stringify(opts.actions?.complete?.label ?? "Complete survey");
241
+ const submitLabel = JSON.stringify(opts.actions?.accept?.label ?? "Send");
208
242
  return `<!DOCTYPE html><html><head>
209
243
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
210
- <meta name="color-scheme" content="light"/>
211
- <style>${fontFaceCss}html,body{margin:0;padding:0;background:transparent;font-family:${fontFamilyValue}}#mf{width:100%;box-sizing:border-box}#mf *{max-width:100%;box-sizing:border-box}</style>
244
+ <meta name="color-scheme" content="${colorScheme}"/>
245
+ <style>
246
+ ${style_default}
247
+ ${fontFaceCss}
248
+ html,body{margin:0;padding:0;height:100%;font-family:${fontFamilyValue}}
249
+ body{display:${bodyDisplay};justify-content:${pos.justifyContent};align-items:${pos.alignItems};background:${bodyBg};padding:${bodyPadding};box-sizing:border-box}
250
+ .deepdots-popup{position:relative;display:flex;flex-direction:column;justify-content:flex-start;background:${cardBg};color-scheme:${colorScheme};border-radius:${cardRadius};padding:24px;box-shadow:${cardShadow};max-width:${cardMaxWidth};width:${cardWidth};min-height:${cardMinHeight};box-sizing:border-box}
251
+ .deepdots-popup-header{display:flex;justify-content:flex-end;align-items:center;width:100%}
252
+ #dd-close{background:transparent;border:none;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:8px;cursor:pointer;color:${textPrimary};padding:4px}
253
+ .deepdots-popup-container-content{display:flex;flex-direction:column;padding:0 20px 12px 20px;max-height:80vh;overflow:hidden;box-sizing:border-box}
254
+ .deepdots-popup-main{display:flex;flex-direction:column;width:100%;max-height:80vh;overflow-y:auto;overflow-x:hidden;position:relative}
255
+ #dd-form-wrapper{width:100%;flex:1 1 auto}
256
+ #mf{width:100%;box-sizing:border-box;visibility:hidden}
257
+ .deepdots-popup-main *{max-width:100%;box-sizing:border-box}
258
+ #dd-logo{max-height:40px;max-width:100%;object-fit:contain}
259
+ .deepdots-error-hint{display:none;margin:12px 0 0 0;padding:10px 12px;border-radius:6px;background:#FEF3C7;color:#92400E;border:1px solid #FCD34D;font-size:13px}
260
+ .deepdots-popup-footer{display:flex;flex-direction:row-reverse;justify-content:space-between;align-items:center;gap:8px;margin-top:auto;width:100%;padding-top:16px}
261
+ .dd-nav-btn{display:none;border:none;padding:12px 24px;border-radius:4px;cursor:pointer;font-size:14px;box-shadow:0 2px 4px rgba(0,0,0,0.1);align-items:center;justify-content:center;text-align:center}
262
+ #dd-back{background:transparent;color:#333;border:1px solid #999}
263
+ #dd-start,#dd-complete,#dd-submit{background:#1E293B;color:#fff;border:none}
264
+ #dd-submit:disabled,#dd-start:disabled,#dd-back:disabled,#dd-complete:disabled{opacity:0.6;cursor:not-allowed}
265
+ .mf-spinner{display:none;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}
266
+ .mf-spinner-circle{width:28px;height:28px;border:3px solid #e0e6ed;border-top-color:#1E293B;border-radius:50%;animation:ddspin 0.9s linear infinite}
267
+ @keyframes ddspin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}
268
+ @media (max-width:640px){
269
+ .deepdots-popup{width:calc(100% - 24px);max-width:calc(100% - 24px);border-radius:12px}
270
+ .deepdots-popup-footer{flex-direction:column-reverse;gap:12px}
271
+ .dd-nav-btn{width:100%}
272
+ .magicfeedback-checkbox-container,.magicfeedback-radio-container{margin:4px 0;padding:4px 8px}
273
+ .magicfeedback-checkbox label,.magicfeedback-radio-container label{font-size:15px}
274
+ .deepdots-popup input[type="radio"]{margin:6px 0 6px 8px}
275
+ .deepdots-popup input[type="checkbox"]{width:16px;height:16px}
276
+ }
277
+ </style>
212
278
  </head><body>
213
- <div id="mf"></div>
279
+ <div class="${popupClass}" id="dd-popup">
280
+ <div class="deepdots-popup-header">
281
+ <button type="button" id="dd-close" aria-label="Close popup">
282
+ <svg width="28" height="28" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
283
+ <path d="M6 6L18 18M6 18L18 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
284
+ </svg>
285
+ </button>
286
+ </div>
287
+ <div class="deepdots-popup-container-content">
288
+ <div class="deepdots-popup-main" id="dd-main">
289
+ <div id="dd-form-wrapper">
290
+ <div class="mf-spinner" id="dd-spinner" role="status" aria-label="Loading survey"><div class="mf-spinner-circle"></div></div>
291
+ <div id="mf"></div>
292
+ </div>
293
+ <div class="deepdots-error-hint" id="dd-error" role="alert" aria-live="polite"></div>
294
+ <div class="deepdots-popup-footer" id="dd-footer">
295
+ <button type="button" class="dd-nav-btn" id="dd-submit">Send</button>
296
+ <button type="button" class="dd-nav-btn" id="dd-back">Back</button>
297
+ <button type="button" class="dd-nav-btn" id="dd-complete">Complete survey</button>
298
+ <button type="button" class="dd-nav-btn" id="dd-start">Start survey</button>
299
+ </div>
300
+ </div>
301
+ </div>
302
+ </div>
214
303
  <script>
215
304
  (function(){
216
305
  function emit(s){ try{ if(window.ReactNativeWebView){ window.ReactNativeWebView.postMessage(s); } }catch(e){} }
217
306
  function emitJSON(n,p){ try{ emit(JSON.stringify({name:n,payload:p||{}})); }catch(e){} }
218
307
  window.DeepdotsClose = function(){ emitJSON('popup_close'); };
308
+ var chromeOn=${chrome ? "true" : "false"};
309
+
310
+ var popup=document.getElementById('dd-popup');
311
+ var main=document.getElementById('dd-main');
312
+ var footer=document.getElementById('dd-footer');
313
+ var spinner=document.getElementById('dd-spinner');
314
+ var formHost=document.getElementById('mf');
315
+ var errorHint=document.getElementById('dd-error');
316
+ var backBtn=document.getElementById('dd-back');
317
+ var startBtn=document.getElementById('dd-start');
318
+ var completeBtn=document.getElementById('dd-complete');
319
+ var submitBtn=document.getElementById('dd-submit');
320
+
321
+ backBtn.textContent=${backLabel};
322
+ startBtn.textContent=${startLabel};
323
+ completeBtn.textContent=${completeLabel};
324
+ submitBtn.textContent=${submitLabel};
325
+
326
+ document.getElementById('dd-close').onclick=function(){ emitJSON('popup_close'); };
327
+ backBtn.onclick=function(){ if(window.DeepdotsForm && window.DeepdotsForm.back){ window.DeepdotsForm.back(); } };
328
+ startBtn.onclick=function(){ if(window.DeepdotsForm && window.DeepdotsForm.startForm){ window.DeepdotsForm.startForm(); } };
329
+ completeBtn.onclick=function(){ emitJSON('popup_close'); };
330
+ var surveyCompletedEmitted=false;
331
+ submitBtn.onclick=function(){
332
+ if(surveyCompletedEmitted) return;
333
+ if(window.DeepdotsForm && window.DeepdotsForm.send){ window.DeepdotsForm.send(); }
334
+ };
335
+
336
+ function updateButtons(state){
337
+ backBtn.style.display='none';
338
+ startBtn.style.display='none';
339
+ submitBtn.style.display='none';
340
+ completeBtn.style.display='none';
341
+ backBtn.style.width='';
342
+ startBtn.style.width='';
343
+ submitBtn.style.width='';
344
+ completeBtn.style.width='';
345
+ if(state==='start'){
346
+ startBtn.style.display='inline-flex';
347
+ startBtn.style.width='100%';
348
+ } else if(state==='in_progress_first'){
349
+ submitBtn.style.display='inline-flex';
350
+ } else if(state==='in_progress_next'){
351
+ backBtn.style.display='inline-flex';
352
+ submitBtn.style.display='inline-flex';
353
+ } else if(state==='completed'){
354
+ completeBtn.style.display='inline-flex';
355
+ completeBtn.style.width='100%';
356
+ }
357
+ }
358
+
359
+ function setLoading(isLoading){
360
+ spinner.style.display=isLoading?'flex':'none';
361
+ if(!isLoading){ formHost.style.visibility='visible'; }
362
+ footer.style.display=isLoading?'none':'flex';
363
+ backBtn.disabled=isLoading; startBtn.disabled=isLoading; completeBtn.disabled=isLoading; submitBtn.disabled=isLoading;
364
+ if(isLoading){ updateButtons(null); }
365
+ }
366
+ setLoading(true);
367
+
219
368
  var initialized=false;
220
369
  function initMF(){
221
370
  if(initialized || !window.magicfeedback) return initialized;
@@ -224,23 +373,85 @@ function buildSurveyHtml(opts) {
224
373
  window.magicfeedback.init({ debug:false, env:'${env}' });
225
374
  var form = window.magicfeedback.form(${sid}, ${pid}, ${profileJson}, ${metaJson});
226
375
  window.DeepdotsForm = form;
376
+ var stylesInjected=false;
227
377
  form.generate('mf', {
228
378
  addButton:false,
229
379
  getMetaData:true,
230
- onLoadedEvent:function(){ emitJSON('loaded'); },
231
- beforeSubmitEvent:function(){ emitJSON('before_submit'); },
380
+ onLoadedEvent:function(args){
381
+ var s=args && args.formData ? args.formData.style : null;
382
+ if(s && !stylesInjected){
383
+ stylesInjected=true;
384
+ if(chromeOn && s.boxBackgroundColor){ popup.style.background=s.boxBackgroundColor; }
385
+ if(s.contentAlign){ main.style.justifyContent = s.contentAlign==='center' ? 'center' : 'flex-start'; }
386
+ if(s.buttonPrimaryColor){
387
+ submitBtn.style.background=s.buttonPrimaryColor; submitBtn.style.border='none'; submitBtn.style.color='#fff';
388
+ startBtn.style.background=s.buttonPrimaryColor; startBtn.style.border='none'; startBtn.style.color='#fff';
389
+ completeBtn.style.background=s.buttonPrimaryColor; completeBtn.style.border='none'; completeBtn.style.color='#fff';
390
+ }
391
+ if(s.buttonSecondaryColor){
392
+ backBtn.style.background='#fff'; backBtn.style.color=s.buttonSecondaryColor; backBtn.style.border='1px solid '+s.buttonSecondaryColor;
393
+ }
394
+ if(s.logo && !document.getElementById('dd-logo')){
395
+ var logoImg=document.createElement('img');
396
+ logoImg.id='dd-logo'; logoImg.src=s.logo; logoImg.alt='Logo';
397
+ if(s.logoSize==='small'){ logoImg.style.maxHeight='30px'; }
398
+ else if(s.logoSize==='medium'){ logoImg.style.maxHeight='50px'; }
399
+ else if(s.logoSize==='large'){ logoImg.style.maxHeight='70px'; }
400
+ if(s.logoPosition==='left'){ logoImg.style.margin='0 16px 20px 0'; logoImg.style.display='block'; logoImg.style.marginLeft='0'; }
401
+ else if(s.logoPosition==='right'){ logoImg.style.margin='0 0 20px 16px'; logoImg.style.display='block'; logoImg.style.marginLeft='auto'; }
402
+ else if(s.logoPosition==='center'){ logoImg.style.margin='0 auto 20px auto'; logoImg.style.display='block'; }
403
+ main.insertBefore(logoImg, document.getElementById('dd-form-wrapper'));
404
+ }
405
+ if(s.startMessage && s.startMessage!==''){ updateButtons('start'); }
406
+ else { updateButtons('in_progress_first'); }
407
+ } else {
408
+ updateButtons('in_progress_first');
409
+ }
410
+ emitJSON('loaded');
411
+ setLoading(false);
412
+ },
413
+ beforeSubmitEvent:function(){
414
+ setLoading(true);
415
+ emitJSON('before_submit');
416
+ },
232
417
  afterSubmitEvent:function(p){
233
- try{ if(p && p.completed){ emitJSON('survey_completed', p); } else { emitJSON('after_submit', p); } }
234
- catch(e){ emitJSON('after_submit'); }
418
+ var error=p && p.error ? (typeof p.error==='string' ? p.error : String(p.error)) : '';
419
+ if(error){
420
+ setLoading(false);
421
+ if(error.toLowerCase().indexOf('no response')!==-1){
422
+ errorHint.textContent='Please answer the required question to continue.';
423
+ errorHint.style.display='block';
424
+ updateButtons('in_progress_next');
425
+ return;
426
+ }
427
+ errorHint.textContent='An error occurred while submitting. Please try again or close the popup.';
428
+ errorHint.style.display='block';
429
+ return;
430
+ }
431
+ errorHint.style.display='none';
432
+ setLoading(false);
433
+ if(p && p.completed){
434
+ surveyCompletedEmitted=true;
435
+ updateButtons('completed');
436
+ emitJSON('survey_completed', p);
437
+ return;
438
+ }
439
+ if(p && p.total>1 && p.progress>0 && p.progress<p.total){ updateButtons('in_progress_next'); }
440
+ else { updateButtons('in_progress_first'); }
441
+ emitJSON('after_submit', p);
235
442
  },
236
- onBackEvent:function(p){ emitJSON('back', p); }
237
- }).catch(function(e){ emit('error:init'); });
238
- }catch(e){ emit('error:exception'); }
443
+ onBackEvent:function(p){
444
+ if(p && p.progress===0){ updateButtons('in_progress_first'); }
445
+ else { updateButtons('in_progress_next'); }
446
+ emitJSON('back', p);
447
+ }
448
+ }).catch(function(e){ setLoading(false); emit('error:init'); });
449
+ }catch(e){ setLoading(false); emit('error:exception'); }
239
450
  return true;
240
451
  }
241
452
  var s=document.createElement('script'); s.src=${JSON.stringify(cdn)}; s.async=true;
242
453
  s.onload=function(){ initMF(); };
243
- s.onerror=function(){ emit('error:load'); };
454
+ s.onerror=function(){ setLoading(false); emit('error:load'); };
244
455
  document.head.appendChild(s);
245
456
  var tries=0, t=setInterval(function(){ if(initMF()||++tries>40){ clearInterval(t); } },150);
246
457
  })();
@@ -259,18 +470,30 @@ var ReactNativePopupRenderer = class {
259
470
  }
260
471
  init() {
261
472
  }
262
- show(surveyId, productId, _actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
473
+ show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId, renderChrome) {
263
474
  this.emitFn = emit;
264
475
  this.onCloseFn = onClose;
265
476
  this.currentSurveyId = surveyId;
266
477
  this.partialEmitted = false;
267
478
  const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
268
- const html = buildSurveyHtml({ surveyId, productId, env, profile, metadata, font: style?.font });
479
+ const html = buildSurveyHtml({
480
+ surveyId,
481
+ productId,
482
+ env,
483
+ profile,
484
+ metadata,
485
+ font: style?.font,
486
+ theme: style?.theme,
487
+ position: style?.position,
488
+ actions,
489
+ // renderChrome:false (init) → HTML sin tarjeta/overlay: el host lo envuelve en su Modal.
490
+ chrome: renderChrome
491
+ });
269
492
  if (this.options.onShow) {
270
493
  this.options.onShow({ surveyId, productId, html });
271
494
  } else {
272
495
  sdkWarn(
273
- "[Deepdots] ReactNativePopupRenderer sin onShow: pasa { onShow } a new ReactNativePopupRenderer({...}) para montar el WebView del survey."
496
+ "[Deepdots] ReactNativePopupRenderer has no onShow: pass { onShow } to new ReactNativePopupRenderer({...}) to mount the survey WebView."
274
497
  );
275
498
  }
276
499
  }
@@ -355,13 +578,13 @@ var BrowserPopupRenderer = class {
355
578
  document.body.appendChild(this.container);
356
579
  }
357
580
  }
358
- show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
581
+ show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId, _renderChrome) {
359
582
  if (this.visible) return;
360
583
  if (!this.container || !document.body.contains(this.container)) this.init();
361
584
  if (!this.container) return;
362
585
  this.visible = true;
363
586
  const container = this.container;
364
- void import("./renderPopup-R53C7DB3.mjs").then(({ renderPopup }) => {
587
+ void import("./renderPopup-TCKR5Z3P.mjs").then(({ renderPopup }) => {
365
588
  renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId);
366
589
  });
367
590
  }
@@ -385,7 +608,7 @@ function createDefaultRenderer() {
385
608
  // src/analytics/analytics-manager.ts
386
609
  function createDryRunSink(log = console.log) {
387
610
  return (payload) => log(
388
- "[DeepdotsAnalytics] (dry-run \xB7 NO enviado \xB7 sin init.analytics) POST /sdk/feedback \u2192",
611
+ "[DeepdotsAnalytics] (dry-run \xB7 NOT sent \xB7 no init.analytics) POST /sdk/feedback \u2192",
389
612
  JSON.stringify(payload, null, 2)
390
613
  );
391
614
  }
@@ -627,14 +850,14 @@ function createFeedbackSink(options) {
627
850
  if (final && options.sendBeaconImpl && small) {
628
851
  const payload = typeof Blob !== "undefined" ? new Blob([json], { type: "application/json" }) : json;
629
852
  if (options.sendBeaconImpl(url, payload)) {
630
- options.log?.("[DeepdotsAnalytics] flush final v\xEDa sendBeacon");
853
+ options.log?.("[DeepdotsAnalytics] final flush via sendBeacon");
631
854
  return;
632
855
  }
633
- options.log?.("[DeepdotsAnalytics] sendBeacon rechaz\xF3 el lote; fallback a fetch");
856
+ options.log?.("[DeepdotsAnalytics] sendBeacon rejected the batch; falling back to fetch");
634
857
  }
635
858
  const f = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
636
859
  if (!f) {
637
- options.log?.("[DeepdotsAnalytics] no fetch disponible; payload no enviado", body);
860
+ options.log?.("[DeepdotsAnalytics] no fetch available; payload not sent", body);
638
861
  return;
639
862
  }
640
863
  const res = await f(url, {
@@ -647,10 +870,10 @@ function createFeedbackSink(options) {
647
870
  if (!res.ok) {
648
871
  const detail = await safeBodyText(res);
649
872
  if (isRetryableStatus(res.status)) {
650
- throw new Error(`POST /sdk/feedback ${res.status} (reintentable): ${detail}`);
873
+ throw new Error(`POST /sdk/feedback ${res.status} (retryable): ${detail}`);
651
874
  }
652
875
  options.log?.(
653
- `[DeepdotsAnalytics] POST /sdk/feedback rechazado con ${res.status}; lote DESCARTADO:`,
876
+ `[DeepdotsAnalytics] POST /sdk/feedback rejected with ${res.status}; batch DISCARDED:`,
654
877
  detail
655
878
  );
656
879
  return;
@@ -660,7 +883,7 @@ function createFeedbackSink(options) {
660
883
  const data = await res.json();
661
884
  if (data?.sessionId && data.sessionId !== feedbackSessionId) {
662
885
  feedbackSessionId = data.sessionId;
663
- options.log?.("[DeepdotsAnalytics] feedbackSessionId cacheado:", feedbackSessionId);
886
+ options.log?.("[DeepdotsAnalytics] feedbackSessionId cached:", feedbackSessionId);
664
887
  options.onSessionId?.(feedbackSessionId);
665
888
  }
666
889
  } catch {
@@ -1140,7 +1363,7 @@ var MessageGuard = class {
1140
1363
  return {
1141
1364
  emit: false,
1142
1365
  reason: "invalid_channel",
1143
- detail: `channel "${channel}" no v\xE1lido (esperado ${MESSAGE_CHANNELS.join(" | ")})`
1366
+ detail: `invalid channel "${channel}" (expected ${MESSAGE_CHANNELS.join(" | ")})`
1144
1367
  };
1145
1368
  }
1146
1369
  const entry = this.seen.get(options.id);
@@ -1152,14 +1375,14 @@ var MessageGuard = class {
1152
1375
  return {
1153
1376
  emit: false,
1154
1377
  reason: "channel_conflict",
1155
- detail: `message_id "${options.id}" ya se report\xF3 en channel "${entry.channel}"; se descarta "${channel}"`
1378
+ detail: `message_id "${options.id}" was already reported on channel "${entry.channel}"; discarding "${channel}"`
1156
1379
  };
1157
1380
  }
1158
1381
  if (entry.stages.has(stage)) {
1159
1382
  return {
1160
1383
  emit: false,
1161
1384
  reason: "duplicate_stage",
1162
- detail: `stage "${stage}" ya emitido para message_id "${options.id}"`
1385
+ detail: `stage "${stage}" was already sent for message_id "${options.id}"`
1163
1386
  };
1164
1387
  }
1165
1388
  entry.stages.add(stage);
@@ -1209,6 +1432,8 @@ var DeepdotsPopups = class {
1209
1432
  this.deferredExitTimers = [];
1210
1433
  this.baseUrl = "";
1211
1434
  this.env = "production";
1435
+ /** Si el SDK pinta el "modal" del popup (RN). Default true; false = el host lo envuelve. */
1436
+ this.renderChrome = true;
1212
1437
  /** Identidad + sesión (Fase 1 tracking). Null hasta init(). */
1213
1438
  this.tracking = null;
1214
1439
  /** Capa de analytics (canal separado del feedback). Null hasta init(). */
@@ -1259,6 +1484,7 @@ var DeepdotsPopups = class {
1259
1484
  const env = resolveEnvironment(config.nodeEnv);
1260
1485
  this.baseUrl = env.apiBaseUrl;
1261
1486
  this.env = config.nodeEnv || "production";
1487
+ this.renderChrome = config.renderChrome ?? true;
1262
1488
  this.config = {
1263
1489
  apiKey: config.apiKey || void 0,
1264
1490
  debug: config.debug || false,
@@ -1444,6 +1670,22 @@ var DeepdotsPopups = class {
1444
1670
  trackFunnelStep(funnel, step, taskId, params) {
1445
1671
  this.track("deepdots_funnel_step", { funnel, step, task_id: taskId, ...params ?? {} });
1446
1672
  }
1673
+ /**
1674
+ * Product Effectiveness: una interacción significativa del usuario (`interaction_type` es la
1675
+ * dimensión de agrupación, p. ej. `get_help`, `homepage`, `contact_support`).
1676
+ *
1677
+ * Existe como evento RESERVADO para que la convención no la invente cada host: un `track()`
1678
+ * custom acaba como `deepdots_event_…` y no lo cuenta la analítica de effectiveness.
1679
+ * Un `interactionType` vacío se descarta con warning (no aporta dato agrupable).
1680
+ */
1681
+ trackMeaningfulInteraction(interactionType, params) {
1682
+ const type = typeof interactionType === "string" ? interactionType.trim() : "";
1683
+ if (!type) {
1684
+ console.warn("[DeepdotsPopups] trackMeaningfulInteraction discarded: empty interaction_type");
1685
+ return;
1686
+ }
1687
+ this.track("deepdots_meaningful_interaction", { interaction_type: type, ...params ?? {} });
1688
+ }
1447
1689
  /**
1448
1690
  * Messaging (#18–22): registra una etapa del funnel de una notificación (push/in-app).
1449
1691
  * No-op si tracking off. El evento se descarta (con warning) si el `channel` no es válido,
@@ -1453,7 +1695,7 @@ var DeepdotsPopups = class {
1453
1695
  trackMessage(stage, options) {
1454
1696
  const verdict = this.messageGuard.evaluate(stage, options);
1455
1697
  if (!verdict.emit) {
1456
- console.warn(`[DeepdotsPopups] trackMessage descartado (${verdict.reason}): ${verdict.detail}`);
1698
+ console.warn(`[DeepdotsPopups] trackMessage discarded (${verdict.reason}): ${verdict.detail}`);
1457
1699
  return;
1458
1700
  }
1459
1701
  this.track("deepdots_message", buildMessageParams(stage, options));
@@ -2044,13 +2286,14 @@ var DeepdotsPopups = class {
2044
2286
  style,
2045
2287
  sessionId,
2046
2288
  miniService,
2047
- analyticsFeedbackSessionId
2289
+ analyticsFeedbackSessionId,
2290
+ this.renderChrome
2048
2291
  );
2049
2292
  return;
2050
2293
  }
2051
2294
  if (!this.popupContainer) return;
2052
2295
  const container = this.popupContainer;
2053
- void import("./renderPopup-R53C7DB3.mjs").then(({ renderPopup }) => {
2296
+ void import("./renderPopup-TCKR5Z3P.mjs").then(({ renderPopup }) => {
2054
2297
  renderPopup(
2055
2298
  container,
2056
2299
  surveyId,