fix(analytics): resolve sessionId before assigning trackScreen state (v1.12.1+17)

The sessionId getter mints a fresh session on cold start or after a
30-min lapse, and as a side effect nulls _currentScreenName/_currentStepUid.
trackScreen() set those fields before calling the sessionId getter, so the
mint-on-cold-start side effect could immediately null the fields it had
just set, breaking the same-screen idempotency check and firing a
duplicate session_enter right after cold start.

Fix: resolve visitorId and sessionId into local variables before
assigning _currentScreenName/_currentStepUid, and use the locals in the
_enqueue() payload.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 14:58:56 +10:00
co-authored by Claude Sonnet 5
parent 729822c97e
commit 7c9e730cf0
2 changed files with 21 additions and 4 deletions
+20 -3
View File
@@ -57,7 +57,13 @@
/// `flutter_screen_views` row per screen; this is a Phase-1 backend gap, /// `flutter_screen_views` row per screen; this is a Phase-1 backend gap,
/// not a client bug, and applies identically here). /// not a client bug, and applies identically here).
/// ///
/// Version: 1.0.0 | Created: 2026-09-25 /// Version: 1.0.1 | Created: 2026-09-25 | Updated: 2026-09-25
/// v1.0.1: trackScreen() resolved visitorId/sessionId into locals
/// (resolvedVisitorId/resolvedSessionId) BEFORE assigning
/// _currentScreenName/_currentStepUid, so the sessionId getter's
/// mint-on-cold-start side effect (which nulls those fields) can no
/// longer race the same-screen idempotency check and cause a duplicate
/// session_enter right after cold start.
library; library;
import 'dart:async'; import 'dart:async';
@@ -365,6 +371,17 @@ class SessionJourney extends WidgetsBindingObserver {
return; return;
} }
_leaveCurrentScreen(); _leaveCurrentScreen();
// Resolve visitorId/sessionId BEFORE assigning _currentScreenName:
// sessionId's getter mints (and persists) a fresh session on a lapsed
// or cold window, and as a documented side effect of that mint it
// nulls _currentScreenName/_currentStepUid so the next trackScreen()
// re-enters cleanly under the new session. Reading it after already
// setting _currentScreenName below would immediately undo that
// assignment on every cold start, breaking this method's own
// same-screen idempotency and causing a duplicate session_enter on
// the very next call for the same screen. Found and fixed 2026-09-25.
final resolvedVisitorId = visitorId;
final resolvedSessionId = sessionId;
final stepUid = newUuidV4(); final stepUid = newUuidV4();
_currentScreenName = screenName; _currentScreenName = screenName;
_currentStepUid = stepUid; _currentStepUid = stepUid;
@@ -373,8 +390,8 @@ class SessionJourney extends WidgetsBindingObserver {
_enqueue({ _enqueue({
'action': 'session_enter', 'action': 'session_enter',
'appKey': sessionJourneyAppKey, 'appKey': sessionJourneyAppKey,
'visitorId': visitorId, 'visitorId': resolvedVisitorId,
'sessionId': sessionId, 'sessionId': resolvedSessionId,
'stepUid': stepUid, 'stepUid': stepUid,
'platform': kIsWeb ? 'web' : 'unknown', 'platform': kIsWeb ? 'web' : 'unknown',
'appVersion': appVersion, 'appVersion': appVersion,
+1 -1
View File
@@ -1,7 +1,7 @@
name: m8chat_app name: m8chat_app
description: "M8Chat — Matrix chat client for Android, iOS, and Web." description: "M8Chat — Matrix chat client for Android, iOS, and Web."
publish_to: 'none' publish_to: 'none'
version: 1.12.0+16 version: 1.12.1+17
environment: environment:
sdk: '>=3.11.0 <4.0.0' sdk: '>=3.11.0 <4.0.0'