@reservamos/browser-analytics 1.1.8-alpha.1 → 1.1.8-alpha.3

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 (30) hide show
  1. package/CHANGELOG.md +19 -2
  2. package/README.md +32 -4
  3. package/dist/browser-analytics.cjs +2 -2
  4. package/dist/browser-analytics.cjs.map +1 -1
  5. package/dist/browser-analytics.d.ts +23 -10
  6. package/dist/browser-analytics.esm.js +466 -154
  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.recovery.test.ts +367 -0
  12. package/src/events/identify/identify.robustness.test.ts +112 -13
  13. package/src/events/identify/identify.ts +72 -35
  14. package/src/events/passengersCreated/passengersCreatedSchema.test.ts +142 -0
  15. package/src/events/passengersCreated/passengersCreatedSchema.ts +7 -1
  16. package/src/events/purchaseConfirmation/purchaseConfirmation.integration.test.ts +49 -0
  17. package/src/events/purchaseConfirmation/purchaseConfirmation.test.ts +17 -0
  18. package/src/events/purchaseConfirmation/purchaseConfirmationSchema.ts +2 -0
  19. package/src/index.ts +1 -0
  20. package/src/init.ts +10 -1
  21. package/src/profiles/createAnonymousProfile/createAnonymousProfile.ts +18 -2
  22. package/src/services/botDetectionRelay.test.ts +3 -2
  23. package/src/services/fingerprint.test.ts +172 -0
  24. package/src/services/fingerprint.ts +106 -5
  25. package/src/services/fingerprint.wait.integration.test.ts +129 -0
  26. package/src/services/mixpanel.batch.test.ts +108 -0
  27. package/src/services/mixpanel.test.ts +77 -73
  28. package/src/services/mixpanel.ts +45 -18
  29. package/src/services/profileEnrichment.ts +258 -0
  30. package/src/track.storage.test.ts +27 -12
@@ -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,
@@ -13339,13 +13341,13 @@ function loadNoop(_src, onload) {
13339
13341
  onload();
13340
13342
  }
13341
13343
  var mixpanel = init_as_module(loadNoop);
13342
- let generation = 0;
13344
+ let generation$1 = 0;
13343
13345
  let initialized = false;
13344
13346
  let userId;
13345
13347
  const subscribers = /* @__PURE__ */ new Set();
13346
13348
  const initializationSubscribers = /* @__PURE__ */ new Set();
13347
13349
  function invalidate$1() {
13348
- generation++;
13350
+ generation$1++;
13349
13351
  subscribers.forEach((notify) => notify());
13350
13352
  }
13351
13353
  function beginInitialization() {
@@ -13361,8 +13363,8 @@ function identify$2(nextUserId) {
13361
13363
  }
13362
13364
  }
13363
13365
  function capture() {
13364
- const captured = generation;
13365
- return () => captured === generation;
13366
+ const captured = generation$1;
13367
+ return () => captured === generation$1;
13366
13368
  }
13367
13369
  function onChange(notify) {
13368
13370
  subscribers.add(notify);
@@ -13471,7 +13473,7 @@ function observeIdentifyResult(stage, report, onTimeout) {
13471
13473
  }, 5e3);
13472
13474
  return { complete };
13473
13475
  }
13474
- const version = "1.1.8-alpha.1";
13476
+ const version = "1.1.8-alpha.3";
13475
13477
  const MIXPANEL_DISTINCT_ID_CACHE_KEY = "mp_distinct_id";
13476
13478
  const readinessSubscribers$1 = /* @__PURE__ */ new Set();
13477
13479
  let initializing = false;
@@ -13501,6 +13503,7 @@ function init$2(mixpanelToken, debug = false, proxyUrl) {
13501
13503
  return new Promise((resolve) => {
13502
13504
  mixpanel.init(mixpanelToken, {
13503
13505
  debug,
13506
+ batch_flush_interval_ms: 1e3,
13504
13507
  loaded: () => {
13505
13508
  if (attempt !== initialization$2) {
13506
13509
  resolve();
@@ -13528,34 +13531,43 @@ function identify$1(userId2, properties, report) {
13528
13531
  trackingLifecycle.identify(userId2);
13529
13532
  mixpanel.identify(userId2);
13530
13533
  if (!updateCachedDistinctId(userId2)) report == null ? void 0 : report("cache", "unavailable");
13531
- if (!report) {
13534
+ writePeopleProperties(properties, report);
13535
+ }
13536
+ function attachProperty(key, value) {
13537
+ if (!isReady()) {
13538
+ return;
13539
+ }
13540
+ mixpanel.people.union(key, [value]);
13541
+ }
13542
+ function setPeopleProperties(properties, report, onAccepted) {
13543
+ if (!isReady()) {
13544
+ return;
13545
+ }
13546
+ writePeopleProperties(properties, report, onAccepted);
13547
+ }
13548
+ function restorePeopleProperties(userId2, properties, report, onAccepted) {
13549
+ if (!isReady() || mixpanel.get_distinct_id() !== userId2) return;
13550
+ mixpanel.identify(userId2);
13551
+ writePeopleProperties(properties, report, onAccepted);
13552
+ }
13553
+ function writePeopleProperties(properties, report, onAccepted) {
13554
+ if (!report && !onAccepted) {
13532
13555
  mixpanel.people.set(properties);
13533
13556
  return;
13534
13557
  }
13535
- const observer = observeIdentifyResult("people", report);
13558
+ const observer = report ? observeIdentifyResult("people", report) : void 0;
13536
13559
  try {
13537
13560
  mixpanel.people.set(properties, (response) => {
13538
13561
  const status = typeof response === "number" ? response : response == null ? void 0 : response.status;
13539
- observer.complete(
13562
+ if (status === 1) onAccepted == null ? void 0 : onAccepted();
13563
+ observer == null ? void 0 : observer.complete(
13540
13564
  status === 1 ? void 0 : status === 0 ? "failed" : "unconfirmed"
13541
13565
  );
13542
13566
  });
13543
13567
  } catch {
13544
- observer.complete("failed");
13568
+ observer == null ? void 0 : observer.complete("failed");
13545
13569
  }
13546
13570
  }
13547
- function attachProperty(key, value) {
13548
- if (!isReady()) {
13549
- return;
13550
- }
13551
- mixpanel.people.union(key, [value]);
13552
- }
13553
- function setPeopleProperties(properties) {
13554
- if (!isReady()) {
13555
- return;
13556
- }
13557
- mixpanel.people.set(properties);
13558
- }
13559
13571
  function isReady() {
13560
13572
  return !initializing && window.mixpanel !== void 0;
13561
13573
  }
@@ -13566,8 +13578,7 @@ function track(eventName, properties) {
13566
13578
  mixpanel.track(eventName, { ...properties, "Reservamos Version": version });
13567
13579
  }
13568
13580
  const getMixpanelDistinctId = () => {
13569
- var _a;
13570
- const distinctId = (_a = mixpanel == null ? void 0 : mixpanel.get_distinct_id) == null ? void 0 : _a.call(mixpanel);
13581
+ const distinctId = isReady() ? mixpanel.get_distinct_id() : null;
13571
13582
  if (distinctId) {
13572
13583
  updateCachedDistinctId(distinctId);
13573
13584
  return distinctId;
@@ -13595,6 +13606,7 @@ const mixpanelService = {
13595
13606
  identify: identify$1,
13596
13607
  attachProperty,
13597
13608
  setPeopleProperties,
13609
+ restorePeopleProperties,
13598
13610
  getMixpanelDistinctId
13599
13611
  };
13600
13612
  const DEFAULT_CACHE_NAME = "default_fingerprint_cache";
@@ -13602,7 +13614,10 @@ const DEFAULT_CACHE_TIME_IN_DAYS = 7;
13602
13614
  const PROXY_QUERY_PARAM = "apiKey=<apiKey>&version=<version>&loaderVersion=<loaderVersion>";
13603
13615
  let fingerprintAgentReady = false;
13604
13616
  let initialization$1 = 0;
13617
+ let startedInitialization = -1;
13605
13618
  const readinessSubscribers = /* @__PURE__ */ new Set();
13619
+ const waitingConsumers = /* @__PURE__ */ new Set();
13620
+ let pendingIdentification;
13606
13621
  function onReady(notify) {
13607
13622
  readinessSubscribers.add(notify);
13608
13623
  return () => {
@@ -13612,6 +13627,8 @@ function onReady(notify) {
13612
13627
  function invalidate() {
13613
13628
  initialization$1++;
13614
13629
  fingerprintAgentReady = false;
13630
+ pendingIdentification = void 0;
13631
+ waitingConsumers.forEach((cancel) => cancel());
13615
13632
  }
13616
13633
  const getExpirationDate = (cacheTimeInDays) => {
13617
13634
  const currentDate = /* @__PURE__ */ new Date();
@@ -13658,7 +13675,8 @@ async function initFingerprint(apiKey, proxyUrl) {
13658
13675
  customEndpointArray.unshift(proxyUrl);
13659
13676
  customScriptEndpointArray.unshift(buildProxyUrl(proxyUrl));
13660
13677
  }
13661
- invalidate();
13678
+ if (startedInitialization === initialization$1) invalidate();
13679
+ startedInitialization = initialization$1;
13662
13680
  const attempt = initialization$1;
13663
13681
  window.fingerprintConfig = {};
13664
13682
  const client = new FpjsClient({
@@ -13678,6 +13696,7 @@ async function initFingerprint(apiKey, proxyUrl) {
13678
13696
  } catch (error) {
13679
13697
  if (window.fpClient === client && attempt === initialization$1) {
13680
13698
  fingerprintAgentReady = false;
13699
+ waitingConsumers.forEach((cancel) => cancel());
13681
13700
  }
13682
13701
  console.error("Error initializing identification service:", error);
13683
13702
  throw error;
@@ -13704,12 +13723,51 @@ async function identifyVisitor(ignoreCache = false, context) {
13704
13723
  return requestIdentification(ignoreCache, context);
13705
13724
  }
13706
13725
  async function requestIdentification(ignoreCache, context) {
13726
+ if (!ignoreCache && (pendingIdentification == null ? void 0 : pendingIdentification.isCurrent())) {
13727
+ for (const participant of pendingIdentification.contexts) {
13728
+ if (participant && !participant.isActive())
13729
+ pendingIdentification.contexts.delete(participant);
13730
+ }
13731
+ pendingIdentification.contexts.add(context);
13732
+ return pendingIdentification.promise;
13733
+ }
13734
+ const contexts = /* @__PURE__ */ new Set([context]);
13735
+ const attempt = initialization$1;
13736
+ const currentLifecycle = trackingLifecycle.capture();
13737
+ const isCurrent = () => attempt === initialization$1 && currentLifecycle();
13738
+ const sharedContext = {
13739
+ isActive: () => isCurrent() && [...contexts].some(
13740
+ (participant) => !participant || participant.isActive()
13741
+ ),
13742
+ report: (...args) => {
13743
+ for (const participant of contexts) {
13744
+ if (participant == null ? void 0 : participant.isActive()) participant.report(...args);
13745
+ }
13746
+ }
13747
+ };
13748
+ const request = {
13749
+ contexts,
13750
+ isCurrent,
13751
+ promise: performIdentification(
13752
+ ignoreCache,
13753
+ sharedContext,
13754
+ () => contexts.has(void 0)
13755
+ )
13756
+ };
13757
+ pendingIdentification = request;
13758
+ const clear = () => {
13759
+ if (pendingIdentification === request) pendingIdentification = void 0;
13760
+ };
13761
+ void request.promise.then(clear, clear);
13762
+ return request.promise;
13763
+ }
13764
+ async function performIdentification(ignoreCache, context, hasUnscopedConsumer) {
13707
13765
  if (!isFingerprintReady()) {
13708
13766
  throw new Error("Identification service is not initialized.");
13709
13767
  }
13710
13768
  try {
13711
13769
  const result = await window.fpClient.getVisitorData({}, ignoreCache);
13712
- if (!context || context.isActive()) {
13770
+ if (context.isActive()) {
13713
13771
  try {
13714
13772
  setCachedFingerprint(result.visitorId);
13715
13773
  } catch {
@@ -13721,8 +13779,9 @@ async function requestIdentification(ignoreCache, context) {
13721
13779
  "Visitor Found": result.visitorFound
13722
13780
  });
13723
13781
  } catch (error) {
13724
- if (context) context.report("fingerprint", "failed");
13725
- else console.error("Error updating Mixpanel visitor profile:", error);
13782
+ context.report("fingerprint", "failed");
13783
+ if (hasUnscopedConsumer())
13784
+ console.error("Error updating Mixpanel visitor profile:", error);
13726
13785
  }
13727
13786
  }
13728
13787
  }
@@ -13734,10 +13793,46 @@ async function requestIdentification(ignoreCache, context) {
13734
13793
  cacheHit: result.cacheHit
13735
13794
  };
13736
13795
  } catch (error) {
13737
- if (!context) console.error("Error retrieving fingerprint:", error);
13796
+ if (hasUnscopedConsumer())
13797
+ console.error("Error retrieving fingerprint:", error);
13738
13798
  throw error;
13739
13799
  }
13740
13800
  }
13801
+ function waitForFingerprintId(timeoutMs = 2e3) {
13802
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0 || timeoutMs > 2147483647)
13803
+ return Promise.resolve(null);
13804
+ const cached = getCachedFingerprint();
13805
+ if (cached) return Promise.resolve(cached);
13806
+ return new Promise((resolve) => {
13807
+ let active = true;
13808
+ let started = false;
13809
+ const finish = (visitorId) => {
13810
+ if (!active) return;
13811
+ active = false;
13812
+ clearTimeout(timer);
13813
+ stopReady();
13814
+ stopLifecycle();
13815
+ waitingConsumers.delete(cancel);
13816
+ resolve(visitorId);
13817
+ };
13818
+ const cancel = () => finish(null);
13819
+ const start = () => {
13820
+ if (!active || started || !isFingerprintReady()) return;
13821
+ started = true;
13822
+ const identity = getFingerprint(false, {
13823
+ // Caller timeout ends waiting; the shared request still checks its lifecycle.
13824
+ isActive: () => true,
13825
+ report: () => void 0
13826
+ });
13827
+ void identity.then((visitorId) => finish(visitorId || null), cancel);
13828
+ };
13829
+ const timer = setTimeout(cancel, timeoutMs);
13830
+ const stopReady = onReady(start);
13831
+ const stopLifecycle = trackingLifecycle.onChange(cancel);
13832
+ waitingConsumers.add(cancel);
13833
+ start();
13834
+ });
13835
+ }
13741
13836
  function isFingerprintReady() {
13742
13837
  return window.fpClient !== void 0 && fingerprintAgentReady;
13743
13838
  }
@@ -13748,7 +13843,8 @@ const fingerprintService = {
13748
13843
  isFingerprintReady,
13749
13844
  onReady,
13750
13845
  invalidate,
13751
- getCachedFingerprint
13846
+ getCachedFingerprint,
13847
+ waitForFingerprintId
13752
13848
  };
13753
13849
  trackingLifecycle.onInitialization(invalidate);
13754
13850
  function createPendingWorkQueue(options) {
@@ -14073,7 +14169,8 @@ const passengersCreatedSchema = z.object({
14073
14169
  "Trip Count": intField("Trip Count").optional(),
14074
14170
  "Total": numberField("Total"),
14075
14171
  "product": productValidation,
14076
- "Passengers": arrayField(passengerSchema, "Passengers", 1).optional()
14172
+ "Passengers": arrayField(passengerSchema, "Passengers", 1).optional(),
14173
+ "Coupon": stringField("Coupon").optional()
14077
14174
  }).strict();
14078
14175
  const EVENT_NAME$8 = "Passengers Created";
14079
14176
  function trackPassengersCreated(eventProps, meta = {}) {
@@ -14141,7 +14238,8 @@ const purchaseConfirmationSchema = z.object({
14141
14238
  "Operation Id": z.string().min(1, "Operation Id is required"),
14142
14239
  "Payment Status": z.enum(["confirmed", "pending_pix"]),
14143
14240
  "Payment Type": z.string().optional(),
14144
- "Total": z.number().optional()
14241
+ "Total": z.number().optional(),
14242
+ "product": productValidation.optional()
14145
14243
  }).strict();
14146
14244
  const EVENT_NAME$4 = "Purchase Confirmation Viewed";
14147
14245
  function trackPurchaseConfirmation(eventData, meta = {}) {
@@ -14325,11 +14423,285 @@ const validatorService = {
14325
14423
  parseIdentifyProps,
14326
14424
  validateProps
14327
14425
  };
14426
+ function isCamelCase(word) {
14427
+ if (!word || word.includes(" ")) return false;
14428
+ if (word[0] !== word[0].toLowerCase()) return false;
14429
+ if (/[^a-zA-Z]/.test(word)) return false;
14430
+ if (!/[A-Z]/.test(word)) return false;
14431
+ return true;
14432
+ }
14433
+ const CreateAnonymousProfileSchema = z.object({
14434
+ email: z.string().email().optional(),
14435
+ phone: z.string().optional(),
14436
+ cpf: z.string().optional(),
14437
+ passport: z.string().optional(),
14438
+ rg: z.string().optional(),
14439
+ firstName: z.string().optional(),
14440
+ lastName: z.string().optional()
14441
+ }).refine((data) => data.email || data.phone, {
14442
+ message: "At least one of 'email' or 'phone' must be provided"
14443
+ });
14444
+ function getAnonymousProfilePayload(values, identifiersProps) {
14445
+ let identifier_key = "phone";
14446
+ let identifier_value = values.phone || "";
14447
+ const identifiers = [];
14448
+ const details = {};
14449
+ if (values.email) {
14450
+ identifier_key = "email";
14451
+ identifier_value = values.email;
14452
+ }
14453
+ const allowedKeys = [
14454
+ "cpf",
14455
+ "passport",
14456
+ "rg",
14457
+ "email",
14458
+ "phone",
14459
+ "salesforceid"
14460
+ ];
14461
+ const allowedKeysDetails = ["name", "firstName", "lastName"];
14462
+ Object.entries(values).forEach(([key, value]) => {
14463
+ key = isCamelCase(key) ? key : key.toLocaleLowerCase();
14464
+ if (allowedKeys.includes(key) && value) {
14465
+ if (key !== identifier_key) {
14466
+ identifiers.push({ key, value });
14467
+ }
14468
+ } else if (allowedKeysDetails.includes(key) && value) {
14469
+ details[key] = value;
14470
+ }
14471
+ });
14472
+ if (identifiersProps.length) {
14473
+ identifiersProps.forEach((item) => identifiers.push(item));
14474
+ }
14475
+ return {
14476
+ identifier_key,
14477
+ identifier_value,
14478
+ details,
14479
+ identifiers
14480
+ };
14481
+ }
14482
+ async function createAnonymousProfile(payload) {
14483
+ try {
14484
+ return await requestAnonymousProfile(payload);
14485
+ } catch (error) {
14486
+ console.error("Could not create anonymous profile:", error);
14487
+ return void 0;
14488
+ }
14489
+ }
14490
+ function createAnonymousProfileForIdentify(payload, report) {
14491
+ return requestAnonymousProfile(payload, report);
14492
+ }
14493
+ async function requestAnonymousProfile(payload, report) {
14494
+ return requestPreparedAnonymousProfile(
14495
+ prepareAnonymousProfileForIdentify(payload, report)
14496
+ );
14497
+ }
14498
+ function prepareAnonymousProfileForIdentify(payload, report) {
14499
+ validatorService.validateProps(payload, CreateAnonymousProfileSchema);
14500
+ const identifiers = [];
14501
+ const readIdentifier = (read2) => {
14502
+ if (!report) return read2();
14503
+ try {
14504
+ return read2();
14505
+ } catch {
14506
+ report("cache", "unavailable");
14507
+ return null;
14508
+ }
14509
+ };
14510
+ const userFingerprintId = readIdentifier(
14511
+ fingerprintService.getCachedFingerprint
14512
+ );
14513
+ const distinctId = readIdentifier(mixpanelService.getMixpanelDistinctId);
14514
+ if (userFingerprintId)
14515
+ identifiers.push({ key: "fingerprint", value: userFingerprintId });
14516
+ if (distinctId) identifiers.push({ key: "distinct_id", value: distinctId });
14517
+ return getAnonymousProfilePayload(payload, identifiers);
14518
+ }
14519
+ function requestPreparedAnonymousProfile(payload) {
14520
+ return a$1.profiles.createAnonymousProfile(payload);
14521
+ }
14522
+ const STORAGE_KEY = "reservamos_pending_profile_v1";
14523
+ const LIFETIME_MS = 10 * 60 * 1e3;
14524
+ const MAX_ATTEMPTS = 3;
14525
+ const MAX_BYTES = 4096;
14526
+ const profileIdSchema = z.union([
14527
+ z.string().min(1),
14528
+ z.number().int().positive()
14529
+ ]);
14530
+ const requestSchema = z.object({
14531
+ identifier_key: z.enum(["email", "phone"]),
14532
+ identifier_value: z.string().min(1),
14533
+ identifiers: z.array(
14534
+ z.object({ key: z.string().min(1), value: z.string().min(1) }).strict()
14535
+ ),
14536
+ details: z.record(z.string())
14537
+ }).strict();
14538
+ const pendingSchema = z.object({
14539
+ version: z.literal(1),
14540
+ id: z.string().min(1),
14541
+ userId: z.string().min(1),
14542
+ scope: z.string().min(1),
14543
+ expiresAt: z.number().int(),
14544
+ attempts: z.number().int().min(1).max(MAX_ATTEMPTS),
14545
+ request: requestSchema.optional(),
14546
+ profileId: profileIdSchema.optional()
14547
+ }).strict().refine((entry) => entry.request || entry.profileId);
14548
+ let scope;
14549
+ let generation = 0;
14550
+ function remove(report, id) {
14551
+ try {
14552
+ if (id) {
14553
+ const stored = JSON.parse(
14554
+ sessionStorage.getItem(STORAGE_KEY) || "null"
14555
+ );
14556
+ if ((stored == null ? void 0 : stored.id) !== id) return;
14557
+ }
14558
+ sessionStorage.removeItem(STORAGE_KEY);
14559
+ } catch {
14560
+ report("cache", "unavailable");
14561
+ }
14562
+ }
14563
+ function save(entry, report) {
14564
+ try {
14565
+ const serialized = JSON.stringify(entry);
14566
+ if (new Blob([serialized]).size > MAX_BYTES) {
14567
+ remove(report);
14568
+ report("cache", "unavailable");
14569
+ return false;
14570
+ }
14571
+ sessionStorage.setItem(STORAGE_KEY, serialized);
14572
+ return true;
14573
+ } catch {
14574
+ remove(report);
14575
+ report("cache", "unavailable");
14576
+ return false;
14577
+ }
14578
+ }
14579
+ function read(report) {
14580
+ try {
14581
+ const serialized = sessionStorage.getItem(STORAGE_KEY);
14582
+ if (!serialized) return void 0;
14583
+ if (new Blob([serialized]).size > MAX_BYTES) throw new Error();
14584
+ const entry = pendingSchema.parse(JSON.parse(serialized));
14585
+ if (entry.expiresAt <= Date.now() || entry.expiresAt > Date.now() + LIFETIME_MS) {
14586
+ remove(report);
14587
+ return void 0;
14588
+ }
14589
+ return entry;
14590
+ } catch {
14591
+ remove(report);
14592
+ report("cache", "unavailable");
14593
+ return void 0;
14594
+ }
14595
+ }
14596
+ function currentUserIs(userId2) {
14597
+ return mixpanelService.isReady() && mixpanelService.getMixpanelDistinctId() === userId2;
14598
+ }
14599
+ async function resolveProfile(userId2, properties, isCurrent, report, entry, recovering = false) {
14600
+ if (!isCurrent()) return void 0;
14601
+ const profile = (entry == null ? void 0 : entry.profileId) !== void 0 ? { id: entry.profileId } : await waitForIdentifyStage(
14602
+ "profile",
14603
+ 3e3,
14604
+ (context) => isCurrent() ? (entry == null ? void 0 : entry.request) ? requestPreparedAnonymousProfile(entry.request) : createAnonymousProfileForIdentify(
14605
+ { ...properties },
14606
+ context.report
14607
+ ) : Promise.resolve(void 0),
14608
+ report
14609
+ );
14610
+ if (!isCurrent()) return void 0;
14611
+ if (!profile) {
14612
+ if (entry && entry.attempts >= MAX_ATTEMPTS) remove(report, entry.id);
14613
+ return void 0;
14614
+ }
14615
+ const parsedId = profileIdSchema.safeParse(profile.id);
14616
+ if (!parsedId.success) {
14617
+ report("profile", "failed");
14618
+ return void 0;
14619
+ }
14620
+ const profileId = parsedId.data;
14621
+ if (entry) save({ ...entry, request: void 0, profileId }, report);
14622
+ const accepted = () => {
14623
+ if (entry && isCurrent()) remove(report, entry.id);
14624
+ };
14625
+ try {
14626
+ const properties2 = { reservamos_one_id: profileId };
14627
+ if (recovering) {
14628
+ mixpanelService.restorePeopleProperties(
14629
+ userId2,
14630
+ properties2,
14631
+ report,
14632
+ accepted
14633
+ );
14634
+ } else if (entry) {
14635
+ mixpanelService.setPeopleProperties(properties2, report, accepted);
14636
+ } else {
14637
+ mixpanelService.setPeopleProperties(properties2, report);
14638
+ }
14639
+ } catch {
14640
+ report("profile", "failed");
14641
+ }
14642
+ return { id: profileId };
14643
+ }
14644
+ function enrich(userId2, properties, isActive, report) {
14645
+ if (!isActive()) return Promise.resolve(void 0);
14646
+ const attempt = ++generation;
14647
+ let request;
14648
+ if (scope) {
14649
+ try {
14650
+ const prepared = requestSchema.safeParse(
14651
+ prepareAnonymousProfileForIdentify({ ...properties }, report)
14652
+ );
14653
+ if (prepared.success) request = prepared.data;
14654
+ } catch {
14655
+ }
14656
+ }
14657
+ const entry = scope && request ? {
14658
+ version: 1,
14659
+ id: `${Date.now()}-${Math.random()}`,
14660
+ userId: userId2,
14661
+ scope,
14662
+ expiresAt: Date.now() + LIFETIME_MS,
14663
+ attempts: 1,
14664
+ request
14665
+ } : void 0;
14666
+ if (entry) save(entry, report);
14667
+ else if (scope) remove(report);
14668
+ const isCurrent = () => attempt === generation && isActive() && (!entry || Date.now() < entry.expiresAt) && (!scope || currentUserIs(userId2));
14669
+ return resolveProfile(userId2, properties, isCurrent, report, entry);
14670
+ }
14671
+ function initialize(configuration) {
14672
+ scope = JSON.stringify([
14673
+ configuration.project,
14674
+ configuration.proxy ?? "",
14675
+ configuration.apiBaseUrl,
14676
+ configuration.appName ?? ""
14677
+ ]);
14678
+ const attempt = ++generation;
14679
+ const report = createIdentifyReporter();
14680
+ const entry = read(report);
14681
+ if (!entry) return;
14682
+ if (entry.scope !== scope || !currentUserIs(entry.userId) || entry.profileId === void 0 && entry.attempts >= MAX_ATTEMPTS) {
14683
+ remove(report, entry.id);
14684
+ return;
14685
+ }
14686
+ const lifecycle = trackingLifecycle.capture();
14687
+ const isCurrent = () => attempt === generation && lifecycle() && Date.now() < entry.expiresAt && currentUserIs(entry.userId);
14688
+ const resumed = {
14689
+ ...entry,
14690
+ attempts: entry.attempts + (entry.profileId === void 0 ? 1 : 0)
14691
+ };
14692
+ if (entry.profileId === void 0 && !save(resumed, report)) return;
14693
+ const scopedReport = (...args) => {
14694
+ if (isCurrent()) report(...args);
14695
+ };
14696
+ void resolveProfile(entry.userId, {}, isCurrent, scopedReport, resumed, true);
14697
+ }
14698
+ const profileEnrichment = { enrich, initialize };
14328
14699
  let initialization = 0;
14329
14700
  function onLoaded() {
14330
14701
  window.dispatchEvent(new CustomEvent("Tracker Ready"));
14331
14702
  }
14332
14703
  async function init$1(config) {
14704
+ var _a;
14333
14705
  validatorService.parseInitProps(config);
14334
14706
  const {
14335
14707
  mixpanelToken,
@@ -14349,9 +14721,16 @@ async function init$1(config) {
14349
14721
  if (appName2) {
14350
14722
  configService.setAppName(appName2);
14351
14723
  }
14352
- a$1.setConfig(configService.getCoreAPIConfig());
14724
+ const coreConfig = configService.getCoreAPIConfig();
14725
+ a$1.setConfig(coreConfig);
14353
14726
  onLoaded();
14354
14727
  if (!isCurrent()) return;
14728
+ profileEnrichment.initialize({
14729
+ project: mixpanelToken,
14730
+ proxy: mixpanelProxyUrl,
14731
+ apiBaseUrl: coreConfig.baseUrl,
14732
+ appName: (_a = coreConfig.defaultHeaders) == null ? void 0 : _a["x-app-name"]
14733
+ });
14355
14734
  await Promise.all([
14356
14735
  initializeOptionalStage(
14357
14736
  "fingerprint",
@@ -14366,7 +14745,7 @@ async function init$1(config) {
14366
14745
  )
14367
14746
  ]);
14368
14747
  }
14369
- function initializeOptionalStage(stage, initialize) {
14748
+ function initializeOptionalStage(stage, initialize2) {
14370
14749
  return new Promise((resolve) => {
14371
14750
  let active = true;
14372
14751
  const finish = (reason) => {
@@ -14378,7 +14757,7 @@ function initializeOptionalStage(stage, initialize) {
14378
14757
  resolve();
14379
14758
  };
14380
14759
  const timer = setTimeout(() => finish("timeout"), 3e3);
14381
- void Promise.resolve().then(initialize).then(
14760
+ void Promise.resolve().then(initialize2).then(
14382
14761
  () => finish(),
14383
14762
  () => finish("failed")
14384
14763
  );
@@ -14866,97 +15245,9 @@ function trackCustomEvent(eventName, eventData = {}, meta = {}) {
14866
15245
  trackEventError(eventName, error, eventData);
14867
15246
  }
14868
15247
  }
14869
- function isCamelCase(word) {
14870
- if (!word || word.includes(" ")) return false;
14871
- if (word[0] !== word[0].toLowerCase()) return false;
14872
- if (/[^a-zA-Z]/.test(word)) return false;
14873
- if (!/[A-Z]/.test(word)) return false;
14874
- return true;
14875
- }
14876
- const CreateAnonymousProfileSchema = z.object({
14877
- email: z.string().email().optional(),
14878
- phone: z.string().optional(),
14879
- cpf: z.string().optional(),
14880
- passport: z.string().optional(),
14881
- rg: z.string().optional(),
14882
- firstName: z.string().optional(),
14883
- lastName: z.string().optional()
14884
- }).refine((data) => data.email || data.phone, {
14885
- message: "At least one of 'email' or 'phone' must be provided"
14886
- });
14887
- function getAnonymousProfilePayload(values, identifiersProps) {
14888
- let identifier_key = "phone";
14889
- let identifier_value = values.phone || "";
14890
- const identifiers = [];
14891
- const details = {};
14892
- if (values.email) {
14893
- identifier_key = "email";
14894
- identifier_value = values.email;
14895
- }
14896
- const allowedKeys = [
14897
- "cpf",
14898
- "passport",
14899
- "rg",
14900
- "email",
14901
- "phone",
14902
- "salesforceid"
14903
- ];
14904
- const allowedKeysDetails = ["name", "firstName", "lastName"];
14905
- Object.entries(values).forEach(([key, value]) => {
14906
- key = isCamelCase(key) ? key : key.toLocaleLowerCase();
14907
- if (allowedKeys.includes(key) && value) {
14908
- if (key !== identifier_key) {
14909
- identifiers.push({ key, value });
14910
- }
14911
- } else if (allowedKeysDetails.includes(key) && value) {
14912
- details[key] = value;
14913
- }
14914
- });
14915
- if (identifiersProps.length) {
14916
- identifiersProps.forEach((item) => identifiers.push(item));
14917
- }
14918
- return {
14919
- identifier_key,
14920
- identifier_value,
14921
- details,
14922
- identifiers
14923
- };
14924
- }
14925
- async function createAnonymousProfile(payload) {
14926
- try {
14927
- return await requestAnonymousProfile(payload);
14928
- } catch (error) {
14929
- console.error("Could not create anonymous profile:", error);
14930
- return void 0;
14931
- }
14932
- }
14933
- function createAnonymousProfileForIdentify(payload, report) {
14934
- return requestAnonymousProfile(payload, report);
14935
- }
14936
- async function requestAnonymousProfile(payload, report) {
14937
- validatorService.validateProps(payload, CreateAnonymousProfileSchema);
14938
- const identifiers = [];
14939
- const readIdentifier = (read) => {
14940
- if (!report) return read();
14941
- try {
14942
- return read();
14943
- } catch {
14944
- report("cache", "unavailable");
14945
- return null;
14946
- }
14947
- };
14948
- const userFingerprintId = readIdentifier(
14949
- fingerprintService.getCachedFingerprint
14950
- );
14951
- const distinctId = readIdentifier(mixpanelService.getMixpanelDistinctId);
14952
- if (userFingerprintId)
14953
- identifiers.push({ key: "fingerprint", value: userFingerprintId });
14954
- if (distinctId) identifiers.push({ key: "distinct_id", value: distinctId });
14955
- const dataPayload = getAnonymousProfilePayload(payload, identifiers);
14956
- return await a$1.profiles.createAnonymousProfile(dataPayload);
14957
- }
14958
15248
  const KNOWN_PROPERTIES = ["firstName", "lastName", "email", "phone"];
14959
15249
  const FINGERPRINT_PROPERTY = "Known Fingerprints";
15250
+ let latestAttempt = 0;
14960
15251
  function mapProperties(properties) {
14961
15252
  const parsedProperties = {
14962
15253
  $first_name: properties == null ? void 0 : properties.firstName,
@@ -15026,12 +15317,48 @@ const identifyWithLocation = async (userId2, properties, report) => {
15026
15317
  else report("datalake", "failed");
15027
15318
  }
15028
15319
  };
15320
+ async function enrichProfileAndLocation(userId2, properties, isActive, report) {
15321
+ const profile = await profileEnrichment.enrich(
15322
+ userId2,
15323
+ properties,
15324
+ isActive,
15325
+ report
15326
+ );
15327
+ if (!isActive()) return;
15328
+ const enriched = {
15329
+ ...properties,
15330
+ ...profile && { reservamos_one_id: profile.id }
15331
+ };
15332
+ void identifyWithLocation(userId2, enriched, report);
15333
+ }
15334
+ async function enrichFingerprint(isActive, report) {
15335
+ const fingerprint = await waitForIdentifyStage(
15336
+ "fingerprint",
15337
+ 2e3,
15338
+ (context) => !isActive() ? Promise.resolve("") : getFingerprintForIdentify({
15339
+ isActive: () => context.isActive() && isActive(),
15340
+ report: context.report
15341
+ }),
15342
+ report
15343
+ );
15344
+ if (!fingerprint || !isActive()) return;
15345
+ try {
15346
+ mixpanelService.attachProperty(FINGERPRINT_PROPERTY, fingerprint);
15347
+ } catch {
15348
+ report("fingerprint", "failed");
15349
+ }
15350
+ }
15029
15351
  async function identify(userId2, properties = {}) {
15352
+ const attempt = ++latestAttempt;
15353
+ const input = { ...properties };
15030
15354
  const report = createIdentifyReporter();
15031
- if (!await waitForIdentifyReady(report)) return;
15355
+ const initialLifecycle = trackingLifecycle.capture();
15356
+ if (!await waitForIdentifyReady(report) || attempt !== latestAttempt || !initialLifecycle())
15357
+ return;
15358
+ let submitted = false;
15032
15359
  try {
15033
15360
  try {
15034
- validatorService.parseIdentifyProps(properties);
15361
+ validatorService.parseIdentifyProps(input);
15035
15362
  if (!userId2) {
15036
15363
  report("validation", "invalid_input");
15037
15364
  return;
@@ -15040,38 +15367,22 @@ async function identify(userId2, properties = {}) {
15040
15367
  report("validation", "invalid_input");
15041
15368
  return;
15042
15369
  }
15043
- const anonymousProfile = await waitForIdentifyStage(
15044
- "profile",
15045
- 3e3,
15046
- (context) => createAnonymousProfileForIdentify({ ...properties }, context.report),
15047
- report
15048
- );
15049
- if (anonymousProfile) {
15050
- properties.reservamos_one_id = anonymousProfile.id;
15051
- }
15052
- const mappedProps = mapProperties(properties);
15053
15370
  try {
15054
- mixpanelService.identify(userId2, mappedProps, report);
15371
+ mixpanelService.identify(userId2, mapProperties(input), report);
15055
15372
  } catch {
15056
15373
  report("mixpanel", "failed");
15374
+ return;
15057
15375
  }
15058
- const fingerprint = await waitForIdentifyStage(
15059
- "fingerprint",
15060
- 2e3,
15061
- getFingerprintForIdentify,
15062
- report
15063
- );
15064
- if (fingerprint) {
15065
- try {
15066
- mixpanelService.attachProperty(FINGERPRINT_PROPERTY, fingerprint);
15067
- } catch {
15068
- report("fingerprint", "failed");
15069
- }
15070
- }
15071
- } catch {
15072
- report("mixpanel", "failed");
15376
+ const currentLifecycle = trackingLifecycle.capture();
15377
+ const isActive = () => attempt === latestAttempt && currentLifecycle();
15378
+ const scopedReport = (...args) => {
15379
+ if (isActive()) report(...args);
15380
+ };
15381
+ submitted = true;
15382
+ void enrichProfileAndLocation(userId2, input, isActive, scopedReport);
15383
+ void enrichFingerprint(isActive, scopedReport);
15073
15384
  } finally {
15074
- void identifyWithLocation(userId2, properties, report);
15385
+ if (!submitted) void identifyWithLocation(userId2, { ...input }, report);
15075
15386
  }
15076
15387
  }
15077
15388
  z.object({
@@ -15273,6 +15584,7 @@ const analytics = {
15273
15584
  identify,
15274
15585
  identifiers: {
15275
15586
  getFingerprintId: fingerprintService.getCachedFingerprint,
15587
+ waitForFingerprintId: fingerprintService.waitForFingerprintId,
15276
15588
  getDistinctId: mixpanelService.getMixpanelDistinctId
15277
15589
  },
15278
15590
  profiles: {