@reservamos/browser-analytics 1.1.8-alpha.0 → 1.1.8-alpha.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +21 -3
  2. package/README.md +33 -7
  3. package/dist/browser-analytics.cjs +2 -2
  4. package/dist/browser-analytics.cjs.map +1 -1
  5. package/dist/browser-analytics.d.ts +15 -2
  6. package/dist/browser-analytics.esm.js +590 -109
  7. package/dist/browser-analytics.esm.js.map +1 -1
  8. package/dist/browser-analytics.iife.js +2 -2
  9. package/dist/browser-analytics.iife.js.map +1 -1
  10. package/package.json +2 -2
  11. package/src/events/identify/identify.robustness.test.ts +112 -13
  12. package/src/events/identify/identify.ts +84 -34
  13. package/src/events/passengersCreated/passengersCreatedSchema.test.ts +142 -0
  14. package/src/events/passengersCreated/passengersCreatedSchema.ts +7 -1
  15. package/src/events/purchaseConfirmation/purchaseConfirmation.integration.test.ts +49 -0
  16. package/src/events/purchaseConfirmation/purchaseConfirmation.test.ts +17 -0
  17. package/src/events/purchaseConfirmation/purchaseConfirmationSchema.ts +2 -0
  18. package/src/events/sharedSchemas/tripSchema.ts +3 -8
  19. package/src/index.ts +1 -0
  20. package/src/init.botDetection.test.ts +187 -0
  21. package/src/init.test.ts +1 -0
  22. package/src/init.ts +15 -2
  23. package/src/services/botDetectionRelay.test.ts +3 -2
  24. package/src/services/deferredBotDetection.ts +60 -0
  25. package/src/services/fingerprint.test.ts +190 -0
  26. package/src/services/fingerprint.ts +141 -13
  27. package/src/services/fingerprint.wait.integration.test.ts +129 -0
  28. package/src/services/mixpanel.test.ts +119 -73
  29. package/src/services/mixpanel.ts +82 -23
  30. package/src/services/trackingLifecycle.ts +54 -0
  31. package/src/services/validator/validator.test.ts +79 -0
  32. package/src/track.readiness.test.ts +131 -0
  33. package/src/track.recovery.test.ts +339 -0
  34. package/src/track.storage.test.ts +142 -0
  35. package/src/track.test.ts +130 -5
  36. package/src/track.ts +110 -25
  37. package/src/util/pendingWork.test.ts +164 -0
  38. package/src/util/pendingWork.ts +148 -0
  39. package/src/util/snapshotEventData.ts +15 -0
@@ -4173,7 +4173,8 @@ function r$3({ apiName: r2, defaultApiVersion: i }) {
4173
4173
  method: "POST",
4174
4174
  body: JSON.stringify(p3),
4175
4175
  headers: _2,
4176
- ...(f3 == null ? void 0 : f3.signal) && { signal: f3.signal }
4176
+ ...(f3 == null ? void 0 : f3.signal) && { signal: f3.signal },
4177
+ ...(f3 == null ? void 0 : f3.keepalive) !== void 0 && { keepalive: f3.keepalive }
4177
4178
  });
4178
4179
  return f3 ? y2 : await y2.json();
4179
4180
  }
@@ -4269,7 +4270,8 @@ async function a$3({ requestId: a2, visitorId: o2, distinctId: s2, purchaseToken
4269
4270
  responseType: "response",
4270
4271
  apiPrefix: "api",
4271
4272
  omitHeaders: ["Origin"],
4272
- signal: u2.signal
4273
+ signal: u2.signal,
4274
+ keepalive: true
4273
4275
  });
4274
4276
  return l2 && r$2(l2, {
4275
4277
  httpStatus: e3.status,
@@ -11260,9 +11262,9 @@ MixpanelRecorder.prototype._flushEvents = addOptOutCheckMixpanelLib(function(dat
11260
11262
  if (deviceId) {
11261
11263
  reqParams["$device_id"] = deviceId;
11262
11264
  }
11263
- var userId = this._mixpanel.get_property("$user_id");
11264
- if (userId) {
11265
- reqParams["$user_id"] = userId;
11265
+ var userId2 = this._mixpanel.get_property("$user_id");
11266
+ if (userId2) {
11267
+ reqParams["$user_id"] = userId2;
11266
11268
  }
11267
11269
  if (CompressionStream) {
11268
11270
  var jsonStream = new Blob([eventsJson], { type: "application/json" }).stream();
@@ -13339,6 +13341,50 @@ function loadNoop(_src, onload) {
13339
13341
  onload();
13340
13342
  }
13341
13343
  var mixpanel = init_as_module(loadNoop);
13344
+ let generation = 0;
13345
+ let initialized = false;
13346
+ let userId;
13347
+ const subscribers = /* @__PURE__ */ new Set();
13348
+ const initializationSubscribers = /* @__PURE__ */ new Set();
13349
+ function invalidate$1() {
13350
+ generation++;
13351
+ subscribers.forEach((notify) => notify());
13352
+ }
13353
+ function beginInitialization() {
13354
+ if (initialized) invalidate$1();
13355
+ initialized = true;
13356
+ userId = void 0;
13357
+ initializationSubscribers.forEach((notify) => notify());
13358
+ }
13359
+ function identify$2(nextUserId) {
13360
+ if (userId !== nextUserId) {
13361
+ userId = nextUserId;
13362
+ invalidate$1();
13363
+ }
13364
+ }
13365
+ function capture() {
13366
+ const captured = generation;
13367
+ return () => captured === generation;
13368
+ }
13369
+ function onChange(notify) {
13370
+ subscribers.add(notify);
13371
+ return () => {
13372
+ subscribers.delete(notify);
13373
+ };
13374
+ }
13375
+ function onInitialization(notify) {
13376
+ initializationSubscribers.add(notify);
13377
+ return () => {
13378
+ initializationSubscribers.delete(notify);
13379
+ };
13380
+ }
13381
+ const trackingLifecycle = {
13382
+ beginInitialization,
13383
+ identify: identify$2,
13384
+ capture,
13385
+ onChange,
13386
+ onInitialization
13387
+ };
13342
13388
  function createIdentifyReporter() {
13343
13389
  const reported = /* @__PURE__ */ new Set();
13344
13390
  return (stage, reason, httpStatus) => {
@@ -13373,16 +13419,16 @@ function waitForIdentifyReady(report) {
13373
13419
  if (!active) return;
13374
13420
  active = false;
13375
13421
  clearTimeout(timer);
13376
- window.removeEventListener("Tracker Ready", onReady);
13422
+ window.removeEventListener("Tracker Ready", onReady2);
13377
13423
  if (!ready) report("readiness", "timeout");
13378
13424
  resolve(ready);
13379
13425
  };
13380
- const onReady = () => {
13426
+ const onReady2 = () => {
13381
13427
  if (mixpanelService.isReady()) finish(true);
13382
13428
  };
13383
13429
  const timer = setTimeout(() => finish(false), 5e3);
13384
- window.addEventListener("Tracker Ready", onReady);
13385
- onReady();
13430
+ window.addEventListener("Tracker Ready", onReady2);
13431
+ onReady2();
13386
13432
  });
13387
13433
  }
13388
13434
  function waitForIdentifyStage(stage, timeout, run, report) {
@@ -13427,28 +13473,78 @@ function observeIdentifyResult(stage, report, onTimeout) {
13427
13473
  }, 5e3);
13428
13474
  return { complete };
13429
13475
  }
13430
- const version = "1.1.8-alpha.0";
13476
+ const version = "1.1.8-alpha.2";
13431
13477
  const MIXPANEL_DISTINCT_ID_CACHE_KEY = "mp_distinct_id";
13478
+ const readinessSubscribers$1 = /* @__PURE__ */ new Set();
13479
+ let initializing = false;
13480
+ let initialization$2 = 0;
13481
+ let initializedConfig;
13482
+ function onReady$1(callback) {
13483
+ readinessSubscribers$1.add(callback);
13484
+ return () => {
13485
+ readinessSubscribers$1.delete(callback);
13486
+ };
13487
+ }
13432
13488
  const SDK_SUPER_PROPERTIES = {
13433
13489
  "Event Source": "sdk",
13434
13490
  "Reservamos Version": version
13435
13491
  };
13436
13492
  function init$2(mixpanelToken, debug = false, proxyUrl) {
13493
+ validateConfiguration(mixpanelToken, proxyUrl);
13494
+ const attempt = ++initialization$2;
13495
+ initializing = true;
13496
+ trackingLifecycle.beginInitialization();
13497
+ if (initializedConfig && window.mixpanel === mixpanel) {
13498
+ mixpanel.set_config({ debug });
13499
+ initializing = false;
13500
+ readinessSubscribers$1.forEach((notify) => notify());
13501
+ return Promise.resolve();
13502
+ }
13437
13503
  return new Promise((resolve) => {
13438
13504
  mixpanel.init(mixpanelToken, {
13439
13505
  debug,
13440
13506
  loaded: () => {
13507
+ if (attempt !== initialization$2) {
13508
+ resolve();
13509
+ return;
13510
+ }
13441
13511
  window.mixpanel = mixpanel;
13442
13512
  mixpanel.register({ ...SDK_SUPER_PROPERTIES });
13513
+ initializedConfig = { token: mixpanelToken, proxyUrl };
13514
+ initializing = false;
13515
+ readinessSubscribers$1.forEach((notify) => notify());
13443
13516
  resolve();
13444
13517
  },
13445
13518
  ...proxyUrl && { api_host: proxyUrl }
13446
13519
  });
13447
13520
  });
13448
13521
  }
13449
- function identify$1(userId, properties, report) {
13450
- mixpanel.identify(userId);
13451
- if (!updateCachedDistinctId(userId)) report == null ? void 0 : report("cache", "unavailable");
13522
+ function validateConfiguration(token, proxyUrl) {
13523
+ if (initializedConfig && window.mixpanel === mixpanel && (initializedConfig.token !== token || initializedConfig.proxyUrl !== proxyUrl)) {
13524
+ throw new Error(
13525
+ "Changing the Mixpanel project or proxy requires a new page session."
13526
+ );
13527
+ }
13528
+ }
13529
+ function identify$1(userId2, properties, report) {
13530
+ trackingLifecycle.identify(userId2);
13531
+ mixpanel.identify(userId2);
13532
+ if (!updateCachedDistinctId(userId2)) report == null ? void 0 : report("cache", "unavailable");
13533
+ writePeopleProperties(properties, report);
13534
+ }
13535
+ function attachProperty(key, value) {
13536
+ if (!isReady()) {
13537
+ return;
13538
+ }
13539
+ mixpanel.people.union(key, [value]);
13540
+ }
13541
+ function setPeopleProperties(properties, report) {
13542
+ if (!isReady()) {
13543
+ return;
13544
+ }
13545
+ writePeopleProperties(properties, report);
13546
+ }
13547
+ function writePeopleProperties(properties, report) {
13452
13548
  if (!report) {
13453
13549
  mixpanel.people.set(properties);
13454
13550
  return;
@@ -13465,20 +13561,8 @@ function identify$1(userId, properties, report) {
13465
13561
  observer.complete("failed");
13466
13562
  }
13467
13563
  }
13468
- function attachProperty(key, value) {
13469
- if (!isReady()) {
13470
- return;
13471
- }
13472
- mixpanel.people.union(key, [value]);
13473
- }
13474
- function setPeopleProperties(properties) {
13475
- if (!isReady()) {
13476
- return;
13477
- }
13478
- mixpanel.people.set(properties);
13479
- }
13480
13564
  function isReady() {
13481
- return window.mixpanel !== void 0;
13565
+ return !initializing && window.mixpanel !== void 0;
13482
13566
  }
13483
13567
  function track(eventName, properties) {
13484
13568
  if (!isReady()) {
@@ -13487,8 +13571,7 @@ function track(eventName, properties) {
13487
13571
  mixpanel.track(eventName, { ...properties, "Reservamos Version": version });
13488
13572
  }
13489
13573
  const getMixpanelDistinctId = () => {
13490
- var _a;
13491
- const distinctId = (_a = mixpanel == null ? void 0 : mixpanel.get_distinct_id) == null ? void 0 : _a.call(mixpanel);
13574
+ const distinctId = isReady() ? mixpanel.get_distinct_id() : null;
13492
13575
  if (distinctId) {
13493
13576
  updateCachedDistinctId(distinctId);
13494
13577
  return distinctId;
@@ -13509,7 +13592,9 @@ function updateCachedDistinctId(distinctId) {
13509
13592
  }
13510
13593
  const mixpanelService = {
13511
13594
  init: init$2,
13595
+ validateConfiguration,
13512
13596
  isReady,
13597
+ onReady: onReady$1,
13513
13598
  track,
13514
13599
  identify: identify$1,
13515
13600
  attachProperty,
@@ -13520,6 +13605,23 @@ const DEFAULT_CACHE_NAME = "default_fingerprint_cache";
13520
13605
  const DEFAULT_CACHE_TIME_IN_DAYS = 7;
13521
13606
  const PROXY_QUERY_PARAM = "apiKey=<apiKey>&version=<version>&loaderVersion=<loaderVersion>";
13522
13607
  let fingerprintAgentReady = false;
13608
+ let initialization$1 = 0;
13609
+ let startedInitialization = -1;
13610
+ const readinessSubscribers = /* @__PURE__ */ new Set();
13611
+ const waitingConsumers = /* @__PURE__ */ new Set();
13612
+ let pendingIdentification;
13613
+ function onReady(notify) {
13614
+ readinessSubscribers.add(notify);
13615
+ return () => {
13616
+ readinessSubscribers.delete(notify);
13617
+ };
13618
+ }
13619
+ function invalidate() {
13620
+ initialization$1++;
13621
+ fingerprintAgentReady = false;
13622
+ pendingIdentification = void 0;
13623
+ waitingConsumers.forEach((cancel) => cancel());
13624
+ }
13523
13625
  const getExpirationDate = (cacheTimeInDays) => {
13524
13626
  const currentDate = /* @__PURE__ */ new Date();
13525
13627
  const futureDate = new Date(
@@ -13565,7 +13667,9 @@ async function initFingerprint(apiKey, proxyUrl) {
13565
13667
  customEndpointArray.unshift(proxyUrl);
13566
13668
  customScriptEndpointArray.unshift(buildProxyUrl(proxyUrl));
13567
13669
  }
13568
- fingerprintAgentReady = false;
13670
+ if (startedInitialization === initialization$1) invalidate();
13671
+ startedInitialization = initialization$1;
13672
+ const attempt = initialization$1;
13569
13673
  window.fingerprintConfig = {};
13570
13674
  const client = new FpjsClient({
13571
13675
  loadOptions: {
@@ -13577,19 +13681,21 @@ async function initFingerprint(apiKey, proxyUrl) {
13577
13681
  window.fpClient = client;
13578
13682
  try {
13579
13683
  await client.init();
13580
- if (window.fpClient === client) {
13684
+ if (window.fpClient === client && attempt === initialization$1) {
13581
13685
  fingerprintAgentReady = true;
13686
+ readinessSubscribers.forEach((notify) => notify());
13582
13687
  }
13583
13688
  } catch (error) {
13584
- if (window.fpClient === client) {
13689
+ if (window.fpClient === client && attempt === initialization$1) {
13585
13690
  fingerprintAgentReady = false;
13691
+ waitingConsumers.forEach((cancel) => cancel());
13586
13692
  }
13587
13693
  console.error("Error initializing identification service:", error);
13588
13694
  throw error;
13589
13695
  }
13590
13696
  }
13591
- async function getFingerprint(cacheOnly = false) {
13592
- return readFingerprint(cacheOnly);
13697
+ async function getFingerprint(cacheOnly = false, context) {
13698
+ return readFingerprint(cacheOnly, context);
13593
13699
  }
13594
13700
  function getFingerprintForIdentify(context) {
13595
13701
  return readFingerprint(false, context);
@@ -13605,21 +13711,59 @@ async function readFingerprint(cacheOnly, context) {
13605
13711
  const { visitorId } = await requestIdentification(false, context);
13606
13712
  return visitorId;
13607
13713
  }
13608
- async function identifyVisitor(ignoreCache = false) {
13609
- return requestIdentification(ignoreCache);
13714
+ async function identifyVisitor(ignoreCache = false, context) {
13715
+ return requestIdentification(ignoreCache, context);
13610
13716
  }
13611
13717
  async function requestIdentification(ignoreCache, context) {
13718
+ if (!ignoreCache && (pendingIdentification == null ? void 0 : pendingIdentification.isCurrent())) {
13719
+ for (const participant of pendingIdentification.contexts) {
13720
+ if (participant && !participant.isActive())
13721
+ pendingIdentification.contexts.delete(participant);
13722
+ }
13723
+ pendingIdentification.contexts.add(context);
13724
+ return pendingIdentification.promise;
13725
+ }
13726
+ const contexts = /* @__PURE__ */ new Set([context]);
13727
+ const attempt = initialization$1;
13728
+ const currentLifecycle = trackingLifecycle.capture();
13729
+ const isCurrent = () => attempt === initialization$1 && currentLifecycle();
13730
+ const sharedContext = {
13731
+ isActive: () => isCurrent() && [...contexts].some(
13732
+ (participant) => !participant || participant.isActive()
13733
+ ),
13734
+ report: (...args) => {
13735
+ for (const participant of contexts) {
13736
+ if (participant == null ? void 0 : participant.isActive()) participant.report(...args);
13737
+ }
13738
+ }
13739
+ };
13740
+ const request = {
13741
+ contexts,
13742
+ isCurrent,
13743
+ promise: performIdentification(
13744
+ ignoreCache,
13745
+ sharedContext,
13746
+ () => contexts.has(void 0)
13747
+ )
13748
+ };
13749
+ pendingIdentification = request;
13750
+ const clear = () => {
13751
+ if (pendingIdentification === request) pendingIdentification = void 0;
13752
+ };
13753
+ void request.promise.then(clear, clear);
13754
+ return request.promise;
13755
+ }
13756
+ async function performIdentification(ignoreCache, context, hasUnscopedConsumer) {
13612
13757
  if (!isFingerprintReady()) {
13613
13758
  throw new Error("Identification service is not initialized.");
13614
13759
  }
13615
13760
  try {
13616
13761
  const result = await window.fpClient.getVisitorData({}, ignoreCache);
13617
- if (!context || context.isActive()) {
13762
+ if (context.isActive()) {
13618
13763
  try {
13619
13764
  setCachedFingerprint(result.visitorId);
13620
- } catch (error) {
13621
- if (!context) throw error;
13622
- context.report("cache", "unavailable");
13765
+ } catch {
13766
+ context == null ? void 0 : context.report("cache", "unavailable");
13623
13767
  }
13624
13768
  if (typeof result.visitorFound === "boolean") {
13625
13769
  try {
@@ -13627,8 +13771,9 @@ async function requestIdentification(ignoreCache, context) {
13627
13771
  "Visitor Found": result.visitorFound
13628
13772
  });
13629
13773
  } catch (error) {
13630
- if (context) context.report("fingerprint", "failed");
13631
- else console.error("Error updating Mixpanel visitor profile:", error);
13774
+ context.report("fingerprint", "failed");
13775
+ if (hasUnscopedConsumer())
13776
+ console.error("Error updating Mixpanel visitor profile:", error);
13632
13777
  }
13633
13778
  }
13634
13779
  }
@@ -13640,10 +13785,46 @@ async function requestIdentification(ignoreCache, context) {
13640
13785
  cacheHit: result.cacheHit
13641
13786
  };
13642
13787
  } catch (error) {
13643
- if (!context) console.error("Error retrieving fingerprint:", error);
13788
+ if (hasUnscopedConsumer())
13789
+ console.error("Error retrieving fingerprint:", error);
13644
13790
  throw error;
13645
13791
  }
13646
13792
  }
13793
+ function waitForFingerprintId(timeoutMs = 2e3) {
13794
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > 2147483647)
13795
+ return Promise.resolve(null);
13796
+ const cached = getCachedFingerprint();
13797
+ if (cached) return Promise.resolve(cached);
13798
+ return new Promise((resolve) => {
13799
+ let active = true;
13800
+ let started = false;
13801
+ const finish = (visitorId) => {
13802
+ if (!active) return;
13803
+ active = false;
13804
+ clearTimeout(timer);
13805
+ stopReady();
13806
+ stopLifecycle();
13807
+ waitingConsumers.delete(cancel);
13808
+ resolve(visitorId);
13809
+ };
13810
+ const cancel = () => finish(null);
13811
+ const start = () => {
13812
+ if (!active || started || !isFingerprintReady()) return;
13813
+ started = true;
13814
+ const identity = getFingerprint(false, {
13815
+ // Caller timeout ends waiting; the shared request still checks its lifecycle.
13816
+ isActive: () => true,
13817
+ report: () => void 0
13818
+ });
13819
+ void identity.then((visitorId) => finish(visitorId || null), cancel);
13820
+ };
13821
+ const timer = setTimeout(cancel, timeoutMs);
13822
+ const stopReady = onReady(start);
13823
+ const stopLifecycle = trackingLifecycle.onChange(cancel);
13824
+ waitingConsumers.add(cancel);
13825
+ start();
13826
+ });
13827
+ }
13647
13828
  function isFingerprintReady() {
13648
13829
  return window.fpClient !== void 0 && fingerprintAgentReady;
13649
13830
  }
@@ -13652,8 +13833,169 @@ const fingerprintService = {
13652
13833
  getFingerprint,
13653
13834
  identifyVisitor,
13654
13835
  isFingerprintReady,
13655
- getCachedFingerprint
13656
- };
13836
+ onReady,
13837
+ invalidate,
13838
+ getCachedFingerprint,
13839
+ waitForFingerprintId
13840
+ };
13841
+ trackingLifecycle.onInitialization(invalidate);
13842
+ function createPendingWorkQueue(options) {
13843
+ const entries = /* @__PURE__ */ new Set();
13844
+ let bytes = 0;
13845
+ let active = 0;
13846
+ let draining = false;
13847
+ let unsubscribe;
13848
+ let timer;
13849
+ function diagnostic(entry, reason) {
13850
+ console.error("Tracking recovery diagnostic", {
13851
+ eventName: entry.eventName,
13852
+ stage: options.stage,
13853
+ reason
13854
+ });
13855
+ }
13856
+ function resolveForeground(entry) {
13857
+ var _a;
13858
+ (_a = entry.resolve) == null ? void 0 : _a.call(entry);
13859
+ entry.resolve = void 0;
13860
+ }
13861
+ function close(entry, reason) {
13862
+ if (entry.state === "closed") return;
13863
+ entry.state = "closed";
13864
+ entries.delete(entry);
13865
+ bytes -= entry.bytes;
13866
+ resolveForeground(entry);
13867
+ if (reason) diagnostic(entry, reason);
13868
+ }
13869
+ function schedule() {
13870
+ clearTimeout(timer);
13871
+ timer = void 0;
13872
+ if (entries.size === 0) {
13873
+ unsubscribe == null ? void 0 : unsubscribe();
13874
+ unsubscribe = void 0;
13875
+ return;
13876
+ }
13877
+ let next = Infinity;
13878
+ for (const entry of entries) {
13879
+ next = Math.min(next, entry.deadline);
13880
+ if (entry.resolve) next = Math.min(next, entry.foregroundDeadline);
13881
+ }
13882
+ timer = setTimeout(drain, Math.max(0, next - Date.now()));
13883
+ }
13884
+ function drain() {
13885
+ if (draining) return;
13886
+ draining = true;
13887
+ try {
13888
+ for (const entry of entries) {
13889
+ if (!entry.isCurrent()) close(entry, "identity_changed");
13890
+ else if (Date.now() >= entry.deadline) close(entry, "timeout");
13891
+ else if (entry.resolve && Date.now() >= entry.foregroundDeadline) {
13892
+ resolveForeground(entry);
13893
+ }
13894
+ }
13895
+ for (const entry of entries) {
13896
+ if (active >= options.maxActive || !options.isReady()) break;
13897
+ if (entry.state !== "waiting") continue;
13898
+ entry.state = "active";
13899
+ active++;
13900
+ const isActive = () => entry.state === "active" && Date.now() < entry.deadline && entry.isCurrent();
13901
+ const finish = (failed = false) => {
13902
+ if (failed && isActive()) diagnostic(entry, "failed");
13903
+ close(entry);
13904
+ active--;
13905
+ drain();
13906
+ };
13907
+ try {
13908
+ void Promise.resolve(entry.run(isActive)).then(
13909
+ () => finish(),
13910
+ () => finish(true)
13911
+ );
13912
+ } catch {
13913
+ finish(true);
13914
+ }
13915
+ }
13916
+ } finally {
13917
+ draining = false;
13918
+ schedule();
13919
+ }
13920
+ }
13921
+ function enqueue(work) {
13922
+ if (work.bytes > options.maxBytes) {
13923
+ diagnostic(work, "capacity");
13924
+ return Promise.resolve();
13925
+ }
13926
+ while (entries.size >= options.maxEntries || bytes + work.bytes > options.maxBytes) {
13927
+ const oldest = [...entries].find((entry) => entry.state === "waiting");
13928
+ if (!oldest) {
13929
+ diagnostic(work, "capacity");
13930
+ return Promise.resolve();
13931
+ }
13932
+ close(oldest, "capacity");
13933
+ }
13934
+ return new Promise((resolve) => {
13935
+ const entry = {
13936
+ ...work,
13937
+ state: "waiting",
13938
+ deadline: Date.now() + options.lifetime,
13939
+ foregroundDeadline: Date.now() + options.wait,
13940
+ resolve
13941
+ };
13942
+ entries.add(entry);
13943
+ bytes += entry.bytes;
13944
+ unsubscribe ?? (unsubscribe = options.subscribe(drain));
13945
+ drain();
13946
+ });
13947
+ }
13948
+ return { enqueue };
13949
+ }
13950
+ const observations = createPendingWorkQueue({
13951
+ isReady: () => fingerprintService.isFingerprintReady(),
13952
+ subscribe(notify) {
13953
+ const stopReady = fingerprintService.onReady(notify);
13954
+ const stopIdentity = trackingLifecycle.onChange(notify);
13955
+ return () => {
13956
+ stopReady();
13957
+ stopIdentity();
13958
+ };
13959
+ },
13960
+ maxEntries: 100,
13961
+ maxBytes: 1024 * 1024,
13962
+ maxActive: 3,
13963
+ lifetime: 1e4,
13964
+ wait: 0,
13965
+ stage: "fingerprint"
13966
+ });
13967
+ function deferBotDetection(eventName, distinctId, isCurrent) {
13968
+ void observations.enqueue({
13969
+ eventName,
13970
+ bytes: new Blob([eventName, distinctId ?? ""]).size,
13971
+ isCurrent,
13972
+ async run(isActive) {
13973
+ let stage = "fingerprint";
13974
+ try {
13975
+ const identification = await fingerprintService.identifyVisitor(true, {
13976
+ isActive,
13977
+ report: () => void 0
13978
+ });
13979
+ if (!isActive() || !identification.requestId) return;
13980
+ stage = "relay";
13981
+ await botDetectionRelayService.relay({
13982
+ requestId: identification.requestId,
13983
+ visitorId: identification.visitorId,
13984
+ ...distinctId && { distinctId },
13985
+ eventName
13986
+ });
13987
+ } catch {
13988
+ if (isActive()) {
13989
+ console.error("Bot Detection diagnostic", {
13990
+ eventName,
13991
+ stage,
13992
+ reason: "failed"
13993
+ });
13994
+ }
13995
+ }
13996
+ }
13997
+ });
13998
+ }
13657
13999
  let environment;
13658
14000
  let appName;
13659
14001
  const origin = window.location.origin;
@@ -13792,7 +14134,7 @@ const tripSchema = z.object({
13792
14134
  "Departure Origin Terminal": stringField("Departure Origin Terminal"),
13793
14135
  "Departure Price": numberField("Departure Price"),
13794
14136
  "Departure Route": stringField("Departure Route"),
13795
- "Departure Stops": intField("Departure Stops"),
14137
+ "Departure Stops": z.number().int(),
13796
14138
  "Departure Time": dateField("Departure Time"),
13797
14139
  "Departure Transport Type": stringField("Departure Transport Type"),
13798
14140
  "Departure Transporter": stringField("Departure Transporter"),
@@ -13806,7 +14148,7 @@ const tripSchema = z.object({
13806
14148
  "Return Origin Terminal": stringField("Return Origin Terminal").optional(),
13807
14149
  "Return Price": numberField("Return Price").optional(),
13808
14150
  "Return Route": stringField("Return Route").optional(),
13809
- "Return Stops": intField("Return Stops").optional(),
14151
+ "Return Stops": z.number().int().optional(),
13810
14152
  "Return Time": dateField("Return Time").optional(),
13811
14153
  "Return Transport Type": stringField("Return Transport Type").optional(),
13812
14154
  "Return Transporter": stringField("Return Transporter").optional(),
@@ -13819,7 +14161,8 @@ const passengersCreatedSchema = z.object({
13819
14161
  "Trip Count": intField("Trip Count").optional(),
13820
14162
  "Total": numberField("Total"),
13821
14163
  "product": productValidation,
13822
- "Passengers": arrayField(passengerSchema, "Passengers", 1).optional()
14164
+ "Passengers": arrayField(passengerSchema, "Passengers", 1).optional(),
14165
+ "Coupon": stringField("Coupon").optional()
13823
14166
  }).strict();
13824
14167
  const EVENT_NAME$8 = "Passengers Created";
13825
14168
  function trackPassengersCreated(eventProps, meta = {}) {
@@ -13887,7 +14230,8 @@ const purchaseConfirmationSchema = z.object({
13887
14230
  "Operation Id": z.string().min(1, "Operation Id is required"),
13888
14231
  "Payment Status": z.enum(["confirmed", "pending_pix"]),
13889
14232
  "Payment Type": z.string().optional(),
13890
- "Total": z.number().optional()
14233
+ "Total": z.number().optional(),
14234
+ "product": productValidation.optional()
13891
14235
  }).strict();
13892
14236
  const EVENT_NAME$4 = "Purchase Confirmation Viewed";
13893
14237
  function trackPurchaseConfirmation(eventData, meta = {}) {
@@ -14071,6 +14415,7 @@ const validatorService = {
14071
14415
  parseIdentifyProps,
14072
14416
  validateProps
14073
14417
  };
14418
+ let initialization = 0;
14074
14419
  function onLoaded() {
14075
14420
  window.dispatchEvent(new CustomEvent("Tracker Ready"));
14076
14421
  }
@@ -14085,19 +14430,30 @@ async function init$1(config) {
14085
14430
  identifyProxyUrl,
14086
14431
  appName: appName2
14087
14432
  } = config;
14433
+ mixpanelService.validateConfiguration(mixpanelToken, mixpanelProxyUrl);
14434
+ const attempt = ++initialization;
14435
+ const isCurrent = () => attempt === initialization;
14088
14436
  await mixpanelService.init(mixpanelToken, debug, mixpanelProxyUrl);
14437
+ if (!isCurrent()) return;
14089
14438
  configService.setEnvironment(isSandbox ? "sandbox" : "prod");
14090
14439
  if (appName2) {
14091
14440
  configService.setAppName(appName2);
14092
14441
  }
14093
14442
  a$1.setConfig(configService.getCoreAPIConfig());
14094
14443
  onLoaded();
14444
+ if (!isCurrent()) return;
14095
14445
  await Promise.all([
14096
14446
  initializeOptionalStage(
14097
14447
  "fingerprint",
14098
- () => fingerprintService.initFingerprint(identificationKey, identifyProxyUrl)
14448
+ () => isCurrent() ? fingerprintService.initFingerprint(
14449
+ identificationKey,
14450
+ identifyProxyUrl
14451
+ ) : Promise.resolve()
14099
14452
  ),
14100
- initializeOptionalStage("geolocation", () => geolocationService.init())
14453
+ initializeOptionalStage(
14454
+ "geolocation",
14455
+ () => isCurrent() ? geolocationService.init() : Promise.resolve()
14456
+ )
14101
14457
  ]);
14102
14458
  }
14103
14459
  function initializeOptionalStage(stage, initialize) {
@@ -14323,6 +14679,20 @@ const geolocationService = {
14323
14679
  getCoordinates,
14324
14680
  getGeolocationSource
14325
14681
  };
14682
+ function snapshotEventData(value) {
14683
+ if (Array.isArray(value))
14684
+ return value.map((item) => snapshotEventData(item));
14685
+ if (value instanceof Date) return new Date(value.getTime());
14686
+ if (value !== null && typeof value === "object") {
14687
+ return Object.fromEntries(
14688
+ Object.entries(value).map(([key, item]) => [
14689
+ key,
14690
+ snapshotEventData(item)
14691
+ ])
14692
+ );
14693
+ }
14694
+ return value;
14695
+ }
14326
14696
  const FP_TRIGGER_EVENTS = ["Search", "View Results"];
14327
14697
  const EXTRA_UTM_FIELDS = {
14328
14698
  utm_brand: "UTM Brand",
@@ -14377,15 +14747,17 @@ async function tryTrackEvent(trackEventCallback, eventName = "Track Event Error"
14377
14747
  const ready = await new Promise((resolve) => {
14378
14748
  const finish = (isReady2) => {
14379
14749
  clearTimeout(timer);
14380
- window.removeEventListener("Tracker Ready", onReady);
14750
+ window.removeEventListener("Tracker Ready", onReady2);
14751
+ unsubscribe();
14381
14752
  resolve(isReady2);
14382
14753
  };
14383
- const onReady = () => {
14754
+ const onReady2 = () => {
14384
14755
  if (mixpanelService.isReady()) finish(true);
14385
14756
  };
14386
14757
  const timer = setTimeout(() => finish(mixpanelService.isReady()), 5e3);
14387
- window.addEventListener("Tracker Ready", onReady);
14388
- onReady();
14758
+ const unsubscribe = mixpanelService.onReady(onReady2);
14759
+ window.addEventListener("Tracker Ready", onReady2);
14760
+ onReady2();
14389
14761
  });
14390
14762
  if (!ready) {
14391
14763
  console.error("Tracking diagnostic", {
@@ -14406,28 +14778,94 @@ async function tryTrackEvent(trackEventCallback, eventName = "Track Event Error"
14406
14778
  });
14407
14779
  }
14408
14780
  }
14781
+ const pendingEvents = createPendingWorkQueue({
14782
+ isReady: () => mixpanelService.isReady(),
14783
+ subscribe(notify) {
14784
+ const stopReady = mixpanelService.onReady(notify);
14785
+ const stopIdentity = trackingLifecycle.onChange(notify);
14786
+ window.addEventListener("Tracker Ready", notify);
14787
+ return () => {
14788
+ stopReady();
14789
+ stopIdentity();
14790
+ window.removeEventListener("Tracker Ready", notify);
14791
+ };
14792
+ },
14793
+ maxEntries: 100,
14794
+ maxBytes: 1024 * 1024,
14795
+ maxActive: 100,
14796
+ lifetime: 6e4,
14797
+ wait: 5e3,
14798
+ stage: "readiness"
14799
+ });
14409
14800
  function trackEvent(eventName, eventProperties, meta = {}) {
14410
- return tryTrackEvent(
14411
- () => submitEvent(eventName, eventProperties, meta),
14412
- eventName
14413
- );
14801
+ const isCurrent = trackingLifecycle.capture();
14802
+ const trackingParams = extractTrackingParams();
14803
+ if (mixpanelService.isReady()) {
14804
+ return submitEvent(
14805
+ eventName,
14806
+ eventProperties,
14807
+ meta,
14808
+ isCurrent,
14809
+ trackingParams
14810
+ );
14811
+ }
14812
+ try {
14813
+ validatorService.parseEventProps(eventName, eventProperties);
14814
+ } catch (error) {
14815
+ trackEventError(eventName, error, eventProperties);
14816
+ return Promise.resolve();
14817
+ }
14818
+ let snapshot2;
14819
+ let bytes;
14820
+ try {
14821
+ const serialized = JSON.stringify(
14822
+ { eventName, eventProperties, meta, trackingParams },
14823
+ (_key, value) => {
14824
+ if (typeof value === "function" || typeof value === "symbol")
14825
+ throw new Error();
14826
+ return value === void 0 ? null : value;
14827
+ }
14828
+ );
14829
+ bytes = new Blob([serialized]).size;
14830
+ if (bytes > 1024 * 1024) throw new Error();
14831
+ snapshot2 = snapshotEventData({ eventProperties, meta });
14832
+ } catch {
14833
+ console.error("Tracking recovery diagnostic", {
14834
+ eventName,
14835
+ stage: "readiness",
14836
+ reason: "snapshot_unavailable"
14837
+ });
14838
+ return Promise.resolve();
14839
+ }
14840
+ return pendingEvents.enqueue({
14841
+ eventName,
14842
+ bytes,
14843
+ isCurrent,
14844
+ run: (isActive) => submitEvent(
14845
+ eventName,
14846
+ snapshot2.eventProperties,
14847
+ snapshot2.meta,
14848
+ isActive,
14849
+ trackingParams
14850
+ )
14851
+ });
14414
14852
  }
14415
- async function submitEvent(eventName, eventProperties, meta) {
14853
+ async function submitEvent(eventName, eventProperties, meta, isCurrent, trackingParams) {
14854
+ const observationContext = trackingLifecycle.capture();
14416
14855
  try {
14417
14856
  validatorService.parseEventProps(eventName, eventProperties);
14418
14857
  const distinctId = mixpanelService.getMixpanelDistinctId();
14419
14858
  const botDetectionEnabled = botDetectionRelayService.isEnabledFor(eventName);
14420
14859
  let fingerprint = "";
14421
- if (botDetectionEnabled) {
14860
+ if (botDetectionEnabled || FP_TRIGGER_EVENTS.includes(eventName)) {
14422
14861
  try {
14423
14862
  fingerprint = fingerprintService.getCachedFingerprint() ?? "";
14424
14863
  } catch (error) {
14425
14864
  console.error("Error reading cached fingerprint:", error);
14426
14865
  }
14427
14866
  } else {
14428
- const cacheOnly = !FP_TRIGGER_EVENTS.includes(eventName);
14429
14867
  try {
14430
- fingerprint = await fingerprintService.getFingerprint(cacheOnly);
14868
+ fingerprint = await fingerprintService.getFingerprint(true);
14431
14869
  } catch {
14432
14870
  console.error("Tracking diagnostic", {
14433
14871
  eventName,
@@ -14449,7 +14887,6 @@ async function submitEvent(eventName, eventProperties, meta) {
14449
14887
  const defaultProperties = {
14450
14888
  "User Fingerprint": fingerprint
14451
14889
  };
14452
- const trackingParams = extractTrackingParams();
14453
14890
  const simplifiedData = flattenEventData(eventProperties);
14454
14891
  const properties = {
14455
14892
  ...defaultProperties,
@@ -14458,28 +14895,40 @@ async function submitEvent(eventName, eventProperties, meta) {
14458
14895
  ...defaultCoordinates,
14459
14896
  ...meta
14460
14897
  };
14461
- mixpanelService.track(eventName, properties);
14898
+ if (!isCurrent()) return;
14899
+ try {
14900
+ mixpanelService.track(eventName, properties);
14901
+ } catch {
14902
+ console.error("Tracking diagnostic", {
14903
+ eventName,
14904
+ stage: "submission",
14905
+ reason: "uncertain"
14906
+ });
14907
+ return;
14908
+ }
14462
14909
  if (botDetectionEnabled) {
14463
- void identifyAndRelayFingerprint(eventName, distinctId);
14910
+ deferBotDetection(eventName, distinctId, observationContext);
14911
+ } else if (FP_TRIGGER_EVENTS.includes(eventName)) {
14912
+ void refreshFingerprint(eventName, observationContext);
14464
14913
  }
14465
14914
  } catch (error) {
14466
14915
  console.error(`Error tracking event '${eventName}':`, error);
14467
14916
  trackEventError(eventName, error, eventProperties);
14468
14917
  }
14469
14918
  }
14470
- async function identifyAndRelayFingerprint(eventName, distinctId) {
14919
+ async function refreshFingerprint(eventName, isCurrent) {
14920
+ const deadline = Date.now() + 1e4;
14471
14921
  try {
14472
- if (!fingerprintService.isFingerprintReady()) return;
14473
- const identification = await fingerprintService.identifyVisitor(true);
14474
- if (!identification.requestId) return;
14475
- await botDetectionRelayService.relay({
14476
- requestId: identification.requestId,
14477
- visitorId: identification.visitorId,
14478
- ...distinctId && { distinctId },
14479
- eventName
14922
+ await fingerprintService.getFingerprint(false, {
14923
+ isActive: () => isCurrent() && Date.now() < deadline,
14924
+ report: () => void 0
14925
+ });
14926
+ } catch {
14927
+ console.error("Tracking diagnostic", {
14928
+ eventName,
14929
+ stage: "fingerprint",
14930
+ reason: "failed"
14480
14931
  });
14481
- } catch (error) {
14482
- console.error(`Error running Bot Detection for '${eventName}':`, error);
14483
14932
  }
14484
14933
  }
14485
14934
  function extractTrackingParams() {
@@ -14598,6 +15047,7 @@ async function requestAnonymousProfile(payload, report) {
14598
15047
  }
14599
15048
  const KNOWN_PROPERTIES = ["firstName", "lastName", "email", "phone"];
14600
15049
  const FINGERPRINT_PROPERTY = "Known Fingerprints";
15050
+ let latestAttempt = 0;
14601
15051
  function mapProperties(properties) {
14602
15052
  const parsedProperties = {
14603
15053
  $first_name: properties == null ? void 0 : properties.firstName,
@@ -14616,7 +15066,7 @@ function mapProperties(properties) {
14616
15066
  );
14617
15067
  return { ...parsedProperties, ...additionalProperties };
14618
15068
  }
14619
- const identifyWithLocation = async (userId, properties, report) => {
15069
+ const identifyWithLocation = async (userId2, properties, report) => {
14620
15070
  var _a, _b, _c;
14621
15071
  let observer;
14622
15072
  try {
@@ -14653,7 +15103,7 @@ const identifyWithLocation = async (userId, properties, report) => {
14653
15103
  body: JSON.stringify({
14654
15104
  profile_params: {
14655
15105
  ...properties,
14656
- distinct_id: userId
15106
+ distinct_id: userId2
14657
15107
  }
14658
15108
  })
14659
15109
  }
@@ -14667,13 +15117,59 @@ const identifyWithLocation = async (userId, properties, report) => {
14667
15117
  else report("datalake", "failed");
14668
15118
  }
14669
15119
  };
14670
- async function identify(userId, properties = {}) {
15120
+ async function enrichProfileAndLocation(userId2, properties, isActive, report) {
15121
+ const profile = await waitForIdentifyStage(
15122
+ "profile",
15123
+ 3e3,
15124
+ (context) => isActive() ? createAnonymousProfileForIdentify({ ...properties }, context.report) : Promise.resolve(void 0),
15125
+ report
15126
+ );
15127
+ if (!isActive()) return;
15128
+ const enriched = {
15129
+ ...properties,
15130
+ ...profile && { reservamos_one_id: profile.id }
15131
+ };
15132
+ if (profile) {
15133
+ try {
15134
+ mixpanelService.setPeopleProperties(
15135
+ { reservamos_one_id: profile.id },
15136
+ report
15137
+ );
15138
+ } catch {
15139
+ report("profile", "failed");
15140
+ }
15141
+ }
15142
+ void identifyWithLocation(userId2, enriched, report);
15143
+ }
15144
+ async function enrichFingerprint(isActive, report) {
15145
+ const fingerprint = await waitForIdentifyStage(
15146
+ "fingerprint",
15147
+ 2e3,
15148
+ (context) => !isActive() ? Promise.resolve("") : getFingerprintForIdentify({
15149
+ isActive: () => context.isActive() && isActive(),
15150
+ report: context.report
15151
+ }),
15152
+ report
15153
+ );
15154
+ if (!fingerprint || !isActive()) return;
15155
+ try {
15156
+ mixpanelService.attachProperty(FINGERPRINT_PROPERTY, fingerprint);
15157
+ } catch {
15158
+ report("fingerprint", "failed");
15159
+ }
15160
+ }
15161
+ async function identify(userId2, properties = {}) {
15162
+ const attempt = ++latestAttempt;
15163
+ const input = { ...properties };
14671
15164
  const report = createIdentifyReporter();
14672
- if (!await waitForIdentifyReady(report)) return;
15165
+ const initialLifecycle = trackingLifecycle.capture();
15166
+ if (!await waitForIdentifyReady(report) || attempt !== latestAttempt || !initialLifecycle())
15167
+ return;
15168
+ let submitted = false;
14673
15169
  try {
14674
15170
  try {
14675
- validatorService.parseIdentifyProps(properties);
14676
- if (!userId) {
15171
+ validatorService.parseIdentifyProps(input);
15172
+ if (!userId2) {
14677
15173
  report("validation", "invalid_input");
14678
15174
  return;
14679
15175
  }
@@ -14681,38 +15177,22 @@ async function identify(userId, properties = {}) {
14681
15177
  report("validation", "invalid_input");
14682
15178
  return;
14683
15179
  }
14684
- const anonymousProfile = await waitForIdentifyStage(
14685
- "profile",
14686
- 3e3,
14687
- (context) => createAnonymousProfileForIdentify({ ...properties }, context.report),
14688
- report
14689
- );
14690
- if (anonymousProfile) {
14691
- properties.reservamos_one_id = anonymousProfile.id;
14692
- }
14693
- const mappedProps = mapProperties(properties);
14694
15180
  try {
14695
- mixpanelService.identify(userId, mappedProps, report);
15181
+ mixpanelService.identify(userId2, mapProperties(input), report);
14696
15182
  } catch {
14697
15183
  report("mixpanel", "failed");
15184
+ return;
14698
15185
  }
14699
- const fingerprint = await waitForIdentifyStage(
14700
- "fingerprint",
14701
- 2e3,
14702
- getFingerprintForIdentify,
14703
- report
14704
- );
14705
- if (fingerprint) {
14706
- try {
14707
- mixpanelService.attachProperty(FINGERPRINT_PROPERTY, fingerprint);
14708
- } catch {
14709
- report("fingerprint", "failed");
14710
- }
14711
- }
14712
- } catch {
14713
- report("mixpanel", "failed");
15186
+ const currentLifecycle = trackingLifecycle.capture();
15187
+ const isActive = () => attempt === latestAttempt && currentLifecycle();
15188
+ const scopedReport = (...args) => {
15189
+ if (isActive()) report(...args);
15190
+ };
15191
+ submitted = true;
15192
+ void enrichProfileAndLocation(userId2, input, isActive, scopedReport);
15193
+ void enrichFingerprint(isActive, scopedReport);
14714
15194
  } finally {
14715
- void identifyWithLocation(userId, properties, report);
15195
+ if (!submitted) void identifyWithLocation(userId2, { ...input }, report);
14716
15196
  }
14717
15197
  }
14718
15198
  z.object({
@@ -14914,6 +15394,7 @@ const analytics = {
14914
15394
  identify,
14915
15395
  identifiers: {
14916
15396
  getFingerprintId: fingerprintService.getCachedFingerprint,
15397
+ waitForFingerprintId: fingerprintService.waitForFingerprintId,
14917
15398
  getDistinctId: mixpanelService.getMixpanelDistinctId
14918
15399
  },
14919
15400
  profiles: {