@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.
@@ -1,9 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { c as OutlitOptions, O as Outlit } from '../tracker-DFcTv3EM.mjs';
4
+ import { j as OutlitOptions, U as UserIdentity, O as Outlit } from '../tracker-DK-2gYCi.mjs';
5
5
  import { BrowserTrackOptions, BrowserIdentifyOptions } from '@outlit/core';
6
- export { BrowserIdentifyOptions, BrowserTrackOptions, TrackerConfig } from '@outlit/core';
6
+ export { BrowserIdentifyOptions, BrowserTrackOptions, ExplicitJourneyStage, TrackerConfig } from '@outlit/core';
7
7
 
8
8
  interface OutlitContextValue {
9
9
  outlit: Outlit | null;
@@ -26,6 +26,29 @@ interface OutlitProviderProps extends Omit<OutlitOptions, "trackPageviews"> {
26
26
  * @default true
27
27
  */
28
28
  autoTrack?: boolean;
29
+ /**
30
+ * Current user identity.
31
+ * When provided with email or userId, calls setUser() to identify the user.
32
+ * When null, undefined, or missing identity fields, calls clearUser().
33
+ *
34
+ * This is the recommended way to handle user identity in server-rendered apps:
35
+ * pass the user from your auth system as a prop.
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * // Server component (layout.tsx)
40
+ * const session = await auth()
41
+ * return (
42
+ * <OutlitProvider
43
+ * publicKey="pk_xxx"
44
+ * user={session?.user ? { email: session.user.email, userId: session.user.id } : null}
45
+ * >
46
+ * {children}
47
+ * </OutlitProvider>
48
+ * )
49
+ * ```
50
+ */
51
+ user?: UserIdentity | null;
29
52
  }
30
53
  /**
31
54
  * Outlit Provider component.
@@ -67,7 +90,7 @@ interface OutlitProviderProps extends Omit<OutlitOptions, "trackPageviews"> {
67
90
  * }
68
91
  * ```
69
92
  */
70
- declare function OutlitProvider({ children, publicKey, apiHost, trackPageviews, trackForms, formFieldDenylist, flushInterval, autoTrack, autoIdentify, }: OutlitProviderProps): react_jsx_runtime.JSX.Element;
93
+ declare function OutlitProvider({ children, publicKey, apiHost, trackPageviews, trackForms, formFieldDenylist, flushInterval, autoTrack, autoIdentify, user, }: OutlitProviderProps): react_jsx_runtime.JSX.Element;
71
94
 
72
95
  interface UseOutlitReturn {
73
96
  /**
@@ -84,6 +107,35 @@ interface UseOutlitReturn {
84
107
  * Returns null if tracking is not enabled.
85
108
  */
86
109
  getVisitorId: () => string | null;
110
+ /**
111
+ * Set the current user identity.
112
+ * Use this for persistent identity after login.
113
+ */
114
+ setUser: (identity: UserIdentity) => void;
115
+ /**
116
+ * Clear the current user identity (on logout).
117
+ */
118
+ clearUser: () => void;
119
+ /**
120
+ * Mark the current user as activated.
121
+ * Call after a user completes onboarding or activation milestone.
122
+ */
123
+ activate: (properties?: Record<string, string | number | boolean | null>) => void;
124
+ /**
125
+ * Mark the current user as engaged.
126
+ * Call when user reaches a usage milestone.
127
+ */
128
+ engaged: (properties?: Record<string, string | number | boolean | null>) => void;
129
+ /**
130
+ * Mark the current user as paid.
131
+ * Call after successful payment/subscription.
132
+ */
133
+ paid: (properties?: Record<string, string | number | boolean | null>) => void;
134
+ /**
135
+ * Mark the current user as churned.
136
+ * Call when subscription is cancelled.
137
+ */
138
+ churned: (properties?: Record<string, string | number | boolean | null>) => void;
87
139
  /**
88
140
  * Whether Outlit is initialized.
89
141
  */
@@ -167,4 +219,4 @@ declare function useTrack(): (eventName: string, properties?: BrowserTrackOption
167
219
  */
168
220
  declare function useIdentify(): (options: BrowserIdentifyOptions) => void;
169
221
 
170
- export { OutlitContext, type OutlitContextValue, OutlitProvider, type OutlitProviderProps, type UseOutlitReturn, useIdentify, useOutlit, useTrack };
222
+ export { OutlitContext, type OutlitContextValue, OutlitProvider, type OutlitProviderProps, type UseOutlitReturn, UserIdentity, useIdentify, useOutlit, useTrack };
@@ -1,9 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { c as OutlitOptions, O as Outlit } from '../tracker-DFcTv3EM.js';
4
+ import { j as OutlitOptions, U as UserIdentity, O as Outlit } from '../tracker-DK-2gYCi.js';
5
5
  import { BrowserTrackOptions, BrowserIdentifyOptions } from '@outlit/core';
6
- export { BrowserIdentifyOptions, BrowserTrackOptions, TrackerConfig } from '@outlit/core';
6
+ export { BrowserIdentifyOptions, BrowserTrackOptions, ExplicitJourneyStage, TrackerConfig } from '@outlit/core';
7
7
 
8
8
  interface OutlitContextValue {
9
9
  outlit: Outlit | null;
@@ -26,6 +26,29 @@ interface OutlitProviderProps extends Omit<OutlitOptions, "trackPageviews"> {
26
26
  * @default true
27
27
  */
28
28
  autoTrack?: boolean;
29
+ /**
30
+ * Current user identity.
31
+ * When provided with email or userId, calls setUser() to identify the user.
32
+ * When null, undefined, or missing identity fields, calls clearUser().
33
+ *
34
+ * This is the recommended way to handle user identity in server-rendered apps:
35
+ * pass the user from your auth system as a prop.
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * // Server component (layout.tsx)
40
+ * const session = await auth()
41
+ * return (
42
+ * <OutlitProvider
43
+ * publicKey="pk_xxx"
44
+ * user={session?.user ? { email: session.user.email, userId: session.user.id } : null}
45
+ * >
46
+ * {children}
47
+ * </OutlitProvider>
48
+ * )
49
+ * ```
50
+ */
51
+ user?: UserIdentity | null;
29
52
  }
30
53
  /**
31
54
  * Outlit Provider component.
@@ -67,7 +90,7 @@ interface OutlitProviderProps extends Omit<OutlitOptions, "trackPageviews"> {
67
90
  * }
68
91
  * ```
69
92
  */
70
- declare function OutlitProvider({ children, publicKey, apiHost, trackPageviews, trackForms, formFieldDenylist, flushInterval, autoTrack, autoIdentify, }: OutlitProviderProps): react_jsx_runtime.JSX.Element;
93
+ declare function OutlitProvider({ children, publicKey, apiHost, trackPageviews, trackForms, formFieldDenylist, flushInterval, autoTrack, autoIdentify, user, }: OutlitProviderProps): react_jsx_runtime.JSX.Element;
71
94
 
72
95
  interface UseOutlitReturn {
73
96
  /**
@@ -84,6 +107,35 @@ interface UseOutlitReturn {
84
107
  * Returns null if tracking is not enabled.
85
108
  */
86
109
  getVisitorId: () => string | null;
110
+ /**
111
+ * Set the current user identity.
112
+ * Use this for persistent identity after login.
113
+ */
114
+ setUser: (identity: UserIdentity) => void;
115
+ /**
116
+ * Clear the current user identity (on logout).
117
+ */
118
+ clearUser: () => void;
119
+ /**
120
+ * Mark the current user as activated.
121
+ * Call after a user completes onboarding or activation milestone.
122
+ */
123
+ activate: (properties?: Record<string, string | number | boolean | null>) => void;
124
+ /**
125
+ * Mark the current user as engaged.
126
+ * Call when user reaches a usage milestone.
127
+ */
128
+ engaged: (properties?: Record<string, string | number | boolean | null>) => void;
129
+ /**
130
+ * Mark the current user as paid.
131
+ * Call after successful payment/subscription.
132
+ */
133
+ paid: (properties?: Record<string, string | number | boolean | null>) => void;
134
+ /**
135
+ * Mark the current user as churned.
136
+ * Call when subscription is cancelled.
137
+ */
138
+ churned: (properties?: Record<string, string | number | boolean | null>) => void;
87
139
  /**
88
140
  * Whether Outlit is initialized.
89
141
  */
@@ -167,4 +219,4 @@ declare function useTrack(): (eventName: string, properties?: BrowserTrackOption
167
219
  */
168
220
  declare function useIdentify(): (options: BrowserIdentifyOptions) => void;
169
221
 
170
- export { OutlitContext, type OutlitContextValue, OutlitProvider, type OutlitProviderProps, type UseOutlitReturn, useIdentify, useOutlit, useTrack };
222
+ export { OutlitContext, type OutlitContextValue, OutlitProvider, type OutlitProviderProps, type UseOutlitReturn, UserIdentity, useIdentify, useOutlit, useTrack };
@@ -251,6 +251,7 @@ var DEFAULT_IDLE_TIMEOUT = 3e4;
251
251
  var SESSION_TIMEOUT = 30 * 60 * 1e3;
252
252
  var TIME_UPDATE_INTERVAL = 1e3;
253
253
  var MIN_SPURIOUS_THRESHOLD = 50;
254
+ var MIN_PAGE_TIME_FOR_ENGAGEMENT = 500;
254
255
  var SESSION_ID_KEY = "outlit_session_id";
255
256
  var SESSION_LAST_ACTIVITY_KEY = "outlit_session_last_activity";
256
257
  var SessionTracker = class {
@@ -295,7 +296,8 @@ var SessionTracker = class {
295
296
  this.updateActiveTime();
296
297
  const totalTimeMs = Date.now() - this.state.pageEntryTime;
297
298
  const isSpuriousEvent = this.state.activeTimeMs < MIN_SPURIOUS_THRESHOLD && totalTimeMs < MIN_SPURIOUS_THRESHOLD;
298
- if (!isSpuriousEvent) {
299
+ const isTooSoonAfterNavigation = totalTimeMs < MIN_PAGE_TIME_FOR_ENGAGEMENT;
300
+ if (!isSpuriousEvent && !isTooSoonAfterNavigation) {
299
301
  const event = (0, import_core2.buildEngagementEvent)({
300
302
  url: this.state.currentUrl,
301
303
  referrer: document.referrer,
@@ -636,6 +638,9 @@ var Outlit = class {
636
638
  options;
637
639
  hasHandledExit = false;
638
640
  sessionTracker = null;
641
+ // User identity state for stage events
642
+ currentUser = null;
643
+ pendingUser = null;
639
644
  constructor(options) {
640
645
  this.publicKey = options.publicKey;
641
646
  this.apiHost = options.apiHost ?? import_core3.DEFAULT_API_HOST;
@@ -694,6 +699,10 @@ var Outlit = class {
694
699
  this.initCalendarTracking();
695
700
  }
696
701
  this.isTrackingEnabled = true;
702
+ if (this.pendingUser) {
703
+ this.applyUser(this.pendingUser);
704
+ this.pendingUser = null;
705
+ }
697
706
  }
698
707
  /**
699
708
  * Check if tracking is currently enabled.
@@ -720,12 +729,21 @@ var Outlit = class {
720
729
  /**
721
730
  * Identify the current visitor.
722
731
  * Links the anonymous visitor to a known user.
732
+ *
733
+ * When email or userId is provided, also sets the current user identity
734
+ * for stage events (activate, engaged, paid).
723
735
  */
724
736
  identify(options) {
725
737
  if (!this.isTrackingEnabled) {
726
738
  console.warn("[Outlit] Tracking not enabled. Call enableTracking() first.");
727
739
  return;
728
740
  }
741
+ if (options.email || options.userId) {
742
+ this.currentUser = {
743
+ email: options.email,
744
+ userId: options.userId
745
+ };
746
+ }
729
747
  const event = (0, import_core3.buildIdentifyEvent)({
730
748
  url: window.location.href,
731
749
  referrer: document.referrer,
@@ -735,6 +753,98 @@ var Outlit = class {
735
753
  });
736
754
  this.enqueue(event);
737
755
  }
756
+ /**
757
+ * Set the current user identity.
758
+ * This is useful for SPA applications where you know the user's identity
759
+ * after authentication. Calls identify() under the hood.
760
+ *
761
+ * If called before tracking is enabled, the identity is stored as pending
762
+ * and applied automatically when enableTracking() is called.
763
+ *
764
+ * Note: Both setUser() and identify() enable stage events. The difference is
765
+ * setUser() can be called before tracking is enabled (identity is queued),
766
+ * while identify() requires tracking to be enabled first.
767
+ */
768
+ setUser(identity) {
769
+ if (!identity.email && !identity.userId) {
770
+ console.warn("[Outlit] setUser requires at least email or userId");
771
+ return;
772
+ }
773
+ if (!this.isTrackingEnabled) {
774
+ this.pendingUser = identity;
775
+ return;
776
+ }
777
+ this.applyUser(identity);
778
+ }
779
+ /**
780
+ * Clear the current user identity.
781
+ * Call this when the user logs out.
782
+ */
783
+ clearUser() {
784
+ this.currentUser = null;
785
+ this.pendingUser = null;
786
+ }
787
+ /**
788
+ * Apply user identity and send identify event.
789
+ */
790
+ applyUser(identity) {
791
+ this.currentUser = identity;
792
+ this.identify({ email: identity.email, userId: identity.userId, traits: identity.traits });
793
+ }
794
+ /**
795
+ * Mark the current user as activated.
796
+ * This is typically called after a user completes onboarding or a key activation milestone.
797
+ * Requires the user to be identified (via setUser or identify with userId).
798
+ */
799
+ activate(properties) {
800
+ this.sendStageEvent("activated", properties);
801
+ }
802
+ /**
803
+ * Mark the current user as engaged.
804
+ * This is typically called when a user reaches a usage milestone.
805
+ * Can also be computed automatically by the engagement cron.
806
+ */
807
+ engaged(properties) {
808
+ this.sendStageEvent("engaged", properties);
809
+ }
810
+ /**
811
+ * Mark the current user as paid.
812
+ * This is typically called after a successful payment/subscription.
813
+ * Can also be triggered by Stripe integration.
814
+ */
815
+ paid(properties) {
816
+ this.sendStageEvent("paid", properties);
817
+ }
818
+ /**
819
+ * Mark the current user as churned.
820
+ * This is typically called when a subscription is cancelled.
821
+ * Can also be triggered by Stripe integration.
822
+ */
823
+ churned(properties) {
824
+ this.sendStageEvent("churned", properties);
825
+ }
826
+ /**
827
+ * Internal method to send a stage event.
828
+ */
829
+ sendStageEvent(stage, properties) {
830
+ if (!this.isTrackingEnabled) {
831
+ console.warn("[Outlit] Tracking not enabled. Call enableTracking() first.");
832
+ return;
833
+ }
834
+ if (!this.currentUser) {
835
+ console.warn(
836
+ `[Outlit] Cannot call ${stage}() without setting user identity. Call setUser() or identify() first.`
837
+ );
838
+ return;
839
+ }
840
+ const event = (0, import_core3.buildStageEvent)({
841
+ url: window.location.href,
842
+ referrer: document.referrer,
843
+ stage,
844
+ properties
845
+ });
846
+ this.enqueue(event);
847
+ }
738
848
  /**
739
849
  * Get the current visitor ID.
740
850
  * Returns null if tracking is not enabled.
@@ -842,7 +952,8 @@ var Outlit = class {
842
952
  async sendEvents(events) {
843
953
  if (events.length === 0) return;
844
954
  if (!this.visitorId) return;
845
- const payload = (0, import_core3.buildIngestPayload)(this.visitorId, "client", events);
955
+ const userIdentity = this.currentUser ?? void 0;
956
+ const payload = (0, import_core3.buildIngestPayload)(this.visitorId, "client", events, userIdentity);
846
957
  const url = `${this.apiHost}/api/i/v1/${this.publicKey}/events`;
847
958
  try {
848
959
  if (typeof navigator !== "undefined" && navigator.sendBeacon) {
@@ -882,7 +993,8 @@ function OutlitProvider({
882
993
  formFieldDenylist,
883
994
  flushInterval,
884
995
  autoTrack = true,
885
- autoIdentify = true
996
+ autoIdentify = true,
997
+ user
886
998
  }) {
887
999
  const outlitRef = (0, import_react.useRef)(null);
888
1000
  const initializedRef = (0, import_react.useRef)(false);
@@ -914,6 +1026,14 @@ function OutlitProvider({
914
1026
  autoTrack,
915
1027
  autoIdentify
916
1028
  ]);
1029
+ (0, import_react.useEffect)(() => {
1030
+ if (!outlitRef.current) return;
1031
+ if (user && (user.email || user.userId)) {
1032
+ outlitRef.current.setUser(user);
1033
+ } else {
1034
+ outlitRef.current.clearUser();
1035
+ }
1036
+ }, [user]);
917
1037
  const enableTracking = (0, import_react.useCallback)(() => {
918
1038
  if (outlitRef.current) {
919
1039
  outlitRef.current.enableTracking();
@@ -962,10 +1082,73 @@ function useOutlit() {
962
1082
  if (!outlit) return null;
963
1083
  return outlit.getVisitorId();
964
1084
  }, [outlit]);
1085
+ const setUser = (0, import_react2.useCallback)(
1086
+ (identity) => {
1087
+ if (!outlit) {
1088
+ console.warn("[Outlit] Not initialized. Make sure OutlitProvider is mounted.");
1089
+ return;
1090
+ }
1091
+ outlit.setUser(identity);
1092
+ },
1093
+ [outlit]
1094
+ );
1095
+ const clearUser = (0, import_react2.useCallback)(() => {
1096
+ if (!outlit) {
1097
+ console.warn("[Outlit] Not initialized. Make sure OutlitProvider is mounted.");
1098
+ return;
1099
+ }
1100
+ outlit.clearUser();
1101
+ }, [outlit]);
1102
+ const activate = (0, import_react2.useCallback)(
1103
+ (properties) => {
1104
+ if (!outlit) {
1105
+ console.warn("[Outlit] Not initialized. Make sure OutlitProvider is mounted.");
1106
+ return;
1107
+ }
1108
+ outlit.activate(properties);
1109
+ },
1110
+ [outlit]
1111
+ );
1112
+ const engaged = (0, import_react2.useCallback)(
1113
+ (properties) => {
1114
+ if (!outlit) {
1115
+ console.warn("[Outlit] Not initialized. Make sure OutlitProvider is mounted.");
1116
+ return;
1117
+ }
1118
+ outlit.engaged(properties);
1119
+ },
1120
+ [outlit]
1121
+ );
1122
+ const paid = (0, import_react2.useCallback)(
1123
+ (properties) => {
1124
+ if (!outlit) {
1125
+ console.warn("[Outlit] Not initialized. Make sure OutlitProvider is mounted.");
1126
+ return;
1127
+ }
1128
+ outlit.paid(properties);
1129
+ },
1130
+ [outlit]
1131
+ );
1132
+ const churned = (0, import_react2.useCallback)(
1133
+ (properties) => {
1134
+ if (!outlit) {
1135
+ console.warn("[Outlit] Not initialized. Make sure OutlitProvider is mounted.");
1136
+ return;
1137
+ }
1138
+ outlit.churned(properties);
1139
+ },
1140
+ [outlit]
1141
+ );
965
1142
  return {
966
1143
  track,
967
1144
  identify,
968
1145
  getVisitorId,
1146
+ setUser,
1147
+ clearUser,
1148
+ activate,
1149
+ engaged,
1150
+ paid,
1151
+ churned,
969
1152
  isInitialized,
970
1153
  isTrackingEnabled,
971
1154
  enableTracking