From e0d5a4b3ed4c3f62a82aafe2c569f9fa7f04885e Mon Sep 17 00:00:00 2001 From: Sunita Prajapati Date: Fri, 6 Jun 2025 14:31:29 +0530 Subject: [PATCH 1/3] refactor: sessionID logic --- .../src/AmplitudeSessionPlugin.tsx | 217 +++++++++++++++++- 1 file changed, 209 insertions(+), 8 deletions(-) diff --git a/packages/plugins/plugin-amplitudeSession/src/AmplitudeSessionPlugin.tsx b/packages/plugins/plugin-amplitudeSession/src/AmplitudeSessionPlugin.tsx index 3d0644bc..fe694d55 100644 --- a/packages/plugins/plugin-amplitudeSession/src/AmplitudeSessionPlugin.tsx +++ b/packages/plugins/plugin-amplitudeSession/src/AmplitudeSessionPlugin.tsx @@ -1,3 +1,123 @@ +// import { +// EventPlugin, +// EventType, +// IdentifyEventType, +// PluginType, +// SegmentAPISettings, +// SegmentEvent, +// TrackEventType, +// ScreenEventType, +// GroupEventType, +// UpdateType, +// AliasEventType, +// SegmentClient, +// } from '@segment/analytics-react-native'; + +// const MAX_SESSION_TIME_IN_MS = 300000; +// export class AmplitudeSessionPlugin extends EventPlugin { +// type = PluginType.enrichment; +// key = 'Actions Amplitude'; +// active = false; +// sessionId: number | undefined; +// sessionTimer: ReturnType | undefined; + +// configure(_analytics: SegmentClient): void { +// this.analytics = _analytics; +// } + +// update(settings: SegmentAPISettings, _: UpdateType) { +// const integrations = settings.integrations; +// if (this.key in integrations) { +// this.active = true; +// this.refreshSession(); +// } +// } + +// execute(event: SegmentEvent) { +// if (!this.active) { +// return event; +// } + +// this.refreshSession(); + +// let result = event; +// switch (result.type) { +// case EventType.IdentifyEvent: +// result = this.identify(result); +// break; +// case EventType.TrackEvent: +// result = this.track(result); +// break; +// case EventType.ScreenEvent: +// result = this.screen(result); +// break; +// case EventType.AliasEvent: +// result = this.alias(result); +// break; +// case EventType.GroupEvent: +// result = this.group(result); +// break; +// } +// return result; +// } + +// identify(event: IdentifyEventType) { +// return this.insertSession(event) as IdentifyEventType; +// } + +// track(event: TrackEventType) { +// return this.insertSession(event) as TrackEventType; +// } + +// screen(event: ScreenEventType) { +// return this.insertSession(event) as ScreenEventType; +// } + +// group(event: GroupEventType) { +// return this.insertSession(event) as GroupEventType; +// } + +// alias(event: AliasEventType) { +// return this.insertSession(event) as AliasEventType; +// } + +// reset() { +// this.resetSession(); +// } + +// private insertSession = (event: SegmentEvent) => { +// const returnEvent = event; +// const integrations = event.integrations; +// returnEvent.integrations = { +// ...integrations, +// [this.key]: { +// session_id: this.sessionId, +// }, +// }; +// return returnEvent; +// }; + +// private resetSession = () => { +// this.sessionId = Date.now(); +// this.sessionTimer = undefined; +// }; + +// private refreshSession = () => { +// if (this.sessionId === undefined) { +// this.sessionId = Date.now(); +// } + +// if (this.sessionTimer !== undefined) { +// clearTimeout(this.sessionTimer); +// } + +// this.sessionTimer = setTimeout( +// () => this.resetSession(), +// MAX_SESSION_TIME_IN_MS +// ); +// }; +// } + import { EventPlugin, EventType, @@ -10,16 +130,31 @@ 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 = 'amplitude_session_id'; +const LAST_EVENT_TIME_KEY = 'amplitude_last_event_time'; + export class AmplitudeSessionPlugin extends EventPlugin { type = PluginType.enrichment; key = 'Actions Amplitude'; active = false; sessionId: number | undefined; + lastEventTime: number | undefined; sessionTimer: ReturnType | undefined; + configure(_analytics: SegmentClient): void { + this.analytics = _analytics; + AppState.addEventListener('change', this.handleAppStateChange); + this.loadSessionData(); + } + + // Called when plugin is initialized with Segment settings update(settings: SegmentAPISettings, _: UpdateType) { const integrations = settings.integrations; if (this.key in integrations) { @@ -28,12 +163,13 @@ export class AmplitudeSessionPlugin extends EventPlugin { } } - execute(event: SegmentEvent) { + // Core event processing hook + async execute(event: SegmentEvent) { if (!this.active) { return event; } - this.refreshSession(); + await this.refreshSession(); let result = event; switch (result.type) { @@ -80,11 +216,11 @@ export class AmplitudeSessionPlugin extends EventPlugin { this.resetSession(); } + // Injects session_id into event's integrations private insertSession = (event: SegmentEvent) => { const returnEvent = event; - const integrations = event.integrations; returnEvent.integrations = { - ...integrations, + ...event.integrations, [this.key]: { session_id: this.sessionId, }, @@ -92,14 +228,51 @@ export class AmplitudeSessionPlugin extends EventPlugin { return returnEvent; }; - private resetSession = () => { + // Emits session start/end events manually (optional for debugging or analytics) + private trackSessionEvent = (eventName: string) => { + if (this.analytics && this.sessionId != null) { + this.analytics.track(eventName, { + integrations: { + [this.key]: { + session_id: this.sessionId, + }, + }, + }); + } + }; + + // Creates new session and tracks it + private resetSession = async () => { + this.trackSessionEvent('session_end'); this.sessionId = Date.now(); + this.lastEventTime = this.sessionId; + this.trackSessionEvent('session_start'); this.sessionTimer = undefined; + await this.saveSessionData(); }; - private refreshSession = () => { - if (this.sessionId === undefined) { - this.sessionId = Date.now(); + // Refreshes session only if session timeout occurred + private refreshSession = async () => { + const now = Date.now(); + console.log('sessionId', this.sessionId); + console.log('this.lastEventTime', this.lastEventTime); + if (this.lastEventTime != null) { + console.log( + 'now - this.lastEventTime > MAX_SESSION_TIME_IN_MS', + now - this.lastEventTime > MAX_SESSION_TIME_IN_MS + ); + } + if ( + this.sessionId === undefined || + this.lastEventTime === undefined || + now - this.lastEventTime > MAX_SESSION_TIME_IN_MS + ) { + console.log(' await this.resetSession()'); + await this.resetSession(); + } else { + this.lastEventTime = now; + console.log(' await this.saveSessionData()'); + await this.saveSessionData(); } if (this.sessionTimer !== undefined) { @@ -111,4 +284,32 @@ export class AmplitudeSessionPlugin extends EventPlugin { MAX_SESSION_TIME_IN_MS ); }; + + // Loads stored session state from persistent storage + 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) : undefined; + this.lastEventTime = + storedLastEventTime != null ? Number(storedLastEventTime) : undefined; + } + + // Persists session state + private async saveSessionData() { + if (this.sessionId !== undefined) { + await AsyncStorage.setItem(SESSION_ID_KEY, this.sessionId.toString()); + await AsyncStorage.setItem(LAST_EVENT_TIME_KEY, Date.now().toString()); + } + } + + // Handles app going to foreground/background + private handleAppStateChange = (nextAppState: string) => { + console.log('nextAppState', nextAppState); + if (nextAppState === 'active') { + this.refreshSession(); + } else if (nextAppState === 'background') { + this.saveSessionData(); + } + }; } From 3643518a5450cfd3fd025672323fb5d3fd763c6f Mon Sep 17 00:00:00 2001 From: Sunita Prajapati Date: Mon, 9 Jun 2025 17:15:05 +0530 Subject: [PATCH 2/3] refactor: for session_id --- .../src/AmplitudeSessionPlugin.tsx | 292 ++++++------------ 1 file changed, 92 insertions(+), 200 deletions(-) diff --git a/packages/plugins/plugin-amplitudeSession/src/AmplitudeSessionPlugin.tsx b/packages/plugins/plugin-amplitudeSession/src/AmplitudeSessionPlugin.tsx index fe694d55..02b23445 100644 --- a/packages/plugins/plugin-amplitudeSession/src/AmplitudeSessionPlugin.tsx +++ b/packages/plugins/plugin-amplitudeSession/src/AmplitudeSessionPlugin.tsx @@ -1,123 +1,3 @@ -// import { -// EventPlugin, -// EventType, -// IdentifyEventType, -// PluginType, -// SegmentAPISettings, -// SegmentEvent, -// TrackEventType, -// ScreenEventType, -// GroupEventType, -// UpdateType, -// AliasEventType, -// SegmentClient, -// } from '@segment/analytics-react-native'; - -// const MAX_SESSION_TIME_IN_MS = 300000; -// export class AmplitudeSessionPlugin extends EventPlugin { -// type = PluginType.enrichment; -// key = 'Actions Amplitude'; -// active = false; -// sessionId: number | undefined; -// sessionTimer: ReturnType | undefined; - -// configure(_analytics: SegmentClient): void { -// this.analytics = _analytics; -// } - -// update(settings: SegmentAPISettings, _: UpdateType) { -// const integrations = settings.integrations; -// if (this.key in integrations) { -// this.active = true; -// this.refreshSession(); -// } -// } - -// execute(event: SegmentEvent) { -// if (!this.active) { -// return event; -// } - -// this.refreshSession(); - -// let result = event; -// switch (result.type) { -// case EventType.IdentifyEvent: -// result = this.identify(result); -// break; -// case EventType.TrackEvent: -// result = this.track(result); -// break; -// case EventType.ScreenEvent: -// result = this.screen(result); -// break; -// case EventType.AliasEvent: -// result = this.alias(result); -// break; -// case EventType.GroupEvent: -// result = this.group(result); -// break; -// } -// return result; -// } - -// identify(event: IdentifyEventType) { -// return this.insertSession(event) as IdentifyEventType; -// } - -// track(event: TrackEventType) { -// return this.insertSession(event) as TrackEventType; -// } - -// screen(event: ScreenEventType) { -// return this.insertSession(event) as ScreenEventType; -// } - -// group(event: GroupEventType) { -// return this.insertSession(event) as GroupEventType; -// } - -// alias(event: AliasEventType) { -// return this.insertSession(event) as AliasEventType; -// } - -// reset() { -// this.resetSession(); -// } - -// private insertSession = (event: SegmentEvent) => { -// const returnEvent = event; -// const integrations = event.integrations; -// returnEvent.integrations = { -// ...integrations, -// [this.key]: { -// session_id: this.sessionId, -// }, -// }; -// return returnEvent; -// }; - -// private resetSession = () => { -// this.sessionId = Date.now(); -// this.sessionTimer = undefined; -// }; - -// private refreshSession = () => { -// if (this.sessionId === undefined) { -// this.sessionId = Date.now(); -// } - -// if (this.sessionTimer !== undefined) { -// clearTimeout(this.sessionTimer); -// } - -// this.sessionTimer = setTimeout( -// () => this.resetSession(), -// MAX_SESSION_TIME_IN_MS -// ); -// }; -// } - import { EventPlugin, EventType, @@ -135,43 +15,51 @@ import { import AsyncStorage from '@react-native-async-storage/async-storage'; import { AppState } from 'react-native'; +//import { AppState } from 'react-native'; const MAX_SESSION_TIME_IN_MS = 300000; -const SESSION_ID_KEY = 'amplitude_session_id'; -const LAST_EVENT_TIME_KEY = 'amplitude_last_event_time'; +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; - lastEventTime: number | undefined; + sessionId = -1; + lastEventTime = -1; sessionTimer: ReturnType | undefined; - configure(_analytics: SegmentClient): void { - this.analytics = _analytics; + configure(analytics: SegmentClient): void { + this.analytics = analytics; AppState.addEventListener('change', this.handleAppStateChange); this.loadSessionData(); + if (this.sessionId === -1) { + this.startNewSession(); + } else { + this.startNewSessionIfNecessary(); + } } - // Called when plugin is initialized with Segment settings - update(settings: SegmentAPISettings, _: UpdateType) { - const integrations = settings.integrations; - if (this.key in integrations) { - this.active = true; - this.refreshSession(); + update(settings: SegmentAPISettings, type: UpdateType) { + if (type !== UpdateType.initial) { + return; } + this.active = settings.integrations?.hasOwnProperty(this.key) ?? false; } - // Core event processing hook async execute(event: SegmentEvent) { if (!this.active) { return event; } - - await this.refreshSession(); + await this.startNewSessionIfNecessary(); let result = event; + if (result.type === EventType.TrackEvent) { + console.log(result.event); + } + switch (result.type) { case EventType.IdentifyEvent: result = this.identify(result); @@ -189,6 +77,10 @@ export class AmplitudeSessionPlugin extends EventPlugin { result = this.group(result); break; } + + this.lastEventTime = Date.now(); + await this.saveSessionData(); + return result; } @@ -201,6 +93,10 @@ export class AmplitudeSessionPlugin extends EventPlugin { } screen(event: ScreenEventType) { + event.properties = { + ...event.properties, + name: event.name, + }; return this.insertSession(event) as ScreenEventType; } @@ -213,14 +109,25 @@ export class AmplitudeSessionPlugin extends EventPlugin { } reset() { - this.resetSession(); + //this.resetSession(); } - // Injects session_id into event's integrations private insertSession = (event: SegmentEvent) => { const returnEvent = event; + const integrations = event.integrations || {}; + const existingIntegration = integrations[this.key]; + const hasSessionId = + typeof existingIntegration === 'object' && + existingIntegration !== null && + 'session_id' in existingIntegration; + + // If session_id exists, return as is + if (hasSessionId) { + return returnEvent; + } + returnEvent.integrations = { - ...event.integrations, + ...integrations, [this.key]: { session_id: this.sessionId, }, @@ -228,88 +135,73 @@ export class AmplitudeSessionPlugin extends EventPlugin { return returnEvent; }; - // Emits session start/end events manually (optional for debugging or analytics) - private trackSessionEvent = (eventName: string) => { - if (this.analytics && this.sessionId != null) { - this.analytics.track(eventName, { + private onBackground() { + this.lastEventTime = Date.now(); + + this.saveSessionData(); + } + + private onForeground() { + this.startNewSessionIfNecessary(); + } + + private async startNewSessionIfNecessary() { + const current = Date.now(); + const withinSessionLimit = + current - this.lastEventTime < MAX_SESSION_TIME_IN_MS; + if (this.sessionId >= 0 && withinSessionLimit) { + return; + } + this.lastEventTime = current; + await this.endSession(); + await this.startNewSession(); + } + + private async startNewSession() { + this.sessionId = Date.now(); + const copy = this.sessionId; + if (this.analytics) { + this.analytics.track(AMP_SESSION_START_EVENT, { integrations: { - [this.key]: { - session_id: this.sessionId, - }, + [this.key]: { session_id: copy }, }, }); } - }; - - // Creates new session and tracks it - private resetSession = async () => { - this.trackSessionEvent('session_end'); - this.sessionId = Date.now(); - this.lastEventTime = this.sessionId; - this.trackSessionEvent('session_start'); - this.sessionTimer = undefined; await this.saveSessionData(); - }; - - // Refreshes session only if session timeout occurred - private refreshSession = async () => { - const now = Date.now(); - console.log('sessionId', this.sessionId); - console.log('this.lastEventTime', this.lastEventTime); - if (this.lastEventTime != null) { - console.log( - 'now - this.lastEventTime > MAX_SESSION_TIME_IN_MS', - now - this.lastEventTime > MAX_SESSION_TIME_IN_MS - ); - } - if ( - this.sessionId === undefined || - this.lastEventTime === undefined || - now - this.lastEventTime > MAX_SESSION_TIME_IN_MS - ) { - console.log(' await this.resetSession()'); - await this.resetSession(); - } else { - this.lastEventTime = now; - console.log(' await this.saveSessionData()'); - await this.saveSessionData(); - } + } - if (this.sessionTimer !== undefined) { - clearTimeout(this.sessionTimer); + private async endSession() { + const copy = this.sessionId; + if (this.analytics) { + this.analytics.track(AMP_SESSION_END_EVENT, { + integrations: { + [this.key]: { session_id: copy }, + }, + }); } + } - this.sessionTimer = setTimeout( - () => this.resetSession(), - MAX_SESSION_TIME_IN_MS - ); - }; - - // Loads stored session state from persistent storage 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) : undefined; + this.sessionId = storedSessionId != null ? Number(storedSessionId) : -1; this.lastEventTime = - storedLastEventTime != null ? Number(storedLastEventTime) : undefined; + storedLastEventTime != null ? Number(storedLastEventTime) : -1; } - // Persists session state private async saveSessionData() { - if (this.sessionId !== undefined) { - await AsyncStorage.setItem(SESSION_ID_KEY, this.sessionId.toString()); - await AsyncStorage.setItem(LAST_EVENT_TIME_KEY, Date.now().toString()); - } + await AsyncStorage.setItem(SESSION_ID_KEY, this.sessionId.toString()); + await AsyncStorage.setItem( + LAST_EVENT_TIME_KEY, + this.lastEventTime.toString() + ); } - // Handles app going to foreground/background private handleAppStateChange = (nextAppState: string) => { - console.log('nextAppState', nextAppState); if (nextAppState === 'active') { - this.refreshSession(); + this.onForeground(); } else if (nextAppState === 'background') { - this.saveSessionData(); + this.onBackground(); } }; } From d896ef4977610a1eab05cf6ba5433c3c736551a2 Mon Sep 17 00:00:00 2001 From: Sunita Prajapati Date: Wed, 11 Jun 2025 13:08:59 +0530 Subject: [PATCH 3/3] refactor: handled foreground and background app state --- .../src/AmplitudeSessionPlugin.tsx | 95 +++++++++---------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/packages/plugins/plugin-amplitudeSession/src/AmplitudeSessionPlugin.tsx b/packages/plugins/plugin-amplitudeSession/src/AmplitudeSessionPlugin.tsx index 02b23445..0cd1099d 100644 --- a/packages/plugins/plugin-amplitudeSession/src/AmplitudeSessionPlugin.tsx +++ b/packages/plugins/plugin-amplitudeSession/src/AmplitudeSessionPlugin.tsx @@ -15,7 +15,6 @@ import { import AsyncStorage from '@react-native-async-storage/async-storage'; import { AppState } from 'react-native'; -//import { AppState } from 'react-native'; const MAX_SESSION_TIME_IN_MS = 300000; const SESSION_ID_KEY = 'previous_session_id'; @@ -29,18 +28,12 @@ export class AmplitudeSessionPlugin extends EventPlugin { active = false; sessionId = -1; lastEventTime = -1; - sessionTimer: ReturnType | undefined; - configure(analytics: SegmentClient): void { + configure = async (analytics: SegmentClient): Promise => { this.analytics = analytics; + await this.loadSessionData(); AppState.addEventListener('change', this.handleAppStateChange); - this.loadSessionData(); - if (this.sessionId === -1) { - this.startNewSession(); - } else { - this.startNewSessionIfNecessary(); - } - } + }; update(settings: SegmentAPISettings, type: UpdateType) { if (type !== UpdateType.initial) { @@ -53,13 +46,14 @@ export class AmplitudeSessionPlugin extends EventPlugin { if (!this.active) { return event; } - await this.startNewSessionIfNecessary(); - let result = event; - if (result.type === EventType.TrackEvent) { - console.log(result.event); + if (this.sessionId === -1 || this.lastEventTime === -1) { + await this.loadSessionData(); } + await this.startNewSessionIfNecessary(); + + let result = event; switch (result.type) { case EventType.IdentifyEvent: result = this.identify(result); @@ -108,12 +102,14 @@ 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 || {}; const existingIntegration = integrations[this.key]; const hasSessionId = @@ -121,64 +117,67 @@ export class AmplitudeSessionPlugin extends EventPlugin { existingIntegration !== null && 'session_id' in existingIntegration; - // If session_id exists, return as is if (hasSessionId) { - return returnEvent; + return event; } - returnEvent.integrations = { - ...integrations, - [this.key]: { - session_id: this.sessionId, + return { + ...event, + integrations: { + ...integrations, + [this.key]: { session_id: this.sessionId }, }, }; - return returnEvent; }; - private onBackground() { + private onBackground = () => { this.lastEventTime = Date.now(); - this.saveSessionData(); - } + }; - private onForeground() { + private onForeground = () => { this.startNewSessionIfNecessary(); - } + }; private async startNewSessionIfNecessary() { const current = Date.now(); - const withinSessionLimit = - current - this.lastEventTime < MAX_SESSION_TIME_IN_MS; - if (this.sessionId >= 0 && withinSessionLimit) { + + 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; } - this.lastEventTime = current; + await this.endSession(); await this.startNewSession(); } private async startNewSession() { this.sessionId = Date.now(); - const copy = this.sessionId; - if (this.analytics) { - this.analytics.track(AMP_SESSION_START_EVENT, { - integrations: { - [this.key]: { session_id: copy }, - }, - }); - } + this.lastEventTime = this.sessionId; await this.saveSessionData(); + + this.analytics?.track(AMP_SESSION_START_EVENT, { + integrations: { + [this.key]: { session_id: this.sessionId }, + }, + }); } private async endSession() { - const copy = this.sessionId; - if (this.analytics) { - this.analytics.track(AMP_SESSION_END_EVENT, { - integrations: { - [this.key]: { session_id: copy }, - }, - }); + if (this.sessionId === -1) { + return; } + + this.analytics?.track(AMP_SESSION_END_EVENT, { + integrations: { + [this.key]: { session_id: this.sessionId }, + }, + }); } private async loadSessionData() {