@outlit/browser 0.2.1 → 0.3.0

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,
@@ -614,6 +617,9 @@ var Outlit = class {
614
617
  options;
615
618
  hasHandledExit = false;
616
619
  sessionTracker = null;
620
+ // User identity state for stage events
621
+ currentUser = null;
622
+ pendingUser = null;
617
623
  constructor(options) {
618
624
  this.publicKey = options.publicKey;
619
625
  this.apiHost = options.apiHost ?? DEFAULT_API_HOST;
@@ -672,6 +678,10 @@ var Outlit = class {
672
678
  this.initCalendarTracking();
673
679
  }
674
680
  this.isTrackingEnabled = true;
681
+ if (this.pendingUser) {
682
+ this.applyUser(this.pendingUser);
683
+ this.pendingUser = null;
684
+ }
675
685
  }
676
686
  /**
677
687
  * Check if tracking is currently enabled.
@@ -698,12 +708,21 @@ var Outlit = class {
698
708
  /**
699
709
  * Identify the current visitor.
700
710
  * Links the anonymous visitor to a known user.
711
+ *
712
+ * When email or userId is provided, also sets the current user identity
713
+ * for stage events (activate, engaged, paid).
701
714
  */
702
715
  identify(options) {
703
716
  if (!this.isTrackingEnabled) {
704
717
  console.warn("[Outlit] Tracking not enabled. Call enableTracking() first.");
705
718
  return;
706
719
  }
720
+ if (options.email || options.userId) {
721
+ this.currentUser = {
722
+ email: options.email,
723
+ userId: options.userId
724
+ };
725
+ }
707
726
  const event = buildIdentifyEvent({
708
727
  url: window.location.href,
709
728
  referrer: document.referrer,
@@ -713,6 +732,98 @@ var Outlit = class {
713
732
  });
714
733
  this.enqueue(event);
715
734
  }
735
+ /**
736
+ * Set the current user identity.
737
+ * This is useful for SPA applications where you know the user's identity
738
+ * after authentication. Calls identify() under the hood.
739
+ *
740
+ * If called before tracking is enabled, the identity is stored as pending
741
+ * and applied automatically when enableTracking() is called.
742
+ *
743
+ * Note: Both setUser() and identify() enable stage events. The difference is
744
+ * setUser() can be called before tracking is enabled (identity is queued),
745
+ * while identify() requires tracking to be enabled first.
746
+ */
747
+ setUser(identity) {
748
+ if (!identity.email && !identity.userId) {
749
+ console.warn("[Outlit] setUser requires at least email or userId");
750
+ return;
751
+ }
752
+ if (!this.isTrackingEnabled) {
753
+ this.pendingUser = identity;
754
+ return;
755
+ }
756
+ this.applyUser(identity);
757
+ }
758
+ /**
759
+ * Clear the current user identity.
760
+ * Call this when the user logs out.
761
+ */
762
+ clearUser() {
763
+ this.currentUser = null;
764
+ this.pendingUser = null;
765
+ }
766
+ /**
767
+ * Apply user identity and send identify event.
768
+ */
769
+ applyUser(identity) {
770
+ this.currentUser = identity;
771
+ this.identify({ email: identity.email, userId: identity.userId, traits: identity.traits });
772
+ }
773
+ /**
774
+ * Mark the current user as activated.
775
+ * This is typically called after a user completes onboarding or a key activation milestone.
776
+ * Requires the user to be identified (via setUser or identify with userId).
777
+ */
778
+ activate(properties) {
779
+ this.sendStageEvent("activated", properties);
780
+ }
781
+ /**
782
+ * Mark the current user as engaged.
783
+ * This is typically called when a user reaches a usage milestone.
784
+ * Can also be computed automatically by the engagement cron.
785
+ */
786
+ engaged(properties) {
787
+ this.sendStageEvent("engaged", properties);
788
+ }
789
+ /**
790
+ * Mark the current user as paid.
791
+ * This is typically called after a successful payment/subscription.
792
+ * Can also be triggered by Stripe integration.
793
+ */
794
+ paid(properties) {
795
+ this.sendStageEvent("paid", properties);
796
+ }
797
+ /**
798
+ * Mark the current user as churned.
799
+ * This is typically called when a subscription is cancelled.
800
+ * Can also be triggered by Stripe integration.
801
+ */
802
+ churned(properties) {
803
+ this.sendStageEvent("churned", properties);
804
+ }
805
+ /**
806
+ * Internal method to send a stage event.
807
+ */
808
+ sendStageEvent(stage, properties) {
809
+ if (!this.isTrackingEnabled) {
810
+ console.warn("[Outlit] Tracking not enabled. Call enableTracking() first.");
811
+ return;
812
+ }
813
+ if (!this.currentUser) {
814
+ console.warn(
815
+ `[Outlit] Cannot call ${stage}() without setting user identity. Call setUser() or identify() first.`
816
+ );
817
+ return;
818
+ }
819
+ const event = buildStageEvent({
820
+ url: window.location.href,
821
+ referrer: document.referrer,
822
+ stage,
823
+ properties
824
+ });
825
+ this.enqueue(event);
826
+ }
716
827
  /**
717
828
  * Get the current visitor ID.
718
829
  * Returns null if tracking is not enabled.
@@ -820,7 +931,8 @@ var Outlit = class {
820
931
  async sendEvents(events) {
821
932
  if (events.length === 0) return;
822
933
  if (!this.visitorId) return;
823
- const payload = buildIngestPayload(this.visitorId, "client", events);
934
+ const userIdentity = this.currentUser ?? void 0;
935
+ const payload = buildIngestPayload(this.visitorId, "client", events, userIdentity);
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