@amplitude/analytics-react-native 0.5.1 → 0.6.1

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.
package/src/config.ts CHANGED
@@ -39,10 +39,11 @@ export const getDefaultConfig = () => {
39
39
  disableCookies: false,
40
40
  domain: '',
41
41
  sessionManager: new SessionManager(cookieStorage, ''),
42
- sessionTimeout: 30 * 60 * 1000,
42
+ sessionTimeout: 5 * 60 * 1000,
43
43
  storageProvider: new MemoryStorage<Event[]>(),
44
44
  trackingOptions,
45
45
  transportProvider: new FetchTransport(),
46
+ trackingSessionEvents: false,
46
47
  };
47
48
  };
48
49
 
@@ -58,6 +59,7 @@ export class ReactNativeConfig extends Config implements IReactNativeConfig {
58
59
  sessionTimeout: number;
59
60
  trackingOptions: ReactNativeTrackingOptions;
60
61
  sessionManager: ISessionManager;
62
+ trackingSessionEvents: boolean;
61
63
 
62
64
  constructor(apiKey: string, userId?: string, options?: ReactNativeOptions) {
63
65
  const defaultConfig = getDefaultConfig();
@@ -87,6 +89,7 @@ export class ReactNativeConfig extends Config implements IReactNativeConfig {
87
89
  this.sessionId = options?.sessionId;
88
90
  this.trackingOptions = options?.trackingOptions ?? defaultConfig.trackingOptions;
89
91
  this.userId = userId;
92
+ this.trackingSessionEvents = options?.trackingSessionEvents ?? defaultConfig.trackingSessionEvents;
90
93
  }
91
94
 
92
95
  get deviceId() {
@@ -55,15 +55,6 @@ export class Context implements BeforePlugin {
55
55
  }
56
56
 
57
57
  async execute(context: Event): Promise<Event> {
58
- /**
59
- * Manages user session triggered by new events
60
- */
61
- if (!this.isSessionValid()) {
62
- // Creates new session
63
- this.config.sessionId = Date.now();
64
- } // else use previously creates session
65
- // Updates last event time to extend time-based session
66
- this.config.lastEventTime = Date.now();
67
58
  const time = new Date().getTime();
68
59
  const nativeContext = await this.nativeModule?.getApplicationContext();
69
60
  const appVersion = nativeContext?.version || this.config.appVersion;
@@ -106,10 +97,4 @@ export class Context implements BeforePlugin {
106
97
  };
107
98
  return event;
108
99
  }
109
-
110
- isSessionValid() {
111
- const lastEventTime = this.config.lastEventTime || Date.now();
112
- const timeSinceLastEvent = Date.now() - lastEventTime;
113
- return timeSinceLastEvent < this.config.sessionTimeout;
114
- }
115
100
  }
@@ -1,3 +1,4 @@
1
+ import { AppState, AppStateStatus } from 'react-native';
1
2
  import {
2
3
  AmplitudeCore,
3
4
  Destination,
@@ -16,19 +17,28 @@ import {
16
17
  ReactNativeClient,
17
18
  Identify as IIdentify,
18
19
  EventOptions,
20
+ Event,
21
+ Result,
19
22
  } from '@amplitude/analytics-types';
20
23
  import { Context } from './plugins/context';
21
24
  import { useReactNativeConfig, createFlexibleStorage } from './config';
22
25
  import { parseOldCookies } from './cookie-migration';
23
26
  import { isNative } from './utils/platform';
24
27
 
28
+ const START_SESSION_EVENT = 'session_start';
29
+ const END_SESSION_EVENT = 'session_end';
30
+
25
31
  export class AmplitudeReactNative extends AmplitudeCore<ReactNativeConfig> {
32
+ appState: AppStateStatus = 'background';
33
+ explicitSessionId: number | undefined;
34
+
26
35
  async init(apiKey = '', userId?: string, options?: ReactNativeOptions) {
27
36
  // Step 0: Block concurrent initialization
28
37
  if (this.initializing) {
29
38
  return;
30
39
  }
31
40
  this.initializing = true;
41
+ this.explicitSessionId = options?.sessionId;
32
42
 
33
43
  // Step 1: Read cookies stored by old SDK
34
44
  const oldCookies = await parseOldCookies(apiKey, options);
@@ -37,25 +47,12 @@ export class AmplitudeReactNative extends AmplitudeCore<ReactNativeConfig> {
37
47
  const reactNativeOptions = await useReactNativeConfig(apiKey, userId || oldCookies.userId, {
38
48
  ...options,
39
49
  deviceId: oldCookies.deviceId ?? options?.deviceId,
40
- sessionId: oldCookies.sessionId ?? options?.sessionId,
50
+ sessionId: oldCookies.sessionId,
41
51
  optOut: options?.optOut ?? oldCookies.optOut,
42
52
  lastEventTime: oldCookies.lastEventTime,
43
53
  });
44
54
  await super._init(reactNativeOptions);
45
55
 
46
- // Step 3: Manage session
47
- let isNewSession = !this.config.lastEventTime;
48
- if (
49
- !this.config.sessionId ||
50
- (this.config.lastEventTime && Date.now() - this.config.lastEventTime > this.config.sessionTimeout)
51
- ) {
52
- // Either
53
- // 1) No previous session; or
54
- // 2) Previous session expired
55
- this.setSessionId(Date.now());
56
- isNewSession = true;
57
- }
58
-
59
56
  // Set up the analytics connector to integrate with the experiment SDK.
60
57
  // Send events from the experiment SDK and forward identifies to the
61
58
  // identity store.
@@ -68,27 +65,36 @@ export class AmplitudeReactNative extends AmplitudeCore<ReactNativeConfig> {
68
65
  deviceId: this.config.deviceId,
69
66
  });
70
67
 
71
- // Step 4: Install plugins
68
+ // Step 3: Install plugins
72
69
  // Do not track any events before this
73
70
  await this.add(new Context());
74
71
  await this.add(new IdentityEventSender());
75
72
  await this.add(new Destination());
76
73
 
74
+ // Step 4: Manage session
75
+ this.appState = AppState.currentState;
76
+ const isNewSession = this.startNewSessionIfNeeded();
77
+ AppState.addEventListener('change', this.handleAppStateChange);
78
+
77
79
  this.initializing = false;
78
80
 
79
81
  // Step 5: Track attributions
80
82
  await this.runAttributionStrategy(options?.attribution, isNewSession);
81
83
 
82
- // Step 6: Run queued dispatch functions
84
+ // Step 6: Run queued functions
83
85
  await this.runQueuedFunctions('dispatchQ');
84
86
  }
85
87
 
88
+ shutdown() {
89
+ AppState.removeEventListener('change', this.handleAppStateChange);
90
+ }
91
+
86
92
  async runAttributionStrategy(attributionConfig?: AttributionOptions, isNewSession = false) {
87
93
  if (isNative()) {
88
94
  return;
89
95
  }
90
96
  const track = this.track.bind(this);
91
- const onNewCampaign = this.setSessionId.bind(this, Date.now());
97
+ const onNewCampaign = this.setSessionId.bind(this, this.currentTimeMillis());
92
98
 
93
99
  const storage = await createFlexibleStorage<Campaign>(this.config);
94
100
  const campaignTracker = new CampaignTracker(this.config.apiKey, {
@@ -161,8 +167,98 @@ export class AmplitudeReactNative extends AmplitudeCore<ReactNativeConfig> {
161
167
  this.q.push(this.setSessionId.bind(this, sessionId));
162
168
  return;
163
169
  }
170
+
171
+ this.explicitSessionId = sessionId;
172
+ void this.setSessionIdInternal(sessionId, this.currentTimeMillis());
173
+ }
174
+
175
+ private setSessionIdInternal(sessionId: number, eventTime: number) {
176
+ const previousSessionId = this.config.sessionId;
177
+ if (previousSessionId === sessionId) {
178
+ return;
179
+ }
180
+
164
181
  this.config.sessionId = sessionId;
182
+
183
+ if (this.config.trackingSessionEvents) {
184
+ if (previousSessionId !== undefined) {
185
+ const sessionEndEvent: Event = {
186
+ event_type: END_SESSION_EVENT,
187
+ time: this.config.lastEventTime !== undefined ? this.config.lastEventTime + 1 : sessionId, // increment lastEventTime to sort events properly in UI - session_end should be the last event in a session
188
+ session_id: previousSessionId,
189
+ };
190
+ void this.track(sessionEndEvent);
191
+ }
192
+
193
+ const sessionStartEvent: Event = {
194
+ event_type: START_SESSION_EVENT,
195
+ time: eventTime,
196
+ session_id: sessionId,
197
+ };
198
+ void this.track(sessionStartEvent);
199
+ }
200
+
201
+ this.config.lastEventTime = eventTime;
165
202
  }
203
+
204
+ async process(event: Event): Promise<Result> {
205
+ if (!this.config.optOut) {
206
+ const eventTime = event.time ?? this.currentTimeMillis();
207
+ if (event.time === undefined) {
208
+ event = { ...event, time: eventTime };
209
+ }
210
+
211
+ if (event.event_type != START_SESSION_EVENT && event.event_type != END_SESSION_EVENT) {
212
+ if (this.appState !== 'active') {
213
+ this.startNewSessionIfNeeded(eventTime);
214
+ }
215
+ }
216
+ this.config.lastEventTime = eventTime;
217
+
218
+ if (event.session_id == undefined) {
219
+ event.session_id = this.getSessionId();
220
+ }
221
+ }
222
+
223
+ return super.process(event);
224
+ }
225
+
226
+ currentTimeMillis() {
227
+ return Date.now();
228
+ }
229
+
230
+ private startNewSessionIfNeeded(eventTime?: number): boolean {
231
+ eventTime = eventTime ?? this.currentTimeMillis();
232
+ const sessionId = this.explicitSessionId ?? eventTime;
233
+
234
+ if (
235
+ this.inSession() &&
236
+ (this.explicitSessionId === this.config.sessionId ||
237
+ (this.explicitSessionId === undefined && this.isWithinMinTimeBetweenSessions(sessionId)))
238
+ ) {
239
+ this.config.lastEventTime = eventTime;
240
+ return false;
241
+ }
242
+
243
+ this.setSessionIdInternal(sessionId, eventTime);
244
+ return true;
245
+ }
246
+
247
+ private isWithinMinTimeBetweenSessions(eventTime: number) {
248
+ return eventTime - (this.config.lastEventTime ?? 0) < this.config.sessionTimeout;
249
+ }
250
+
251
+ private inSession() {
252
+ return this.config.sessionId != undefined;
253
+ }
254
+
255
+ private readonly handleAppStateChange = (nextAppState: AppStateStatus) => {
256
+ const currentAppState = this.appState;
257
+ this.appState = nextAppState;
258
+ if (currentAppState !== nextAppState && nextAppState === 'active') {
259
+ this.startNewSessionIfNeeded();
260
+ }
261
+ };
166
262
  }
167
263
 
168
264
  export const createInstance = (): ReactNativeClient => {
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.5.1';
1
+ export const VERSION = '0.6.1';