@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.
package/dist/index.mjs CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  TrackingManager,
5
5
  buildSurveyIdentity,
6
6
  createDefaultStorage
7
- } from "./chunk-5VVCJBND.mjs";
7
+ } from "./chunk-6WTOQGET.mjs";
8
8
 
9
9
  // src/types/index.ts
10
10
  var POPUP_TRIGGER_CONDITION_STATUSES = [
@@ -253,12 +253,12 @@ var ReactNativePopupRenderer = class {
253
253
  }
254
254
  init() {
255
255
  }
256
- show(surveyId, productId, _actions, emit, onClose, env = "production", userId, _style, sessionId, miniService) {
256
+ show(surveyId, productId, _actions, emit, onClose, env = "production", userId, _style, sessionId, miniService, analyticsFeedbackSessionId) {
257
257
  this.emitFn = emit;
258
258
  this.onCloseFn = onClose;
259
259
  this.currentSurveyId = surveyId;
260
260
  this.partialEmitted = false;
261
- const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null);
261
+ const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
262
262
  const html = buildSurveyHtml({ surveyId, productId, env, profile, metadata });
263
263
  if (this.options.onShow) {
264
264
  this.options.onShow({ surveyId, productId, html });
@@ -349,14 +349,14 @@ var BrowserPopupRenderer = class {
349
349
  document.body.appendChild(this.container);
350
350
  }
351
351
  }
352
- show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService) {
352
+ show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
353
353
  if (this.visible) return;
354
354
  if (!this.container || !document.body.contains(this.container)) this.init();
355
355
  if (!this.container) return;
356
356
  this.visible = true;
357
357
  const container = this.container;
358
- void import("./renderPopup-Y4WG3575.mjs").then(({ renderPopup }) => {
359
- renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService);
358
+ void import("./renderPopup-YLFRGPQN.mjs").then(({ renderPopup }) => {
359
+ renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId);
360
360
  });
361
361
  }
362
362
  hide() {
@@ -395,6 +395,12 @@ var AnalyticsManager = class {
395
395
  this.language = options.language;
396
396
  this.device = options.device;
397
397
  this.platform = options.platform ?? "web";
398
+ this.maxBatchSize = options.maxBatchSize ?? 20;
399
+ this.onFlushNeeded = options.onFlushNeeded;
400
+ }
401
+ /** Actualiza campos de device en runtime (p.ej. country/city tras resolver geo). */
402
+ updateDevice(partial) {
403
+ this.device = { ...this.device, ...partial };
398
404
  }
399
405
  /** Mezcla user attributes (se coercionan a string). Mutable en runtime. */
400
406
  setUserAttributes(attrs) {
@@ -428,6 +434,9 @@ var AnalyticsManager = class {
428
434
  timestamp: this.now(),
429
435
  params: Object.keys(merged).length ? merged : void 0
430
436
  });
437
+ if (this.events.length >= this.maxBatchSize) {
438
+ this.onFlushNeeded?.();
439
+ }
431
440
  }
432
441
  /** Mini-service activo (para inyectarlo en la metadata del survey, #33). */
433
442
  getMiniService() {
@@ -465,44 +474,55 @@ var AnalyticsManager = class {
465
474
  // src/analytics/feedback-payload.ts
466
475
  function pushKV(list, key, value) {
467
476
  if (value === void 0 || value === null || value === "") return;
468
- list.push({ key, value: String(value) });
477
+ list.push({ key, value: [String(value)] });
469
478
  }
470
479
  function buildAnalyticsFeedbackBody(envelope, keys, feedbackSessionId) {
471
480
  const { context } = envelope;
472
- const metrics = envelope.events.map((e) => ({
473
- key: e.name,
474
- value: JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })
475
- }));
476
481
  const profile = [];
477
482
  pushKV(profile, "external-user-id", envelope.userId);
478
483
  const metadata = [];
479
- pushKV(metadata, "user_id", envelope.userId);
480
- pushKV(metadata, "session_id", envelope.sessionId);
481
- pushKV(metadata, "platform", context.platform);
482
- pushKV(metadata, "language", context.language);
484
+ pushKV(metadata, "deepdots_user_id", envelope.userId);
485
+ pushKV(metadata, "deepdots_session_id", envelope.sessionId);
486
+ pushKV(metadata, "deepdots_platform", context.platform);
487
+ pushKV(metadata, "deepdots_language", context.language);
483
488
  if (context.device) {
484
- pushKV(metadata, "device_type", context.device.device_type);
485
- pushKV(metadata, "os_version", context.device.os_version);
486
- pushKV(metadata, "device_model", context.device.device_model);
487
- pushKV(metadata, "app_version", context.device.app_version);
488
- pushKV(metadata, "user_agent", context.device.user_agent);
489
+ const d = context.device;
490
+ pushKV(metadata, "deepdots_device_type", d.device_type);
491
+ pushKV(metadata, "deepdots_os_version", d.os_version);
492
+ pushKV(metadata, "deepdots_device_model", d.device_model);
493
+ pushKV(metadata, "deepdots_app_version", d.app_version);
494
+ pushKV(metadata, "deepdots_user_agent", d.user_agent);
495
+ pushKV(metadata, "deepdots_timezone", d.timezone);
496
+ pushKV(metadata, "deepdots_referrer", d.referrer);
497
+ pushKV(metadata, "deepdots_viewport_size", d.viewport_size);
498
+ pushKV(metadata, "deepdots_screen_resolution", d.screen_resolution);
499
+ pushKV(metadata, "deepdots_pixel_ratio", d.pixel_ratio);
500
+ pushKV(metadata, "deepdots_entry_type", d.entry_type);
501
+ pushKV(metadata, "deepdots_page_load_ms", d.page_load_ms);
502
+ pushKV(metadata, "deepdots_connection_type", d.connection_type);
503
+ pushKV(metadata, "deepdots_country", d.country);
504
+ pushKV(metadata, "deepdots_city", d.city);
489
505
  }
490
506
  for (const [k, v] of Object.entries(context.attributes ?? {})) {
491
507
  pushKV(metadata, k, v);
492
508
  }
509
+ for (const e of envelope.events) {
510
+ metadata.push({
511
+ key: e.name,
512
+ value: [JSON.stringify({ timestamp: e.timestamp, ...e.params ?? {} })]
513
+ });
514
+ }
493
515
  return {
494
516
  feedback: {
495
517
  text: "",
496
518
  answers: [],
497
519
  metadata,
498
- metrics,
499
520
  profile,
500
521
  finished: false
501
522
  },
502
523
  publicKey: keys.publicKey,
503
524
  integration: keys.integration,
504
525
  completed: false,
505
- // finished: false,
506
526
  ...feedbackSessionId ? { sessionId: feedbackSessionId } : {}
507
527
  };
508
528
  }
@@ -527,6 +547,7 @@ function createFeedbackSink(options) {
527
547
  if (data?.sessionId && data.sessionId !== feedbackSessionId) {
528
548
  feedbackSessionId = data.sessionId;
529
549
  options.log?.("[DeepdotsAnalytics] feedbackSessionId cacheado:", feedbackSessionId);
550
+ options.onSessionId?.(feedbackSessionId);
530
551
  }
531
552
  } catch {
532
553
  }
@@ -640,15 +661,57 @@ function parseDeviceType(ua) {
640
661
  }
641
662
  return "desktop";
642
663
  }
664
+ function getEntryType() {
665
+ if (typeof performance === "undefined") return void 0;
666
+ const entries = performance.getEntriesByType("navigation");
667
+ if (!entries.length) return void 0;
668
+ return entries[0].type;
669
+ }
670
+ function getPageLoadMs() {
671
+ if (typeof performance === "undefined") return void 0;
672
+ const entries = performance.getEntriesByType("navigation");
673
+ if (!entries.length) return void 0;
674
+ const load = entries[0].loadEventEnd;
675
+ return load > 0 ? Math.round(load) : void 0;
676
+ }
677
+ function getConnectionType() {
678
+ if (typeof navigator === "undefined") return void 0;
679
+ return navigator.connection?.effectiveType ?? void 0;
680
+ }
643
681
  function collectDeviceInfo(appVersion) {
644
682
  const ua = typeof navigator !== "undefined" ? navigator.userAgent : "";
645
683
  return {
646
684
  device_type: parseDeviceType(ua),
647
685
  user_agent: ua || void 0,
648
- app_version: appVersion
686
+ app_version: appVersion,
687
+ timezone: typeof Intl !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : void 0,
688
+ referrer: typeof document !== "undefined" && document.referrer ? document.referrer : void 0,
689
+ viewport_size: typeof window !== "undefined" ? `${window.innerWidth}x${window.innerHeight}` : void 0,
690
+ screen_resolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : void 0,
691
+ pixel_ratio: typeof window !== "undefined" ? window.devicePixelRatio : void 0,
692
+ entry_type: getEntryType(),
693
+ page_load_ms: getPageLoadMs(),
694
+ connection_type: getConnectionType()
649
695
  };
650
696
  }
651
697
 
698
+ // src/analytics/geo-info.ts
699
+ async function collectGeoInfo(fetchImpl) {
700
+ const f = fetchImpl ?? (typeof fetch !== "undefined" ? fetch : void 0);
701
+ if (!f) return null;
702
+ try {
703
+ const res = await f("https://ipapi.co/json/");
704
+ if (!res.ok) return null;
705
+ const data = await res.json();
706
+ const country = data.country_code || void 0;
707
+ const city = data.city || void 0;
708
+ if (!country && !city) return null;
709
+ return { country, city };
710
+ } catch {
711
+ return null;
712
+ }
713
+ }
714
+
652
715
  // src/analytics/engagement-tracker.ts
653
716
  var EngagementTracker = class {
654
717
  constructor(now = () => Date.now()) {
@@ -719,6 +782,8 @@ var ContactManager = class {
719
782
  // src/core/deepdots-popups.ts
720
783
  var EXIT_QUEUE_STORAGE_KEY = "__deepdots_exit_popup_queue__";
721
784
  var DAY_IN_MS = 24 * 60 * 60 * 1e3;
785
+ var ANALYTICS_MAX_BATCH_SIZE = 20;
786
+ var ANALYTICS_FLUSH_INTERVAL_MS = 3e4;
722
787
  var DeepdotsPopups = class {
723
788
  constructor() {
724
789
  this.config = null;
@@ -744,6 +809,10 @@ var DeepdotsPopups = class {
744
809
  this.tracking = null;
745
810
  /** Capa de analytics (canal separado del feedback). Null hasta init(). */
746
811
  this.analytics = null;
812
+ /** feedbackSessionId cacheado del canal de analytics (devuelto por POST /sdk/feedback). */
813
+ this.analyticsFeedbackSessionId = void 0;
814
+ /** Timer del flush periódico de analytics (cada ANALYTICS_FLUSH_INTERVAL_MS). */
815
+ this.analyticsFlushTimer = void 0;
747
816
  /** Observador de navegación (Fase 2): emite page_view por el canal de analytics. */
748
817
  this.navObserver = null;
749
818
  /** Tiempo activo (engagement time, #8). */
@@ -790,14 +859,23 @@ var DeepdotsPopups = class {
790
859
  const analyticsSink = config.analytics ? createFeedbackSink({
791
860
  baseUrl: this.baseUrl,
792
861
  keys: config.analytics,
793
- log: (...a) => this.log(...a)
862
+ log: (...a) => this.log(...a),
863
+ onSessionId: (id) => {
864
+ this.analyticsFeedbackSessionId = id;
865
+ }
794
866
  }) : void 0;
795
867
  this.analytics = new AnalyticsManager({
796
868
  sink: analyticsSink,
797
869
  publicKey: config.analytics?.publicKey ?? this.config.apiKey,
798
870
  language: typeof navigator !== "undefined" ? navigator.language : void 0,
799
871
  platform: config.platform ?? "web",
800
- device: config.device ?? collectDeviceInfo(config.appVersion)
872
+ device: config.device ?? collectDeviceInfo(config.appVersion),
873
+ maxBatchSize: ANALYTICS_MAX_BATCH_SIZE,
874
+ onFlushNeeded: () => this.flushAnalytics()
875
+ });
876
+ collectGeoInfo().then((geo) => {
877
+ if (geo) this.analytics?.updateDevice(geo);
878
+ }).catch(() => {
801
879
  });
802
880
  this.navObserver = new NavigationObserver();
803
881
  this.navObserver.onVisit((v) => this.track("page_view", { screen: v.screen, duration_seconds: v.durationSeconds }));
@@ -938,7 +1016,9 @@ var DeepdotsPopups = class {
938
1016
  /** Flush automático al ocultar/cerrar la página (no perder el lote pendiente). */
939
1017
  setupAnalyticsFlush() {
940
1018
  if (typeof document === "undefined" || typeof window === "undefined") return;
1019
+ this.analyticsFlushTimer = setInterval(() => this.flushAnalytics(), ANALYTICS_FLUSH_INTERVAL_MS);
941
1020
  window.addEventListener("pagehide", () => {
1021
+ clearInterval(this.analyticsFlushTimer);
942
1022
  this.navObserver?.stop();
943
1023
  this.exitMiniService();
944
1024
  this.flushEngagement();
@@ -1382,7 +1462,8 @@ var DeepdotsPopups = class {
1382
1462
  const userId = this.tracking?.getUserId() ?? this.config?.userId;
1383
1463
  const sessionId = this.tracking?.getSessionId() ?? void 0;
1384
1464
  const miniService = this.analytics?.getMiniService() ?? void 0;
1385
- this.log("tracking \xB7 survey identity \u2192", { userId: userId ?? null, sessionId: sessionId ?? null, miniService: miniService ?? null });
1465
+ const analyticsFeedbackSessionId = this.analyticsFeedbackSessionId;
1466
+ this.log("tracking \xB7 survey identity \u2192", { userId: userId ?? null, sessionId: sessionId ?? null, miniService: miniService ?? null, analyticsFeedbackSessionId: analyticsFeedbackSessionId ?? null });
1386
1467
  if (this.renderer) {
1387
1468
  this.renderer.show(
1388
1469
  surveyId,
@@ -1394,13 +1475,14 @@ var DeepdotsPopups = class {
1394
1475
  userId,
1395
1476
  style,
1396
1477
  sessionId,
1397
- miniService
1478
+ miniService,
1479
+ analyticsFeedbackSessionId
1398
1480
  );
1399
1481
  return;
1400
1482
  }
1401
1483
  if (!this.popupContainer) return;
1402
1484
  const container = this.popupContainer;
1403
- void import("./renderPopup-Y4WG3575.mjs").then(({ renderPopup }) => {
1485
+ void import("./renderPopup-YLFRGPQN.mjs").then(({ renderPopup }) => {
1404
1486
  renderPopup(
1405
1487
  container,
1406
1488
  surveyId,
@@ -1412,7 +1494,8 @@ var DeepdotsPopups = class {
1412
1494
  userId,
1413
1495
  style,
1414
1496
  sessionId,
1415
- miniService
1497
+ miniService,
1498
+ analyticsFeedbackSessionId
1416
1499
  );
1417
1500
  });
1418
1501
  }