fix(amplitude-session): fix 0-second sessions on iOS#1169
Open
fix(amplitude-session): fix 0-second sessions on iOS#1169
Conversation
Fix AmplitudeSessionPlugin producing 0-second sessions (session_start + session_end at the same timestamp) on iOS. Root cause: the plugin ignored iOS's `inactive` AppState, so the `active → inactive` transition before backgrounding was treated as a no-op, causing stale lastEventTime and immediate session expiration on resume. Changes: - Handle iOS `inactive` AppState: track `_previousAppState` and treat `inactive` the same as `background` for session timeout purposes, matching the core library's pattern in analytics.ts - Await `onForeground`: was fire-and-forget, so session transitions could race with incoming events - Replace `resetPending` flag with `_sessionTransition` promise: the flag was cleared asynchronously via the event pipeline when `session_start` flowed back through `track()`, creating a race window. The promise lets concurrent callers await the same transition. - Remove dual-ID concept (`eventSessionId` / `_eventSessionId`): the v0.4.2 rewrite added a separate `eventSessionId` for enrichment, but `track()` overwrote it when session events flowed back through the pipeline after the transition completed. With the transition promise serializing access, no event can be enriched mid-transition, making the second ID unnecessary. Removed `EVENT_SESSION_ID_KEY` from persistence as well. - Fix `track()` to preserve existing `session_id`: when session events (session_start/session_end) flow back through `track()`, preserve the session_id already set by `performSessionTransition` instead of overwriting it with the current sessionId. - Fix `reset()` to fire `session_end` before clearing state, matching the Swift SDK behavior. - Batch persistence: use `AsyncStorage.multiSet` in transitions and `multiRemove` in reset instead of individual fire-and-forget writes. - Remove 3 unconditional `console.log` debug statements and a dead `//await this.saveSessionData()` comment. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fix AmplitudeSessionPlugin producing 0-second sessions (
session_start+session_endat the same timestamp) on iOS. Addresses customer escalation from v0.4.2 rewrite (PR #1104).Root cause: The plugin ignored iOS's
inactiveAppState. On iOS, the lifecycle isactive → inactive → background, but the plugin only handledbackground, so theactive → inactivetransition was a no-op. This meantlastEventTimewas never updated on backgrounding, causing immediate session expiration when the app resumed.Changes
Handle iOS
inactiveAppState (primary fix)Track
_previousAppStateand treatinactivethe same asbackgroundfor session timeout, matching the core library's pattern atanalytics.ts:771. Without this, every iOS background/foreground cycle produces a spurioussession_end+session_start.Await
onForegroundonForegroundwas fire-and-forget, so the session transition could race with incoming events. Now awaited.Replace
resetPendingwith_sessionTransitionpromiseThe
resetPendingflag was cleared asynchronously via the event pipeline whensession_startflowed back throughtrack(). Between the flag being set and cleared, a race window existed where concurrentexecute()calls could skip the transition check. The promise lets concurrent callers await the same in-flight transition.Remove dual-ID concept (
eventSessionId)The v0.4.2 rewrite added
eventSessionIdas a separate ID for event enrichment, intended to let events keep the old session ID during transitions. Buttrack()overwrote it when session events flowed back through the pipeline — by that point the transition had already completed, soeventSessionIdheld the new value anyway. With the transition promise serializing access (no event can be enriched mid-transition), the second ID is unnecessary. Removed_eventSessionId,EVENT_SESSION_ID_KEY, and related getter/setter.Fix
track()to preserve existingsession_idWhen
session_start/session_endevents flow back throughtrack(), preserve thesession_idalready set byperformSessionTransitioninstead of overwriting it with the currentsessionId.Fix
reset()to firesession_endreset()now firessession_endbefore clearing state, matching the Swift SDK behavior. UsesmultiRemovefor atomic cleanup.Batch persistence
Use
AsyncStorage.multiSetin transitions instead of individual fire-and-forgetsetItemcalls. RemovessessionIdsetter's persistence side-effect (now only persisted inperformSessionTransitionandreset()).Cleanup
Remove 3 unconditional
console.logdebug statements and a dead//await this.saveSessionData()comment left from the v0.4.2 rewrite.Test plan
🤖 Generated with Claude Code