Skip to content

Commit 603adff

Browse files
refactor: for session_id (#1080)
* refactor: sessionID logic * refactor: for session_id * refactor: handled foreground and background app state
1 parent 3dc7631 commit 603adff

File tree

1 file changed

+122
-30
lines changed

1 file changed

+122
-30
lines changed

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

Lines changed: 122 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
1519
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+
1625
export 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

Comments
 (0)