@outlit/browser 0.0.0-canary-202512180440-754f4ab-20251218044038 → 0.0.0-canary-202601161920-3feb1db-20260116192046

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.
@@ -9,7 +9,8 @@ import {
9
9
  buildFormEvent,
10
10
  buildIdentifyEvent,
11
11
  buildIngestPayload,
12
- buildPageviewEvent
12
+ buildPageviewEvent,
13
+ buildStageEvent
13
14
  } from "@outlit/core";
14
15
 
15
16
  // src/autocapture.ts
@@ -229,6 +230,7 @@ var DEFAULT_IDLE_TIMEOUT = 3e4;
229
230
  var SESSION_TIMEOUT = 30 * 60 * 1e3;
230
231
  var TIME_UPDATE_INTERVAL = 1e3;
231
232
  var MIN_SPURIOUS_THRESHOLD = 50;
233
+ var MIN_PAGE_TIME_FOR_ENGAGEMENT = 500;
232
234
  var SESSION_ID_KEY = "outlit_session_id";
233
235
  var SESSION_LAST_ACTIVITY_KEY = "outlit_session_last_activity";
234
236
  var SessionTracker = class {
@@ -273,7 +275,8 @@ var SessionTracker = class {
273
275
  this.updateActiveTime();
274
276
  const totalTimeMs = Date.now() - this.state.pageEntryTime;
275
277
  const isSpuriousEvent = this.state.activeTimeMs < MIN_SPURIOUS_THRESHOLD && totalTimeMs < MIN_SPURIOUS_THRESHOLD;
276
- if (!isSpuriousEvent) {
278
+ const isTooSoonAfterNavigation = totalTimeMs < MIN_PAGE_TIME_FOR_ENGAGEMENT;
279
+ if (!isSpuriousEvent && !isTooSoonAfterNavigation) {
277
280
  const event = buildEngagementEvent({
278
281
  url: this.state.currentUrl,
279
282
  referrer: document.referrer,
@@ -459,6 +462,7 @@ var SessionTracker = class {
459
462
  this.checkSessionExpiry();
460
463
  this.state.lastActiveTime = Date.now();
461
464
  this.state.hasEmittedEngagement = false;
465
+ this.updateSessionActivity();
462
466
  }
463
467
  }
464
468
  /**
@@ -614,6 +618,9 @@ var Outlit = class {
614
618
  options;
615
619
  hasHandledExit = false;
616
620
  sessionTracker = null;
621
+ // User identity state for stage events
622
+ currentUser = null;
623
+ pendingUser = null;
617
624
  constructor(options) {
618
625
  this.publicKey = options.publicKey;
619
626
  this.apiHost = options.apiHost ?? DEFAULT_API_HOST;
@@ -659,9 +666,7 @@ var Outlit = class {
659
666
  }
660
667
  this.visitorId = getOrCreateVisitorId();
661
668
  this.startFlushTimer();
662
- if (this.options.trackEngagement !== false) {
663
- this.initSessionTracking();
664
- }
669
+ this.initSessionTracking();
665
670
  if (this.options.trackPageviews !== false) {
666
671
  this.initPageviewTracking();
667
672
  }
@@ -672,6 +677,10 @@ var Outlit = class {
672
677
  this.initCalendarTracking();
673
678
  }
674
679
  this.isTrackingEnabled = true;
680
+ if (this.pendingUser) {
681
+ this.applyUser(this.pendingUser);
682
+ this.pendingUser = null;
683
+ }
675
684
  }
676
685
  /**
677
686
  * Check if tracking is currently enabled.
@@ -698,12 +707,21 @@ var Outlit = class {
698
707
  /**
699
708
  * Identify the current visitor.
700
709
  * Links the anonymous visitor to a known user.
710
+ *
711
+ * When email or userId is provided, also sets the current user identity
712
+ * for stage events (activate, engaged, paid).
701
713
  */
702
714
  identify(options) {
703
715
  if (!this.isTrackingEnabled) {
704
716
  console.warn("[Outlit] Tracking not enabled. Call enableTracking() first.");
705
717
  return;
706
718
  }
719
+ if (options.email || options.userId) {
720
+ this.currentUser = {
721
+ email: options.email,
722
+ userId: options.userId
723
+ };
724
+ }
707
725
  const event = buildIdentifyEvent({
708
726
  url: window.location.href,
709
727
  referrer: document.referrer,
@@ -713,6 +731,98 @@ var Outlit = class {
713
731
  });
714
732
  this.enqueue(event);
715
733
  }
734
+ /**
735
+ * Set the current user identity.
736
+ * This is useful for SPA applications where you know the user's identity
737
+ * after authentication. Calls identify() under the hood.
738
+ *
739
+ * If called before tracking is enabled, the identity is stored as pending
740
+ * and applied automatically when enableTracking() is called.
741
+ *
742
+ * Note: Both setUser() and identify() enable stage events. The difference is
743
+ * setUser() can be called before tracking is enabled (identity is queued),
744
+ * while identify() requires tracking to be enabled first.
745
+ */
746
+ setUser(identity) {
747
+ if (!identity.email && !identity.userId) {
748
+ console.warn("[Outlit] setUser requires at least email or userId");
749
+ return;
750
+ }
751
+ if (!this.isTrackingEnabled) {
752
+ this.pendingUser = identity;
753
+ return;
754
+ }
755
+ this.applyUser(identity);
756
+ }
757
+ /**
758
+ * Clear the current user identity.
759
+ * Call this when the user logs out.
760
+ */
761
+ clearUser() {
762
+ this.currentUser = null;
763
+ this.pendingUser = null;
764
+ }
765
+ /**
766
+ * Apply user identity and send identify event.
767
+ */
768
+ applyUser(identity) {
769
+ this.currentUser = identity;
770
+ this.identify({ email: identity.email, userId: identity.userId, traits: identity.traits });
771
+ }
772
+ /**
773
+ * Mark the current user as activated.
774
+ * This is typically called after a user completes onboarding or a key activation milestone.
775
+ * Requires the user to be identified (via setUser or identify with userId).
776
+ */
777
+ activate(properties) {
778
+ this.sendStageEvent("activated", properties);
779
+ }
780
+ /**
781
+ * Mark the current user as engaged.
782
+ * This is typically called when a user reaches a usage milestone.
783
+ * Can also be computed automatically by the engagement cron.
784
+ */
785
+ engaged(properties) {
786
+ this.sendStageEvent("engaged", properties);
787
+ }
788
+ /**
789
+ * Mark the current user as paid.
790
+ * This is typically called after a successful payment/subscription.
791
+ * Can also be triggered by Stripe integration.
792
+ */
793
+ paid(properties) {
794
+ this.sendStageEvent("paid", properties);
795
+ }
796
+ /**
797
+ * Mark the current user as churned.
798
+ * This is typically called when a subscription is cancelled.
799
+ * Can also be triggered by Stripe integration.
800
+ */
801
+ churned(properties) {
802
+ this.sendStageEvent("churned", properties);
803
+ }
804
+ /**
805
+ * Internal method to send a stage event.
806
+ */
807
+ sendStageEvent(stage, properties) {
808
+ if (!this.isTrackingEnabled) {
809
+ console.warn("[Outlit] Tracking not enabled. Call enableTracking() first.");
810
+ return;
811
+ }
812
+ if (!this.currentUser) {
813
+ console.warn(
814
+ `[Outlit] Cannot call ${stage}() without setting user identity. Call setUser() or identify() first.`
815
+ );
816
+ return;
817
+ }
818
+ const event = buildStageEvent({
819
+ url: window.location.href,
820
+ referrer: document.referrer,
821
+ stage,
822
+ properties
823
+ });
824
+ this.enqueue(event);
825
+ }
716
826
  /**
717
827
  * Get the current visitor ID.
718
828
  * Returns null if tracking is not enabled.
@@ -748,8 +858,8 @@ var Outlit = class {
748
858
  // ============================================
749
859
  initSessionTracking() {
750
860
  this.sessionTracker = initSessionTracking({
751
- onEngagement: (event) => {
752
- this.enqueue(event);
861
+ // Only emit engagement events when trackEngagement is enabled (default: true)
862
+ onEngagement: this.options.trackEngagement !== false ? (event) => this.enqueue(event) : () => {
753
863
  },
754
864
  idleTimeout: this.options.idleTimeout
755
865
  });
@@ -820,7 +930,9 @@ var Outlit = class {
820
930
  async sendEvents(events) {
821
931
  if (events.length === 0) return;
822
932
  if (!this.visitorId) return;
823
- const payload = buildIngestPayload(this.visitorId, "client", events);
933
+ const userIdentity = this.currentUser ?? void 0;
934
+ const sessionId = this.sessionTracker?.getSessionId();
935
+ const payload = buildIngestPayload(this.visitorId, "client", events, userIdentity, sessionId);
824
936
  const url = `${this.apiHost}/api/i/v1/${this.publicKey}/events`;
825
937
  try {
826
938
  if (typeof navigator !== "undefined" && navigator.sendBeacon) {
@@ -860,7 +972,8 @@ function OutlitProvider({
860
972
  formFieldDenylist,
861
973
  flushInterval,
862
974
  autoTrack = true,
863
- autoIdentify = true
975
+ autoIdentify = true,
976
+ user
864
977
  }) {
865
978
  const outlitRef = useRef(null);
866
979
  const initializedRef = useRef(false);
@@ -892,6 +1005,14 @@ function OutlitProvider({
892
1005
  autoTrack,
893
1006
  autoIdentify
894
1007
  ]);
1008
+ useEffect(() => {
1009
+ if (!outlitRef.current) return;
1010
+ if (user && (user.email || user.userId)) {
1011
+ outlitRef.current.setUser(user);
1012
+ } else {
1013
+ outlitRef.current.clearUser();
1014
+ }
1015
+ }, [user]);
895
1016
  const enableTracking = useCallback(() => {
896
1017
  if (outlitRef.current) {
897
1018
  outlitRef.current.enableTracking();
@@ -940,10 +1061,73 @@ function useOutlit() {
940
1061
  if (!outlit) return null;
941
1062
  return outlit.getVisitorId();
942
1063
  }, [outlit]);
1064
+ const setUser = useCallback2(
1065
+ (identity) => {
1066
+ if (!outlit) {
1067
+ console.warn("[Outlit] Not initialized. Make sure OutlitProvider is mounted.");
1068
+ return;
1069
+ }
1070
+ outlit.setUser(identity);
1071
+ },
1072
+ [outlit]
1073
+ );
1074
+ const clearUser = useCallback2(() => {
1075
+ if (!outlit) {
1076
+ console.warn("[Outlit] Not initialized. Make sure OutlitProvider is mounted.");
1077
+ return;
1078
+ }
1079
+ outlit.clearUser();
1080
+ }, [outlit]);
1081
+ const activate = useCallback2(
1082
+ (properties) => {
1083
+ if (!outlit) {
1084
+ console.warn("[Outlit] Not initialized. Make sure OutlitProvider is mounted.");
1085
+ return;
1086
+ }
1087
+ outlit.activate(properties);
1088
+ },
1089
+ [outlit]
1090
+ );
1091
+ const engaged = useCallback2(
1092
+ (properties) => {
1093
+ if (!outlit) {
1094
+ console.warn("[Outlit] Not initialized. Make sure OutlitProvider is mounted.");
1095
+ return;
1096
+ }
1097
+ outlit.engaged(properties);
1098
+ },
1099
+ [outlit]
1100
+ );
1101
+ const paid = useCallback2(
1102
+ (properties) => {
1103
+ if (!outlit) {
1104
+ console.warn("[Outlit] Not initialized. Make sure OutlitProvider is mounted.");
1105
+ return;
1106
+ }
1107
+ outlit.paid(properties);
1108
+ },
1109
+ [outlit]
1110
+ );
1111
+ const churned = useCallback2(
1112
+ (properties) => {
1113
+ if (!outlit) {
1114
+ console.warn("[Outlit] Not initialized. Make sure OutlitProvider is mounted.");
1115
+ return;
1116
+ }
1117
+ outlit.churned(properties);
1118
+ },
1119
+ [outlit]
1120
+ );
943
1121
  return {
944
1122
  track,
945
1123
  identify,
946
1124
  getVisitorId,
1125
+ setUser,
1126
+ clearUser,
1127
+ activate,
1128
+ engaged,
1129
+ paid,
1130
+ churned,
947
1131
  isInitialized,
948
1132
  isTrackingEnabled,
949
1133
  enableTracking