@@ -10,30 +10,48 @@ import {
1010 GroupEventType ,
1111 UpdateType ,
1212 AliasEventType ,
13+ SegmentClient ,
1314} from '@segment/analytics-react-native' ;
1415
16+ import AsyncStorage from '@react-native-async-storage/async-storage' ;
17+ import { AppState } from 'react-native' ;
18+
1519const MAX_SESSION_TIME_IN_MS = 300000 ;
20+ const SESSION_ID_KEY = 'previous_session_id' ;
21+ const LAST_EVENT_TIME_KEY = 'last_event_time' ;
22+ const AMP_SESSION_START_EVENT = 'session_start' ;
23+ const AMP_SESSION_END_EVENT = 'session_end' ;
24+
1625export class AmplitudeSessionPlugin extends EventPlugin {
1726 type = PluginType . enrichment ;
1827 key = 'Actions Amplitude' ;
1928 active = false ;
20- sessionId : number | undefined ;
21- sessionTimer : ReturnType < typeof setTimeout > | undefined ;
22-
23- update ( settings : SegmentAPISettings , _ : UpdateType ) {
24- const integrations = settings . integrations ;
25- if ( this . key in integrations ) {
26- this . active = true ;
27- this . refreshSession ( ) ;
29+ sessionId = - 1 ;
30+ lastEventTime = - 1 ;
31+
32+ configure = async ( analytics : SegmentClient ) : Promise < void > => {
33+ this . analytics = analytics ;
34+ await this . loadSessionData ( ) ;
35+ AppState . addEventListener ( 'change' , this . handleAppStateChange ) ;
36+ } ;
37+
38+ update ( settings : SegmentAPISettings , type : UpdateType ) {
39+ if ( type !== UpdateType . initial ) {
40+ return ;
2841 }
42+ this . active = settings . integrations ?. hasOwnProperty ( this . key ) ?? false ;
2943 }
3044
31- execute ( event : SegmentEvent ) {
45+ async execute ( event : SegmentEvent ) {
3246 if ( ! this . active ) {
3347 return event ;
3448 }
3549
36- this . refreshSession ( ) ;
50+ if ( this . sessionId === - 1 || this . lastEventTime === - 1 ) {
51+ await this . loadSessionData ( ) ;
52+ }
53+
54+ await this . startNewSessionIfNecessary ( ) ;
3755
3856 let result = event ;
3957 switch ( result . type ) {
@@ -53,6 +71,10 @@ export class AmplitudeSessionPlugin extends EventPlugin {
5371 result = this . group ( result ) ;
5472 break ;
5573 }
74+
75+ this . lastEventTime = Date . now ( ) ;
76+ await this . saveSessionData ( ) ;
77+
5678 return result ;
5779 }
5880
@@ -65,6 +87,10 @@ export class AmplitudeSessionPlugin extends EventPlugin {
6587 }
6688
6789 screen ( event : ScreenEventType ) {
90+ event . properties = {
91+ ...event . properties ,
92+ name : event . name ,
93+ } ;
6894 return this . insertSession ( event ) as ScreenEventType ;
6995 }
7096
@@ -76,39 +102,105 @@ export class AmplitudeSessionPlugin extends EventPlugin {
76102 return this . insertSession ( event ) as AliasEventType ;
77103 }
78104
79- reset ( ) {
80- this . resetSession ( ) ;
105+ async reset ( ) {
106+ this . sessionId = - 1 ;
107+ this . lastEventTime = - 1 ;
108+ await AsyncStorage . removeItem ( SESSION_ID_KEY ) ;
109+ await AsyncStorage . removeItem ( LAST_EVENT_TIME_KEY ) ;
81110 }
82111
83112 private insertSession = ( event : SegmentEvent ) => {
84- const returnEvent = event ;
85- const integrations = event . integrations ;
86- returnEvent . integrations = {
87- ...integrations ,
88- [ this . key ] : {
89- session_id : this . sessionId ,
113+ const integrations = event . integrations || { } ;
114+ const existingIntegration = integrations [ this . key ] ;
115+ const hasSessionId =
116+ typeof existingIntegration === 'object' &&
117+ existingIntegration !== null &&
118+ 'session_id' in existingIntegration ;
119+
120+ if ( hasSessionId ) {
121+ return event ;
122+ }
123+
124+ return {
125+ ...event ,
126+ integrations : {
127+ ...integrations ,
128+ [ this . key ] : { session_id : this . sessionId } ,
90129 } ,
91130 } ;
92- return returnEvent ;
93131 } ;
94132
95- private resetSession = ( ) => {
96- this . sessionId = Date . now ( ) ;
97- this . sessionTimer = undefined ;
133+ private onBackground = ( ) => {
134+ this . lastEventTime = Date . now ( ) ;
135+ this . saveSessionData ( ) ;
136+ } ;
137+
138+ private onForeground = ( ) => {
139+ this . startNewSessionIfNecessary ( ) ;
98140 } ;
99141
100- private refreshSession = ( ) => {
101- if ( this . sessionId === undefined ) {
102- this . sessionId = Date . now ( ) ;
142+ private async startNewSessionIfNecessary ( ) {
143+ const current = Date . now ( ) ;
144+
145+ const sessionExpired =
146+ this . sessionId === - 1 ||
147+ this . lastEventTime === - 1 ||
148+ current - this . lastEventTime >= MAX_SESSION_TIME_IN_MS ;
149+
150+ // Avoid loop: if session just started recently, skip restarting
151+ if ( ! sessionExpired || current - this . sessionId < 1000 ) {
152+ return ;
103153 }
104154
105- if ( this . sessionTimer !== undefined ) {
106- clearTimeout ( this . sessionTimer ) ;
155+ await this . endSession ( ) ;
156+ await this . startNewSession ( ) ;
157+ }
158+
159+ private async startNewSession ( ) {
160+ this . sessionId = Date . now ( ) ;
161+ this . lastEventTime = this . sessionId ;
162+ await this . saveSessionData ( ) ;
163+
164+ this . analytics ?. track ( AMP_SESSION_START_EVENT , {
165+ integrations : {
166+ [ this . key ] : { session_id : this . sessionId } ,
167+ } ,
168+ } ) ;
169+ }
170+
171+ private async endSession ( ) {
172+ if ( this . sessionId === - 1 ) {
173+ return ;
107174 }
108175
109- this . sessionTimer = setTimeout (
110- ( ) => this . resetSession ( ) ,
111- MAX_SESSION_TIME_IN_MS
176+ this . analytics ?. track ( AMP_SESSION_END_EVENT , {
177+ integrations : {
178+ [ this . key ] : { session_id : this . sessionId } ,
179+ } ,
180+ } ) ;
181+ }
182+
183+ private async loadSessionData ( ) {
184+ const storedSessionId = await AsyncStorage . getItem ( SESSION_ID_KEY ) ;
185+ const storedLastEventTime = await AsyncStorage . getItem ( LAST_EVENT_TIME_KEY ) ;
186+ this . sessionId = storedSessionId != null ? Number ( storedSessionId ) : - 1 ;
187+ this . lastEventTime =
188+ storedLastEventTime != null ? Number ( storedLastEventTime ) : - 1 ;
189+ }
190+
191+ private async saveSessionData ( ) {
192+ await AsyncStorage . setItem ( SESSION_ID_KEY , this . sessionId . toString ( ) ) ;
193+ await AsyncStorage . setItem (
194+ LAST_EVENT_TIME_KEY ,
195+ this . lastEventTime . toString ( )
112196 ) ;
197+ }
198+
199+ private handleAppStateChange = ( nextAppState : string ) => {
200+ if ( nextAppState === 'active' ) {
201+ this . onForeground ( ) ;
202+ } else if ( nextAppState === 'background' ) {
203+ this . onBackground ( ) ;
204+ }
113205 } ;
114206}
0 commit comments