@magicfeedback/popup-sdk 1.1.6 → 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.
@@ -399,6 +399,8 @@ var AnalyticsManager = class {
399
399
  constructor(options = {}) {
400
400
  this.events = [];
401
401
  this.attributes = {};
402
+ /** Métricas del host (valores medibles persistentes). Sobrescriben por key; se reenvían en cada flush. */
403
+ this.metrics = {};
402
404
  /** Mini-services activos: nombre → timestamp de entrada. El orden de inserción marca el "más reciente". */
403
405
  this.activeMiniServices = /* @__PURE__ */ new Map();
404
406
  this.sink = options.sink ?? dryRunSink;
@@ -421,6 +423,14 @@ var AnalyticsManager = class {
421
423
  this.attributes[k] = String(v);
422
424
  }
423
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
+ }
424
434
  /**
425
435
  * Marca el inicio de un mini-service; etiqueta los eventos siguientes con `mini_service`.
426
436
  * Admite varios activos a la vez (concurrencia); el "actual" para etiquetar es el más reciente.
@@ -484,7 +494,8 @@ var AnalyticsManager = class {
484
494
  platform: this.platform,
485
495
  language: this.language,
486
496
  device: this.device,
487
- attributes: { ...this.attributes }
497
+ attributes: { ...this.attributes },
498
+ metrics: { ...this.metrics }
488
499
  },
489
500
  events: [...this.events]
490
501
  };
@@ -540,10 +551,15 @@ function buildAnalyticsFeedbackBody(envelope, keys, feedbackSessionId) {
540
551
  value: [JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })]
541
552
  });
542
553
  }
554
+ const metrics = [];
555
+ for (const [k, v] of Object.entries(context.metrics ?? {})) {
556
+ pushKV(metrics, k, v);
557
+ }
543
558
  return {
544
559
  feedback: {
545
560
  text: "",
546
561
  answers: [],
562
+ metrics,
547
563
  metadata,
548
564
  profile,
549
565
  finished: false
@@ -724,20 +740,95 @@ function collectDeviceInfo(appVersion) {
724
740
  }
725
741
 
726
742
  // src/analytics/geo-info.ts
727
- async function collectGeoInfo(fetchImpl) {
728
- const f = fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
729
- 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) {
730
747
  try {
731
- const res = await f("https://ipapi.co/json/");
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);
793
+ try {
794
+ const res = await f(provider.url, { signal: controller.signal });
732
795
  if (!res.ok) return null;
733
796
  const data = await res.json();
734
- const country = data.country_code || void 0;
735
- const city = data.city || void 0;
736
- if (!country && !city) return null;
737
- return { country, city };
797
+ return provider.parse(data);
738
798
  } catch {
739
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 {
740
830
  }
831
+ return void 0;
741
832
  }
742
833
 
743
834
  // src/analytics/engagement-tracker.ts
@@ -1044,7 +1135,11 @@ var DeepdotsPopups = class {
1044
1135
  this.analytics = new AnalyticsManager({
1045
1136
  sink: analyticsSink,
1046
1137
  publicKey: config.analytics?.publicKey ?? this.config.apiKey,
1047
- 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
+ }),
1048
1143
  platform: config.platform ?? "web",
1049
1144
  device,
1050
1145
  maxBatchSize: ANALYTICS_MAX_BATCH_SIZE,
@@ -1061,8 +1156,13 @@ var DeepdotsPopups = class {
1061
1156
  if (this.tracking?.isTrackingEnabled()) {
1062
1157
  this.crashReporter.install();
1063
1158
  }
1064
- collectGeoInfo().then((geo) => {
1065
- 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
+ }
1066
1166
  }).catch(() => {
1067
1167
  });
1068
1168
  this.navObserver = new NavigationObserver();
@@ -1119,6 +1219,15 @@ var DeepdotsPopups = class {
1119
1219
  if (!this.tracking?.isTrackingEnabled()) return;
1120
1220
  this.analytics?.setUserAttributes(attributes);
1121
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
+ }
1122
1231
  /** Reporta un error del host (manejado o no) → evento `deepdots_app_crash`. No-op si tracking off. */
1123
1232
  reportError(error, options) {
1124
1233
  if (!this.tracking?.isTrackingEnabled()) return;