@magicfeedback/popup-sdk 1.1.1 → 1.1.2

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.
@@ -4,7 +4,7 @@ import {
4
4
  __require,
5
5
  buildSurveyIdentity,
6
6
  createDefaultStorage
7
- } from "./chunk-M6PEFLLQ.mjs";
7
+ } from "./chunk-IOAZQPZE.mjs";
8
8
 
9
9
  // src/react-native/index.tsx
10
10
  import { createContext, useContext, useEffect, useRef, useState } from "react";
@@ -258,12 +258,12 @@ var ReactNativePopupRenderer = class {
258
258
  }
259
259
  init() {
260
260
  }
261
- show(surveyId, productId, _actions, emit, onClose, env = "production", userId, _style, sessionId, miniService) {
261
+ show(surveyId, productId, _actions, emit, onClose, env = "production", userId, _style, sessionId, miniService, analyticsFeedbackSessionId) {
262
262
  this.emitFn = emit;
263
263
  this.onCloseFn = onClose;
264
264
  this.currentSurveyId = surveyId;
265
265
  this.partialEmitted = false;
266
- const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null);
266
+ const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
267
267
  const html = buildSurveyHtml({ surveyId, productId, env, profile, metadata });
268
268
  if (this.options.onShow) {
269
269
  this.options.onShow({ surveyId, productId, html });
@@ -354,14 +354,14 @@ var BrowserPopupRenderer = class {
354
354
  document.body.appendChild(this.container);
355
355
  }
356
356
  }
357
- show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService) {
357
+ show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
358
358
  if (this.visible) return;
359
359
  if (!this.container || !document.body.contains(this.container)) this.init();
360
360
  if (!this.container) return;
361
361
  this.visible = true;
362
362
  const container = this.container;
363
- void import("./renderPopup-7GGKR5BY.mjs").then(({ renderPopup }) => {
364
- renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService);
363
+ void import("./renderPopup-KBDWWSUU.mjs").then(({ renderPopup }) => {
364
+ renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId);
365
365
  });
366
366
  }
367
367
  hide() {
@@ -400,6 +400,12 @@ var AnalyticsManager = class {
400
400
  this.language = options.language;
401
401
  this.device = options.device;
402
402
  this.platform = options.platform ?? "web";
403
+ this.maxBatchSize = options.maxBatchSize ?? 20;
404
+ this.onFlushNeeded = options.onFlushNeeded;
405
+ }
406
+ /** Actualiza campos de device en runtime (p.ej. country/city tras resolver geo). */
407
+ updateDevice(partial) {
408
+ this.device = { ...this.device, ...partial };
403
409
  }
404
410
  /** Mezcla user attributes (se coercionan a string). Mutable en runtime. */
405
411
  setUserAttributes(attrs) {
@@ -433,6 +439,9 @@ var AnalyticsManager = class {
433
439
  timestamp: this.now(),
434
440
  params: Object.keys(merged).length ? merged : void 0
435
441
  });
442
+ if (this.events.length >= this.maxBatchSize) {
443
+ this.onFlushNeeded?.();
444
+ }
436
445
  }
437
446
  /** Mini-service activo (para inyectarlo en la metadata del survey, #33). */
438
447
  getMiniService() {
@@ -470,44 +479,55 @@ var AnalyticsManager = class {
470
479
  // src/analytics/feedback-payload.ts
471
480
  function pushKV(list, key, value) {
472
481
  if (value === void 0 || value === null || value === "") return;
473
- list.push({ key, value: String(value) });
482
+ list.push({ key, value: [String(value)] });
474
483
  }
475
484
  function buildAnalyticsFeedbackBody(envelope, keys, feedbackSessionId) {
476
485
  const { context } = envelope;
477
- const metrics = envelope.events.map((e) => ({
478
- key: e.name,
479
- value: JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })
480
- }));
481
486
  const profile = [];
482
487
  pushKV(profile, "external-user-id", envelope.userId);
483
488
  const metadata = [];
484
- pushKV(metadata, "user_id", envelope.userId);
485
- pushKV(metadata, "session_id", envelope.sessionId);
486
- pushKV(metadata, "platform", context.platform);
487
- pushKV(metadata, "language", context.language);
489
+ pushKV(metadata, "deepdots_user_id", envelope.userId);
490
+ pushKV(metadata, "deepdots_session_id", envelope.sessionId);
491
+ pushKV(metadata, "deepdots_platform", context.platform);
492
+ pushKV(metadata, "deepdots_language", context.language);
488
493
  if (context.device) {
489
- pushKV(metadata, "device_type", context.device.device_type);
490
- pushKV(metadata, "os_version", context.device.os_version);
491
- pushKV(metadata, "device_model", context.device.device_model);
492
- pushKV(metadata, "app_version", context.device.app_version);
493
- pushKV(metadata, "user_agent", context.device.user_agent);
494
+ const d = context.device;
495
+ pushKV(metadata, "deepdots_device_type", d.device_type);
496
+ pushKV(metadata, "deepdots_os_version", d.os_version);
497
+ pushKV(metadata, "deepdots_device_model", d.device_model);
498
+ pushKV(metadata, "deepdots_app_version", d.app_version);
499
+ pushKV(metadata, "deepdots_user_agent", d.user_agent);
500
+ pushKV(metadata, "deepdots_timezone", d.timezone);
501
+ pushKV(metadata, "deepdots_referrer", d.referrer);
502
+ pushKV(metadata, "deepdots_viewport_size", d.viewport_size);
503
+ pushKV(metadata, "deepdots_screen_resolution", d.screen_resolution);
504
+ pushKV(metadata, "deepdots_pixel_ratio", d.pixel_ratio);
505
+ pushKV(metadata, "deepdots_entry_type", d.entry_type);
506
+ pushKV(metadata, "deepdots_page_load_ms", d.page_load_ms);
507
+ pushKV(metadata, "deepdots_connection_type", d.connection_type);
508
+ pushKV(metadata, "deepdots_country", d.country);
509
+ pushKV(metadata, "deepdots_city", d.city);
494
510
  }
495
511
  for (const [k, v] of Object.entries(context.attributes ?? {})) {
496
512
  pushKV(metadata, k, v);
497
513
  }
514
+ for (const e of envelope.events) {
515
+ metadata.push({
516
+ key: e.name,
517
+ value: [JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })]
518
+ });
519
+ }
498
520
  return {
499
521
  feedback: {
500
522
  text: "",
501
523
  answers: [],
502
524
  metadata,
503
- metrics,
504
525
  profile,
505
526
  finished: false
506
527
  },
507
528
  publicKey: keys.publicKey,
508
529
  integration: keys.integration,
509
530
  completed: false,
510
- // finished: false,
511
531
  ...feedbackSessionId ? { sessionId: feedbackSessionId } : {}
512
532
  };
513
533
  }
@@ -532,6 +552,7 @@ function createFeedbackSink(options) {
532
552
  if (data?.sessionId && data.sessionId !== feedbackSessionId) {
533
553
  feedbackSessionId = data.sessionId;
534
554
  options.log?.("[DeepdotsAnalytics] feedbackSessionId cacheado:", feedbackSessionId);
555
+ options.onSessionId?.(feedbackSessionId);
535
556
  }
536
557
  } catch {
537
558
  }
@@ -645,15 +666,57 @@ function parseDeviceType(ua) {
645
666
  }
646
667
  return "desktop";
647
668
  }
669
+ function getEntryType() {
670
+ if (typeof performance === "undefined") return void 0;
671
+ const entries = performance.getEntriesByType("navigation");
672
+ if (!entries.length) return void 0;
673
+ return entries[0].type;
674
+ }
675
+ function getPageLoadMs() {
676
+ if (typeof performance === "undefined") return void 0;
677
+ const entries = performance.getEntriesByType("navigation");
678
+ if (!entries.length) return void 0;
679
+ const load = entries[0].loadEventEnd;
680
+ return load > 0 ? Math.round(load) : void 0;
681
+ }
682
+ function getConnectionType() {
683
+ if (typeof navigator === "undefined") return void 0;
684
+ return navigator.connection?.effectiveType ?? void 0;
685
+ }
648
686
  function collectDeviceInfo(appVersion) {
649
687
  const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
650
688
  return {
651
689
  device_type: parseDeviceType(ua),
652
690
  user_agent: ua || void 0,
653
- app_version: appVersion
691
+ app_version: appVersion,
692
+ timezone: typeof Intl !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : void 0,
693
+ referrer: typeof document !== "undefined" && document.referrer ? document.referrer : void 0,
694
+ viewport_size: typeof window !== "undefined" ? `${window.innerWidth}x${window.innerHeight}` : void 0,
695
+ screen_resolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : void 0,
696
+ pixel_ratio: typeof window !== "undefined" ? window.devicePixelRatio : void 0,
697
+ entry_type: getEntryType(),
698
+ page_load_ms: getPageLoadMs(),
699
+ connection_type: getConnectionType()
654
700
  };
655
701
  }
656
702
 
703
+ // src/analytics/geo-info.ts
704
+ async function collectGeoInfo(fetchImpl) {
705
+ const f = fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
706
+ if (!f) return null;
707
+ try {
708
+ const res = await f("https://ipapi.co/json/");
709
+ if (!res.ok) return null;
710
+ const data = await res.json();
711
+ const country = data.country_code || void 0;
712
+ const city = data.city || void 0;
713
+ if (!country && !city) return null;
714
+ return { country, city };
715
+ } catch {
716
+ return null;
717
+ }
718
+ }
719
+
657
720
  // src/analytics/engagement-tracker.ts
658
721
  var EngagementTracker = class {
659
722
  constructor(now = () => Date.now()) {
@@ -724,6 +787,8 @@ var ContactManager = class {
724
787
  // src/core/deepdots-popups.ts
725
788
  var EXIT_QUEUE_STORAGE_KEY = "__deepdots_exit_popup_queue__";
726
789
  var DAY_IN_MS = 24 * 60 * 60 * 1e3;
790
+ var ANALYTICS_MAX_BATCH_SIZE = 20;
791
+ var ANALYTICS_FLUSH_INTERVAL_MS = 3e4;
727
792
  var DeepdotsPopups = class {
728
793
  constructor() {
729
794
  this.config = null;
@@ -749,6 +814,10 @@ var DeepdotsPopups = class {
749
814
  this.tracking = null;
750
815
  /** Capa de analytics (canal separado del feedback). Null hasta init(). */
751
816
  this.analytics = null;
817
+ /** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */
818
+ this.analyticsFeedbackSessionId = void 0;
819
+ /** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
820
+ this.analyticsFlushTimer = void 0;
752
821
  /** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
753
822
  this.navObserver = null;
754
823
  /** Tiempo activo (engagement time, #8). */
@@ -795,14 +864,23 @@ var DeepdotsPopups = class {
795
864
  const analyticsSink = config.analytics ? createFeedbackSink({
796
865
  baseUrl: this.baseUrl,
797
866
  keys: config.analytics,
798
- log: (...a) => this.log(...a)
867
+ log: (...a) => this.log(...a),
868
+ onSessionId: (id) => {
869
+ this.analyticsFeedbackSessionId = id;
870
+ }
799
871
  }) : void 0;
800
872
  this.analytics = new AnalyticsManager({
801
873
  sink: analyticsSink,
802
874
  publicKey: config.analytics?.publicKey ?? this.config.apiKey,
803
875
  language: typeof navigator !== "undefined" ? navigator.language : void 0,
804
876
  platform: config.platform ?? "web",
805
- device: config.device ?? collectDeviceInfo(config.appVersion)
877
+ device: config.device ?? collectDeviceInfo(config.appVersion),
878
+ maxBatchSize: ANALYTICS_MAX_BATCH_SIZE,
879
+ onFlushNeeded: () => this.flushAnalytics()
880
+ });
881
+ collectGeoInfo().then((geo) => {
882
+ if (geo) this.analytics?.updateDevice(geo);
883
+ }).catch(() => {
806
884
  });
807
885
  this.navObserver = new NavigationObserver();
808
886
  this.navObserver.onVisit((v) => this.track("page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
@@ -943,7 +1021,9 @@ var DeepdotsPopups = class {
943
1021
  /** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
944
1022
  setupAnalyticsFlush() {
945
1023
  if (typeof document === "undefined" || typeof window === "undefined") return;
1024
+ this.analyticsFlushTimer = setInterval(() => this.flushAnalytics(), ANALYTICS_FLUSH_INTERVAL_MS);
946
1025
  window.addEventListener("pagehide", () => {
1026
+ clearInterval(this.analyticsFlushTimer);
947
1027
  this.navObserver?.stop();
948
1028
  this.exitMiniService();
949
1029
  this.flushEngagement();
@@ -1387,7 +1467,8 @@ var DeepdotsPopups = class {
1387
1467
  const userId = this.tracking?.getUserId() ?? this.config?.userId;
1388
1468
  const sessionId = this.tracking?.getSessionId() ?? void 0;
1389
1469
  const miniService = this.analytics?.getMiniService() ?? void 0;
1390
- this.log("tracking \xB7 survey identity \u2192", { userId: userId ?? null, sessionId: sessionId ?? null, miniService: miniService ?? null });
1470
+ const analyticsFeedbackSessionId = this.analyticsFeedbackSessionId;
1471
+ this.log("tracking \xB7 survey identity \u2192", { userId: userId ?? null, sessionId: sessionId ?? null, miniService: miniService ?? null, analyticsFeedbackSessionId: analyticsFeedbackSessionId ?? null });
1391
1472
  if (this.renderer) {
1392
1473
  this.renderer.show(
1393
1474
  surveyId,
@@ -1399,13 +1480,14 @@ var DeepdotsPopups = class {
1399
1480
  userId,
1400
1481
  style,
1401
1482
  sessionId,
1402
- miniService
1483
+ miniService,
1484
+ analyticsFeedbackSessionId
1403
1485
  );
1404
1486
  return;
1405
1487
  }
1406
1488
  if (!this.popupContainer) return;
1407
1489
  const container = this.popupContainer;
1408
- void import("./renderPopup-7GGKR5BY.mjs").then(({ renderPopup }) => {
1490
+ void import("./renderPopup-KBDWWSUU.mjs").then(({ renderPopup }) => {
1409
1491
  renderPopup(
1410
1492
  container,
1411
1493
  surveyId,
@@ -1417,7 +1499,8 @@ var DeepdotsPopups = class {
1417
1499
  userId,
1418
1500
  style,
1419
1501
  sessionId,
1420
- miniService
1502
+ miniService,
1503
+ analyticsFeedbackSessionId
1421
1504
  );
1422
1505
  });
1423
1506
  }