Skip to content

Commit 24e1741

Browse files
sunitaprajapati89Sunita Prajapati
andauthored
fix: Amplitude session_id fix (#1104)
* feat: fixed session_ID issue - added unit test cases - refactored plugin code to be in sync with swift code * refactor: removed unused code, comments and console logs * refactor: fixed ESlint issues * refactor: refactored to add properties for Event_Session_ID and Last_Event_Time to automatic update to storage - Added new properties - Modified persistent test cases * refactor: removed used code - removed unused code - refactored reset methode * fix: fixed when application is killed by user and re-opened - Stored sessionID when user kill the app --------- Co-authored-by: Sunita Prajapati <[email protected]>
1 parent 9d1aee7 commit 24e1741

File tree

3 files changed

+919
-29
lines changed

3 files changed

+919
-29
lines changed

packages/plugins/plugin-amplitudeSession/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@segment/analytics-react-native-plugin-amplitude-session",
3-
"version": "0.4.1",
3+
"version": "0.4.2",
44
"description": "The hassle-free way to add Segment analytics to your React-Native app.",
55
"main": "lib/commonjs/index",
66
"scripts": {
@@ -48,7 +48,7 @@
4848
"@segment/analytics-react-native": "^2.18.0"
4949
},
5050
"devDependencies": {
51-
"@segment/analytics-react-native": "^2.18.0",
51+
"@segment/analytics-react-native": "^2.21.4",
5252
"@segment/analytics-rn-shared": "workspace:^",
5353
"@segment/sovran-react-native": "^1.1.0",
5454
"@types/jest": "^29.5.8",

packages/plugins/plugin-amplitudeSession/src/AmplitudeSessionPlugin.tsx

Lines changed: 148 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { AppState } from 'react-native';
1818

1919
const MAX_SESSION_TIME_IN_MS = 300000;
2020
const SESSION_ID_KEY = 'previous_session_id';
21+
const EVENT_SESSION_ID_KEY = 'event_session_id';
2122
const LAST_EVENT_TIME_KEY = 'last_event_time';
2223
const AMP_SESSION_START_EVENT = 'session_start';
2324
const AMP_SESSION_END_EVENT = 'session_end';
@@ -26,8 +27,56 @@ export class AmplitudeSessionPlugin extends EventPlugin {
2627
type = PluginType.enrichment;
2728
key = 'Actions Amplitude';
2829
active = false;
29-
sessionId = -1;
30-
lastEventTime = -1;
30+
private _sessionId = -1;
31+
private _eventSessionId = -1;
32+
private _lastEventTime = -1;
33+
resetPending = false;
34+
35+
get eventSessionId() {
36+
return this._eventSessionId;
37+
}
38+
set eventSessionId(value: number) {
39+
this._eventSessionId = value;
40+
if (value !== -1) {
41+
AsyncStorage.setItem(EVENT_SESSION_ID_KEY, value.toString()).catch(
42+
(err) =>
43+
console.warn(
44+
'[AmplitudeSessionPlugin] Failed to persist eventSessionId:',
45+
err
46+
)
47+
);
48+
}
49+
}
50+
51+
get lastEventTime() {
52+
return this._lastEventTime;
53+
}
54+
set lastEventTime(value: number) {
55+
this._lastEventTime = value;
56+
if (value !== -1) {
57+
AsyncStorage.setItem(LAST_EVENT_TIME_KEY, value.toString()).catch((err) =>
58+
console.warn(
59+
'[AmplitudeSessionPlugin] Failed to persist lastEventTime:',
60+
err
61+
)
62+
);
63+
}
64+
}
65+
66+
get sessionId() {
67+
return this._sessionId;
68+
}
69+
set sessionId(value: number) {
70+
this._sessionId = value;
71+
if (value !== -1) {
72+
AsyncStorage.setItem(SESSION_ID_KEY, value.toString()).catch((err) =>
73+
console.warn(
74+
'[AmplitudeSessionPlugin] Failed to persist sessionId:',
75+
err
76+
)
77+
);
78+
}
79+
}
3180

3281
configure = async (analytics: SegmentClient): Promise<void> => {
3382
this.analytics = analytics;
@@ -50,9 +99,7 @@ export class AmplitudeSessionPlugin extends EventPlugin {
5099
if (this.sessionId === -1 || this.lastEventTime === -1) {
51100
await this.loadSessionData();
52101
}
53-
54102
await this.startNewSessionIfNecessary();
55-
56103
let result = event;
57104
switch (result.type) {
58105
case EventType.IdentifyEvent:
@@ -73,8 +120,7 @@ export class AmplitudeSessionPlugin extends EventPlugin {
73120
}
74121

75122
this.lastEventTime = Date.now();
76-
await this.saveSessionData();
77-
123+
//await this.saveSessionData();
78124
return result;
79125
}
80126

@@ -83,6 +129,32 @@ export class AmplitudeSessionPlugin extends EventPlugin {
83129
}
84130

85131
track(event: TrackEventType) {
132+
const eventName = event.event;
133+
134+
if (eventName === AMP_SESSION_START_EVENT) {
135+
this.resetPending = false;
136+
this.eventSessionId = this.sessionId;
137+
}
138+
139+
if (eventName === AMP_SESSION_END_EVENT) {
140+
console.log(`[AmplitudeSession] EndSession = ${this.eventSessionId}`);
141+
}
142+
143+
if (
144+
eventName.startsWith('Amplitude') ||
145+
eventName === AMP_SESSION_START_EVENT ||
146+
eventName === AMP_SESSION_END_EVENT
147+
) {
148+
const integrations = this.disableAllIntegrations(event.integrations);
149+
return {
150+
...event,
151+
integrations: {
152+
...integrations,
153+
[this.key]: { session_id: this.eventSessionId },
154+
},
155+
};
156+
}
157+
86158
return this.insertSession(event) as TrackEventType;
87159
}
88160

@@ -104,9 +176,9 @@ export class AmplitudeSessionPlugin extends EventPlugin {
104176

105177
async reset() {
106178
this.sessionId = -1;
179+
this.eventSessionId = -1;
107180
this.lastEventTime = -1;
108181
await AsyncStorage.removeItem(SESSION_ID_KEY);
109-
await AsyncStorage.removeItem(LAST_EVENT_TIME_KEY);
110182
}
111183

112184
private insertSession = (event: SegmentEvent) => {
@@ -132,68 +204,117 @@ export class AmplitudeSessionPlugin extends EventPlugin {
132204

133205
private onBackground = () => {
134206
this.lastEventTime = Date.now();
135-
this.saveSessionData();
136207
};
137208

138209
private onForeground = () => {
139210
this.startNewSessionIfNecessary();
140211
};
141212

142213
private async startNewSessionIfNecessary() {
214+
if (this.eventSessionId === -1) {
215+
this.eventSessionId = this.sessionId;
216+
}
217+
218+
if (this.resetPending) {
219+
return;
220+
}
221+
143222
const current = Date.now();
223+
const withinSessionLimit = this.withinMinSessionTime(current);
144224

145-
const sessionExpired =
146-
this.sessionId === -1 ||
147-
this.lastEventTime === -1 ||
148-
current - this.lastEventTime >= MAX_SESSION_TIME_IN_MS;
225+
const isSessionExpired =
226+
this.sessionId === -1 || this.lastEventTime === -1 || !withinSessionLimit;
149227

150-
// Avoid loop: if session just started recently, skip restarting
151-
if (!sessionExpired || current - this.sessionId < 1000) {
228+
if (this.sessionId >= 0 && !isSessionExpired) {
152229
return;
153230
}
154231

155-
await this.endSession();
232+
// End old session and start a new one
156233
await this.startNewSession();
157234
}
158235

236+
/**
237+
* Handles the entire process of starting a new session.
238+
* Can be called directly or from startNewSessionIfNecessary()
239+
*/
159240
private async startNewSession() {
160-
this.sessionId = Date.now();
161-
this.lastEventTime = this.sessionId;
162-
await this.saveSessionData();
241+
if (this.resetPending) {
242+
return;
243+
}
244+
245+
this.resetPending = true;
246+
247+
const oldSessionId = this.sessionId;
248+
if (oldSessionId >= 0) {
249+
await this.endSession(oldSessionId);
250+
}
251+
252+
const newSessionId = Date.now();
253+
this.sessionId = newSessionId;
254+
this.eventSessionId =
255+
this.eventSessionId === -1 ? newSessionId : this.eventSessionId;
256+
this.lastEventTime = newSessionId;
257+
258+
console.log(`[AmplitudeSession] startNewSession -> ${newSessionId}`);
259+
260+
await this.trackSessionStart(newSessionId);
261+
}
163262

263+
/**
264+
* Extracted analytics tracking into its own method
265+
*/
266+
private async trackSessionStart(sessionId: number) {
164267
this.analytics?.track(AMP_SESSION_START_EVENT, {
165268
integrations: {
166-
[this.key]: { session_id: this.sessionId },
269+
[this.key]: { session_id: sessionId },
167270
},
168271
});
169272
}
170273

171-
private async endSession() {
274+
private async endSession(sessionId: number) {
172275
if (this.sessionId === -1) {
173276
return;
174277
}
175278

279+
console.log(`[AmplitudeSession] endSession -> ${this.sessionId}`);
280+
176281
this.analytics?.track(AMP_SESSION_END_EVENT, {
177282
integrations: {
178-
[this.key]: { session_id: this.sessionId },
283+
[this.key]: { session_id: sessionId },
179284
},
180285
});
181286
}
182287

183288
private async loadSessionData() {
184289
const storedSessionId = await AsyncStorage.getItem(SESSION_ID_KEY);
185290
const storedLastEventTime = await AsyncStorage.getItem(LAST_EVENT_TIME_KEY);
291+
const storedEventSessionId = await AsyncStorage.getItem(
292+
EVENT_SESSION_ID_KEY
293+
);
294+
186295
this.sessionId = storedSessionId != null ? Number(storedSessionId) : -1;
187296
this.lastEventTime =
188297
storedLastEventTime != null ? Number(storedLastEventTime) : -1;
298+
this.eventSessionId =
299+
storedEventSessionId != null ? Number(storedEventSessionId) : -1;
189300
}
190301

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()
196-
);
302+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
303+
private disableAllIntegrations(integrations?: Record<string, any>) {
304+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
305+
const result: Record<string, any> = {};
306+
if (!integrations) {
307+
return result;
308+
}
309+
for (const key of Object.keys(integrations)) {
310+
result[key] = false;
311+
}
312+
return result;
313+
}
314+
315+
private withinMinSessionTime(timestamp: number): boolean {
316+
const timeDelta = timestamp - this.lastEventTime;
317+
return timeDelta < MAX_SESSION_TIME_IN_MS;
197318
}
198319

199320
private handleAppStateChange = (nextAppState: string) => {

0 commit comments

Comments
 (0)