Skip to content

refactor: for session_id #1080

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 26, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 122 additions & 30 deletions packages/plugins/plugin-amplitudeSession/src/AmplitudeSessionPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,48 @@ import {
GroupEventType,
UpdateType,
AliasEventType,
SegmentClient,
} from '@segment/analytics-react-native';

import AsyncStorage from '@react-native-async-storage/async-storage';
import { AppState } from 'react-native';

const MAX_SESSION_TIME_IN_MS = 300000;
const SESSION_ID_KEY = 'previous_session_id';
const LAST_EVENT_TIME_KEY = 'last_event_time';
const AMP_SESSION_START_EVENT = 'session_start';
const AMP_SESSION_END_EVENT = 'session_end';

export class AmplitudeSessionPlugin extends EventPlugin {
type = PluginType.enrichment;
key = 'Actions Amplitude';
active = false;
sessionId: number | undefined;
sessionTimer: ReturnType<typeof setTimeout> | undefined;

update(settings: SegmentAPISettings, _: UpdateType) {
const integrations = settings.integrations;
if (this.key in integrations) {
this.active = true;
this.refreshSession();
sessionId = -1;
lastEventTime = -1;

configure = async (analytics: SegmentClient): Promise<void> => {
this.analytics = analytics;
await this.loadSessionData();
AppState.addEventListener('change', this.handleAppStateChange);
};

update(settings: SegmentAPISettings, type: UpdateType) {
if (type !== UpdateType.initial) {
return;
}
this.active = settings.integrations?.hasOwnProperty(this.key) ?? false;
}

execute(event: SegmentEvent) {
async execute(event: SegmentEvent) {
if (!this.active) {
return event;
}

this.refreshSession();
if (this.sessionId === -1 || this.lastEventTime === -1) {
await this.loadSessionData();
}

await this.startNewSessionIfNecessary();

let result = event;
switch (result.type) {
Expand All @@ -53,6 +71,10 @@ export class AmplitudeSessionPlugin extends EventPlugin {
result = this.group(result);
break;
}

this.lastEventTime = Date.now();
await this.saveSessionData();

return result;
}

Expand All @@ -65,6 +87,10 @@ export class AmplitudeSessionPlugin extends EventPlugin {
}

screen(event: ScreenEventType) {
event.properties = {
...event.properties,
name: event.name,
};
return this.insertSession(event) as ScreenEventType;
}

Expand All @@ -76,39 +102,105 @@ export class AmplitudeSessionPlugin extends EventPlugin {
return this.insertSession(event) as AliasEventType;
}

reset() {
this.resetSession();
async reset() {
this.sessionId = -1;
this.lastEventTime = -1;
await AsyncStorage.removeItem(SESSION_ID_KEY);
await AsyncStorage.removeItem(LAST_EVENT_TIME_KEY);
}

private insertSession = (event: SegmentEvent) => {
const returnEvent = event;
const integrations = event.integrations;
returnEvent.integrations = {
...integrations,
[this.key]: {
session_id: this.sessionId,
const integrations = event.integrations || {};
const existingIntegration = integrations[this.key];
const hasSessionId =
typeof existingIntegration === 'object' &&
existingIntegration !== null &&
'session_id' in existingIntegration;

if (hasSessionId) {
return event;
}

return {
...event,
integrations: {
...integrations,
[this.key]: { session_id: this.sessionId },
},
};
return returnEvent;
};

private resetSession = () => {
this.sessionId = Date.now();
this.sessionTimer = undefined;
private onBackground = () => {
this.lastEventTime = Date.now();
this.saveSessionData();
};

private onForeground = () => {
this.startNewSessionIfNecessary();
};

private refreshSession = () => {
if (this.sessionId === undefined) {
this.sessionId = Date.now();
private async startNewSessionIfNecessary() {
const current = Date.now();

const sessionExpired =
this.sessionId === -1 ||
this.lastEventTime === -1 ||
current - this.lastEventTime >= MAX_SESSION_TIME_IN_MS;

// Avoid loop: if session just started recently, skip restarting
if (!sessionExpired || current - this.sessionId < 1000) {
return;
}

if (this.sessionTimer !== undefined) {
clearTimeout(this.sessionTimer);
await this.endSession();
await this.startNewSession();
}

private async startNewSession() {
this.sessionId = Date.now();
this.lastEventTime = this.sessionId;
await this.saveSessionData();

this.analytics?.track(AMP_SESSION_START_EVENT, {
integrations: {
[this.key]: { session_id: this.sessionId },
},
});
}

private async endSession() {
if (this.sessionId === -1) {
return;
}

this.sessionTimer = setTimeout(
() => this.resetSession(),
MAX_SESSION_TIME_IN_MS
this.analytics?.track(AMP_SESSION_END_EVENT, {
integrations: {
[this.key]: { session_id: this.sessionId },
},
});
}

private async loadSessionData() {
const storedSessionId = await AsyncStorage.getItem(SESSION_ID_KEY);
const storedLastEventTime = await AsyncStorage.getItem(LAST_EVENT_TIME_KEY);
this.sessionId = storedSessionId != null ? Number(storedSessionId) : -1;
this.lastEventTime =
storedLastEventTime != null ? Number(storedLastEventTime) : -1;
}

private async saveSessionData() {
await AsyncStorage.setItem(SESSION_ID_KEY, this.sessionId.toString());
await AsyncStorage.setItem(
LAST_EVENT_TIME_KEY,
this.lastEventTime.toString()
);
}

private handleAppStateChange = (nextAppState: string) => {
if (nextAppState === 'active') {
this.onForeground();
} else if (nextAppState === 'background') {
this.onBackground();
}
};
}
Loading