@@ -10,30 +10,48 @@ import {
10
10
GroupEventType ,
11
11
UpdateType ,
12
12
AliasEventType ,
13
+ SegmentClient ,
13
14
} from '@segment/analytics-react-native' ;
14
15
16
+ import AsyncStorage from '@react-native-async-storage/async-storage' ;
17
+ import { AppState } from 'react-native' ;
18
+
15
19
const 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
+
16
25
export class AmplitudeSessionPlugin extends EventPlugin {
17
26
type = PluginType . enrichment ;
18
27
key = 'Actions Amplitude' ;
19
28
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 ;
28
41
}
42
+ this . active = settings . integrations ?. hasOwnProperty ( this . key ) ?? false ;
29
43
}
30
44
31
- execute ( event : SegmentEvent ) {
45
+ async execute ( event : SegmentEvent ) {
32
46
if ( ! this . active ) {
33
47
return event ;
34
48
}
35
49
36
- this . refreshSession ( ) ;
50
+ if ( this . sessionId === - 1 || this . lastEventTime === - 1 ) {
51
+ await this . loadSessionData ( ) ;
52
+ }
53
+
54
+ await this . startNewSessionIfNecessary ( ) ;
37
55
38
56
let result = event ;
39
57
switch ( result . type ) {
@@ -53,6 +71,10 @@ export class AmplitudeSessionPlugin extends EventPlugin {
53
71
result = this . group ( result ) ;
54
72
break ;
55
73
}
74
+
75
+ this . lastEventTime = Date . now ( ) ;
76
+ await this . saveSessionData ( ) ;
77
+
56
78
return result ;
57
79
}
58
80
@@ -65,6 +87,10 @@ export class AmplitudeSessionPlugin extends EventPlugin {
65
87
}
66
88
67
89
screen ( event : ScreenEventType ) {
90
+ event . properties = {
91
+ ...event . properties ,
92
+ name : event . name ,
93
+ } ;
68
94
return this . insertSession ( event ) as ScreenEventType ;
69
95
}
70
96
@@ -76,39 +102,105 @@ export class AmplitudeSessionPlugin extends EventPlugin {
76
102
return this . insertSession ( event ) as AliasEventType ;
77
103
}
78
104
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 ) ;
81
110
}
82
111
83
112
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 } ,
90
129
} ,
91
130
} ;
92
- return returnEvent ;
93
131
} ;
94
132
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 ( ) ;
98
140
} ;
99
141
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 ;
103
153
}
104
154
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 ;
107
174
}
108
175
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 ( )
112
196
) ;
197
+ }
198
+
199
+ private handleAppStateChange = ( nextAppState : string ) => {
200
+ if ( nextAppState === 'active' ) {
201
+ this . onForeground ( ) ;
202
+ } else if ( nextAppState === 'background' ) {
203
+ this . onBackground ( ) ;
204
+ }
113
205
} ;
114
206
}
0 commit comments