@magicfeedback/popup-sdk 1.1.5 → 1.1.7

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
@@ -5,8 +5,10 @@ import {
5
5
  buildFontFaceCss,
6
6
  buildFontFamilyValue,
7
7
  buildSurveyIdentity,
8
- createDefaultStorage
9
- } from "./chunk-7TOLYAFL.mjs";
8
+ createDefaultStorage,
9
+ sdkWarn,
10
+ setLogger
11
+ } from "./chunk-XOTM5CHX.mjs";
10
12
 
11
13
  // src/types/index.ts
12
14
  var POPUP_TRIGGER_CONDITION_STATUSES = [
@@ -267,7 +269,7 @@ var ReactNativePopupRenderer = class {
267
269
  if (this.options.onShow) {
268
270
  this.options.onShow({ surveyId, productId, html });
269
271
  } else {
270
- console.warn(
272
+ sdkWarn(
271
273
  "[Deepdots] ReactNativePopupRenderer sin onShow: pasa { onShow } a new ReactNativePopupRenderer({...}) para montar el WebView del survey."
272
274
  );
273
275
  }
@@ -359,7 +361,7 @@ var BrowserPopupRenderer = class {
359
361
  if (!this.container) return;
360
362
  this.visible = true;
361
363
  const container = this.container;
362
- void import("./renderPopup-MES7IINW.mjs").then(({ renderPopup }) => {
364
+ void import("./renderPopup-EZKDMLVZ.mjs").then(({ renderPopup }) => {
363
365
  renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId);
364
366
  });
365
367
  }
@@ -381,16 +383,19 @@ function createDefaultRenderer() {
381
383
  }
382
384
 
383
385
  // src/analytics/analytics-manager.ts
384
- var dryRunSink = (payload) => {
385
- console.log(
386
+ function createDryRunSink(log = console.log) {
387
+ return (payload) => log(
386
388
  "[DeepdotsAnalytics] (dry-run \xB7 NO enviado \xB7 sin init.analytics) POST /sdk/feedback \u2192",
387
389
  JSON.stringify(payload, null, 2)
388
390
  );
389
- };
391
+ }
392
+ var dryRunSink = createDryRunSink();
390
393
  var AnalyticsManager = class {
391
394
  constructor(options = {}) {
392
395
  this.events = [];
393
396
  this.attributes = {};
397
+ /** Métricas del host (valores medibles persistentes). Sobrescriben por key; se reenvían en cada flush. */
398
+ this.metrics = {};
394
399
  /** Mini-services activos: nombre → timestamp de entrada. El orden de inserción marca el "más reciente". */
395
400
  this.activeMiniServices = /* @__PURE__ */ new Map();
396
401
  this.sink = options.sink ?? dryRunSink;
@@ -413,6 +418,14 @@ var AnalyticsManager = class {
413
418
  this.attributes[k] = String(v);
414
419
  }
415
420
  }
421
+ /**
422
+ * Registra/actualiza una métrica (valor medible). Persistente: se reenvía en cada flush.
423
+ * Repetir la misma key SOBRESCRIBE; el valor se coerciona a string; ignora key vacía.
424
+ */
425
+ setMetric(key, value) {
426
+ if (!key) return;
427
+ this.metrics[key] = String(value);
428
+ }
416
429
  /**
417
430
  * Marca el inicio de un mini-service; etiqueta los eventos siguientes con `mini_service`.
418
431
  * Admite varios activos a la vez (concurrencia); el "actual" para etiquetar es el más reciente.
@@ -476,7 +489,8 @@ var AnalyticsManager = class {
476
489
  platform: this.platform,
477
490
  language: this.language,
478
491
  device: this.device,
479
- attributes: { ...this.attributes }
492
+ attributes: { ...this.attributes },
493
+ metrics: { ...this.metrics }
480
494
  },
481
495
  events: [...this.events]
482
496
  };
@@ -532,10 +546,15 @@ function buildAnalyticsFeedbackBody(envelope, keys, feedbackSessionId) {
532
546
  value: [JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })]
533
547
  });
534
548
  }
549
+ const metrics = [];
550
+ for (const [k, v] of Object.entries(context.metrics ?? {})) {
551
+ pushKV(metrics, k, v);
552
+ }
535
553
  return {
536
554
  feedback: {
537
555
  text: "",
538
556
  answers: [],
557
+ metrics,
539
558
  metadata,
540
559
  profile,
541
560
  finished: false
@@ -716,20 +735,95 @@ function collectDeviceInfo(appVersion) {
716
735
  }
717
736
 
718
737
  // src/analytics/geo-info.ts
719
- async function collectGeoInfo(fetchImpl) {
720
- const f = fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
721
- if (!f) return null;
738
+ var GEO_STORAGE_KEY = "deepdots.geo";
739
+ var GEO_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
740
+ var GEO_TIMEOUT_MS = 3e3;
741
+ function readCachedGeo(storage, now) {
742
+ try {
743
+ const raw = storage.getItem(GEO_STORAGE_KEY);
744
+ if (!raw) return null;
745
+ const cached = JSON.parse(raw);
746
+ if (typeof cached.ts !== "number" || now - cached.ts > GEO_TTL_MS) return null;
747
+ const geo = {};
748
+ if (cached.country) geo.country = cached.country;
749
+ if (cached.city) geo.city = cached.city;
750
+ return geo.country || geo.city ? geo : null;
751
+ } catch {
752
+ return null;
753
+ }
754
+ }
755
+ function writeCachedGeo(storage, geo, now) {
756
+ try {
757
+ const payload = { ...geo, ts: now };
758
+ storage.setItem(GEO_STORAGE_KEY, JSON.stringify(payload));
759
+ } catch {
760
+ }
761
+ }
762
+ var DEFAULT_GEO_PROVIDERS = [
763
+ {
764
+ // https://ipapi.co/json/
765
+ url: "https://ipapi.co/json/",
766
+ parse: (d) => geoFrom(d.country_code, d.city)
767
+ },
768
+ {
769
+ // https://ipwho.is/ (devuelve success:false con 200 en errores)
770
+ url: "https://ipwho.is/",
771
+ parse: (d) => d.success === false ? null : geoFrom(d.country_code, d.city)
772
+ },
773
+ {
774
+ // https://ipinfo.io/json (country = código ISO)
775
+ url: "https://ipinfo.io/json",
776
+ parse: (d) => geoFrom(d.country, d.city)
777
+ }
778
+ ];
779
+ function geoFrom(country, city) {
780
+ const c = typeof country === "string" && country ? country : void 0;
781
+ const t = typeof city === "string" && city ? city : void 0;
782
+ if (!c && !t) return null;
783
+ return { country: c, city: t };
784
+ }
785
+ async function fetchOne(provider, f, timeoutMs) {
786
+ const controller = new AbortController();
787
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
722
788
  try {
723
- const res = await f("https://ipapi.co/json/");
789
+ const res = await f(provider.url, { signal: controller.signal });
724
790
  if (!res.ok) return null;
725
791
  const data = await res.json();
726
- const country = data.country_code || void 0;
727
- const city = data.city || void 0;
728
- if (!country && !city) return null;
729
- return { country, city };
792
+ return provider.parse(data);
730
793
  } catch {
731
794
  return null;
795
+ } finally {
796
+ clearTimeout(timer);
797
+ }
798
+ }
799
+ async function fetchGeo(options = {}) {
800
+ const f = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
801
+ if (!f) return null;
802
+ const providers = options.providers ?? DEFAULT_GEO_PROVIDERS;
803
+ const timeoutMs = options.timeoutMs ?? GEO_TIMEOUT_MS;
804
+ for (const provider of providers) {
805
+ const geo = await fetchOne(provider, f, timeoutMs);
806
+ if (geo) return geo;
807
+ }
808
+ return null;
809
+ }
810
+
811
+ // src/analytics/language.ts
812
+ function clean(value) {
813
+ const trimmed = value?.trim();
814
+ return trimmed ? trimmed : void 0;
815
+ }
816
+ function resolveLanguage(sources) {
817
+ const explicit = clean(sources.explicit);
818
+ if (explicit) return explicit;
819
+ const navLang = clean(sources.navigator?.language);
820
+ if (navLang) return navLang;
821
+ try {
822
+ const locale = clean(sources.intl?.DateTimeFormat().resolvedOptions().locale);
823
+ if (locale) return locale;
824
+ } catch {
732
825
  }
826
+ return void 0;
733
827
  }
734
828
 
735
829
  // src/analytics/engagement-tracker.ts
@@ -901,7 +995,7 @@ var CrashReporter = class {
901
995
  }
902
996
  /** Instala los handlers globales de errores no capturados (no-op sin `window`). */
903
997
  install() {
904
- if (typeof window === "undefined") return;
998
+ if (typeof window === "undefined" || typeof window.addEventListener !== "function") return;
905
999
  window.addEventListener("error", (e) => {
906
1000
  if (!this.isEnabled()) return;
907
1001
  this.captureUnhandled(e.error ?? e.message ?? "error");
@@ -953,6 +1047,8 @@ var DeepdotsPopups = class {
953
1047
  this.triggers = [];
954
1048
  this.initialized = false;
955
1049
  this.renderer = createDefaultRenderer();
1050
+ /** Logger para el output de debug. Default `console`; el host puede inyectar el suyo en init(). */
1051
+ this.logger = console;
956
1052
  this.popupContainer = null;
957
1053
  // deprecated: mantenido para compatibilidad interna
958
1054
  this.popupDefinitions = [];
@@ -988,6 +1084,8 @@ var DeepdotsPopups = class {
988
1084
  }
989
1085
  /** Initialize the SDK with configuration */
990
1086
  init(config) {
1087
+ this.logger = config.logger ?? console;
1088
+ setLogger(this.logger);
991
1089
  if (this.initialized) {
992
1090
  this.log("SDK already initialized");
993
1091
  return;
@@ -1027,12 +1125,16 @@ var DeepdotsPopups = class {
1027
1125
  onSessionId: (id) => {
1028
1126
  this.analyticsFeedbackSessionId = id;
1029
1127
  }
1030
- }) : void 0;
1128
+ }) : createDryRunSink((...a) => this.logger.log(...a));
1031
1129
  const device = config.device ?? collectDeviceInfo(config.appVersion);
1032
1130
  this.analytics = new AnalyticsManager({
1033
1131
  sink: analyticsSink,
1034
1132
  publicKey: config.analytics?.publicKey ?? this.config.apiKey,
1035
- language: typeof navigator !== "undefined" ? navigator.language : void 0,
1133
+ language: resolveLanguage({
1134
+ explicit: config.language,
1135
+ navigator: typeof navigator !== "undefined" ? navigator : void 0,
1136
+ intl: typeof Intl !== "undefined" ? Intl : void 0
1137
+ }),
1036
1138
  platform: config.platform ?? "web",
1037
1139
  device,
1038
1140
  maxBatchSize: ANALYTICS_MAX_BATCH_SIZE,
@@ -1049,8 +1151,13 @@ var DeepdotsPopups = class {
1049
1151
  if (this.tracking?.isTrackingEnabled()) {
1050
1152
  this.crashReporter.install();
1051
1153
  }
1052
- collectGeoInfo().then((geo) => {
1053
- if (geo) this.analytics?.updateDevice(geo);
1154
+ const cachedGeo = readCachedGeo(storage, Date.now());
1155
+ if (cachedGeo) this.analytics.updateDevice(cachedGeo);
1156
+ fetchGeo().then((geo) => {
1157
+ if (geo) {
1158
+ this.analytics?.updateDevice(geo);
1159
+ writeCachedGeo(storage, geo, Date.now());
1160
+ }
1054
1161
  }).catch(() => {
1055
1162
  });
1056
1163
  this.navObserver = new NavigationObserver();
@@ -1107,6 +1214,15 @@ var DeepdotsPopups = class {
1107
1214
  if (!this.tracking?.isTrackingEnabled()) return;
1108
1215
  this.analytics?.setUserAttributes(attributes);
1109
1216
  }
1217
+ /**
1218
+ * Registra/actualiza una métrica (valor medible) → `feedback.metrics` del body.
1219
+ * Persistente y sobrescribe por key. No-op si tracking off.
1220
+ */
1221
+ setMetric(key, value) {
1222
+ if (!this.tracking?.isTrackingEnabled()) return;
1223
+ this.analytics?.setMetric(key, value);
1224
+ this.log("analytics \xB7 setMetric:", key, value);
1225
+ }
1110
1226
  /** Reporta un error del host (manejado o no) → evento `deepdots_app_crash`. No-op si tracking off. */
1111
1227
  reportError(error, options) {
1112
1228
  if (!this.tracking?.isTrackingEnabled()) return;
@@ -1680,7 +1796,7 @@ var DeepdotsPopups = class {
1680
1796
  }
1681
1797
  if (!this.popupContainer) return;
1682
1798
  const container = this.popupContainer;
1683
- void import("./renderPopup-MES7IINW.mjs").then(({ renderPopup }) => {
1799
+ void import("./renderPopup-EZKDMLVZ.mjs").then(({ renderPopup }) => {
1684
1800
  renderPopup(
1685
1801
  container,
1686
1802
  surveyId,
@@ -1736,7 +1852,8 @@ var DeepdotsPopups = class {
1736
1852
  try {
1737
1853
  listener(event);
1738
1854
  } catch (error) {
1739
- console.error("Error in event listener:", error);
1855
+ if (this.logger.error) this.logger.error("Error in event listener:", error);
1856
+ else this.logger.log("Error in event listener:", error);
1740
1857
  }
1741
1858
  });
1742
1859
  }
@@ -1744,7 +1861,7 @@ var DeepdotsPopups = class {
1744
1861
  /** Log debug messages */
1745
1862
  log(...args) {
1746
1863
  if (this.config?.debug) {
1747
- console.log("[DeepdotsPopups]", ...args);
1864
+ this.logger.log("[DeepdotsPopups]", ...args);
1748
1865
  }
1749
1866
  }
1750
1867
  /** External debug method for triggers */