@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.
@@ -7,8 +7,9 @@ import {
7
7
  buildSurveyIdentity,
8
8
  createDefaultStorage,
9
9
  sdkWarn,
10
- setLogger
11
- } from "./chunk-4UXOEIAB.mjs";
10
+ setLogger,
11
+ style_default
12
+ } from "./chunk-R4B3K2ZW.mjs";
12
13
 
13
14
  // src/react-native/index.tsx
14
15
  import { createContext, useContext, useEffect, useRef, useState } from "react";
@@ -23,6 +24,10 @@ var POPUP_TRIGGER_CONDITION_STATUSES = [
23
24
  ];
24
25
 
25
26
  // src/triggers/index.ts
27
+ function toNumericValue(value, fallback) {
28
+ const n = Number(value);
29
+ return Number.isFinite(n) ? n : fallback;
30
+ }
26
31
  function setupTrigger(popups, trigger) {
27
32
  switch (trigger.type) {
28
33
  case "time":
@@ -43,14 +48,14 @@ function setupTrigger(popups, trigger) {
43
48
  }
44
49
  }
45
50
  function setupTimeTrigger(popups, trigger) {
46
- const delay = trigger.value || 5e3;
51
+ const delay = toNumericValue(trigger.value, 5e3);
47
52
  setTimeout(() => {
48
53
  popups.triggerSurvey(trigger.surveyId, trigger.popupId);
49
54
  }, delay);
50
55
  popups.debug(`Time trigger set for ${delay}ms`);
51
56
  }
52
57
  function setupScrollTrigger(popups, trigger) {
53
- const threshold = trigger.value || 50;
58
+ const threshold = toNumericValue(trigger.value, 50);
54
59
  const handler = () => {
55
60
  const scrollPercentage = window.scrollY / (document.documentElement.scrollHeight - window.innerHeight) * 100;
56
61
  if (scrollPercentage >= threshold) {
@@ -200,6 +205,15 @@ function resolveEnvironment(nodeEnv) {
200
205
  }
201
206
 
202
207
  // src/ui/surveyHtml.ts
208
+ var POSITION_MAP = {
209
+ center: { justifyContent: "center", alignItems: "center", padding: "0", background: "rgba(0,0,0,0.5)" },
210
+ bottom: { justifyContent: "center", alignItems: "flex-end", padding: "16px", background: "transparent" },
211
+ "bottom-right": { justifyContent: "flex-end", alignItems: "flex-end", padding: "16px", background: "transparent" },
212
+ "bottom-left": { justifyContent: "flex-start", alignItems: "flex-end", padding: "16px", background: "transparent" },
213
+ top: { justifyContent: "center", alignItems: "flex-start", padding: "16px", background: "transparent" },
214
+ "top-right": { justifyContent: "flex-end", alignItems: "flex-start", padding: "16px", background: "transparent" },
215
+ "top-left": { justifyContent: "flex-start", alignItems: "flex-start", padding: "16px", background: "transparent" }
216
+ };
203
217
  function buildSurveyHtml(opts) {
204
218
  const env = opts.env === "development" ? "dev" : "prod";
205
219
  const version = opts.version ?? "2.2.4";
@@ -210,17 +224,152 @@ function buildSurveyHtml(opts) {
210
224
  const metaJson = JSON.stringify(opts.metadata ?? []);
211
225
  const fontFaceCss = opts.font ? buildFontFaceCss(opts.font.family, opts.font.url) : "";
212
226
  const fontFamilyValue = opts.font ? buildFontFamilyValue(opts.font.family) : "-apple-system,system-ui,sans-serif";
227
+ const popupClass = opts.font?.family ? "deepdots-popup deepdots-has-font" : "deepdots-popup";
228
+ const isDark = opts.theme === "dark";
229
+ const popupBg = isDark ? "#1e1e1e" : "#fff";
230
+ const colorScheme = isDark ? "dark" : "light";
231
+ const textPrimary = isDark ? "#f0f0f0" : "#111";
232
+ const pos = POSITION_MAP[opts.position ?? "center"] ?? POSITION_MAP.center;
233
+ const chrome = opts.chrome !== false;
234
+ const bodyBg = chrome ? pos.background : "transparent";
235
+ const bodyPadding = chrome ? pos.padding : "0";
236
+ const bodyDisplay = chrome ? "flex" : "block";
237
+ const cardBg = chrome ? popupBg : "transparent";
238
+ const cardShadow = chrome ? "0 4px 6px rgba(0,0,0,0.1)" : "none";
239
+ const cardRadius = chrome ? "8px" : "0";
240
+ const cardMaxWidth = chrome ? "600px" : "none";
241
+ const cardWidth = chrome ? "90%" : "100%";
242
+ const cardMinHeight = chrome ? "200px" : "0";
243
+ const backLabel = JSON.stringify(opts.actions?.back?.label ?? "Back");
244
+ const startLabel = JSON.stringify(opts.actions?.start?.label ?? "Start survey");
245
+ const completeLabel = JSON.stringify(opts.actions?.complete?.label ?? "Complete survey");
246
+ const submitLabel = JSON.stringify(opts.actions?.accept?.label ?? "Send");
213
247
  return `<!DOCTYPE html><html><head>
214
248
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
215
- <meta name="color-scheme" content="light"/>
216
- <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>
249
+ <meta name="color-scheme" content="${colorScheme}"/>
250
+ <style>
251
+ ${style_default}
252
+ ${fontFaceCss}
253
+ html,body{margin:0;padding:0;height:100%;font-family:${fontFamilyValue}}
254
+ body{display:${bodyDisplay};justify-content:${pos.justifyContent};align-items:${pos.alignItems};background:${bodyBg};padding:${bodyPadding};box-sizing:border-box}
255
+ .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}
256
+ .deepdots-popup-header{display:flex;justify-content:flex-end;align-items:center;width:100%}
257
+ #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}
258
+ .deepdots-popup-container-content{display:flex;flex-direction:column;padding:0 20px 12px 20px;max-height:80vh;overflow:hidden;box-sizing:border-box}
259
+ .deepdots-popup-main{display:flex;flex-direction:column;width:100%;max-height:80vh;overflow-y:auto;overflow-x:hidden;position:relative}
260
+ #dd-form-wrapper{width:100%;flex:1 1 auto}
261
+ #mf{width:100%;box-sizing:border-box;visibility:hidden}
262
+ .deepdots-popup-main *{max-width:100%;box-sizing:border-box}
263
+ #dd-logo{max-height:40px;max-width:100%;object-fit:contain}
264
+ .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}
265
+ .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}
266
+ .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}
267
+ #dd-back{background:transparent;color:#333;border:1px solid #999}
268
+ #dd-start,#dd-complete,#dd-submit{background:#1E293B;color:#fff;border:none}
269
+ #dd-submit:disabled,#dd-start:disabled,#dd-back:disabled,#dd-complete:disabled{opacity:0.6;cursor:not-allowed}
270
+ .mf-spinner{display:none;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}
271
+ .mf-spinner-circle{width:28px;height:28px;border:3px solid #e0e6ed;border-top-color:#1E293B;border-radius:50%;animation:ddspin 0.9s linear infinite}
272
+ @keyframes ddspin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}
273
+ @media (max-width:640px){
274
+ .deepdots-popup{width:calc(100% - 24px);max-width:calc(100% - 24px);border-radius:12px}
275
+ .deepdots-popup-footer{flex-direction:column-reverse;gap:12px}
276
+ .dd-nav-btn{width:100%}
277
+ .magicfeedback-checkbox-container,.magicfeedback-radio-container{margin:4px 0;padding:4px 8px}
278
+ .magicfeedback-checkbox label,.magicfeedback-radio-container label{font-size:15px}
279
+ .deepdots-popup input[type="radio"]{margin:6px 0 6px 8px}
280
+ .deepdots-popup input[type="checkbox"]{width:16px;height:16px}
281
+ }
282
+ </style>
217
283
  </head><body>
218
- <div id="mf"></div>
284
+ <div class="${popupClass}" id="dd-popup">
285
+ <div class="deepdots-popup-header">
286
+ <button type="button" id="dd-close" aria-label="Close popup">
287
+ <svg width="28" height="28" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
288
+ <path d="M6 6L18 18M6 18L18 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
289
+ </svg>
290
+ </button>
291
+ </div>
292
+ <div class="deepdots-popup-container-content">
293
+ <div class="deepdots-popup-main" id="dd-main">
294
+ <div id="dd-form-wrapper">
295
+ <div class="mf-spinner" id="dd-spinner" role="status" aria-label="Loading survey"><div class="mf-spinner-circle"></div></div>
296
+ <div id="mf"></div>
297
+ </div>
298
+ <div class="deepdots-error-hint" id="dd-error" role="alert" aria-live="polite"></div>
299
+ <div class="deepdots-popup-footer" id="dd-footer">
300
+ <button type="button" class="dd-nav-btn" id="dd-submit">Send</button>
301
+ <button type="button" class="dd-nav-btn" id="dd-back">Back</button>
302
+ <button type="button" class="dd-nav-btn" id="dd-complete">Complete survey</button>
303
+ <button type="button" class="dd-nav-btn" id="dd-start">Start survey</button>
304
+ </div>
305
+ </div>
306
+ </div>
307
+ </div>
219
308
  <script>
220
309
  (function(){
221
310
  function emit(s){ try{ if(window.ReactNativeWebView){ window.ReactNativeWebView.postMessage(s); } }catch(e){} }
222
311
  function emitJSON(n,p){ try{ emit(JSON.stringify({name:n,payload:p||{}})); }catch(e){} }
223
312
  window.DeepdotsClose = function(){ emitJSON('popup_close'); };
313
+ var chromeOn=${chrome ? "true" : "false"};
314
+
315
+ var popup=document.getElementById('dd-popup');
316
+ var main=document.getElementById('dd-main');
317
+ var footer=document.getElementById('dd-footer');
318
+ var spinner=document.getElementById('dd-spinner');
319
+ var formHost=document.getElementById('mf');
320
+ var errorHint=document.getElementById('dd-error');
321
+ var backBtn=document.getElementById('dd-back');
322
+ var startBtn=document.getElementById('dd-start');
323
+ var completeBtn=document.getElementById('dd-complete');
324
+ var submitBtn=document.getElementById('dd-submit');
325
+
326
+ backBtn.textContent=${backLabel};
327
+ startBtn.textContent=${startLabel};
328
+ completeBtn.textContent=${completeLabel};
329
+ submitBtn.textContent=${submitLabel};
330
+
331
+ document.getElementById('dd-close').onclick=function(){ emitJSON('popup_close'); };
332
+ backBtn.onclick=function(){ if(window.DeepdotsForm && window.DeepdotsForm.back){ window.DeepdotsForm.back(); } };
333
+ startBtn.onclick=function(){ if(window.DeepdotsForm && window.DeepdotsForm.startForm){ window.DeepdotsForm.startForm(); } };
334
+ completeBtn.onclick=function(){ emitJSON('popup_close'); };
335
+ var surveyCompletedEmitted=false;
336
+ submitBtn.onclick=function(){
337
+ if(surveyCompletedEmitted) return;
338
+ if(window.DeepdotsForm && window.DeepdotsForm.send){ window.DeepdotsForm.send(); }
339
+ };
340
+
341
+ function updateButtons(state){
342
+ backBtn.style.display='none';
343
+ startBtn.style.display='none';
344
+ submitBtn.style.display='none';
345
+ completeBtn.style.display='none';
346
+ backBtn.style.width='';
347
+ startBtn.style.width='';
348
+ submitBtn.style.width='';
349
+ completeBtn.style.width='';
350
+ if(state==='start'){
351
+ startBtn.style.display='inline-flex';
352
+ startBtn.style.width='100%';
353
+ } else if(state==='in_progress_first'){
354
+ submitBtn.style.display='inline-flex';
355
+ } else if(state==='in_progress_next'){
356
+ backBtn.style.display='inline-flex';
357
+ submitBtn.style.display='inline-flex';
358
+ } else if(state==='completed'){
359
+ completeBtn.style.display='inline-flex';
360
+ completeBtn.style.width='100%';
361
+ }
362
+ }
363
+
364
+ function setLoading(isLoading){
365
+ spinner.style.display=isLoading?'flex':'none';
366
+ if(!isLoading){ formHost.style.visibility='visible'; }
367
+ footer.style.display=isLoading?'none':'flex';
368
+ backBtn.disabled=isLoading; startBtn.disabled=isLoading; completeBtn.disabled=isLoading; submitBtn.disabled=isLoading;
369
+ if(isLoading){ updateButtons(null); }
370
+ }
371
+ setLoading(true);
372
+
224
373
  var initialized=false;
225
374
  function initMF(){
226
375
  if(initialized || !window.magicfeedback) return initialized;
@@ -229,23 +378,85 @@ function buildSurveyHtml(opts) {
229
378
  window.magicfeedback.init({ debug:false, env:'${env}' });
230
379
  var form = window.magicfeedback.form(${sid}, ${pid}, ${profileJson}, ${metaJson});
231
380
  window.DeepdotsForm = form;
381
+ var stylesInjected=false;
232
382
  form.generate('mf', {
233
383
  addButton:false,
234
384
  getMetaData:true,
235
- onLoadedEvent:function(){ emitJSON('loaded'); },
236
- beforeSubmitEvent:function(){ emitJSON('before_submit'); },
385
+ onLoadedEvent:function(args){
386
+ var s=args && args.formData ? args.formData.style : null;
387
+ if(s && !stylesInjected){
388
+ stylesInjected=true;
389
+ if(chromeOn && s.boxBackgroundColor){ popup.style.background=s.boxBackgroundColor; }
390
+ if(s.contentAlign){ main.style.justifyContent = s.contentAlign==='center' ? 'center' : 'flex-start'; }
391
+ if(s.buttonPrimaryColor){
392
+ submitBtn.style.background=s.buttonPrimaryColor; submitBtn.style.border='none'; submitBtn.style.color='#fff';
393
+ startBtn.style.background=s.buttonPrimaryColor; startBtn.style.border='none'; startBtn.style.color='#fff';
394
+ completeBtn.style.background=s.buttonPrimaryColor; completeBtn.style.border='none'; completeBtn.style.color='#fff';
395
+ }
396
+ if(s.buttonSecondaryColor){
397
+ backBtn.style.background='#fff'; backBtn.style.color=s.buttonSecondaryColor; backBtn.style.border='1px solid '+s.buttonSecondaryColor;
398
+ }
399
+ if(s.logo && !document.getElementById('dd-logo')){
400
+ var logoImg=document.createElement('img');
401
+ logoImg.id='dd-logo'; logoImg.src=s.logo; logoImg.alt='Logo';
402
+ if(s.logoSize==='small'){ logoImg.style.maxHeight='30px'; }
403
+ else if(s.logoSize==='medium'){ logoImg.style.maxHeight='50px'; }
404
+ else if(s.logoSize==='large'){ logoImg.style.maxHeight='70px'; }
405
+ if(s.logoPosition==='left'){ logoImg.style.margin='0 16px 20px 0'; logoImg.style.display='block'; logoImg.style.marginLeft='0'; }
406
+ else if(s.logoPosition==='right'){ logoImg.style.margin='0 0 20px 16px'; logoImg.style.display='block'; logoImg.style.marginLeft='auto'; }
407
+ else if(s.logoPosition==='center'){ logoImg.style.margin='0 auto 20px auto'; logoImg.style.display='block'; }
408
+ main.insertBefore(logoImg, document.getElementById('dd-form-wrapper'));
409
+ }
410
+ if(s.startMessage && s.startMessage!==''){ updateButtons('start'); }
411
+ else { updateButtons('in_progress_first'); }
412
+ } else {
413
+ updateButtons('in_progress_first');
414
+ }
415
+ emitJSON('loaded');
416
+ setLoading(false);
417
+ },
418
+ beforeSubmitEvent:function(){
419
+ setLoading(true);
420
+ emitJSON('before_submit');
421
+ },
237
422
  afterSubmitEvent:function(p){
238
- try{ if(p && p.completed){ emitJSON('survey_completed', p); } else { emitJSON('after_submit', p); } }
239
- catch(e){ emitJSON('after_submit'); }
423
+ var error=p && p.error ? (typeof p.error==='string' ? p.error : String(p.error)) : '';
424
+ if(error){
425
+ setLoading(false);
426
+ if(error.toLowerCase().indexOf('no response')!==-1){
427
+ errorHint.textContent='Please answer the required question to continue.';
428
+ errorHint.style.display='block';
429
+ updateButtons('in_progress_next');
430
+ return;
431
+ }
432
+ errorHint.textContent='An error occurred while submitting. Please try again or close the popup.';
433
+ errorHint.style.display='block';
434
+ return;
435
+ }
436
+ errorHint.style.display='none';
437
+ setLoading(false);
438
+ if(p && p.completed){
439
+ surveyCompletedEmitted=true;
440
+ updateButtons('completed');
441
+ emitJSON('survey_completed', p);
442
+ return;
443
+ }
444
+ if(p && p.total>1 && p.progress>0 && p.progress<p.total){ updateButtons('in_progress_next'); }
445
+ else { updateButtons('in_progress_first'); }
446
+ emitJSON('after_submit', p);
240
447
  },
241
- onBackEvent:function(p){ emitJSON('back', p); }
242
- }).catch(function(e){ emit('error:init'); });
243
- }catch(e){ emit('error:exception'); }
448
+ onBackEvent:function(p){
449
+ if(p && p.progress===0){ updateButtons('in_progress_first'); }
450
+ else { updateButtons('in_progress_next'); }
451
+ emitJSON('back', p);
452
+ }
453
+ }).catch(function(e){ setLoading(false); emit('error:init'); });
454
+ }catch(e){ setLoading(false); emit('error:exception'); }
244
455
  return true;
245
456
  }
246
457
  var s=document.createElement('script'); s.src=${JSON.stringify(cdn)}; s.async=true;
247
458
  s.onload=function(){ initMF(); };
248
- s.onerror=function(){ emit('error:load'); };
459
+ s.onerror=function(){ setLoading(false); emit('error:load'); };
249
460
  document.head.appendChild(s);
250
461
  var tries=0, t=setInterval(function(){ if(initMF()||++tries>40){ clearInterval(t); } },150);
251
462
  })();
@@ -264,18 +475,30 @@ var ReactNativePopupRenderer = class {
264
475
  }
265
476
  init() {
266
477
  }
267
- show(surveyId, productId, _actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
478
+ show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId, renderChrome) {
268
479
  this.emitFn = emit;
269
480
  this.onCloseFn = onClose;
270
481
  this.currentSurveyId = surveyId;
271
482
  this.partialEmitted = false;
272
483
  const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
273
- const html = buildSurveyHtml({ surveyId, productId, env, profile, metadata, font: style?.font });
484
+ const html = buildSurveyHtml({
485
+ surveyId,
486
+ productId,
487
+ env,
488
+ profile,
489
+ metadata,
490
+ font: style?.font,
491
+ theme: style?.theme,
492
+ position: style?.position,
493
+ actions,
494
+ // renderChrome:false (init) → HTML sin tarjeta/overlay: el host lo envuelve en su Modal.
495
+ chrome: renderChrome
496
+ });
274
497
  if (this.options.onShow) {
275
498
  this.options.onShow({ surveyId, productId, html });
276
499
  } else {
277
500
  sdkWarn(
278
- "[Deepdots] ReactNativePopupRenderer sin onShow: pasa { onShow } a new ReactNativePopupRenderer({...}) para montar el WebView del survey."
501
+ "[Deepdots] ReactNativePopupRenderer has no onShow: pass { onShow } to new ReactNativePopupRenderer({...}) to mount the survey WebView."
279
502
  );
280
503
  }
281
504
  }
@@ -360,13 +583,13 @@ var BrowserPopupRenderer = class {
360
583
  document.body.appendChild(this.container);
361
584
  }
362
585
  }
363
- show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
586
+ show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId, _renderChrome) {
364
587
  if (this.visible) return;
365
588
  if (!this.container || !document.body.contains(this.container)) this.init();
366
589
  if (!this.container) return;
367
590
  this.visible = true;
368
591
  const container = this.container;
369
- void import("./renderPopup-NHWJ7YSI.mjs").then(({ renderPopup }) => {
592
+ void import("./renderPopup-UYMGEBSI.mjs").then(({ renderPopup }) => {
370
593
  renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId);
371
594
  });
372
595
  }
@@ -390,7 +613,7 @@ function createDefaultRenderer() {
390
613
  // src/analytics/analytics-manager.ts
391
614
  function createDryRunSink(log = console.log) {
392
615
  return (payload) => log(
393
- "[DeepdotsAnalytics] (dry-run \xB7 NO enviado \xB7 sin init.analytics) POST /sdk/feedback \u2192",
616
+ "[DeepdotsAnalytics] (dry-run \xB7 NOT sent \xB7 no init.analytics) POST /sdk/feedback \u2192",
394
617
  JSON.stringify(payload, null, 2)
395
618
  );
396
619
  }
@@ -632,14 +855,14 @@ function createFeedbackSink(options) {
632
855
  if (final && options.sendBeaconImpl && small) {
633
856
  const payload = typeof Blob !== "undefined" ? new Blob([json], { type: "application/json" }) : json;
634
857
  if (options.sendBeaconImpl(url, payload)) {
635
- options.log?.("[DeepdotsAnalytics] flush final v\xEDa sendBeacon");
858
+ options.log?.("[DeepdotsAnalytics] final flush via sendBeacon");
636
859
  return;
637
860
  }
638
- options.log?.("[DeepdotsAnalytics] sendBeacon rechaz\xF3 el lote; fallback a fetch");
861
+ options.log?.("[DeepdotsAnalytics] sendBeacon rejected the batch; falling back to fetch");
639
862
  }
640
863
  const f = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
641
864
  if (!f) {
642
- options.log?.("[DeepdotsAnalytics] no fetch disponible; payload no enviado", body);
865
+ options.log?.("[DeepdotsAnalytics] no fetch available; payload not sent", body);
643
866
  return;
644
867
  }
645
868
  const res = await f(url, {
@@ -652,10 +875,10 @@ function createFeedbackSink(options) {
652
875
  if (!res.ok) {
653
876
  const detail = await safeBodyText(res);
654
877
  if (isRetryableStatus(res.status)) {
655
- throw new Error(`POST /sdk/feedback ${res.status} (reintentable): ${detail}`);
878
+ throw new Error(`POST /sdk/feedback ${res.status} (retryable): ${detail}`);
656
879
  }
657
880
  options.log?.(
658
- `[DeepdotsAnalytics] POST /sdk/feedback rechazado con ${res.status}; lote DESCARTADO:`,
881
+ `[DeepdotsAnalytics] POST /sdk/feedback rejected with ${res.status}; batch DISCARDED:`,
659
882
  detail
660
883
  );
661
884
  return;
@@ -665,7 +888,7 @@ function createFeedbackSink(options) {
665
888
  const data = await res.json();
666
889
  if (data?.sessionId && data.sessionId !== feedbackSessionId) {
667
890
  feedbackSessionId = data.sessionId;
668
- options.log?.("[DeepdotsAnalytics] feedbackSessionId cacheado:", feedbackSessionId);
891
+ options.log?.("[DeepdotsAnalytics] feedbackSessionId cached:", feedbackSessionId);
669
892
  options.onSessionId?.(feedbackSessionId);
670
893
  }
671
894
  } catch {
@@ -1145,7 +1368,7 @@ var MessageGuard = class {
1145
1368
  return {
1146
1369
  emit: false,
1147
1370
  reason: "invalid_channel",
1148
- detail: `channel "${channel}" no v\xE1lido (esperado ${MESSAGE_CHANNELS.join(" | ")})`
1371
+ detail: `invalid channel "${channel}" (expected ${MESSAGE_CHANNELS.join(" | ")})`
1149
1372
  };
1150
1373
  }
1151
1374
  const entry = this.seen.get(options.id);
@@ -1157,14 +1380,14 @@ var MessageGuard = class {
1157
1380
  return {
1158
1381
  emit: false,
1159
1382
  reason: "channel_conflict",
1160
- detail: `message_id "${options.id}" ya se report\xF3 en channel "${entry.channel}"; se descarta "${channel}"`
1383
+ detail: `message_id "${options.id}" was already reported on channel "${entry.channel}"; discarding "${channel}"`
1161
1384
  };
1162
1385
  }
1163
1386
  if (entry.stages.has(stage)) {
1164
1387
  return {
1165
1388
  emit: false,
1166
1389
  reason: "duplicate_stage",
1167
- detail: `stage "${stage}" ya emitido para message_id "${options.id}"`
1390
+ detail: `stage "${stage}" was already sent for message_id "${options.id}"`
1168
1391
  };
1169
1392
  }
1170
1393
  entry.stages.add(stage);
@@ -1214,6 +1437,8 @@ var DeepdotsPopups = class {
1214
1437
  this.deferredExitTimers = [];
1215
1438
  this.baseUrl = "";
1216
1439
  this.env = "production";
1440
+ /** Si el SDK pinta el "modal" del popup (RN). Default true; false = el host lo envuelve. */
1441
+ this.renderChrome = true;
1217
1442
  /** Identidad + sesión (Fase 1 tracking). Null hasta init(). */
1218
1443
  this.tracking = null;
1219
1444
  /** Capa de analytics (canal separado del feedback). Null hasta init(). */
@@ -1264,6 +1489,7 @@ var DeepdotsPopups = class {
1264
1489
  const env = resolveEnvironment(config.nodeEnv);
1265
1490
  this.baseUrl = env.apiBaseUrl;
1266
1491
  this.env = config.nodeEnv || "production";
1492
+ this.renderChrome = config.renderChrome ?? true;
1267
1493
  this.config = {
1268
1494
  apiKey: config.apiKey || void 0,
1269
1495
  debug: config.debug || false,
@@ -1449,6 +1675,22 @@ var DeepdotsPopups = class {
1449
1675
  trackFunnelStep(funnel, step, taskId, params) {
1450
1676
  this.track("deepdots_funnel_step", { funnel, step, task_id: taskId, ...params ?? {} });
1451
1677
  }
1678
+ /**
1679
+ * Product Effectiveness: una interacción significativa del usuario (`interaction_type` es la
1680
+ * dimensión de agrupación, p. ej. `get_help`, `homepage`, `contact_support`).
1681
+ *
1682
+ * Existe como evento RESERVADO para que la convención no la invente cada host: un `track()`
1683
+ * custom acaba como `deepdots_event_…` y no lo cuenta la analítica de effectiveness.
1684
+ * Un `interactionType` vacío se descarta con warning (no aporta dato agrupable).
1685
+ */
1686
+ trackMeaningfulInteraction(interactionType, params) {
1687
+ const type = typeof interactionType === "string" ? interactionType.trim() : "";
1688
+ if (!type) {
1689
+ console.warn("[DeepdotsPopups] trackMeaningfulInteraction discarded: empty interaction_type");
1690
+ return;
1691
+ }
1692
+ this.track("deepdots_meaningful_interaction", { interaction_type: type, ...params ?? {} });
1693
+ }
1452
1694
  /**
1453
1695
  * Messaging (#18–22): registra una etapa del funnel de una notificación (push/in-app).
1454
1696
  * No-op si tracking off. El evento se descarta (con warning) si el `channel` no es válido,
@@ -1458,7 +1700,7 @@ var DeepdotsPopups = class {
1458
1700
  trackMessage(stage, options) {
1459
1701
  const verdict = this.messageGuard.evaluate(stage, options);
1460
1702
  if (!verdict.emit) {
1461
- console.warn(`[DeepdotsPopups] trackMessage descartado (${verdict.reason}): ${verdict.detail}`);
1703
+ console.warn(`[DeepdotsPopups] trackMessage discarded (${verdict.reason}): ${verdict.detail}`);
1462
1704
  return;
1463
1705
  }
1464
1706
  this.track("deepdots_message", buildMessageParams(stage, options));
@@ -2049,13 +2291,14 @@ var DeepdotsPopups = class {
2049
2291
  style,
2050
2292
  sessionId,
2051
2293
  miniService,
2052
- analyticsFeedbackSessionId
2294
+ analyticsFeedbackSessionId,
2295
+ this.renderChrome
2053
2296
  );
2054
2297
  return;
2055
2298
  }
2056
2299
  if (!this.popupContainer) return;
2057
2300
  const container = this.popupContainer;
2058
- void import("./renderPopup-NHWJ7YSI.mjs").then(({ renderPopup }) => {
2301
+ void import("./renderPopup-UYMGEBSI.mjs").then(({ renderPopup }) => {
2059
2302
  renderPopup(
2060
2303
  container,
2061
2304
  surveyId,
@@ -2315,9 +2558,10 @@ function DeepdotsProvider({ config, children }) {
2315
2558
  survey ? /* @__PURE__ */ jsx(Modal, { visible: true, transparent: true, animationType: "slide", children: /* @__PURE__ */ jsx(View, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(
2316
2559
  WebView,
2317
2560
  {
2561
+ style: { flex: 1 },
2318
2562
  originWhitelist: ["*"],
2319
2563
  javaScriptEnabled: true,
2320
- source: { html: survey.html },
2564
+ source: { html: survey.html, baseUrl: "https://sdk.deepdots.com/" },
2321
2565
  onMessage: (e) => rendererRef.current?.handleMessage(e.nativeEvent.data)
2322
2566
  }
2323
2567
  ) }) }) : null