@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.
@@ -5,8 +5,10 @@ import {
5
5
  buildFontFaceCss,
6
6
  buildFontFamilyValue,
7
7
  buildSurveyIdentity,
8
- createDefaultStorage
9
- } from "./chunk-LSTKGFU7.mjs";
8
+ createDefaultStorage,
9
+ sdkWarn,
10
+ setLogger
11
+ } from "./chunk-4UXOEIAB.mjs";
10
12
 
11
13
  // src/react-native/index.tsx
12
14
  import { createContext, useContext, useEffect, useRef, useState } from "react";
@@ -272,7 +274,7 @@ var ReactNativePopupRenderer = class {
272
274
  if (this.options.onShow) {
273
275
  this.options.onShow({ surveyId, productId, html });
274
276
  } else {
275
- console.warn(
277
+ sdkWarn(
276
278
  "[Deepdots] ReactNativePopupRenderer sin onShow: pasa { onShow } a new ReactNativePopupRenderer({...}) para montar el WebView del survey."
277
279
  );
278
280
  }
@@ -364,7 +366,7 @@ var BrowserPopupRenderer = class {
364
366
  if (!this.container) return;
365
367
  this.visible = true;
366
368
  const container = this.container;
367
- void import("./renderPopup-YNH2TD3B.mjs").then(({ renderPopup }) => {
369
+ void import("./renderPopup-PS3NJDUR.mjs").then(({ renderPopup }) => {
368
370
  renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId);
369
371
  });
370
372
  }
@@ -386,16 +388,19 @@ function createDefaultRenderer() {
386
388
  }
387
389
 
388
390
  // src/analytics/analytics-manager.ts
389
- var dryRunSink = (payload) => {
390
- console.log(
391
+ function createDryRunSink(log = console.log) {
392
+ return (payload) => log(
391
393
  "[DeepdotsAnalytics] (dry-run \xB7 NO enviado \xB7 sin init.analytics) POST /sdk/feedback \u2192",
392
394
  JSON.stringify(payload, null, 2)
393
395
  );
394
- };
396
+ }
397
+ var dryRunSink = createDryRunSink();
395
398
  var AnalyticsManager = class {
396
399
  constructor(options = {}) {
397
400
  this.events = [];
398
401
  this.attributes = {};
402
+ /** Métricas del host (valores medibles persistentes). Sobrescriben por key; se reenvían en cada flush. */
403
+ this.metrics = {};
399
404
  /** Mini-services activos: nombre → timestamp de entrada. El orden de inserción marca el "más reciente". */
400
405
  this.activeMiniServices = /* @__PURE__ */ new Map();
401
406
  this.sink = options.sink ?? dryRunSink;
@@ -418,6 +423,14 @@ var AnalyticsManager = class {
418
423
  this.attributes[k] = String(v);
419
424
  }
420
425
  }
426
+ /**
427
+ * Registra/actualiza una métrica (valor medible). Persistente: se reenvía en cada flush.
428
+ * Repetir la misma key SOBRESCRIBE; el valor se coerciona a string; ignora key vacía.
429
+ */
430
+ setMetric(key, value) {
431
+ if (!key) return;
432
+ this.metrics[key] = String(value);
433
+ }
421
434
  /**
422
435
  * Marca el inicio de un mini-service; etiqueta los eventos siguientes con `mini_service`.
423
436
  * Admite varios activos a la vez (concurrencia); el "actual" para etiquetar es el más reciente.
@@ -481,7 +494,8 @@ var AnalyticsManager = class {
481
494
  platform: this.platform,
482
495
  language: this.language,
483
496
  device: this.device,
484
- attributes: { ...this.attributes }
497
+ attributes: { ...this.attributes },
498
+ metrics: { ...this.metrics }
485
499
  },
486
500
  events: [...this.events]
487
501
  };
@@ -537,10 +551,15 @@ function buildAnalyticsFeedbackBody(envelope, keys, feedbackSessionId) {
537
551
  value: [JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })]
538
552
  });
539
553
  }
554
+ const metrics = [];
555
+ for (const [k, v] of Object.entries(context.metrics ?? {})) {
556
+ pushKV(metrics, k, v);
557
+ }
540
558
  return {
541
559
  feedback: {
542
560
  text: "",
543
561
  answers: [],
562
+ metrics,
544
563
  metadata,
545
564
  profile,
546
565
  finished: false
@@ -721,20 +740,95 @@ function collectDeviceInfo(appVersion) {
721
740
  }
722
741
 
723
742
  // src/analytics/geo-info.ts
724
- async function collectGeoInfo(fetchImpl) {
725
- const f = fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
726
- if (!f) return null;
743
+ var GEO_STORAGE_KEY = "deepdots.geo";
744
+ var GEO_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
745
+ var GEO_TIMEOUT_MS = 3e3;
746
+ function readCachedGeo(storage, now) {
747
+ try {
748
+ const raw = storage.getItem(GEO_STORAGE_KEY);
749
+ if (!raw) return null;
750
+ const cached = JSON.parse(raw);
751
+ if (typeof cached.ts !== "number" || now - cached.ts > GEO_TTL_MS) return null;
752
+ const geo = {};
753
+ if (cached.country) geo.country = cached.country;
754
+ if (cached.city) geo.city = cached.city;
755
+ return geo.country || geo.city ? geo : null;
756
+ } catch {
757
+ return null;
758
+ }
759
+ }
760
+ function writeCachedGeo(storage, geo, now) {
761
+ try {
762
+ const payload = { ...geo, ts: now };
763
+ storage.setItem(GEO_STORAGE_KEY, JSON.stringify(payload));
764
+ } catch {
765
+ }
766
+ }
767
+ var DEFAULT_GEO_PROVIDERS = [
768
+ {
769
+ // https://ipapi.co/json/
770
+ url: "https://ipapi.co/json/",
771
+ parse: (d) => geoFrom(d.country_code, d.city)
772
+ },
773
+ {
774
+ // https://ipwho.is/ (devuelve success:false con 200 en errores)
775
+ url: "https://ipwho.is/",
776
+ parse: (d) => d.success === false ? null : geoFrom(d.country_code, d.city)
777
+ },
778
+ {
779
+ // https://ipinfo.io/json (country = código ISO)
780
+ url: "https://ipinfo.io/json",
781
+ parse: (d) => geoFrom(d.country, d.city)
782
+ }
783
+ ];
784
+ function geoFrom(country, city) {
785
+ const c = typeof country === "string" && country ? country : void 0;
786
+ const t = typeof city === "string" && city ? city : void 0;
787
+ if (!c && !t) return null;
788
+ return { country: c, city: t };
789
+ }
790
+ async function fetchOne(provider, f, timeoutMs) {
791
+ const controller = new AbortController();
792
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
727
793
  try {
728
- const res = await f("https://ipapi.co/json/");
794
+ const res = await f(provider.url, { signal: controller.signal });
729
795
  if (!res.ok) return null;
730
796
  const data = await res.json();
731
- const country = data.country_code || void 0;
732
- const city = data.city || void 0;
733
- if (!country && !city) return null;
734
- return { country, city };
797
+ return provider.parse(data);
735
798
  } catch {
736
799
  return null;
800
+ } finally {
801
+ clearTimeout(timer);
802
+ }
803
+ }
804
+ async function fetchGeo(options = {}) {
805
+ const f = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
806
+ if (!f) return null;
807
+ const providers = options.providers ?? DEFAULT_GEO_PROVIDERS;
808
+ const timeoutMs = options.timeoutMs ?? GEO_TIMEOUT_MS;
809
+ for (const provider of providers) {
810
+ const geo = await fetchOne(provider, f, timeoutMs);
811
+ if (geo) return geo;
812
+ }
813
+ return null;
814
+ }
815
+
816
+ // src/analytics/language.ts
817
+ function clean(value) {
818
+ const trimmed = value?.trim();
819
+ return trimmed ? trimmed : void 0;
820
+ }
821
+ function resolveLanguage(sources) {
822
+ const explicit = clean(sources.explicit);
823
+ if (explicit) return explicit;
824
+ const navLang = clean(sources.navigator?.language);
825
+ if (navLang) return navLang;
826
+ try {
827
+ const locale = clean(sources.intl?.DateTimeFormat().resolvedOptions().locale);
828
+ if (locale) return locale;
829
+ } catch {
737
830
  }
831
+ return void 0;
738
832
  }
739
833
 
740
834
  // src/analytics/engagement-tracker.ts
@@ -906,7 +1000,7 @@ var CrashReporter = class {
906
1000
  }
907
1001
  /** Instala los handlers globales de errores no capturados (no-op sin `window`). */
908
1002
  install() {
909
- if (typeof window === "undefined") return;
1003
+ if (typeof window === "undefined" || typeof window.addEventListener !== "function") return;
910
1004
  window.addEventListener("error", (e) => {
911
1005
  if (!this.isEnabled()) return;
912
1006
  this.captureUnhandled(e.error ?? e.message ?? "error");
@@ -958,6 +1052,8 @@ var DeepdotsPopups = class {
958
1052
  this.triggers = [];
959
1053
  this.initialized = false;
960
1054
  this.renderer = createDefaultRenderer();
1055
+ /** Logger para el output de debug. Default `console`; el host puede inyectar el suyo en init(). */
1056
+ this.logger = console;
961
1057
  this.popupContainer = null;
962
1058
  // deprecated: mantenido para compatibilidad interna
963
1059
  this.popupDefinitions = [];
@@ -993,6 +1089,8 @@ var DeepdotsPopups = class {
993
1089
  }
994
1090
  /** Initialize the SDK with configuration */
995
1091
  init(config) {
1092
+ this.logger = config.logger ?? console;
1093
+ setLogger(this.logger);
996
1094
  if (this.initialized) {
997
1095
  this.log("SDK already initialized");
998
1096
  return;
@@ -1032,12 +1130,16 @@ var DeepdotsPopups = class {
1032
1130
  onSessionId: (id) => {
1033
1131
  this.analyticsFeedbackSessionId = id;
1034
1132
  }
1035
- }) : void 0;
1133
+ }) : createDryRunSink((...a) => this.logger.log(...a));
1036
1134
  const device = config.device ?? collectDeviceInfo(config.appVersion);
1037
1135
  this.analytics = new AnalyticsManager({
1038
1136
  sink: analyticsSink,
1039
1137
  publicKey: config.analytics?.publicKey ?? this.config.apiKey,
1040
- language: typeof navigator !== "undefined" ? navigator.language : void 0,
1138
+ language: resolveLanguage({
1139
+ explicit: config.language,
1140
+ navigator: typeof navigator !== "undefined" ? navigator : void 0,
1141
+ intl: typeof Intl !== "undefined" ? Intl : void 0
1142
+ }),
1041
1143
  platform: config.platform ?? "web",
1042
1144
  device,
1043
1145
  maxBatchSize: ANALYTICS_MAX_BATCH_SIZE,
@@ -1054,8 +1156,13 @@ var DeepdotsPopups = class {
1054
1156
  if (this.tracking?.isTrackingEnabled()) {
1055
1157
  this.crashReporter.install();
1056
1158
  }
1057
- collectGeoInfo().then((geo) => {
1058
- if (geo) this.analytics?.updateDevice(geo);
1159
+ const cachedGeo = readCachedGeo(storage, Date.now());
1160
+ if (cachedGeo) this.analytics.updateDevice(cachedGeo);
1161
+ fetchGeo().then((geo) => {
1162
+ if (geo) {
1163
+ this.analytics?.updateDevice(geo);
1164
+ writeCachedGeo(storage, geo, Date.now());
1165
+ }
1059
1166
  }).catch(() => {
1060
1167
  });
1061
1168
  this.navObserver = new NavigationObserver();
@@ -1112,6 +1219,15 @@ var DeepdotsPopups = class {
1112
1219
  if (!this.tracking?.isTrackingEnabled()) return;
1113
1220
  this.analytics?.setUserAttributes(attributes);
1114
1221
  }
1222
+ /**
1223
+ * Registra/actualiza una métrica (valor medible) → `feedback.metrics` del body.
1224
+ * Persistente y sobrescribe por key. No-op si tracking off.
1225
+ */
1226
+ setMetric(key, value) {
1227
+ if (!this.tracking?.isTrackingEnabled()) return;
1228
+ this.analytics?.setMetric(key, value);
1229
+ this.log("analytics \xB7 setMetric:", key, value);
1230
+ }
1115
1231
  /** Reporta un error del host (manejado o no) → evento `deepdots_app_crash`. No-op si tracking off. */
1116
1232
  reportError(error, options) {
1117
1233
  if (!this.tracking?.isTrackingEnabled()) return;
@@ -1685,7 +1801,7 @@ var DeepdotsPopups = class {
1685
1801
  }
1686
1802
  if (!this.popupContainer) return;
1687
1803
  const container = this.popupContainer;
1688
- void import("./renderPopup-YNH2TD3B.mjs").then(({ renderPopup }) => {
1804
+ void import("./renderPopup-PS3NJDUR.mjs").then(({ renderPopup }) => {
1689
1805
  renderPopup(
1690
1806
  container,
1691
1807
  surveyId,
@@ -1741,7 +1857,8 @@ var DeepdotsPopups = class {
1741
1857
  try {
1742
1858
  listener(event);
1743
1859
  } catch (error) {
1744
- console.error("Error in event listener:", error);
1860
+ if (this.logger.error) this.logger.error("Error in event listener:", error);
1861
+ else this.logger.log("Error in event listener:", error);
1745
1862
  }
1746
1863
  });
1747
1864
  }
@@ -1749,7 +1866,7 @@ var DeepdotsPopups = class {
1749
1866
  /** Log debug messages */
1750
1867
  log(...args) {
1751
1868
  if (this.config?.debug) {
1752
- console.log("[DeepdotsPopups]", ...args);
1869
+ this.logger.log("[DeepdotsPopups]", ...args);
1753
1870
  }
1754
1871
  }
1755
1872
  /** External debug method for triggers */