Skip to content

feat: add session tracking#7

Merged
gunoooo merged 8 commits intomainfrom
add-session
Feb 13, 2026
Merged

feat: add session tracking#7
gunoooo merged 8 commits intomainfrom
add-session

Conversation

@gunoooo
Copy link
Contributor

@gunoooo gunoooo commented Feb 12, 2026

Summary

  • Add session tracking to the Flutter SDK
  • SessionService tracks SESSION_START events using WidgetsBindingObserver for lifecycle detection (resumed/paused)
  • Timeout-based session management with configurable sessionTimeoutMs (default 30s, minimum 5s enforced)
  • Push notification tap attributes session via pendingMessageId through NotificationService
  • Add sessionTimeoutMs to ClixConfig

Changes

  • New: lib/src/services/session_service.dartSessionService with WidgetsBindingObserver, SessionEvent enum
  • Modified: lib/src/core/clix.dart — Initialize and start SessionService after NotificationService
  • Modified: lib/src/core/clix_config.dart / .g.dart — Add sessionTimeoutMs field (default 30000)
  • Modified: lib/src/services/notification_service.dart — Set pendingMessageId on push tap for session attribution

Spec

Summary by CodeRabbit

  • New Features
    • Added session management to monitor user activity and maintain session continuity.
    • Session timeout is now configurable (default: 30 seconds).
    • Notifications now propagate message IDs to session tracking so session starts include relevant notification context.
  • Documentation
    • Changelog updated and package version bumped for the new release.

@gunoooo gunoooo self-assigned this Feb 12, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 12, 2026

Walkthrough

Adds session tracking: new SessionService manages sessions via app lifecycle, storage, and EventService; ClixConfig gains sessionTimeoutMs; Clix creates, wires, and starts SessionService after notifications initialize; NotificationService forwards tapped message IDs to SessionService; package bumped to 0.0.4 and changelog updated.

Changes

Cohort / File(s) Summary
Configuration
lib/src/core/clix_config.dart, lib/src/core/clix_config.g.dart
Adds sessionTimeoutMs (default 30000) to ClixConfig and wires JSON key session_timeout_ms for serialization/deserialization.
Session Management
lib/src/services/session_service.dart
Adds SessionService and SessionEvent enum; implements WidgetsBindingObserver, lifecycle resume/pause handling, persisted last-activity, pending message ID handling, start(), setPendingMessageId(), cleanup(), and session timeout enforcement (min 5000ms).
Core Integration
lib/src/core/clix.dart
Creates and wires SessionService during Clix configuration using storageService, eventService, and sessionTimeoutMs; passes it to NotificationService.initialize and starts the session after notification initialization.
Service Integration
lib/src/services/notification_service.dart
Adds SessionService? _sessionService and an optional sessionService parameter to initialize(...); stores the dependency and calls setPendingMessageId(...) when push payloads include a message_id; adjusts initialization to await message handler setup.
Release / Metadata
pubspec.yaml, CHANGELOG.md
Bumps package version to 0.0.4 and adds changelog entry for session tracking with automatic SESSION_START lifecycle events.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add session tracking' accurately and concisely describes the main change: introducing session tracking functionality to the SDK.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add-session

No actionable comments were generated in the recent review. 🎉


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 236ddd03f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@lib/src/services/session_service.dart`:
- Around line 47-54: didChangeAppLifecycleState currently calls the async
helpers _onResumed() and _onPaused() fire-and-forget, which can produce
unhandled Future errors (e.g., from _updateLastActivity()); fix by ensuring the
Futures are observed: either make didChangeAppLifecycleState async and await the
calls, or wrap each call with explicit error handling (e.g.,
_onPaused().catchError(...) or a try/catch inside _onPaused/_onResumed that logs
and swallows/report errors). Update references to _onResumed, _onPaused and
_updateLastActivity so storage write failures are caught and logged instead of
becoming unhandled Future exceptions.
🧹 Nitpick comments (3)
lib/src/services/session_service.dart (3)

32-45: Duplicate session-resume logic between start() and _onResumed().

Both methods contain the same "check last activity → continue or start new session" pattern. Extract a shared helper to reduce duplication and keep the logic consistent.

♻️ Proposed refactor
  Future<void> start() async {
    WidgetsBinding.instance.addObserver(this);
-
-    final lastActivity = await _storageService.get<int>(_lastActivityKey);
-    if (lastActivity != null) {
-      final elapsed = DateTime.now().millisecondsSinceEpoch - lastActivity;
-      if (elapsed <= _effectiveTimeoutMs) {
-        await _updateLastActivity();
-        ClixLogger.debug('Continuing existing session');
-        return;
-      }
-    }
-    await _startNewSession();
+    await _resumeOrStartSession();
  }

  Future<void> _onResumed() async {
    // Small delay to allow notification tap handlers to set pendingMessageId
    await Future.delayed(const Duration(milliseconds: 100));
+    await _resumeOrStartSession();
+  }

+  Future<void> _resumeOrStartSession() async {
     final lastActivity = await _storageService.get<int>(_lastActivityKey);
     if (lastActivity != null) {
       final elapsed = DateTime.now().millisecondsSinceEpoch - lastActivity;
       if (elapsed <= _effectiveTimeoutMs) {
         await _updateLastActivity();
+        ClixLogger.debug('Continuing existing session');
         return;
       }
     }
     await _startNewSession();
   }

Also applies to: 60-73


60-62: The 100 ms artificial delay is fragile.

The comment explains the delay allows notification tap handlers to set pendingMessageId, but 100 ms is an arbitrary race-condition workaround with no guarantee. On slow devices or under load, the tap handler may not have executed yet; on fast paths, it adds unnecessary latency. Consider documenting this tradeoff more explicitly or exploring a more deterministic coordination mechanism in the future.


1-99: No removeObserver / teardown method.

addObserver(this) is called in start() but there is no corresponding removeObserver path. If the SDK is ever re-initialized (e.g., during testing or hot-restart scenarios), the old observer remains registered.

♻️ Proposed addition
+ void dispose() {
+   WidgetsBinding.instance.removeObserver(this);
+ }

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@lib/src/services/session_service.dart`:
- Around line 14-29: SessionService registers itself as a WidgetsBindingObserver
in start() but never removes itself; add a public dispose() method on
SessionService that calls WidgetsBinding.instance.removeObserver(this) (and any
other cleanup like cancelling timers or clearing _pendingMessageId if needed)
and ensure callers tear down the service by invoking dispose(); reference the
SessionService class and its start() method to locate where addObserver(this) is
called and add the corresponding removeObserver(this) call in the new dispose()
method.
🧹 Nitpick comments (2)
lib/src/services/session_service.dart (2)

31-44: Duplicate session-check logic between start() and _onResumed().

Lines 34–42 and 63–71 contain identical session-continuation logic. Extract a shared helper to reduce duplication and keep the two paths consistent.

♻️ Proposed refactor
+ Future<bool> _shouldContinueSession() async {
+   final lastActivity = await _storageService.get<int>(_lastActivityKey);
+   if (lastActivity != null) {
+     final elapsed = DateTime.now().millisecondsSinceEpoch - lastActivity;
+     if (elapsed <= _effectiveTimeoutMs) {
+       await _updateLastActivity();
+       return true;
+     }
+   }
+   return false;
+ }
+
  Future<void> start() async {
    WidgetsBinding.instance.addObserver(this);
-
-   final lastActivity = await _storageService.get<int>(_lastActivityKey);
-   if (lastActivity != null) {
-     final elapsed = DateTime.now().millisecondsSinceEpoch - lastActivity;
-     if (elapsed <= _effectiveTimeoutMs) {
-       await _updateLastActivity();
-       ClixLogger.debug('Continuing existing session');
-       return;
-     }
+   if (await _shouldContinueSession()) {
+     ClixLogger.debug('Continuing existing session');
+     return;
    }
    await _startNewSession();
  }

Apply the same extraction to _onResumed().

Also applies to: 59-72


61-61: Hardcoded 100ms delay is a fragile race-condition workaround.

The comment explains the intent, but a fixed delay doesn't guarantee the notification tap handler has completed — it could be too short on slow devices or unnecessarily long otherwise. Consider a more deterministic approach, such as having NotificationService set the pending message ID before the lifecycle event fires, or using a Completer/signal that _onResumed can await.

박건우 and others added 5 commits February 12, 2026 15:35
_handleInitialMessage() was called without await in _setupMessageHandlers(),
causing a race where sessionService.start() could fire SESSION_START before
the initial notification set pendingMessageId on cold-start push tap.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
didChangeAppLifecycleState is synchronous but calls async _onResumed()
and _onPaused(). Wrap both in try-catch to prevent unhandled Future errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents memory leak and stale callbacks when SessionService is torn down.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@lib/src/services/session_service.dart`:
- Around line 31-45: Wrap the SDK operations inside start() in a try/catch that
catches ClixError (and optionally any other errors you want to handle) to
prevent exceptions from bubbling out during initialization; specifically,
surround calls to _storageService.get<int>(_lastActivityKey),
_updateLastActivity(), and _startNewSession() with the try/catch, and in the
catch block log the error via ClixLogger.error (including the error details) and
abort/return gracefully so startup doesn't throw unhandled ClixError exceptions.

Prevent errors from bubbling out during SDK initialization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gunoooo gunoooo merged commit a692fb6 into main Feb 13, 2026
3 checks passed
@gunoooo gunoooo deleted the add-session branch February 13, 2026 04:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant