Skip to content

Conversation

@AndreaDiazCorreia
Copy link
Member

@AndreaDiazCorreia AndreaDiazCorreia commented Nov 16, 2025

This PR implements Firebase Cloud Messaging (FCM) for push notifications in Mostro Mobile. The implementation migrates from WebSocket listeners to a polling-based architecture using Firebase Cloud Functions.

Implementation Details

  • Migrated FCM architecture from WebSocket listeners to polling-based approach
  • Deployed Firebase Cloud Functions to handle push notification delivery
  • Integrated FCM service with background notification system
  • Fixed background and FCM service configurations

Current Status

⚠️ Needs more testing and bug fixes before production deployment

Important Considerations

Firebase Project Setup

  • Current: Using a test Firebase project for development
  • Required: Create a production Firebase project for Mostro that is not centralized under a single developer account
  • Action Needed: Establish proper organizational ownership for the production Firebase project

Cost & Infrastructure Evaluation

Firebase Cloud Functions provide a quick implementation path, but come with important considerations:

Pros:

  • Fast deployment and iteration
  • Managed infrastructure

Cons:

  • Requires credit card - Firebase Functions billing starts based on usage
  • Centralized dependency on Google's infrastructure
  • Ongoing costs need evaluation and monitoring

Alternative Architecture

For decentralization and cost control, consider deploying a self-hosted push server:

Proposed Architecture:

  • Custom server connects to Nostr relay(s) and Firebase
  • Deploy independently from Firebase Functions
  • Full control over infrastructure and costs

Work in Progress:

Summary by CodeRabbit

  • New Features

    • Silent FCM wake-ups with background handler, foreground processing, on-demand test/status endpoints, and privacy-preserving push registration.
    • Local notification processing with deduplication and pending-event handling on resume/startup.
  • Chores

    • Firebase/FCM platform configuration and Android build integration; Cloud Functions and Node/TypeScript scaffolding for polling and silent pushes.
    • Client FCM service and push-registration lifecycle integration.
  • Documentation

    • Added Firebase setup, FCM implementation, and FCM testing guides.

✏️ Tip: You can customize this high-level summary in your review settings.

AndreaDiazCorreia and others added 15 commits November 3, 2025 19:07
- Add Lightning address usage detection in HoldInvoicePaymentAccepted
      - Search previous order confirmation messages for buyerInvoice
      - Display lightningAddressUsed notification for buy order makers
      - Use existing notification system for consistent UX
      - Only applies to buyers who used their configured Lightning address
#354)

* feat: improve countdown timer with order_expires_at and dynamic scaling in pending orders

  - Use exact timestamps from order_expires_at tag for precise calculations
  - Add dynamic day/hour scaling: 14d 20h 06m (>24h) or HH:MM:SS (≤24h)
  - Refactor: Create shared DynamicCountdownWidget to remove duplication
  - Apply consistent behavior across TakeOrderScreen and TradeDetailScreen

* fix: use int.tryParse for order_expires_at parsing and update documentation

* fix: safe parsing for order countdown timestamps

* fix: prevent countdown crashes with zero totals from short orders

* update documentation

* coderabbit suggestion

* use expires_at
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 16, 2025

Walkthrough

Adds Firebase/FCM integration: project config and platform Firebase options, client FCM service and background handler, push registration/encryption service, Cloud Functions poller to send data-only silent pushes, notification deduplication storage, lifecycle hooks to process pending pushes, Gradle/plugin updates, TypeScript/Dart configs, and documentation.

Changes

Cohort / File(s) Summary
Firebase Project Configuration
​.firebaserc, firebase.json, android/app/google-services.json
Adds Firebase project mapping, firebase.json, and Android google-services.json client config.
Gradle & Gitignore
android/settings.gradle, android/app/build.gradle, .gitignore
Declares Google Services Gradle plugin (com.google.gms.google-services) and updates .gitignore with Cloud Functions ignores and explanatory comments.
Cloud Functions (TypeScript)
functions/package.json, functions/src/index.ts, functions/tsconfig.json, functions/tsconfig.dev.json, functions/.eslintrc.js, functions/.gitignore
Adds TypeScript Firebase Functions: scheduled keepAlive poller of Nostr relays (kind=1059), HTTP test/status endpoints, aggregated event counting, and data-only FCM sends; includes build/lint configs.
Flutter Firebase SDK & Options
lib/firebase_options.dart, pubspec.yaml
Adds generated DefaultFirebaseOptions and adds firebase_core and firebase_messaging dependencies.
FCM Integration (client)
lib/services/fcm_service.dart, lib/main.dart, lib/shared/providers/app_init_provider.dart
New FCMService for init/permissions/token/topic lifecycle, background message handler (vm entry-point), and app init wiring to register background handler and initialize FCM where supported.
Background Notification Processing
lib/features/notifications/services/background_notification_service.dart, lib/data/repositories/notification_state_storage.dart, lib/background/background.dart
Adds fetchAndProcessNewEvents, improves recipient extraction/session matching for kind=1059, introduces NotificationStateStorage for dedupe, marks old handler deprecated, and documents background entrypoint.
Push Registration & Server Integration
lib/services/push_notification_service.dart, lib/shared/providers/push_notification_provider.dart, lib/shared/notifiers/session_notifier.dart
Adds PushNotificationService implementing ECDH/HKDF/ChaCha20-Poly1305 token encryption and HTTP registration/unregistration; provider and session notifier wiring to auto-register/unregister tokens per session.
App Lifecycle & Pending-Fetch Handling
lib/services/lifecycle_manager.dart, lib/core/app.dart
Adds lifecycle observer hooks to process pending silent-push fetches on resume/foreground, SharedPreferences flag handling and guarded fetch flow.
Docs & Guides
docs/FIREBASE_SETUP.md, docs/FCM_IMPLEMENTATION.md, docs/FCM_TESTING_GUIDE.md
Adds setup, implementation, and testing guides for the Firebase/FCM silent-push architecture and procedures.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant CF as Cloud Function (keepAlive)
    participant NR as Nostr Relays
    participant FCM as Firebase Cloud Messaging
    participant Device as Mobile Device
    participant SP as SharedPreferences

    Note over CF: Scheduled poll (keepAlive)
    CF->>NR: Open WS, REQ kind=1059 since lastCheckTimestamp
    NR-->>CF: Events per relay
    CF->>CF: Aggregate events, update lastCheckTimestamp

    alt New events found
        CF->>FCM: Send data-only silent push to topic
        FCM-->>Device: Deliver silent push
        Device->>SP: Set fcm.pending_fetch = true
    else No new events
        CF->>CF: Log zero events
    end
Loading
sequenceDiagram
    autonumber
    participant App as Mobile App
    participant LM as Lifecycle Manager / App
    participant Settings as Settings Service
    participant BG as Background Notification Service
    participant NR as Nostr Relays
    participant DB as notification_state Store
    participant SP as SharedPreferences

    App->>LM: App resumes (foreground)
    LM->>SP: Read fcm.pending_fetch
    alt pending_fetch == true
        LM->>Settings: Read relays
        alt relays configured
            LM->>BG: fetchAndProcessNewEvents(relays)
            loop per session
                BG->>NR: Fetch kind=1059 since last timestamp
                NR-->>BG: Return events
                BG->>DB: isProcessed(eventId)?
                alt not processed
                    BG->>BG: Decrypt/extract recipient, show local notification
                    DB->>DB: markAsProcessed(eventId)
                end
            end
            BG->>SP: Update timestamps
            LM->>SP: Clear fcm.pending_fetch
        else No relays configured
            LM->>LM: Log & skip
        end
    else
        LM->>LM: Continue normal resume
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Focus review on:
    • functions/src/index.ts — WebSocket relay polling, timestamp handling, aggregation, and FCM payload correctness.
    • lib/features/notifications/services/background_notification_service.dart — recipient extraction (p-tags), session matching, per-session timeouts, dedupe handling.
    • lib/services/fcm_service.dart — permission flows, token lifecycle, topic subscription idempotency, background/foreground handlers.
    • lib/services/push_notification_service.dart — cryptographic correctness (ECDH/HKDF/ChaCha20) and payload formatting.
    • Cross-cutting SharedPreferences flag flows and lifecycle interactions in lib/main.dart, lib/services/lifecycle_manager.dart, and lib/core/app.dart.

Possibly related PRs

Suggested reviewers

  • grunch
  • Catrya

Poem

🐇 I nibble code beneath the moon,

I poll the relays and hum a tune.
A silent push, a gentle hop,
Wake sleepy phones, then softly stop.
Hooray — the bits and bunnies swap!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'FCM implementation for push notification' is directly related to the main change of the PR: implementing Firebase Cloud Messaging (FCM) for push notifications across the Mostro Mobile app.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch andrea/FCM-impl

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@AndreaDiazCorreia
Copy link
Member Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 16, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@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: 9

🧹 Nitpick comments (13)
lib/data/models/nostr_event.dart (1)

42-42: New expiresAt getter is consistent with existing tag accessors

Exposing the raw expires_at tag via expiresAt fits nicely with the other tag-based getters and keeps parsing concerns in higher layers (e.g., Order.fromEvent). If you find yourself repeatedly converting this to DateTime, consider adding a parallel typed getter later for symmetry with expirationDate, but it’s not required for this PR.

lib/features/order/notfiers/abstract_mostro_notifier.dart (1)

267-294: Lightning-address usage detection works but may double‑notify; confirm protocol assumptions

This post‑holdInvoicePaymentAccepted check for a newOrder message with a buyerInvoice that looks like a Lightning address is reasonable and safely wrapped in a try/catch. Two things worth verifying:

  1. Double notification risk for buyers:
    _handleAddInvoiceWithAutoLightningAddress already shows lightningAddressUsed when a default Lightning address is auto‑sent. With this new block, the same buyer flow could see that message again when the hold invoice is accepted. If that’s not desired, you might want a small guard (e.g., a flag in session or storage) to ensure the message is only shown once per order.

  2. Semantics of buyerInvoice:
    This logic assumes buyerInvoice contains a Lightning address (matching user@domain), not just a BOLT11 invoice. If in some cases it is a standard invoice string, _isValidLightningAddress will always fail and the notification will never fire. Please confirm that on the Mostro side buyerInvoice is indeed populated with the Lightning address in the scenarios you’re targeting.

If both points align with the intended UX/protocol, the implementation here looks good.

PLAN_FCM.md (1)

1-108: Planning document language consideration.

This planning document is written in Spanish, while the coding guidelines specify that all code comments and documentation should be in English. Although this is a planning document rather than code comments, for consistency across the project and to ensure all team members can easily access the documentation, consider creating an English version or translating this document.

The content itself is comprehensive and well-structured, covering all critical aspects of FCM implementation including security, architecture, phases, and important considerations about Firebase project setup and costs.

lib/data/models/order.dart (1)

184-199: Safer expiresAt parsing from Nostr event looks good

Using int.tryParse(event.expiresAt!) with a null fallback avoids runtime cast errors when the tag is missing or malformed and keeps Order.expiresAt consistent with the JSON parsing logic. If you ever need observability around bad expires_at tags, consider adding logging at the extension level, but that’s optional for now.

functions/.eslintrc.js (1)

1-33: TypeScript ESLint setup for Cloud Functions is sensible

Config looks good: Node env, TS parser with project set, and ignoring /lib and /generated will keep linting focused on sources. This should play nicely with your tsconfig.json/tsconfig.dev.json.

One optional hardening tweak: if you ever run ESLint from the monorepo root instead of the functions directory, adding tsconfigRootDir: __dirname under parserOptions helps avoid “failed to load parserOptions.project” path issues:

parserOptions: {
  project: ["tsconfig.json", "tsconfig.dev.json"],
  tsconfigRootDir: __dirname,
  sourceType: "module",
},
lib/features/trades/screens/trade_detail_screen.dart (2)

87-93: Passing expiresAtTimestamp in milliseconds is correct; consider createdAt consistency

Using orderPayload.expiresAt! * 1000 to pass a millisecond timestamp into _CountdownWidget matches how Order.createdAt is treated elsewhere (seconds → ms). That keeps the countdown aligned with Nostr/Mostro timestamps and the new Order.fromEvent parsing.

One follow-up to consider in _CountdownWidget (see below): for cases where orderPayload.createdAt is null or 0, you may want the same fallback you use in _buildSellerAmount (session start time or now) instead of implicitly defaulting to the Unix epoch when deriving createdAt.


970-1000: Pending-state countdown now correctly uses event expiration; improve createdAt fallback and tidy the provider usage

The new pending branch in _buildCountDownTime:

  • Properly guards against missing/invalid expiresAtTimestamp and avoids showing a countdown in those cases.
  • Converts expiresAtTimestamp (ms) and tradeState.order?.createdAt (seconds → ms) into DateTimes and feeds them into DynamicCountdownWidget, which aligns with the model changes in Order.fromEvent.

Two small improvements:

  1. Safer createdAt derivation

    Currently:

    final createdAt = DateTime.fromMillisecondsSinceEpoch(
      (tradeState.order?.createdAt ?? 0) * 1000,
    );

    If createdAt is null or 0, this becomes 1970-01-01, which is inconsistent with the rest of the screen where you fall back to session?.startTime ?? DateTime.now(). Consider something along these lines inside _buildCountDownTime:

    final createdAtSeconds = tradeState.order?.createdAt;
    final createdAt = (createdAtSeconds != null && createdAtSeconds > 0)
        ? DateTime.fromMillisecondsSinceEpoch(createdAtSeconds * 1000)
        : DateTime.now();

    Or, if you’d like exact parity with _buildSellerAmount, pass the already-chosen created date down into _CountdownWidget instead of recomputing here.

  2. Unused currentTime from countdownTimeProvider

    In build, you’re using countdownTimeProvider purely as a ticker, but the value itself (currentTime) is unused. To avoid analyzer warnings, you can mark it as unused:

    return timeAsync.when(
      data: (_) {
        // ...
      },
      // ...
    );

    That keeps the intent (drive rebuilds) while keeping the code warning-free.

Overall, the migration to DynamicCountdownWidget for Status.pending is a nice step toward consistent countdown handling across screens.

lib/services/fcm_service.dart (2)

139-171: Manage FCM stream subscriptions in dispose to avoid leaks/duplicates

_setupTokenRefreshListener and _setupForegroundMessageHandler attach listeners to long‑lived streams but their StreamSubscriptions are not stored or cancelled in dispose(). If the service/provider is ever recreated, this can lead to multiple listeners and prevent instances from being GC’d.

Recommend storing both subscriptions in fields and cancelling them in dispose():

+  StreamSubscription<String>? _tokenRefreshSub;
+  StreamSubscription<RemoteMessage>? _foregroundMessageSub;
...
   void _setupTokenRefreshListener() {
-    _messaging.onTokenRefresh.listen(
+    _tokenRefreshSub = _messaging.onTokenRefresh.listen(
       (newToken) {
         _logger.i('FCM token refreshed: ${newToken.substring(0, 20)}...');
         _saveToken(newToken);
       },
       onError: (error) {
         _logger.e('Error in token refresh listener: $error');
       },
     );
   }
...
   void _setupForegroundMessageHandler() {
-    FirebaseMessaging.onMessage.listen(
+    _foregroundMessageSub = FirebaseMessaging.onMessage.listen(
       (RemoteMessage message) {
         ...
       },
       onError: (error) {
         _logger.e('Error in foreground message handler: $error');
       },
     );
   }
...
   void dispose() {
+    _tokenRefreshSub?.cancel();
+    _foregroundMessageSub?.cancel();
     _isInitialized = false;
   }

Also applies to: 209-212


153-170: Consider reducing/guarding foreground message logging for privacy

Logging full message.data and notification title/body on every foreground message may expose user or trade details in production logs. Consider:

  • Lowering log level for payloads (e.g., d only in debug builds), or
  • Logging only derived/high‑level info instead of raw data.

This keeps observability while reducing privacy and noise risks.

lib/shared/widgets/dynamic_countdown_widget.dart (1)

31-35: Confirm UX intent for hiding expirations older than 1 hour

The widget returns SizedBox.shrink() when expiration is more than 1 hour in the past. That’s reasonable if PENDING orders should never legitimately be older than an hour; otherwise users might see no countdown with no indication that the order simply expired.

If there are valid flows with >1h‑ago expirations, consider rendering an explicit “expired” state instead of hiding.

lib/features/notifications/services/background_notification_service.dart (1)

312-418: fetchAndProcessNewEvents flow is coherent; consider a couple of refinements

The silent-push flow is well structured: load sessions → derive since from shared prefs → init Nostr client → fetch kind 1059 events per session (via p tag) → pass through existing notification pipeline → update fcm.last_processed_timestamp. This should play nicely with the FCM topic‑based wake-up.

A few non-blocking refinements to consider:

  • Reuse session data in decryption: _decryptAndProcessEvent reloads sessions again to find a matching session by trade key. Since fetchAndProcessNewEvents already has session in hand, you could extend showLocalNotification / _decryptAndProcessEvent to accept the known Session to avoid extra DB hits per event.
  • Last-processed timestamp semantics: on partial relay failures or transient errors, you update fcm.last_processed_timestamp to now anyway. That can skip some events if they arrive only on a relay that was temporarily failing. If this is a concern, you might:
    • Only advance the timestamp when at least one relay succeeded, or
    • Track per-relay last-processed timestamps.

These are mostly resilience/perf tweaks; the current implementation is functionally consistent.

functions/src/index.ts (1)

223-269: Global lastCheckTimestamp is simple but may miss events on relay failures

keepAlive uses a single in‑memory lastCheckTimestamp shared across all relays and advances it to checkStartTime after each run, even when some relays fail. That’s acceptable if:

  • You rely on a single Mostro relay (current config), and
  • Missing a small window of events in rare failure scenarios is an acceptable trade‑off.

If you plan to add more relays or want stronger guarantees, consider:

  • Tracking per‑relay last timestamps, or
  • Only advancing the global timestamp when all relays succeed (or at least one successfully reports events).

This is an enhancement, not a blocker, but worth deciding explicitly.

lib/features/order/screens/take_order_screen.dart (1)

467-487: LGTM - Robust countdown logic with proper validation.

The implementation correctly:

  • Validates expiresAt and createdAt availability
  • Handles dynamic types safely using toString() + tryParse() (appropriate for Nostr event data)
  • Converts Unix timestamps (seconds) to DateTime (milliseconds)
  • Gracefully handles invalid data by returning an empty widget

Consider adding validation to catch data inconsistencies during development:

  Widget _buildCountDownTime(
      BuildContext context, WidgetRef ref, NostrEvent order) {
    if (order.expiresAt == null) {
      return const SizedBox.shrink();
    }

    final expiresAtSeconds = int.tryParse(order.expiresAt.toString());
    if (expiresAtSeconds == null || expiresAtSeconds <= 0) {
      return const SizedBox.shrink();
    }
    final expiration = DateTime.fromMillisecondsSinceEpoch(expiresAtSeconds * 1000);
    final createdAt = order.createdAt;
    if (createdAt == null) {
      return const SizedBox.shrink();
    }
+   
+   // Optional: Validate timestamp consistency
+   assert(expiration.isAfter(createdAt), 
+     'Expiration ($expiration) must be after creation ($createdAt)');

    return DynamicCountdownWidget(
      expiration: expiration,
      createdAt: createdAt,
    );
  }

This assertion would help catch invalid Nostr event data during development without affecting production builds.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 196289e and f93d269.

⛔ Files ignored due to path filters (2)
  • functions/package-lock.json is excluded by !**/package-lock.json
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (26)
  • .firebaserc (1 hunks)
  • .gitignore (1 hunks)
  • CLAUDE.md (2 hunks)
  • PLAN_FCM.md (1 hunks)
  • android/app/build.gradle (1 hunks)
  • android/settings.gradle (1 hunks)
  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md (4 hunks)
  • firebase.json (1 hunks)
  • functions/.eslintrc.js (1 hunks)
  • functions/.gitignore (1 hunks)
  • functions/package.json (1 hunks)
  • functions/src/index.ts (1 hunks)
  • functions/tsconfig.dev.json (1 hunks)
  • functions/tsconfig.json (1 hunks)
  • lib/data/models/nostr_event.dart (1 hunks)
  • lib/data/models/order.dart (1 hunks)
  • lib/features/notifications/services/background_notification_service.dart (5 hunks)
  • lib/features/order/notfiers/abstract_mostro_notifier.dart (1 hunks)
  • lib/features/order/screens/take_order_screen.dart (4 hunks)
  • lib/features/trades/screens/trade_detail_screen.dart (2 hunks)
  • lib/firebase_options.dart (1 hunks)
  • lib/main.dart (1 hunks)
  • lib/services/fcm_service.dart (1 hunks)
  • lib/shared/providers/app_init_provider.dart (2 hunks)
  • lib/shared/widgets/dynamic_countdown_widget.dart (1 hunks)
  • pubspec.yaml (2 hunks)
🧰 Additional context used
🧠 Learnings (13)
📚 Learning: 2025-05-08T16:29:52.154Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/background/background.dart:74-77
Timestamp: 2025-05-08T16:29:52.154Z
Learning: In the Mostro Mobile background service architecture, events aren't stored by the background process. Instead, the background service only checks if events exist in the eventStore and sends notifications for new ones, while the foreground process is responsible for storing and processing events. This is an intentional design decision to separate concerns.

Applied to files:

  • PLAN_FCM.md
  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, `eventStorageProvider` is exported from `package:mostro_mobile/shared/providers/mostro_service_provider.dart` and not from a separate `event_storage_provider.dart` file.

Applied to files:

  • lib/features/trades/screens/trade_detail_screen.dart
  • lib/shared/providers/app_init_provider.dart
  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-10-14T21:12:06.887Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:141-154
Timestamp: 2025-10-14T21:12:06.887Z
Learning: In the MostroP2P mobile codebase, the notification system uses a two-layer localization pattern: providers/notifiers (without BuildContext access) call `showCustomMessage()` with string keys (e.g., 'orderTimeoutMaker', 'orderCanceled'), and the UI layer's `NotificationListenerWidget` has a switch statement that maps these keys to localized strings using `S.of(context)`. This architectural pattern properly separates concerns while maintaining full localization support for all user-facing messages.

Applied to files:

  • lib/features/trades/screens/trade_detail_screen.dart
  • lib/shared/providers/app_init_provider.dart
  • CLAUDE.md
  • lib/features/order/screens/take_order_screen.dart
  • lib/features/notifications/services/background_notification_service.dart
  • lib/features/order/notfiers/abstract_mostro_notifier.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, Riverpod code generation is used with `Riverpod` annotations. Providers like `eventStorageProvider` are generated in `.g.dart` files from annotated functions in the main provider files. These providers are accessible by importing the main provider file (e.g., `mostro_service_provider.dart`), not by importing a separate provider file.

Applied to files:

  • lib/features/trades/screens/trade_detail_screen.dart
  • lib/shared/providers/app_init_provider.dart
  • lib/features/order/screens/take_order_screen.dart
  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-08-15T01:37:12.243Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 270
File: lib/shared/widgets/order_filter.dart:133-135
Timestamp: 2025-08-15T01:37:12.243Z
Learning: The MostroP2P/mobile project requires Flutter >=3.27.0 as specified in pubspec.yaml, which supports the Color.withValues() method, so usage of withValues() throughout the codebase is valid and should not be flagged as a compatibility issue.

Applied to files:

  • lib/features/trades/screens/trade_detail_screen.dart
  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-10-21T21:47:03.451Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:157-182
Timestamp: 2025-10-21T21:47:03.451Z
Learning: In MostroP2P/mobile, for Action.canceled handling in abstract_mostro_notifier.dart (Riverpod StateNotifier), do not add mounted checks after async sessionNotifier.deleteSession(orderId) as they break order state synchronization during app restart. The Action.canceled flow contains critical business logic that must complete fully; Riverpod handles provider disposal automatically. Mounted checks should only protect UI operations, not business logic in StateNotifiers.

Applied to files:

  • lib/shared/providers/app_init_provider.dart
  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
  • CLAUDE.md
  • lib/features/notifications/services/background_notification_service.dart
  • lib/features/order/notfiers/abstract_mostro_notifier.dart
📚 Learning: 2025-08-21T14:45:43.974Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 272
File: lib/features/relays/widgets/relay_selector.dart:13-15
Timestamp: 2025-08-21T14:45:43.974Z
Learning: In the Mostro mobile app's RelaySelector widget (lib/features/relays/widgets/relay_selector.dart), watching relaysProvider.notifier correctly triggers rebuilds because the relaysProvider itself depends on settingsProvider (line 8 in relays_provider.dart). When blacklist changes via toggleMostroRelayBlacklist(), the settingsProvider updates, causing relaysProvider to rebuild, which then notifies widgets watching the notifier. The UI correctly reflects active/inactive states in real-time through this dependency chain.

Applied to files:

  • lib/shared/providers/app_init_provider.dart
📚 Learning: 2025-06-26T15:03:23.529Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 127
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:45-54
Timestamp: 2025-06-26T15:03:23.529Z
Learning: In AbstractMostroNotifier, state updates occur for all messages regardless of timestamp to hydrate the OrderNotifier from MostroStorage during sync, while handleEvent is only called for recent messages (within 60 seconds) to prevent re-triggering side effects like notifications and navigation for previously handled messages. This design prevents displaying stale notifications when the app is reopened or brought to the foreground.

Applied to files:

  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
  • CLAUDE.md
  • lib/features/notifications/services/background_notification_service.dart
  • lib/features/order/notfiers/abstract_mostro_notifier.dart
📚 Learning: 2025-05-06T15:49:55.079Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:97-103
Timestamp: 2025-05-06T15:49:55.079Z
Learning: In the Mostro protocol, an order cannot be canceled unless it has a valid orderId, so null checks on orderId during cancel operations are unnecessary.

Applied to files:

  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
📚 Learning: 2025-06-04T19:35:20.209Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 110
File: test/notifiers/take_order_notifier_test.dart:72-74
Timestamp: 2025-06-04T19:35:20.209Z
Learning: MostroService methods like takeBuyOrder() and takeSellOrder() return Future<void> and trigger side effects through other mechanisms rather than direct return values. When testing these methods, focus on verifying method calls and testing state changes through the provider system rather than mocking return values.

Applied to files:

  • docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md
  • lib/features/order/screens/take_order_screen.dart
📚 Learning: 2025-08-19T17:54:15.016Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 272
File: lib/features/relays/relays_notifier.dart:41-49
Timestamp: 2025-08-19T17:54:15.016Z
Learning: In the Mostro mobile app relay synchronization system, the hardcoded 'wss://relay.mostro.network' relay in RelaysNotifier._loadRelays() is intentional for bootstrapping. Config.nostrRelays only contains this single active relay anyway (other entries are commented-out dev relays), so hardcoding is functionally equivalent and more explicit about the bootstrap requirement for fetching kind 10002 relay list events.

Applied to files:

  • functions/src/index.ts
📚 Learning: 2025-05-06T15:46:08.942Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/nostr_service.dart:3-4
Timestamp: 2025-05-06T15:46:08.942Z
Learning: The file in dart_nostr library is named "ease.dart" (not "eose.dart" as might be expected), as confirmed by the project maintainer.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-07-20T23:33:17.689Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 215
File: lib/features/order/notfiers/order_notifier.dart:0-0
Timestamp: 2025-07-20T23:33:17.689Z
Learning: In AbstractMostroNotifier, the handleEvent method is synchronous and returns void, not Future<void>. Do not suggest adding await to super.handleEvent() calls as this would cause compilation errors.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
  • lib/features/order/notfiers/abstract_mostro_notifier.dart
🪛 Gitleaks (8.29.0)
lib/firebase_options.dart

[high] 44-44: Uncovered a GCP API key, which could lead to unauthorized access to Google Cloud services and data breaches.

(gcp-api-key)


[high] 53-53: Uncovered a GCP API key, which could lead to unauthorized access to Google Cloud services and data breaches.

(gcp-api-key)


[high] 61-61: Uncovered a GCP API key, which could lead to unauthorized access to Google Cloud services and data breaches.

(gcp-api-key)


[high] 70-70: Uncovered a GCP API key, which could lead to unauthorized access to Google Cloud services and data breaches.

(gcp-api-key)


[high] 79-79: Uncovered a GCP API key, which could lead to unauthorized access to Google Cloud services and data breaches.

(gcp-api-key)

🪛 LanguageTool
PLAN_FCM.md

[grammar] ~1-~1: Corrige la mayúscula.
Context: Plan de Implementación: Firebase Cloud Messaging (FCM) Objetivo...

(QB_NEW_ES_OTHER_ERROR_IDS_REPLACEMENT_ORTHOGRAPHY_UPPERCASE)


[grammar] ~4-~4: Corrige la mayúscula.
Context: ...uridad del sistema actual. Arquitectura Propuesta 1. Modelo de Seguridad FCM Payload: Sol...

(QB_NEW_ES_OTHER_ERROR_IDS_REPLACEMENT_ORTHOGRAPHY_UPPERCASE)


[grammar] ~5-~5: Corrige la mayúscula.
Context: ...al. Arquitectura Propuesta 1. Modelo de Seguridad FCM Payload: Solo event_id + recipient_...

(QB_NEW_ES_OTHER_ERROR_IDS_REPLACEMENT_ORTHOGRAPHY_UPPERCASE)


[grammar] ~7-~7: Agrega un signo de puntuación.
Context: ...mpre en el dispositivo usando trade keys Privacy: Nunca enviar contenido sensible...

(QB_NEW_ES_OTHER_ERROR_IDS_MISSING_PUNCTUATION)


[grammar] ~48-~48: Agrega un signo de puntuación.
Context: ...izerProvider: Agregar inicialización de FCM Solicitar permisos de notificación Obte...

(QB_NEW_ES_OTHER_ERROR_IDS_MISSING_PUNCTUATION)


[grammar] ~49-~49: Agrega un signo de puntuación.
Context: ...ialización de FCM Solicitar permisos de notificación Obtener token inicial Modificar Backgro...

(QB_NEW_ES_OTHER_ERROR_IDS_MISSING_PUNCTUATION)


[grammar] ~50-~50: Agrega un signo de puntuación.
Context: ... permisos de notificación Obtener token inicial Modificar BackgroundNotificationService...

(QB_NEW_ES_OTHER_ERROR_IDS_MISSING_PUNCTUATION)


[grammar] ~101-~101: Cambia la palabra o signo.
Context: ...? Si no existe, habría que implementarlo ¿El backend Mostro está bajo tu control ...

(QB_NEW_ES_OTHER_ERROR_IDS_REPLACEMENT_OTHER)


[grammar] ~103-~103: Elimina el articulo
Context: ... envíe a un backend tuyo o directamente al Mostro instance? ¿Quieres implementar F...

(QB_NEW_ES_OTHER_ERROR_IDS_UNNECESSARY_DETERMINER)


[grammar] ~104-~104: Agrega un signo de puntuación.
Context: ...lytics/Crashlytics también, o solo FCM? Estimación Con backend existente: 2-3 días de desa...

(QB_NEW_ES_OTHER_ERROR_IDS_MISSING_PUNCTUATION)


[grammar] ~106-~106: Aquí puede haber un error.
Context: ...d existente: 2-3 días de desarrollo + 1 día testing Sin backend: +3-5 días para bac...

(QB_NEW_ES)


[grammar] ~106-~106: Se encontraron 2 posibles errores
Context: ...istente: 2-3 días de desarrollo + 1 día testing Sin backend: +3-5 días para backend FCM...

(QB_NEW_ES_OTHER_ERROR_IDS_MERGED_MATCHES___MISSING_ADPOSITION___MISSING_PUNCTUATION)


[grammar] ~107-~107: Agrega un signo de puntuación.
Context: ...ckend: +3-5 días para backend FCM relay listener Complejidad: Media-Alta (manejo de isol...

(QB_NEW_ES_OTHER_ERROR_IDS_MISSING_PUNCTUATION)


[grammar] ~108-~108: Corrige el error ortográfico.
Context: ...d FCM relay listener Complejidad: Media-Alta (manejo de isolates, crypto en backgrou...

(QB_NEW_ES_OTHER_ERROR_IDS_REPLACEMENT_ORTHOGRAPHY_OTHERCASE)

🔇 Additional comments (20)
.firebaserc (1)

1-5: Default Firebase project is test-only; ensure prod switch before release

Using mostro-test as the default project is fine for development, but it’s easy to forget when cutting a production build. Consider documenting or scripting the prod .firebaserc (or using separate configs per env) so release builds can’t accidentally point at the test project.

pubspec.yaml (1)

19-19: Version bump and Firebase deps look coherent; verify FlutterFire compatibility

version: 1.0.4 is a reasonable bump for adding FCM + background changes, and adding firebase_core / firebase_messaging here matches the rest of the PR. Please double‑check that:

  • These versions are compatible with your current Flutter SDK (3.5.x) and Android/iOS Gradle/Xcode stack.
  • firebase_options.dart / platform configs were generated with the same Firebase project and plugin versions.

Running flutter pub outdated and checking the FlutterFire docs against these versions before merging would help avoid subtle build/runtime issues.

Also applies to: 87-90

.gitignore (1)

74-78: Cloud Functions ignore patterns are appropriate

Ignoring functions/node_modules/, compiled functions/lib/, and .runtimeconfig.json is standard for Firebase Functions and avoids noisy or sensitive artifacts in Git. This overlaps slightly with functions/.gitignore, but the redundancy is harmless and helps when working only from the repo root.

functions/.gitignore (1)

1-10: Local functions ignores are sensible and aligned with TS/Node workflow

Ignoring compiled JS (lib/**/*.js*), typings, node_modules/, and *.local matches a standard TypeScript Firebase Functions setup and keeps only source + config in version control.

CLAUDE.md (2)

128-135: LGTM! Method references updated correctly.

The documentation now properly references the explicit method names (startSessionTimeoutCleanup() and startSessionTimeoutCleanupForRequestId()) instead of line ranges, improving clarity and maintainability.


248-259: LGTM! Comprehensive Dynamic Countdown Widget documentation.

The new documentation clearly explains the DynamicCountdownWidget system with its automatic day/hour scaling, data sources, and integration points. This will help developers understand the countdown timer architecture.

docs/architecture/TIMEOUT_DETECTION_AND_SESSION_CLEANUP.md (1)

309-378: LGTM! Excellent architectural documentation for DynamicCountdownWidget.

The new Dynamic Countdown Timer System section provides comprehensive coverage of the widget architecture, including:

  • Clear explanation of automatic scaling between day/hour formats
  • Code examples showing the implementation
  • Integration points for both TakeOrderScreen and TradeDetailScreen
  • Scope and limitations clearly defined

This documentation will be valuable for understanding the countdown timer system.

functions/tsconfig.json (1)

1-17: LGTM! Solid TypeScript configuration for Firebase Functions.

The configuration uses appropriate settings for Firebase Functions:

  • NodeNext modules align with Node 22 engine specified in package.json
  • Strict mode and lint-friendly options (noImplicitReturns, noUnusedLocals) ensure code quality
  • Output to lib directory matches the main entry point in package.json
functions/package.json (2)

17-21: I need to verify if these versions are current. Let me check the latest available versions:

Dependencies are secure and current.

The verification confirms all dependencies are free from known security vulnerabilities:

  • firebase-admin latest version is 13.6.0, while the codebase uses 12.6.0. No known security advisories exist for firebase-admin.
  • firebase-functions latest version is 7.0.0, while the codebase uses 6.0.1. No known security advisories exist for firebase-functions. The version choice may be intentional for compatibility considerations.
  • ws latest version is 8.18.3, and the codebase specifies ^8.18.3, which is the latest available. The dependency is protected against the known HIGH severity DoS vulnerability affecting earlier versions.

All three dependencies are secure and appropriate for use.


13-15: Node.js 22 is officially supported for these Firebase Functions.

Firebase Cloud Functions (v2) supports Node.js 22, and the codebase is confirmed to use Gen2 functions based on the firebase-functions/v2/* imports in functions/src/index.ts and the disallowLegacyRuntimeConfig: true setting in firebase.json. The engine specification is correct and requires no changes.

android/app/build.gradle (1)

1-9: Google Services plugin integration is wired correctly

Adding id 'com.google.gms.google-services' in the plugins block after com.android.application is the expected setup for Firebase/FCM. This should work fine together with the corresponding plugin declaration in settings.gradle and the google-services.json configured via firebase.json.

Please just verify on CI/device that the Gradle sync picks up the generated android/app/google-services.json without additional apply plugin statements at the bottom of the file.

firebase.json (1)

1-39: Firebase CLI config is consistent with the test project / Functions setup

The flutter section correctly wires the Android google-services.json output and lib/firebase_options.dart platform mappings to the mostro-test project. The functions block with codebase: "default" and the ignore patterns is standard for a single Cloud Functions codebase living under functions/.

Since this is pointed at the test project, ensure that:

  • .firebaserc clearly separates dev/test vs future prod projects, and
  • your release pipeline explicitly targets the prod project before shipping to users.

Functionally, this file is good to go for the current test environment.

lib/main.dart (1)

76-86: Firebase init ordering and background handler registration in main() look good

Calling WidgetsFlutterBinding.ensureInitialized(); first, then await Firebase.initializeApp(...), and only then registering FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); is a clean startup sequence. This should keep FCM happy across cold starts and app restarts.

lib/features/trades/screens/trade_detail_screen.dart (1)

26-27: Dynamic countdown import is consistent with new pending-order UX

The new dynamic_countdown_widget.dart import fits with the pending-status refactor below; no issues here.

lib/services/fcm_service.dart (1)

25-65: Initialization flow and failure handling look solid

The init sequence (permissions → token fetch/persist → topic subscription → listeners) is consistent and fails gracefully without breaking app startup. Good use of timeouts and non‑fatal logging around FCM initialization.

lib/features/notifications/services/background_notification_service.dart (1)

420-508: Legacy processFCMBackgroundNotification is safely deprecated and reuses new pipeline

The legacy background handler is clearly marked @Deprecated and still routes fetched events through the central showLocalNotification path, so it benefits from the new dedup logic without diverging behavior.

Once all clients are migrated to the silent-push path, you can consider:

  • Removing this function and its calling wiring, and
  • Cleaning up any now-unused FCM payload fields (eventId, recipientPubkey) on the backend.
functions/src/index.ts (1)

54-142: pollRelay WebSocket lifecycle and timeout handling look robust

The relay polling logic opens a connection, sends a scoped REQ, counts kind‑1059 events, and resolves on EOSE or a 30‑second timeout while ensuring the socket is closed and timeouts are cleared. Using a fixed SUBSCRIPTION_ID per connection is fine here since each WebSocket is isolated and you filter on that ID in the EVENT handler.

No changes needed from a correctness perspective.

lib/features/order/screens/take_order_screen.dart (3)

21-21: LGTM - Centralized countdown widget import.

The import of DynamicCountdownWidget aligns with the PR's goal to centralize countdown rendering across multiple screens.


89-90: LGTM - Widget instantiation updated correctly.

The parameter change from expirationDate: order.expirationDate to order: order is consistent with the widget's new API that extracts expiration directly from the NostrEvent.


445-448: LGTM - Widget API refactored to accept full order object.

The change from accepting DateTime expirationDate to NostrEvent order provides access to both expiresAt and createdAt timestamps, enabling the new DynamicCountdownWidget to render accurate countdowns.

@grunch grunch requested review from Catrya and grunch November 18, 2025 14:32
@grunch
Copy link
Member

grunch commented Nov 18, 2025

Hi @AndreaDiazCorreia I'm not being able to compile it, here the log:

Running Gradle task 'assembleDebug'...

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:processDebugGoogleServices'.
> File google-services.json is missing. The Google Services Plugin cannot function without it. 
   Searched Location: 
  /home/negrunch/dev/mobile/android/app/src/debug/google-services.json
  /home/negrunch/dev/mobile/android/app/src/google-services.json
  /home/negrunch/dev/mobile/android/app/src/Debug/google-services.json
  /home/negrunch/dev/mobile/android/app/google-services.json


* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.

BUILD FAILED in 7s

@AndreaDiazCorreia AndreaDiazCorreia marked this pull request as ready for review November 27, 2025 08:31
@AndreaDiazCorreia
Copy link
Member Author

@grunch @Catrya @BraCR10 en debug las notificaciones background me andaban bien pero en release me falla (capaz pise algo y no me di cuenta), si pueden prueben y me dicen.

Para probar la implementacion de FCM es con la app en release porque tienen que poder cerrarla por completo y que FCM cuando el backend detecte algun evento la despierte.

Copy link
Contributor

@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: 3

♻️ Duplicate comments (4)
functions/src/index.ts (2)

14-14: Global state is not persisted across Cloud Function instances.

Cloud Functions are stateless—each invocation may run on a different instance, and instances can be cold-started at any time. The global lastCheckTimestamp will reset to Date.now() on each cold start, potentially causing missed events or duplicate processing.

Persist lastCheckTimestamp in Firestore:

-let lastCheckTimestamp = Math.floor(Date.now() / 1000);
+const stateRef = admin.firestore().collection("config").doc("pollerState");
+
+async function getLastCheckTimestamp(): Promise<number> {
+  const doc = await stateRef.get();
+  return doc.exists ? (doc.data()?.lastCheckTimestamp ?? Math.floor(Date.now() / 1000)) : Math.floor(Date.now() / 1000);
+}
+
+async function setLastCheckTimestamp(ts: number): Promise<void> {
+  await stateRef.set({ lastCheckTimestamp: ts }, { merge: true });
+}

Then update keepAlive to use these functions instead of the global variable.


140-160: HTTP endpoints lack authentication.

sendTestNotification and getStatus are publicly accessible. While getStatus only exposes configuration, sendTestNotification could be abused to spam all subscribed devices.

Consider removing these before production or adding authentication:

-export const sendTestNotification = onRequest(async (_req, res) => {
+export const sendTestNotification = onRequest(async (req, res) => {
+  const authHeader = req.headers.authorization;
+  if (!authHeader || authHeader !== `Bearer ${process.env.ADMIN_SECRET}`) {
+    res.status(401).json({ success: false, error: "Unauthorized" });
+    return;
+  }
lib/main.dart (1)

96-125: Missing reload() and premature flag clearing.

Same issues as in lifecycle_manager.dart:

  1. Line 104 reads SharedPreferences without reload() - may get stale values from before background isolate wrote.
  2. Line 109 clears the flag before processing completes - if fetchAndProcessNewEvents fails, events are lost.
 Future<void> _checkPendingEventsOnResume(
   SharedPreferencesAsync sharedPrefs,
   SettingsNotifier settings,
 ) async {
   final logger = Logger();

   try {
+    await sharedPrefs.reload();
     final hasPending = await sharedPrefs.getBool('fcm.pending_fetch') ?? false;

     if (hasPending) {
       logger.i('Pending events detected - processing now');

-      await sharedPrefs.setBool('fcm.pending_fetch', false);
-
       final relays = settings.settings.relays;
       if (relays.isEmpty) {
         logger.w('No relays configured - cannot fetch events');
+        await sharedPrefs.setBool('fcm.pending_fetch', false);
         return;
       }

       logger.i('Fetching new events from ${relays.length} relays');
       await fetchAndProcessNewEvents(relays: relays);
+      await sharedPrefs.setBool('fcm.pending_fetch', false);
       logger.i('Successfully processed pending events');
     }
   } catch (e, stackTrace) {
     logger.e('Error processing pending events: $e');
     logger.e('Stack trace: $stackTrace');
   }
 }
lib/features/notifications/services/background_notification_service.dart (1)

111-113: retryNotification still never retries because showLocalNotification swallows errors

retryNotification relies on showLocalNotification throwing on failure, but showLocalNotification catches all exceptions and just logs them. As a result:

  • await showLocalNotification(event); almost never throws.
  • success is set to true on the first loop.
  • The backoff/retry path is effectively dead code.

This matches the earlier review concern and remains unresolved.

A minimal fix is to let showLocalNotification rethrow after logging, so retryNotification can actually handle failures:

 Future<void> showLocalNotification(NostrEvent event) async {
   try {
     ...
-    Logger().i('Shown: ${notificationText.title} - ${notificationText.body}');
-  } catch (e) {
-    Logger().e('Notification error: $e');
-  }
+    Logger().i('Shown: ${notificationText.title} - ${notificationText.body}');
+  } catch (e, stackTrace) {
+    Logger().e('Notification error: $e', e, stackTrace);
+    rethrow;
+  }
 }

With this change, retryNotification’s loop and exponential backoff will actually trigger on real failures, both here and at other call sites that wrap showLocalNotification in try/catch.

Also applies to: 285-305

🧹 Nitpick comments (5)
lib/shared/providers/app_init_provider.dart (1)

27-37: Callback logs warning but doesn't use the same logger instance.

The onMessageReceived callback accesses logger from the outer scope, which is correct. However, if relays.isEmpty, the callback returns early without any subsequent action, which is appropriate for the "no relays" scenario.

Consider whether an empty relays list is a recoverable state or if it warrants notifying the user.

lib/main.dart (1)

86-86: Fire-and-forget async call may silently fail.

_checkPendingEventsOnResume is called without await, so any errors are caught internally but the main thread won't know if processing succeeded. This is acceptable since:

  • The function has its own try/catch
  • Blocking runApp for event processing could delay UI

However, consider using unawaited() to signal this is intentional and avoid linter warnings:

+import 'dart:async';
...
-  _checkPendingEventsOnResume(sharedPreferences, settings);
+  unawaited(_checkPendingEventsOnResume(sharedPreferences, settings));
lib/services/fcm_service.dart (1)

106-114: Inconsistency in SharedPreferences usage exists but with less urgency than stated.

The codebase does mix SharedPreferences.getInstance() (synchronous cached API) and SharedPreferencesAsync() (asynchronous I/O API), which Flutter documentation advises against due to potential cache inconsistencies across isolates. However, the specific concern in this comment—that a background handler writes with SharedPreferencesAsync while fcm_service reads with SharedPreferences on the same keys—is not accurate:

  • fcm_service.dart accesses the 'fcm_token' key
  • background_notification_service.dart accesses 'fcm.last_processed_timestamp', not 'fcm_token'
  • The FCM background handler (firebaseMessagingBackgroundHandler in main.dart) does not directly access either key

That said, using SharedPreferencesAsync consistently throughout the codebase (as recommended by Flutter best practices) remains a valid refactoring to avoid subtle bugs and ensure consistency. This is best practice advice rather than a bug fix.

lib/features/notifications/services/background_notification_service.dart (2)

143-145: Guard substring(0, 16) to avoid potential RangeError

Today recipient/recipientPubkey are expected to be full public keys, but if malformed/short values ever slip through, substring(0, 16) will throw.

You can make the logging safer with a small guard:

-    if (matchingSession == null) {
-      Logger().d('No matching session found for recipient: ${recipient.substring(0, 16)}...');
-      return null;
-    }
+    if (matchingSession == null) {
+      final preview = recipient.length > 16 ? recipient.substring(0, 16) : recipient;
+      Logger().d('No matching session found for recipient: $preview...');
+      return null;
+    }
-    if (matchingSession == null) {
-      logger.w('No matching session found for recipient: ${recipientPubkey.substring(0, 16)}...');
-      return;
-    }
+    if (matchingSession == null) {
+      final preview = recipientPubkey.length > 16
+          ? recipientPubkey.substring(0, 16)
+          : recipientPubkey;
+      logger.w('No matching session found for recipient: $preview...');
+      return;
+    }

Also applies to: 409-411


307-389: Consider basing last_processed_timestamp on max event createdAt, not now

Using now for fcm.last_processed_timestamp can skip events whose createdAt is earlier than now but which are first seen later (e.g., when adding a new relay). Persisting the maximum createdAt among processed events is a safer watermark.

One possible refinement:

-    int processedCount = 0;
-    final now = DateTime.now();
+    int processedCount = 0;
+    final now = DateTime.now();
+    DateTime latestCreatedAt = since;
@@
-        for (final event in events) {
+        for (final event in events) {
           final nostrEvent = NostrEvent(
             id: event.id,
             kind: event.kind,
             content: event.content,
             tags: event.tags,
             createdAt: event.createdAt,
@@
-          await showLocalNotification(nostrEvent);
-          processedCount++;
+          await showLocalNotification(nostrEvent);
+          processedCount++;
+
+          if (event.createdAt != null && event.createdAt!.isAfter(latestCreatedAt)) {
+            latestCreatedAt = event.createdAt!;
+          }
         }
@@
-    final newTimestamp = (now.millisecondsSinceEpoch / 1000).floor();
+    final cutoff = processedCount > 0 ? latestCreatedAt : now;
+    final newTimestamp = (cutoff.millisecondsSinceEpoch / 1000).floor();

This keeps behavior the same when no events are processed, but advances the watermark only up to the latest event timestamp when new events were actually handled.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5149167 and b9ccfaf.

📒 Files selected for processing (10)
  • .fvmrc (1 hunks)
  • .gitignore (2 hunks)
  • functions/src/index.ts (1 hunks)
  • lib/background/background.dart (1 hunks)
  • lib/features/notifications/services/background_notification_service.dart (4 hunks)
  • lib/firebase_options.dart (1 hunks)
  • lib/main.dart (3 hunks)
  • lib/services/fcm_service.dart (1 hunks)
  • lib/services/lifecycle_manager.dart (5 hunks)
  • lib/shared/providers/app_init_provider.dart (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • lib/firebase_options.dart
  • lib/background/background.dart
  • .gitignore
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/background/background.dart:74-77
Timestamp: 2025-05-08T16:29:52.154Z
Learning: In the Mostro Mobile background service architecture, events aren't stored by the background process. Instead, the background service only checks if events exist in the eventStore and sends notifications for new ones, while the foreground process is responsible for storing and processing events. This is an intentional design decision to separate concerns.
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 272
File: lib/features/relays/relays_notifier.dart:41-49
Timestamp: 2025-08-19T17:54:15.016Z
Learning: In the Mostro mobile app relay synchronization system, the hardcoded 'wss://relay.mostro.network' relay in RelaysNotifier._loadRelays() is intentional for bootstrapping. Config.nostrRelays only contains this single active relay anyway (other entries are commented-out dev relays), so hardcoding is functionally equivalent and more explicit about the bootstrap requirement for fetching kind 10002 relay list events.
📚 Learning: 2025-08-15T01:37:12.243Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 270
File: lib/shared/widgets/order_filter.dart:133-135
Timestamp: 2025-08-15T01:37:12.243Z
Learning: The MostroP2P/mobile project requires Flutter >=3.27.0 as specified in pubspec.yaml, which supports the Color.withValues() method, so usage of withValues() throughout the codebase is valid and should not be flagged as a compatibility issue.

Applied to files:

  • .fvmrc
  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, Riverpod code generation is used with `Riverpod` annotations. Providers like `eventStorageProvider` are generated in `.g.dart` files from annotated functions in the main provider files. These providers are accessible by importing the main provider file (e.g., `mostro_service_provider.dart`), not by importing a separate provider file.

Applied to files:

  • lib/shared/providers/app_init_provider.dart
  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-10-21T21:47:03.451Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:157-182
Timestamp: 2025-10-21T21:47:03.451Z
Learning: In MostroP2P/mobile, for Action.canceled handling in abstract_mostro_notifier.dart (Riverpod StateNotifier), do not add mounted checks after async sessionNotifier.deleteSession(orderId) as they break order state synchronization during app restart. The Action.canceled flow contains critical business logic that must complete fully; Riverpod handles provider disposal automatically. Mounted checks should only protect UI operations, not business logic in StateNotifiers.

Applied to files:

  • lib/shared/providers/app_init_provider.dart
  • lib/main.dart
  • lib/features/notifications/services/background_notification_service.dart
  • lib/services/lifecycle_manager.dart
📚 Learning: 2025-08-19T17:54:15.016Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 272
File: lib/features/relays/relays_notifier.dart:41-49
Timestamp: 2025-08-19T17:54:15.016Z
Learning: In the Mostro mobile app relay synchronization system, the hardcoded 'wss://relay.mostro.network' relay in RelaysNotifier._loadRelays() is intentional for bootstrapping. Config.nostrRelays only contains this single active relay anyway (other entries are commented-out dev relays), so hardcoding is functionally equivalent and more explicit about the bootstrap requirement for fetching kind 10002 relay list events.

Applied to files:

  • functions/src/index.ts
📚 Learning: 2025-07-20T23:33:17.689Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 215
File: lib/features/order/notfiers/order_notifier.dart:0-0
Timestamp: 2025-07-20T23:33:17.689Z
Learning: In AbstractMostroNotifier, the handleEvent method is synchronous and returns void, not Future<void>. Do not suggest adding await to super.handleEvent() calls as this would cause compilation errors.

Applied to files:

  • lib/main.dart
  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-08-21T14:45:43.974Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 272
File: lib/features/relays/widgets/relay_selector.dart:13-15
Timestamp: 2025-08-21T14:45:43.974Z
Learning: In the Mostro mobile app's RelaySelector widget (lib/features/relays/widgets/relay_selector.dart), watching relaysProvider.notifier correctly triggers rebuilds because the relaysProvider itself depends on settingsProvider (line 8 in relays_provider.dart). When blacklist changes via toggleMostroRelayBlacklist(), the settingsProvider updates, causing relaysProvider to rebuild, which then notifies widgets watching the notifier. The UI correctly reflects active/inactive states in real-time through this dependency chain.

Applied to files:

  • lib/main.dart
  • lib/features/notifications/services/background_notification_service.dart
  • lib/services/lifecycle_manager.dart
📚 Learning: 2025-05-08T16:29:52.154Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/background/background.dart:74-77
Timestamp: 2025-05-08T16:29:52.154Z
Learning: In the Mostro Mobile background service architecture, events aren't stored by the background process. Instead, the background service only checks if events exist in the eventStore and sends notifications for new ones, while the foreground process is responsible for storing and processing events. This is an intentional design decision to separate concerns.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
  • lib/services/lifecycle_manager.dart
📚 Learning: 2025-10-14T21:12:06.887Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:141-154
Timestamp: 2025-10-14T21:12:06.887Z
Learning: In the MostroP2P mobile codebase, the notification system uses a two-layer localization pattern: providers/notifiers (without BuildContext access) call `showCustomMessage()` with string keys (e.g., 'orderTimeoutMaker', 'orderCanceled'), and the UI layer's `NotificationListenerWidget` has a switch statement that maps these keys to localized strings using `S.of(context)`. This architectural pattern properly separates concerns while maintaining full localization support for all user-facing messages.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, `eventStorageProvider` is exported from `package:mostro_mobile/shared/providers/mostro_service_provider.dart` and not from a separate `event_storage_provider.dart` file.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
  • lib/services/lifecycle_manager.dart
📚 Learning: 2025-05-08T16:31:29.582Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/notifications/notification_service.dart:54-59
Timestamp: 2025-05-08T16:31:29.582Z
Learning: In the Nostr protocol, event.id will never be null in events returned by relay subscriptions, so null safety checks for this property are unnecessary.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-06-04T19:35:20.209Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 110
File: test/notifiers/take_order_notifier_test.dart:72-74
Timestamp: 2025-06-04T19:35:20.209Z
Learning: MostroService methods like takeBuyOrder() and takeSellOrder() return Future<void> and trigger side effects through other mechanisms rather than direct return values. When testing these methods, focus on verifying method calls and testing state changes through the provider system rather than mocking return values.

Applied to files:

  • lib/services/lifecycle_manager.dart
📚 Learning: 2025-06-26T15:03:23.529Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 127
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:45-54
Timestamp: 2025-06-26T15:03:23.529Z
Learning: In AbstractMostroNotifier, state updates occur for all messages regardless of timestamp to hydrate the OrderNotifier from MostroStorage during sync, while handleEvent is only called for recent messages (within 60 seconds) to prevent re-triggering side effects like notifications and navigation for previously handled messages. This design prevents displaying stale notifications when the app is reopened or brought to the foreground.

Applied to files:

  • lib/services/lifecycle_manager.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (10)
.fvmrc (1)

1-3: Flutter version pin in .fvmrc is consistent with the project baseline

Pinning Flutter to 3.35.7 via FVM is fine and remains above your documented >=3.27.0 requirement; just ensure this stays in sync with the Flutter constraint in pubspec.yaml and any CI/build images so all environments use the same SDK version. Based on learnings, this matches your expected minimum.

functions/src/index.ts (2)

101-127: APNs configuration now follows best practices.

The silent push configuration correctly uses apns-priority: "5", apns-push-type: "background", and contentAvailable: true without alert/badge/sound keys. This aligns with Apple's requirements for background pushes.


162-204: Scheduled function logic is sound.

The keepAlive function correctly:

  • Uses Promise.allSettled to handle partial relay failures gracefully
  • Only sends notifications when new events are found
  • Logs comprehensively for debugging

However, lastCheckTimestamp = checkStartTime at line 200 writes to the ephemeral global variable. This should write to Firestore once persistence is implemented.

lib/shared/providers/app_init_provider.dart (1)

24-41: Good error handling for FCM initialization.

The try/catch wrapper correctly prevents FCM failures from blocking app startup, addressing the previous concern. The error is logged with full stack trace for debugging.

lib/services/lifecycle_manager.dart (1)

56-56: Good placement of pending events check.

Calling _checkPendingFCMEvents() early in _switchToForeground() ensures background-flagged events are processed before other foreground services reinitialize. This prevents race conditions with subscription managers.

lib/main.dart (1)

22-43: Background handler correctly simplified to minimal work.

The handler now only:

  1. Initializes Firebase (required for background isolate)
  2. Sets a flag in SharedPreferences
  3. Records timestamp

This addresses the previous concern about heavy plugin usage in background isolates. Heavy lifting is deferred to foreground/lifecycle handlers.

lib/services/fcm_service.dart (2)

20-62: Well-structured initialization with proper idempotency and error handling.

The initialize() method:

  • Checks _isInitialized to prevent duplicate initialization
  • Follows a clear sequence: permissions → token → topic → listeners
  • Logs comprehensively at each step
  • Uses timeouts for network operations
  • Rethrows errors to let callers decide how to handle failures

174-195: Good defensive error handling in deleteToken().

The nested try/catch ensures local storage cleanup even if the remote token deletion fails. This prevents orphaned local tokens.

lib/features/notifications/services/background_notification_service.dart (2)

121-136: Recipient extraction and session matching logic look solid

The new flow for resolving the recipient (direct field + p tag for kind 1059), validating it, and then matching sessions by tradeKey.public is coherent and should correctly route gift‑wrapped events to the right session. Early returns on missing recipient or session keep decryption costs down.

Also applies to: 139-139


391-471: Deprecated processFCMBackgroundNotification path is clear and consistent

The legacy FCM background path correctly:

  • Resolves the session by recipientPubkey,
  • Initializes NostrService with the provided relays,
  • Fetches the specific 1059 event by ID, and
  • Reuses showLocalNotification to display it.

Given it’s marked deprecated in favor of fetchAndProcessNewEvents, keeping this as a thin compatibility wrapper is reasonable.

Comment on lines 88 to 114
Future<void> _checkPendingFCMEvents() async {
try {
final sharedPrefs = SharedPreferencesAsync();
final hasPending = await sharedPrefs.getBool('fcm.pending_fetch') ?? false;

if (hasPending) {
_logger.i('Pending FCM events detected - processing now');

await sharedPrefs.setBool('fcm.pending_fetch', false);

final settings = ref.read(settingsProvider);
final relays = settings.relays;

if (relays.isEmpty) {
_logger.w('No relays configured - cannot fetch events');
return;
}

_logger.i('Fetching new events from ${relays.length} relays');
await fetchAndProcessNewEvents(relays: relays);
_logger.i('Successfully processed pending FCM events');
}
} catch (e, stackTrace) {
_logger.e('Error processing pending FCM events: $e');
_logger.e('Stack trace: $stackTrace');
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing reload() call and premature flag clearing.

Two issues in _checkPendingFCMEvents:

  1. Missing reload(): When the background isolate writes to SharedPreferences, the foreground may have cached stale values. Call reload() before reading.

  2. Premature flag clearing: The flag is cleared at line 96 before fetchAndProcessNewEvents executes. If processing fails, events won't be retried.

 Future<void> _checkPendingFCMEvents() async {
   try {
     final sharedPrefs = SharedPreferencesAsync();
+    await sharedPrefs.reload();
     final hasPending = await sharedPrefs.getBool('fcm.pending_fetch') ?? false;

     if (hasPending) {
       _logger.i('Pending FCM events detected - processing now');

-      await sharedPrefs.setBool('fcm.pending_fetch', false);
-
       final settings = ref.read(settingsProvider);
       final relays = settings.relays;

       if (relays.isEmpty) {
         _logger.w('No relays configured - cannot fetch events');
+        await sharedPrefs.setBool('fcm.pending_fetch', false);
         return;
       }

       _logger.i('Fetching new events from ${relays.length} relays');
       await fetchAndProcessNewEvents(relays: relays);
+      await sharedPrefs.setBool('fcm.pending_fetch', false);
       _logger.i('Successfully processed pending FCM events');
     }
   } catch (e, stackTrace) {
     _logger.e('Error processing pending FCM events: $e');
     _logger.e('Stack trace: $stackTrace');
+    // Flag remains set - will retry on next resume
   }
 }
🤖 Prompt for AI Agents
In lib/services/lifecycle_manager.dart around lines 88 to 114, the method reads
a potentially stale SharedPreferences value and clears the pending flag too
early; to fix it, await sharedPrefs.reload() before reading
getBool('fcm.pending_fetch'), and move/perform the call to
sharedPrefs.setBool('fcm.pending_fetch', false) only after
fetchAndProcessNewEvents completes successfully (i.e., await
fetchAndProcessNewEvents first, then clear the flag), ensuring any exceptions
prevent the flag from being cleared so failed work will be retried.

Copy link
Contributor

@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

♻️ Duplicate comments (1)
lib/features/notifications/services/background_notification_service.dart (1)

282-302: retryNotification still cannot effectively retry and is currently unused

retryNotification wraps showLocalNotification in a retry loop, but showLocalNotification catches all exceptions and never rethrows (Lines 54–111), so await showLocalNotification(event); will not throw here. As a result:

  • success is set to true on the first iteration.
  • The catch block and exponential backoff never run.
  • There are no call sites for retryNotification yet, so this remains effectively dead code.

Either wire this up properly (by letting showLocalNotification rethrow after logging, so real failures trigger the retry), or remove retryNotification until you have a concrete failure mode to handle.

🧹 Nitpick comments (4)
lib/services/fcm_service.dart (1)

33-67: Clarify whether FCM initialization failures should be fatal

The catch block logs that “App will continue without FCM push notifications” but then rethrows (Line 65). If upstream code doesn’t catch this, transient FCM/network errors could break the app init flow rather than just disabling push.

Consider either:

  • Treating init failures as non-fatal by removing rethrow;, or
  • Keeping the rethrow but updating call sites to explicitly handle FCM init failures, and adjusting the log message to reflect that behavior.
lib/features/notifications/services/background_notification_service.dart (3)

134-137: Avoid reloading sessions for each event in the FCM fetch path

_decryptAndProcessEvent loads all sessions from the database and searches them for a matching tradeKey.public (Lines 134–137, 161–173). In fetchAndProcessNewEvents, you already load sessions once (Line 311) and then fetch events per-session (Lines 343–371), but each showLocalNotification call triggers another _loadSessionsFromDatabase().

To reduce DB and key-manager overhead in the FCM polling path, consider:

  • Passing the already-loaded Session (or a map of recipientPubkey → Session) into _decryptAndProcessEvent, or
  • Adding a variant like _decryptAndProcessEventForSession(event, session) used from fetchAndProcessNewEvents, while keeping the current version for other call sites.

This keeps the behavior the same but avoids repeated DB opens and key derivation in tight loops.

Also applies to: 161-173, 343-371


304-385: Timestamp update strategy in fetchAndProcessNewEvents may skip very old late-arriving events

fetchAndProcessNewEvents reads fcm.last_processed_timestamp (seconds), builds a DateTime since from it, and after processing all sessions sets the new timestamp to now (Lines 320–323, 341, 377–379). This works for most flows, but has a subtle edge case:

  • If an event is created far in the past (low createdAt) but only becomes available after you’ve advanced the stored timestamp to now, subsequent runs with since = newTimestamp will never see it.

A more robust pattern is to advance the checkpoint based on the latest processed event’s createdAt, e.g.:

  • Track maxCreatedAt across all fetched events (default to existing since if none).
  • Store maxCreatedAt (or maxCreatedAt + ε) instead of now.

This keeps the behavior monotonic while reducing the risk of missing very old, late-arriving events.


4-4: Consider aliasing dart_nostr to avoid potential type-name collisions

This file imports both package:dart_nostr/dart_nostr.dart (Line 4) and package:mostro_mobile/data/models/nostr_event.dart (Line 13). If nostr_event.dart also defines a NostrEvent type, you can end up with ambiguous references once you use NostrEvent from both contexts (local model vs. library event), especially as you’re now directly constructing NostrFilter instances (Lines 345–350, 430–433) that conceptually pair with the library’s event type.

To keep things unambiguous and self-documenting, consider:

import 'package:dart_nostr/dart_nostr.dart' as nostr;
// ...
final filter = nostr.NostrFilter(...);

and keep your domain model as NostrEvent from nostr_event.dart. This makes it clear which types come from the protocol library and avoids future naming clashes as the file evolves.

Also applies to: 345-350, 430-433

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9ccfaf and 3439bba.

📒 Files selected for processing (3)
  • lib/features/notifications/services/background_notification_service.dart (4 hunks)
  • lib/services/fcm_service.dart (1 hunks)
  • lib/services/lifecycle_manager.dart (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/services/lifecycle_manager.dart
🧰 Additional context used
🧠 Learnings (10)
📚 Learning: 2025-10-21T21:47:03.451Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:157-182
Timestamp: 2025-10-21T21:47:03.451Z
Learning: In MostroP2P/mobile, for Action.canceled handling in abstract_mostro_notifier.dart (Riverpod StateNotifier), do not add mounted checks after async sessionNotifier.deleteSession(orderId) as they break order state synchronization during app restart. The Action.canceled flow contains critical business logic that must complete fully; Riverpod handles provider disposal automatically. Mounted checks should only protect UI operations, not business logic in StateNotifiers.

Applied to files:

  • lib/services/fcm_service.dart
  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-08T16:29:52.154Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/background/background.dart:74-77
Timestamp: 2025-05-08T16:29:52.154Z
Learning: In the Mostro Mobile background service architecture, events aren't stored by the background process. Instead, the background service only checks if events exist in the eventStore and sends notifications for new ones, while the foreground process is responsible for storing and processing events. This is an intentional design decision to separate concerns.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-07-20T23:33:17.689Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 215
File: lib/features/order/notfiers/order_notifier.dart:0-0
Timestamp: 2025-07-20T23:33:17.689Z
Learning: In AbstractMostroNotifier, the handleEvent method is synchronous and returns void, not Future<void>. Do not suggest adding await to super.handleEvent() calls as this would cause compilation errors.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-10-14T21:12:06.887Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:141-154
Timestamp: 2025-10-14T21:12:06.887Z
Learning: In the MostroP2P mobile codebase, the notification system uses a two-layer localization pattern: providers/notifiers (without BuildContext access) call `showCustomMessage()` with string keys (e.g., 'orderTimeoutMaker', 'orderCanceled'), and the UI layer's `NotificationListenerWidget` has a switch statement that maps these keys to localized strings using `S.of(context)`. This architectural pattern properly separates concerns while maintaining full localization support for all user-facing messages.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, `eventStorageProvider` is exported from `package:mostro_mobile/shared/providers/mostro_service_provider.dart` and not from a separate `event_storage_provider.dart` file.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, Riverpod code generation is used with `Riverpod` annotations. Providers like `eventStorageProvider` are generated in `.g.dart` files from annotated functions in the main provider files. These providers are accessible by importing the main provider file (e.g., `mostro_service_provider.dart`), not by importing a separate provider file.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-06T15:46:08.942Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/nostr_service.dart:3-4
Timestamp: 2025-05-06T15:46:08.942Z
Learning: The file in dart_nostr library is named "ease.dart" (not "eose.dart" as might be expected), as confirmed by the project maintainer.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-08-15T01:37:12.243Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 270
File: lib/shared/widgets/order_filter.dart:133-135
Timestamp: 2025-08-15T01:37:12.243Z
Learning: The MostroP2P/mobile project requires Flutter >=3.27.0 as specified in pubspec.yaml, which supports the Color.withValues() method, so usage of withValues() throughout the codebase is valid and should not be flagged as a compatibility issue.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-08-21T14:45:43.974Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 272
File: lib/features/relays/widgets/relay_selector.dart:13-15
Timestamp: 2025-08-21T14:45:43.974Z
Learning: In the Mostro mobile app's RelaySelector widget (lib/features/relays/widgets/relay_selector.dart), watching relaysProvider.notifier correctly triggers rebuilds because the relaysProvider itself depends on settingsProvider (line 8 in relays_provider.dart). When blacklist changes via toggleMostroRelayBlacklist(), the settingsProvider updates, causing relaysProvider to rebuild, which then notifies widgets watching the notifier. The UI correctly reflects active/inactive states in real-time through this dependency chain.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-08T16:31:29.582Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/notifications/notification_service.dart:54-59
Timestamp: 2025-05-08T16:31:29.582Z
Learning: In the Nostr protocol, event.id will never be null in events returned by relay subscriptions, so null safety checks for this property are unnecessary.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (1)
lib/services/fcm_service.dart (1)

18-22: Stream subscription lifecycle is now handled correctly

The _tokenRefreshSub and _foregroundMsgSub fields are created, any existing subscriptions are cancelled before reassigning in _setupTokenRefreshListener and _setupForegroundMessageHandler, and both are cancelled and nulled in dispose().

This closes the earlier leak/duplicate-handler gap and makes initialize()/dispose() idempotent from a subscription perspective.

Also applies to: 135-173, 208-215

Comment on lines 118 to 143
// Extract recipient from event
String? recipient = event.recipient;

// For kind 1059 (gift-wrapped), recipient might be in 'p' tag
if ((recipient == null || recipient.isEmpty) && event.kind == 1059 && event.tags != null) {
final pTags = event.tags!.where((tag) => tag.isNotEmpty && tag[0] == 'p');
if (pTags.isNotEmpty && pTags.first.length > 1) {
recipient = pTags.first[1];
}
}

if (recipient == null || recipient.isEmpty) {
Logger().d('No recipient found for event ${event.id}');
return null;
}

final sessions = await _loadSessionsFromDatabase();
final matchingSession = sessions.cast<Session?>().firstWhere(
(s) => s?.tradeKey.public == event.recipient,
(s) => s?.tradeKey.public == recipient,
orElse: () => null,
);

if (matchingSession == null) return null;
if (matchingSession == null) {
Logger().d('No matching session found for recipient: ${recipient.substring(0, 16)}...');
return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Guard against substring(0, 16) RangeError when logging recipients

In both _decryptAndProcessEvent (Line 141) and processFCMBackgroundNotification (Line 407), substring(0, 16) is called on values derived from external data (recipient / recipientPubkey). If for any reason those strings are shorter than 16 characters, this will throw RangeError and break background processing.

A small defensive helper avoids that:

-    if (matchingSession == null) {
-      Logger().d('No matching session found for recipient: ${recipient.substring(0, 16)}...');
-      return null;
-    }
+    if (matchingSession == null) {
+      final preview = recipient.length > 16 ? recipient.substring(0, 16) : recipient;
+      Logger().d('No matching session found for recipient: $preview...');
+      return null;
+    }

And similarly for recipientPubkey in processFCMBackgroundNotification:

-    if (matchingSession == null) {
-      logger.w('No matching session found for recipient: ${recipientPubkey.substring(0, 16)}...');
-      return;
-    }
+    if (matchingSession == null) {
+      final preview =
+          recipientPubkey.length > 16 ? recipientPubkey.substring(0, 16) : recipientPubkey;
+      logger.w('No matching session found for recipient: $preview...');
+      return;
+    }

Also applies to: 406-408

🤖 Prompt for AI Agents
lib/features/notifications/services/background_notification_service.dart around
lines 118 to 143 — the logging uses recipient.substring(0, 16) which can throw a
RangeError if recipient is shorter than 16 chars; change the log call to safely
truncate by computing a safe length (min(16, recipient.length)) or use a helper
that returns the first N chars or the whole string when shorter, then log that
safe value; apply the same defensive truncation approach for recipientPubkey in
processFCMBackgroundNotification (around lines 406-408).

{
"project_info": {
"project_number": "679654468306",
"project_id": "mostro-test",
Copy link
Member

Choose a reason for hiding this comment

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

can you remove the test from the name?

"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBAwF5XEuNOC-TQqhLrFJQhNVzbFYSc1sI"
Copy link
Member

@grunch grunch Nov 27, 2025

Choose a reason for hiding this comment

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

it is safe to publish this key?


admin.initializeApp();

const NOSTR_RELAYS = ["wss://relay.mostro.network"];
Copy link
Member

Choose a reason for hiding this comment

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

we have this values on lib/core/config.dart, we should probably have them centralized somewhere

Copy link
Member

@Catrya Catrya left a comment

Choose a reason for hiding this comment

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

@AndreaDiazCorreia compilé la app en release y tambien en debug pero no me sale ninguna notificación.

Tampoco me funciona en linux, me da error de que firebase no se usa en linux, habria que deshabilitarlo para linux porque si no luego no vamos a poder probar desde ahi, aunque es más urgente que funione bien en android

Copy link
Contributor

@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

♻️ Duplicate comments (3)
lib/features/notifications/services/background_notification_service.dart (3)

122-148: Recipient extraction logic looks good, but substring RangeError remains unaddressed.

The new logic to extract recipient from event.recipient or p-tags for kind 1059 events is well-structured. However, the substring(0, 16) call at line 146 can still throw RangeError if recipient is shorter than 16 characters.

Apply the fix from the previous review:

     if (matchingSession == null) {
-      Logger().d('No matching session found for recipient: ${recipient.substring(0, 16)}...');
+      final preview = recipient.length > 16 ? recipient.substring(0, 16) : recipient;
+      Logger().d('No matching session found for recipient: $preview...');
       return null;
     }

398-478: Deprecated function has the same issues.

  1. Substring RangeError at line 417: Same issue as flagged in past review—apply the defensive length check.

  2. NostrService not disposed: Same leak concern as fetchAndProcessNewEvents.

Since this function is deprecated, consider whether it should be removed entirely rather than maintained with bug fixes. If keeping it for backwards compatibility:

     if (matchingSession == null) {
-      logger.w('No matching session found for recipient: ${recipientPubkey.substring(0, 16)}...');
+      final preview = recipientPubkey.length > 16 ? recipientPubkey.substring(0, 16) : recipientPubkey;
+      logger.w('No matching session found for recipient: $preview...');
       return;
     }

291-312: Retry logic remains ineffective.

As noted in the previous review, showLocalNotification catches all exceptions internally and never rethrows, so retryNotification always succeeds on the first attempt. The exponential backoff logic is dead code.

Either:

  • Make showLocalNotification rethrow after logging, or
  • Remove the retry wrapper entirely since it provides no value
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b12336 and 9338e1e.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • .gitignore (2 hunks)
  • android/app/build.gradle (1 hunks)
  • lib/background/background.dart (1 hunks)
  • lib/features/notifications/services/background_notification_service.dart (4 hunks)
  • pubspec.yaml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • android/app/build.gradle
  • pubspec.yaml
  • .gitignore
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{dart,flutter}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{dart,flutter}: Run flutter analyze after any code change - Mandatory before commits to ensure zero linting issues
Run flutter test after any code change - Mandatory before commits to ensure all unit tests pass

Files:

  • lib/features/notifications/services/background_notification_service.dart
  • lib/background/background.dart
**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.dart: Use Riverpod for all state management - encapsulate business logic in Notifiers and access data only through repository classes
All code comments must be in English - use clear, concise English for variable names, function names, and comments
Always check mounted before using BuildContext after async operations to prevent errors on disposed widgets
Use const constructors where possible for better performance and immutability
Remove unused imports and dependencies to maintain code cleanliness and reduce build size

**/*.dart: Application code should be organized under lib/, grouped by domain with lib/features/<feature>/ structure, shared utilities in lib/shared/, dependency wiring in lib/core/, and services in lib/services/
Persistence, APIs, and background jobs should live in lib/data/ and lib/background/; generated localization output must be in lib/generated/ and must stay untouched
Apply flutter format . to enforce canonical Dart formatting (two-space indentation, trailing commas) before committing
Resolve every analyzer warning in Dart code
Name Riverpod providers using the <Feature>Provider or <Feature>Notifier convention
Localize all user-facing strings via ARB files and access them with S.of(context) rather than hard-coded literals

Files:

  • lib/features/notifications/services/background_notification_service.dart
  • lib/background/background.dart
🧠 Learnings (26)
📓 Common learnings
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/background/background.dart:74-77
Timestamp: 2025-05-08T16:29:52.154Z
Learning: In the Mostro Mobile background service architecture, events aren't stored by the background process. Instead, the background service only checks if events exist in the eventStore and sends notifications for new ones, while the foreground process is responsible for storing and processing events. This is an intentional design decision to separate concerns.
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 272
File: lib/features/relays/relays_notifier.dart:41-49
Timestamp: 2025-08-19T17:54:15.016Z
Learning: In the Mostro mobile app relay synchronization system, the hardcoded 'wss://relay.mostro.network' relay in RelaysNotifier._loadRelays() is intentional for bootstrapping. Config.nostrRelays only contains this single active relay anyway (other entries are commented-out dev relays), so hardcoding is functionally equivalent and more explicit about the bootstrap requirement for fetching kind 10002 relay list events.
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/services/nostr_service.dart : Manage all relay connections and Nostr messaging through NostrService - automatically reconnect when relay list updates

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
  • lib/background/background.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/notifiers/**/*.dart : Encapsulate business logic in Notifiers - Notifiers should expose state via providers and handle all complex state transitions

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
  • lib/background/background.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/services/**/*.dart : Access NostrService for all Nostr protocol interactions - NostrService manages relay connections and messaging

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
  • lib/background/background.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/providers/**/*.dart : Use Notifier pattern instead of simple StateNotifier for complex state logic requiring business rule encapsulation

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
  • lib/background/background.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/providers/**/*.dart : Organize Riverpod providers by feature in `features/{feature}/providers/` using Notifier pattern for complex state logic

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-08T16:29:52.154Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/background/background.dart:74-77
Timestamp: 2025-05-08T16:29:52.154Z
Learning: In the Mostro Mobile background service architecture, events aren't stored by the background process. Instead, the background service only checks if events exist in the eventStore and sends notifications for new ones, while the foreground process is responsible for storing and processing events. This is an intentional design decision to separate concerns.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
  • lib/background/background.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/subscriptions/subscription_manager.dart : Use SubscriptionManager with `fireImmediately: false` during SessionNotifier initialization to prevent premature execution

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-10-21T21:47:03.451Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:157-182
Timestamp: 2025-10-21T21:47:03.451Z
Learning: In MostroP2P/mobile, for Action.canceled handling in abstract_mostro_notifier.dart (Riverpod StateNotifier), do not add mounted checks after async sessionNotifier.deleteSession(orderId) as they break order state synchronization during app restart. The Action.canceled flow contains critical business logic that must complete fully; Riverpod handles provider disposal automatically. Mounted checks should only protect UI operations, not business logic in StateNotifiers.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-07-20T23:33:17.689Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 215
File: lib/features/order/notfiers/order_notifier.dart:0-0
Timestamp: 2025-07-20T23:33:17.689Z
Learning: In AbstractMostroNotifier, the handleEvent method is synchronous and returns void, not Future<void>. Do not suggest adding await to super.handleEvent() calls as this would cause compilation errors.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-10-14T21:12:06.887Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:141-154
Timestamp: 2025-10-14T21:12:06.887Z
Learning: In the MostroP2P mobile codebase, the notification system uses a two-layer localization pattern: providers/notifiers (without BuildContext access) call `showCustomMessage()` with string keys (e.g., 'orderTimeoutMaker', 'orderCanceled'), and the UI layer's `NotificationListenerWidget` has a switch statement that maps these keys to localized strings using `S.of(context)`. This architectural pattern properly separates concerns while maintaining full localization support for all user-facing messages.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
  • lib/background/background.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Persistence, APIs, and background jobs should live in `lib/data/` and `lib/background/`; generated localization output must be in `lib/generated/` and must stay untouched

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
  • lib/background/background.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Application code should be organized under `lib/`, grouped by domain with `lib/features/<feature>/` structure, shared utilities in `lib/shared/`, dependency wiring in `lib/core/`, and services in `lib/services/`

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Resolve every analyzer warning in Dart code

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to test/mocks.mocks.dart : Never manually modify `test/mocks.mocks.dart` - it already has file-level `// ignore_for_file: must_be_immutable` ignore directives

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to **/*.dart : Remove unused imports and dependencies to maintain code cleanliness and reduce build size

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/shared/**/*.dart : Follow existing feature patterns when adding new shared utilities - refer to order, chat, and auth features as implementation examples

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/core/app_routes.dart : Use GoRouter for all navigation configuration instead of Navigator API - maintain all routes in app_routes.dart

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, `eventStorageProvider` is exported from `package:mostro_mobile/shared/providers/mostro_service_provider.dart` and not from a separate `event_storage_provider.dart` file.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to **/*.dart : Use Riverpod for all state management - encapsulate business logic in Notifiers and access data only through repository classes

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Localize all user-facing strings via ARB files and access them with `S.of(context)` rather than hard-coded literals

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, Riverpod code generation is used with `Riverpod` annotations. Providers like `eventStorageProvider` are generated in `.g.dart` files from annotated functions in the main provider files. These providers are accessible by importing the main provider file (e.g., `mostro_service_provider.dart`), not by importing a separate provider file.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/relays/**/*.dart : Use dual storage strategy: store Mostro/default relays in `settings.relays` and user relays in `settings.userRelays` with full JSON metadata via `toJson()`/`fromJson()`

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/settings/settings.dart : Use null-aware operators (`??`) in Settings `copyWith()` method to preserve existing values for selectedLanguage and defaultLightningAddress when not explicitly overridden

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/screens/**/*.dart : Use `S.of(context)!.yourKey` for all user-facing strings instead of hardcoded text

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/relays/**/*.dart : Implement two-tier relay validation: primary Nostr protocol test (REQ/EVENT/EOSE) with WebSocket fallback for connectivity testing

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
lib/features/notifications/services/background_notification_service.dart (1)

4-4: LGTM on import changes.

The import updates align with the new functionality: dart_nostr for NostrFilter, Settings for relay configuration, and NostrService for fetching events.

Also applies to: 22-22, 28-28

lib/background/background.dart (1)

16-21: Correct the documentation to accurately reflect the deduplication mechanism.

The comment on line 19 claims the service "Uses separate notification_state store for deduplication," but verification shows the actual deduplication is done via eventStore.hasItem() (line 78 in this file), not a separate store. There is no notification_state store in the codebase.

Update the architecture comment to match the actual implementation:

  • Deduplication uses eventStore.hasItem() to check if events were already processed
  • The read-only boundary claim is correct—only hasItem() is called on events.db

Likely an incorrect or invalid review comment.

Copy link
Contributor

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/main.dart (1)

97-148: Pending events check only runs at startup, misses actual app resumes.

The _checkPendingEventsOnResume function at line 140 is called only once during main(). If the background handler sets the fcm.pending_fetch flag while the app is suspended (but not killed), there's no lifecycle observer to trigger the check when the app transitions from background to foreground.

As suggested in past review comments, add a WidgetsBindingObserver to detect AppLifecycleState.resumed and call the pending check, or integrate this into your existing app lifecycle management. This ensures pending events are processed whenever the user brings the app to the foreground, not just on cold starts.

Based on past review comments.

♻️ Duplicate comments (1)
lib/main.dart (1)

150-190: Missing reload() and incorrect flag clearing order in pending events handler.

Two issues remain unresolved despite past review comments:

  1. Missing reload() call: Line 165 reads from SharedPreferences without first calling reload(), which can return stale values if the background isolate recently wrote to the flag.

  2. Flag cleared before processing: Line 170 clears the fcm.pending_fetch flag BEFORE calling fetchAndProcessNewEvents at line 180. A past review comment (marked as "addressed") recommended clearing the flag only AFTER successful processing to ensure failed processing can be retried. The current code will lose events if processing fails.

Apply this fix:

   try {
+    // Reload to get latest values from background isolate
+    await sharedPrefs.reload();
+    
     final hasPending = await sharedPrefs.getBool('fcm.pending_fetch') ?? false;

     if (hasPending) {
       logger.i('Pending events detected (background processing failed) - processing now');

-      await sharedPrefs.setBool('fcm.pending_fetch', false);
-
       final relays = settings.settings.relays;
       if (relays.isEmpty) {
         logger.w('No relays configured - cannot fetch events');
+        await sharedPrefs.setBool('fcm.pending_fetch', false);
         return;
       }

       logger.i('Fetching new events from ${relays.length} relays');
       // Process without limits since app is now active
       await fetchAndProcessNewEvents(
         relays: relays,
         // No limits when app is active
       );
+      
+      // Clear flag only after successful processing
+      await sharedPrefs.setBool('fcm.pending_fetch', false);
       logger.i('Successfully processed pending events');
     }
   } catch (e, stackTrace) {
     logger.e('Error processing pending events: $e');
     logger.e('Stack trace: $stackTrace');
+    // Flag remains set for retry on next resume/FCM wake
   }

Based on past review comments.

🧹 Nitpick comments (2)
docs/FCM_TESTING_GUIDE.md (1)

319-440: Well-defined success metrics and future planning.

The success metrics section provides clear, measurable targets. The test results template will ensure consistent reporting across team members.

Static analysis correctly identifies missing language specifiers on several fenced code blocks (lines 39, 96, 125, 161, 198, 216, 223) and emphasis-as-heading usage (lines 268, 273, 278, 283, 302, 308, 313). These are markdown linting issues that don't affect functionality but could be cleaned up for better documentation hygiene:

-**Check 1: FCM Token Registered**
+#### Check 1: FCM Token Registered

And add language specifiers to code blocks:

-```
+```text
 [FCM] === FCM BACKGROUND WAKE START ===
docs/FCM_IMPLEMENTATION.md (1)

212-382: Comprehensive implementation documentation with realistic limitations and security analysis.

The document provides a complete picture of the FCM implementation including practical limitations (30s background timeout), monitoring guidance, and thoughtful security considerations (timing analysis, device fingerprinting risks).

Similar to the testing guide, static analysis flags markdown linting issues (missing language specifiers on fenced code blocks at lines 39, 96, 125, 161, 198, 216, 223 and emphasis-as-heading at lines 268, 273, 278, 283, 302, 308, 313). These are minor and don't affect functionality but could be cleaned up for documentation consistency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9338e1e and 0167e3d.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • docs/FCM_IMPLEMENTATION.md (1 hunks)
  • docs/FCM_TESTING_GUIDE.md (1 hunks)
  • functions/src/index.ts (1 hunks)
  • lib/features/notifications/services/background_notification_service.dart (4 hunks)
  • lib/main.dart (3 hunks)
  • lib/shared/providers/app_init_provider.dart (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • functions/src/index.ts
  • lib/features/notifications/services/background_notification_service.dart
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{dart,flutter}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{dart,flutter}: Run flutter analyze after any code change - Mandatory before commits to ensure zero linting issues
Run flutter test after any code change - Mandatory before commits to ensure all unit tests pass

Files:

  • lib/shared/providers/app_init_provider.dart
  • lib/main.dart
**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.dart: Use Riverpod for all state management - encapsulate business logic in Notifiers and access data only through repository classes
All code comments must be in English - use clear, concise English for variable names, function names, and comments
Always check mounted before using BuildContext after async operations to prevent errors on disposed widgets
Use const constructors where possible for better performance and immutability
Remove unused imports and dependencies to maintain code cleanliness and reduce build size

**/*.dart: Application code should be organized under lib/, grouped by domain with lib/features/<feature>/ structure, shared utilities in lib/shared/, dependency wiring in lib/core/, and services in lib/services/
Persistence, APIs, and background jobs should live in lib/data/ and lib/background/; generated localization output must be in lib/generated/ and must stay untouched
Apply flutter format . to enforce canonical Dart formatting (two-space indentation, trailing commas) before committing
Resolve every analyzer warning in Dart code
Name Riverpod providers using the <Feature>Provider or <Feature>Notifier convention
Localize all user-facing strings via ARB files and access them with S.of(context) rather than hard-coded literals

Files:

  • lib/shared/providers/app_init_provider.dart
  • lib/main.dart
lib/shared/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Follow existing feature patterns when adding new shared utilities - refer to order, chat, and auth features as implementation examples

Files:

  • lib/shared/providers/app_init_provider.dart
lib/main.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Configure timeago package locales in app initialization for proper relative time formatting (e.g., 'hace X horas' vs 'hours ago')

Files:

  • lib/main.dart
🧠 Learnings (14)
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Run `flutter test` before pushing code and add targeted test scenarios when expanding complex logic or async workflows

Applied to files:

  • lib/shared/providers/app_init_provider.dart
  • docs/FCM_TESTING_GUIDE.md
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/subscriptions/subscription_manager.dart : Use SubscriptionManager with `fireImmediately: false` during SessionNotifier initialization to prevent premature execution

Applied to files:

  • lib/shared/providers/app_init_provider.dart
  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/providers/**/*.dart : Organize Riverpod providers by feature in `features/{feature}/providers/` using Notifier pattern for complex state logic

Applied to files:

  • lib/shared/providers/app_init_provider.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to **/*.dart : Use Riverpod for all state management - encapsulate business logic in Notifiers and access data only through repository classes

Applied to files:

  • lib/shared/providers/app_init_provider.dart
📚 Learning: 2025-10-21T21:47:03.451Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:157-182
Timestamp: 2025-10-21T21:47:03.451Z
Learning: In MostroP2P/mobile, for Action.canceled handling in abstract_mostro_notifier.dart (Riverpod StateNotifier), do not add mounted checks after async sessionNotifier.deleteSession(orderId) as they break order state synchronization during app restart. The Action.canceled flow contains critical business logic that must complete fully; Riverpod handles provider disposal automatically. Mounted checks should only protect UI operations, not business logic in StateNotifiers.

Applied to files:

  • lib/shared/providers/app_init_provider.dart
  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/notifiers/**/*.dart : Encapsulate business logic in Notifiers - Notifiers should expose state via providers and handle all complex state transitions

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/screens/**/*.dart : Keep UI code declarative and side-effect free - use post-frame callbacks for side effects like SnackBars/dialogs

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to **/*.dart : Always check `mounted` before using BuildContext after async operations to prevent errors on disposed widgets

Applied to files:

  • lib/main.dart
📚 Learning: 2025-07-20T23:33:17.689Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 215
File: lib/features/order/notfiers/order_notifier.dart:0-0
Timestamp: 2025-07-20T23:33:17.689Z
Learning: In AbstractMostroNotifier, the handleEvent method is synchronous and returns void, not Future<void>. Do not suggest adding await to super.handleEvent() calls as this would cause compilation errors.

Applied to files:

  • lib/main.dart
📚 Learning: 2025-08-21T14:45:43.974Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 272
File: lib/features/relays/widgets/relay_selector.dart:13-15
Timestamp: 2025-08-21T14:45:43.974Z
Learning: In the Mostro mobile app's RelaySelector widget (lib/features/relays/widgets/relay_selector.dart), watching relaysProvider.notifier correctly triggers rebuilds because the relaysProvider itself depends on settingsProvider (line 8 in relays_provider.dart). When blacklist changes via toggleMostroRelayBlacklist(), the settingsProvider updates, causing relaysProvider to rebuild, which then notifies widgets watching the notifier. The UI correctly reflects active/inactive states in real-time through this dependency chain.

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/services/nostr_service.dart : Manage all relay connections and Nostr messaging through NostrService - automatically reconnect when relay list updates

Applied to files:

  • lib/main.dart
📚 Learning: 2025-09-17T20:45:32.468Z
Learnt from: grunch
Repo: MostroP2P/mobile PR: 306
File: docs/architecture/REQUEST_ID_ANALYSIS.md:176-183
Timestamp: 2025-09-17T20:45:32.468Z
Learning: For PR #306 in MostroP2P/mobile repository: This is a documentation-only PR intended to explain how the current requestId system works, not to fix any bugs or issues in the code. The documentation should accurately reflect existing behavior.

Applied to files:

  • docs/FCM_IMPLEMENTATION.md
📚 Learning: 2025-09-17T20:45:07.179Z
Learnt from: grunch
Repo: MostroP2P/mobile PR: 306
File: docs/architecture/REQUEST_ID_ANALYSIS.md:114-118
Timestamp: 2025-09-17T20:45:07.179Z
Learning: For the MostroP2P/mobile project, grunch prefers to separate documentation PRs from code improvement PRs. Documentation PRs should focus on explaining how the current system works, while code fixes and improvements should be handled in separate PRs.

Applied to files:

  • docs/FCM_IMPLEMENTATION.md
📚 Learning: 2025-05-08T16:29:52.154Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/background/background.dart:74-77
Timestamp: 2025-05-08T16:29:52.154Z
Learning: In the Mostro Mobile background service architecture, events aren't stored by the background process. Instead, the background service only checks if events exist in the eventStore and sends notifications for new ones, while the foreground process is responsible for storing and processing events. This is an intentional design decision to separate concerns.

Applied to files:

  • docs/FCM_IMPLEMENTATION.md
🪛 markdownlint-cli2 (0.18.1)
docs/FCM_TESTING_GUIDE.md

39-39: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


96-96: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


125-125: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


161-161: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


198-198: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


216-216: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


223-223: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


268-268: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


273-273: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


278-278: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


283-283: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


302-302: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


308-308: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


313-313: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

docs/FCM_IMPLEMENTATION.md

39-39: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


96-96: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


125-125: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


161-161: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


198-198: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


216-216: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


223-223: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


268-268: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


273-273: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


278-278: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


283-283: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


302-302: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


308-308: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


313-313: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (6)
docs/FCM_TESTING_GUIDE.md (2)

1-136: Excellent testing documentation structure.

The testing guide provides comprehensive, actionable test scenarios with clear success criteria and troubleshooting steps. The expected log formats align with the implementation patterns observed in the codebase.


207-318: Comprehensive debugging and troubleshooting guidance.

The debugging commands and troubleshooting steps are practical and cover the major failure modes (battery optimization, background restrictions, connectivity issues).

lib/shared/providers/app_init_provider.dart (3)

1-22: Platform guard correctly excludes web and restricts to mobile platforms.

The _isFirebaseSupported check properly gates Firebase initialization to Android and iOS, avoiding unsupported web platform calls.


32-60: FCM initialization with proper error handling and foreground message callback.

The implementation addresses the previous concern about error handling by wrapping FCM initialization in try-catch. The foreground message handler correctly reads relay configuration, validates it, and processes events without background limits.

The onMessageReceived callback is properly marked async and awaits the fetchAndProcessNewEvents call. The error handling logs failures without crashing app startup, allowing graceful degradation if FCM is unavailable.


62-87: Existing initialization flow preserved with added logging.

The FCM integration is cleanly inserted into the initialization sequence without disrupting existing app startup logic. The completion log provides useful visibility.

docs/FCM_IMPLEMENTATION.md (1)

123-211: Accurate flow diagrams and configuration details.

The flow diagrams correctly represent the three processing paths (background, foreground, fallback) implemented in the codebase. Configuration details are specific and actionable.

Copy link
Contributor

@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: 2

♻️ Duplicate comments (3)
lib/features/notifications/services/background_notification_service.dart (3)

145-148: Previous review comment still applies: substring(0, 16) can throw RangeError.

The recipient.substring(0, 16) at line 146 was flagged in a previous review. If recipient is shorter than 16 characters, this throws RangeError.

Apply the fix from the previous review:

     if (matchingSession == null) {
-      Logger().d('No matching session found for recipient: ${recipient.substring(0, 16)}...');
+      final preview = recipient.length > 16 ? recipient.substring(0, 16) : recipient;
+      Logger().d('No matching session found for recipient: $preview...');
       return null;
     }

291-312: Previous review comment still applies: retry logic is ineffective.

As flagged previously, showLocalNotification catches all exceptions internally (lines 110-112) and never rethrows. The try/catch at line 299 will never execute, making the exponential backoff logic dead code.


472-475: Previous review comment still applies: substring(0, 16) can throw RangeError.

Same issue as line 146 - recipientPubkey.substring(0, 16) will throw if the string is shorter than 16 characters.

     if (matchingSession == null) {
-      logger.w('No matching session found for recipient: ${recipientPubkey.substring(0, 16)}...');
+      final preview = recipientPubkey.length > 16 ? recipientPubkey.substring(0, 16) : recipientPubkey;
+      logger.w('No matching session found for recipient: $preview...');
       return;
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0167e3d and e491875.

📒 Files selected for processing (1)
  • lib/features/notifications/services/background_notification_service.dart (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{dart,flutter}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{dart,flutter}: Run flutter analyze after any code change - Mandatory before commits to ensure zero linting issues
Run flutter test after any code change - Mandatory before commits to ensure all unit tests pass

Files:

  • lib/features/notifications/services/background_notification_service.dart
**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.dart: Use Riverpod for all state management - encapsulate business logic in Notifiers and access data only through repository classes
All code comments must be in English - use clear, concise English for variable names, function names, and comments
Always check mounted before using BuildContext after async operations to prevent errors on disposed widgets
Use const constructors where possible for better performance and immutability
Remove unused imports and dependencies to maintain code cleanliness and reduce build size

**/*.dart: Application code should be organized under lib/, grouped by domain with lib/features/<feature>/ structure, shared utilities in lib/shared/, dependency wiring in lib/core/, and services in lib/services/
Persistence, APIs, and background jobs should live in lib/data/ and lib/background/; generated localization output must be in lib/generated/ and must stay untouched
Apply flutter format . to enforce canonical Dart formatting (two-space indentation, trailing commas) before committing
Resolve every analyzer warning in Dart code
Name Riverpod providers using the <Feature>Provider or <Feature>Notifier convention
Localize all user-facing strings via ARB files and access them with S.of(context) rather than hard-coded literals

Files:

  • lib/features/notifications/services/background_notification_service.dart
🧠 Learnings (27)
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/services/nostr_service.dart : Manage all relay connections and Nostr messaging through NostrService - automatically reconnect when relay list updates

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/notifiers/**/*.dart : Encapsulate business logic in Notifiers - Notifiers should expose state via providers and handle all complex state transitions

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/services/**/*.dart : Access NostrService for all Nostr protocol interactions - NostrService manages relay connections and messaging

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/providers/**/*.dart : Use Notifier pattern instead of simple StateNotifier for complex state logic requiring business rule encapsulation

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/subscriptions/subscription_manager.dart : Use SubscriptionManager with `fireImmediately: false` during SessionNotifier initialization to prevent premature execution

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/providers/**/*.dart : Organize Riverpod providers by feature in `features/{feature}/providers/` using Notifier pattern for complex state logic

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-08T16:29:52.154Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/background/background.dart:74-77
Timestamp: 2025-05-08T16:29:52.154Z
Learning: In the Mostro Mobile background service architecture, events aren't stored by the background process. Instead, the background service only checks if events exist in the eventStore and sends notifications for new ones, while the foreground process is responsible for storing and processing events. This is an intentional design decision to separate concerns.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-10-21T21:47:03.451Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:157-182
Timestamp: 2025-10-21T21:47:03.451Z
Learning: In MostroP2P/mobile, for Action.canceled handling in abstract_mostro_notifier.dart (Riverpod StateNotifier), do not add mounted checks after async sessionNotifier.deleteSession(orderId) as they break order state synchronization during app restart. The Action.canceled flow contains critical business logic that must complete fully; Riverpod handles provider disposal automatically. Mounted checks should only protect UI operations, not business logic in StateNotifiers.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-07-20T23:33:17.689Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 215
File: lib/features/order/notfiers/order_notifier.dart:0-0
Timestamp: 2025-07-20T23:33:17.689Z
Learning: In AbstractMostroNotifier, the handleEvent method is synchronous and returns void, not Future<void>. Do not suggest adding await to super.handleEvent() calls as this would cause compilation errors.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-10-14T21:12:06.887Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:141-154
Timestamp: 2025-10-14T21:12:06.887Z
Learning: In the MostroP2P mobile codebase, the notification system uses a two-layer localization pattern: providers/notifiers (without BuildContext access) call `showCustomMessage()` with string keys (e.g., 'orderTimeoutMaker', 'orderCanceled'), and the UI layer's `NotificationListenerWidget` has a switch statement that maps these keys to localized strings using `S.of(context)`. This architectural pattern properly separates concerns while maintaining full localization support for all user-facing messages.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Persistence, APIs, and background jobs should live in `lib/data/` and `lib/background/`; generated localization output must be in `lib/generated/` and must stay untouched

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Application code should be organized under `lib/`, grouped by domain with `lib/features/<feature>/` structure, shared utilities in `lib/shared/`, dependency wiring in `lib/core/`, and services in `lib/services/`

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Resolve every analyzer warning in Dart code

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to test/mocks.mocks.dart : Never manually modify `test/mocks.mocks.dart` - it already has file-level `// ignore_for_file: must_be_immutable` ignore directives

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to **/*.dart : Remove unused imports and dependencies to maintain code cleanliness and reduce build size

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/shared/**/*.dart : Follow existing feature patterns when adding new shared utilities - refer to order, chat, and auth features as implementation examples

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/auth/notifiers/abstract_mostro_notifier.dart : Start 10-second cleanup timer automatically when taking orders via `startSessionTimeoutCleanup()` to prevent orphan sessions

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/relays/**/*.dart : Implement two-tier relay validation: primary Nostr protocol test (REQ/EVENT/EOSE) with WebSocket fallback for connectivity testing

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-06-26T15:03:23.529Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 127
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:45-54
Timestamp: 2025-06-26T15:03:23.529Z
Learning: In AbstractMostroNotifier, state updates occur for all messages regardless of timestamp to hydrate the OrderNotifier from MostroStorage during sync, while handleEvent is only called for recent messages (within 60 seconds) to prevent re-triggering side effects like notifications and navigation for previously handled messages. This design prevents displaying stale notifications when the app is reopened or brought to the foreground.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/core/app_routes.dart : Use GoRouter for all navigation configuration instead of Navigator API - maintain all routes in app_routes.dart

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, `eventStorageProvider` is exported from `package:mostro_mobile/shared/providers/mostro_service_provider.dart` and not from a separate `event_storage_provider.dart` file.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to **/*.dart : Use Riverpod for all state management - encapsulate business logic in Notifiers and access data only through repository classes

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Localize all user-facing strings via ARB files and access them with `S.of(context)` rather than hard-coded literals

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, Riverpod code generation is used with `Riverpod` annotations. Providers like `eventStorageProvider` are generated in `.g.dart` files from annotated functions in the main provider files. These providers are accessible by importing the main provider file (e.g., `mostro_service_provider.dart`), not by importing a separate provider file.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/relays/**/*.dart : Use dual storage strategy: store Mostro/default relays in `settings.relays` and user relays in `settings.userRelays` with full JSON metadata via `toJson()`/`fromJson()`

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/settings/settings.dart : Use null-aware operators (`??`) in Settings `copyWith()` method to preserve existing values for selectedLanguage and defaultLightningAddress when not explicitly overridden

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/screens/**/*.dart : Use `S.of(context)!.yourKey` for all user-facing strings instead of hardcoded text

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
lib/features/notifications/services/background_notification_service.dart (2)

4-4: Import additions align with new functionality.

The new imports for dart_nostr, Settings, and NostrService correctly support the fetchAndProcessNewEvents implementation.

Also applies to: 22-22, 28-28


361-446: Good improvements addressing past concerns; relay cleanup and timestamp tracking now correct.

The implementation now properly:

  • Cleans up NostrService connections in the finally block (lines 442-446)
  • Tracks latestEventTimestamp based on actual event timestamps rather than wall-clock time (line 400-403)
  • Applies per-session timeouts to prevent blocking (lines 378-384)

Copy link
Contributor

@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: 2

♻️ Duplicate comments (3)
lib/features/notifications/services/background_notification_service.dart (2)

149-149: Guard against RangeError when logging recipient substring.

If recipient is shorter than 16 characters, substring(0, 16) will throw a RangeError, potentially crashing background processing.

Apply defensive truncation:

     if (matchingSession == null) {
-      Logger().d('No matching session found for recipient: ${recipient.substring(0, 16)}...');
+      final preview = recipient.length > 16 ? recipient.substring(0, 16) : recipient;
+      Logger().d('No matching session found for recipient: $preview...');
       return null;
     }

Based on past review comments.


470-470: Guard against RangeError when logging recipientPubkey substring.

Same issue as line 149: if recipientPubkey is shorter than 16 characters, substring(0, 16) will throw a RangeError.

Apply defensive truncation:

     if (matchingSession == null) {
-      logger.w('No matching session found for recipient: ${recipientPubkey.substring(0, 16)}...');
+      final preview = recipientPubkey.length > 16 ? recipientPubkey.substring(0, 16) : recipientPubkey;
+      logger.w('No matching session found for recipient: $preview...');
       return;
     }

Based on past review comments.

lib/main.dart (1)

168-186: Flag cleared before processing—events lost on failure.

The pending flag is cleared at line 173 before fetchAndProcessNewEvents executes at line 183-186. If processing fails, the flag is already cleared and events won't be retried on the next resume.

Apply this fix to clear the flag only after successful processing:

     if (hasPending) {
       logger.i('Pending events detected (background processing failed) - processing now');

-      await sharedPrefs.setBool('fcm.pending_fetch', false);
-
       final relays = settings.settings.relays;
       if (relays.isEmpty) {
         logger.w('No relays configured - cannot fetch events');
+        await sharedPrefs.setBool('fcm.pending_fetch', false);
         return;
       }

       logger.i('Fetching new events from ${relays.length} relays');
       // Process without limits since app is now active
       await fetchAndProcessNewEvents(
         relays: relays,
         // No limits when app is active
       );
+      
+      // Clear flag only after successful processing
+      await sharedPrefs.setBool('fcm.pending_fetch', false);
       logger.i('Successfully processed pending events');
     }

Note: The catch block at line 189 should NOT clear the flag so failed processing leaves it set for retry.

🧹 Nitpick comments (2)
docs/FCM_TESTING_GUIDE.md (2)

39-39: Add language specifiers to fenced code blocks for better readability.

Several code blocks are missing language identifiers, which prevents syntax highlighting. Consider adding appropriate identifiers (e.g., ```text or ```log) to improve readability.

Also applies to: 96-96, 125-125, 161-161, 198-198, 216-216, 223-223


268-268: Replace bold emphasis with proper Markdown headings.

Using bold text (**Check 1:**) instead of proper headings (#### Check 1:) reduces semantic structure and accessibility. Consider converting these to proper heading levels.

Also applies to: 273-273, 278-278, 283-283, 302-302, 308-308, 313-313

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e491875 and 2f4d4db.

📒 Files selected for processing (3)
  • docs/FCM_TESTING_GUIDE.md (1 hunks)
  • lib/features/notifications/services/background_notification_service.dart (7 hunks)
  • lib/main.dart (3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{dart,flutter}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{dart,flutter}: Run flutter analyze after any code change - Mandatory before commits to ensure zero linting issues
Run flutter test after any code change - Mandatory before commits to ensure all unit tests pass

Files:

  • lib/main.dart
  • lib/features/notifications/services/background_notification_service.dart
**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.dart: Use Riverpod for all state management - encapsulate business logic in Notifiers and access data only through repository classes
All code comments must be in English - use clear, concise English for variable names, function names, and comments
Always check mounted before using BuildContext after async operations to prevent errors on disposed widgets
Use const constructors where possible for better performance and immutability
Remove unused imports and dependencies to maintain code cleanliness and reduce build size

**/*.dart: Application code should be organized under lib/, grouped by domain with lib/features/<feature>/ structure, shared utilities in lib/shared/, dependency wiring in lib/core/, and services in lib/services/
Persistence, APIs, and background jobs should live in lib/data/ and lib/background/; generated localization output must be in lib/generated/ and must stay untouched
Apply flutter format . to enforce canonical Dart formatting (two-space indentation, trailing commas) before committing
Resolve every analyzer warning in Dart code
Name Riverpod providers using the <Feature>Provider or <Feature>Notifier convention
Localize all user-facing strings via ARB files and access them with S.of(context) rather than hard-coded literals

Files:

  • lib/main.dart
  • lib/features/notifications/services/background_notification_service.dart
lib/main.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Configure timeago package locales in app initialization for proper relative time formatting (e.g., 'hace X horas' vs 'hours ago')

Files:

  • lib/main.dart
🧠 Learnings (34)
📚 Learning: 2025-10-21T21:47:03.451Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:157-182
Timestamp: 2025-10-21T21:47:03.451Z
Learning: In MostroP2P/mobile, for Action.canceled handling in abstract_mostro_notifier.dart (Riverpod StateNotifier), do not add mounted checks after async sessionNotifier.deleteSession(orderId) as they break order state synchronization during app restart. The Action.canceled flow contains critical business logic that must complete fully; Riverpod handles provider disposal automatically. Mounted checks should only protect UI operations, not business logic in StateNotifiers.

Applied to files:

  • lib/main.dart
  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/notifiers/**/*.dart : Encapsulate business logic in Notifiers - Notifiers should expose state via providers and handle all complex state transitions

Applied to files:

  • lib/main.dart
  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/subscriptions/subscription_manager.dart : Use SubscriptionManager with `fireImmediately: false` during SessionNotifier initialization to prevent premature execution

Applied to files:

  • lib/main.dart
  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/screens/**/*.dart : Keep UI code declarative and side-effect free - use post-frame callbacks for side effects like SnackBars/dialogs

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to **/*.dart : Always check `mounted` before using BuildContext after async operations to prevent errors on disposed widgets

Applied to files:

  • lib/main.dart
📚 Learning: 2025-07-20T23:33:17.689Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 215
File: lib/features/order/notfiers/order_notifier.dart:0-0
Timestamp: 2025-07-20T23:33:17.689Z
Learning: In AbstractMostroNotifier, the handleEvent method is synchronous and returns void, not Future<void>. Do not suggest adding await to super.handleEvent() calls as this would cause compilation errors.

Applied to files:

  • lib/main.dart
  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/relays/**/*.dart : Use dual storage strategy: store Mostro/default relays in `settings.relays` and user relays in `settings.userRelays` with full JSON metadata via `toJson()`/`fromJson()`

Applied to files:

  • lib/main.dart
  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/settings/settings.dart : Use null-aware operators (`??`) in Settings `copyWith()` method to preserve existing values for selectedLanguage and defaultLightningAddress when not explicitly overridden

Applied to files:

  • lib/main.dart
  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-08-21T14:45:43.974Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 272
File: lib/features/relays/widgets/relay_selector.dart:13-15
Timestamp: 2025-08-21T14:45:43.974Z
Learning: In the Mostro mobile app's RelaySelector widget (lib/features/relays/widgets/relay_selector.dart), watching relaysProvider.notifier correctly triggers rebuilds because the relaysProvider itself depends on settingsProvider (line 8 in relays_provider.dart). When blacklist changes via toggleMostroRelayBlacklist(), the settingsProvider updates, causing relaysProvider to rebuild, which then notifies widgets watching the notifier. The UI correctly reflects active/inactive states in real-time through this dependency chain.

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/services/nostr_service.dart : Manage all relay connections and Nostr messaging through NostrService - automatically reconnect when relay list updates

Applied to files:

  • lib/main.dart
  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/providers/**/*.dart : Use Notifier pattern instead of simple StateNotifier for complex state logic requiring business rule encapsulation

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/services/**/*.dart : Access NostrService for all Nostr protocol interactions - NostrService manages relay connections and messaging

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.081Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.081Z
Learning: Run `flutter test integration_test/` only for significant changes affecting core services and main user flows

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-10-14T21:12:06.887Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:141-154
Timestamp: 2025-10-14T21:12:06.887Z
Learning: In the MostroP2P mobile codebase, the notification system uses a two-layer localization pattern: providers/notifiers (without BuildContext access) call `showCustomMessage()` with string keys (e.g., 'orderTimeoutMaker', 'orderCanceled'), and the UI layer's `NotificationListenerWidget` has a switch statement that maps these keys to localized strings using `S.of(context)`. This architectural pattern properly separates concerns while maintaining full localization support for all user-facing messages.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-08T16:29:52.154Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/background/background.dart:74-77
Timestamp: 2025-05-08T16:29:52.154Z
Learning: In the Mostro Mobile background service architecture, events aren't stored by the background process. Instead, the background service only checks if events exist in the eventStore and sends notifications for new ones, while the foreground process is responsible for storing and processing events. This is an intentional design decision to separate concerns.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Persistence, APIs, and background jobs should live in `lib/data/` and `lib/background/`; generated localization output must be in `lib/generated/` and must stay untouched

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Application code should be organized under `lib/`, grouped by domain with `lib/features/<feature>/` structure, shared utilities in `lib/shared/`, dependency wiring in `lib/core/`, and services in `lib/services/`

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Resolve every analyzer warning in Dart code

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to test/mocks.mocks.dart : Never manually modify `test/mocks.mocks.dart` - it already has file-level `// ignore_for_file: must_be_immutable` ignore directives

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to **/*.dart : Remove unused imports and dependencies to maintain code cleanliness and reduce build size

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/shared/**/*.dart : Follow existing feature patterns when adding new shared utilities - refer to order, chat, and auth features as implementation examples

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/relays/**/*.dart : Implement two-tier relay validation: primary Nostr protocol test (REQ/EVENT/EOSE) with WebSocket fallback for connectivity testing

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/auth/notifiers/abstract_mostro_notifier.dart : Start 10-second cleanup timer automatically when taking orders via `startSessionTimeoutCleanup()` to prevent orphan sessions

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/orders/notifiers/add_order_notifier.dart : Start session timeout cleanup in `submitOrder()` method to prevent orphan sessions when Mostro doesn't respond within 10 seconds

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/auth/notifiers/abstract_mostro_notifier.dart : Use `startSessionTimeoutCleanupForRequestId()` for order creation timeout protection and cancel timer automatically when any Mostro response received

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/core/app_routes.dart : Use GoRouter for all navigation configuration instead of Navigator API - maintain all routes in app_routes.dart

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, `eventStorageProvider` is exported from `package:mostro_mobile/shared/providers/mostro_service_provider.dart` and not from a separate `event_storage_provider.dart` file.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to **/*.dart : Use Riverpod for all state management - encapsulate business logic in Notifiers and access data only through repository classes

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Localize all user-facing strings via ARB files and access them with `S.of(context)` rather than hard-coded literals

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-06T15:46:08.942Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/nostr_service.dart:3-4
Timestamp: 2025-05-06T15:46:08.942Z
Learning: The file in dart_nostr library is named "ease.dart" (not "eose.dart" as might be expected), as confirmed by the project maintainer.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/providers/**/*.dart : Organize Riverpod providers by feature in `features/{feature}/providers/` using Notifier pattern for complex state logic

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/screens/**/*.dart : Use `S.of(context)!.yourKey` for all user-facing strings instead of hardcoded text

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, Riverpod code generation is used with `Riverpod` annotations. Providers like `eventStorageProvider` are generated in `.g.dart` files from annotated functions in the main provider files. These providers are accessible by importing the main provider file (e.g., `mostro_service_provider.dart`), not by importing a separate provider file.

Applied to files:

  • lib/features/notifications/services/background_notification_service.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Run `flutter test` before pushing code and add targeted test scenarios when expanding complex logic or async workflows

Applied to files:

  • docs/FCM_TESTING_GUIDE.md
🪛 markdownlint-cli2 (0.18.1)
docs/FCM_TESTING_GUIDE.md

39-39: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


96-96: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


125-125: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


161-161: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


198-198: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


216-216: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


223-223: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


268-268: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


273-273: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


278-278: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


283-283: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


302-302: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


308-308: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


313-313: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (6)
lib/main.dart (2)

25-29: LGTM!

The platform check correctly gates Firebase initialization to supported platforms (Android/iOS), excluding web.


103-109: LGTM!

Firebase initialization is correctly gated by platform support and the background message handler is registered following Flutter Firebase Messaging best practices.

lib/features/notifications/services/background_notification_service.dart (4)

3-3: LGTM!

Import changes correctly add dependencies needed for the new relay-based event fetching functionality.

Also applies to: 21-21, 27-27


53-116: LGTM!

The return type change from Future<void> to Future<bool> correctly enables callers to detect notification failures and implement retry logic. The function returns true on success and false on failure/skip conditions.


294-309: LGTM!

The retry logic now works correctly with the updated showLocalNotification signature returning bool. The exponential backoff and retry attempts are properly implemented.


311-449: Excellent implementation addressing all past review concerns.

This new function resolves multiple issues flagged in previous reviews:

  1. NostrService cleanup: The finally block at lines 439-443 ensures relay connections are properly disposed.
  2. Timestamp race condition fixed: Lines 397-400 track the latest event timestamp per event, and lines 427-430 only update the stored timestamp after successful processing, preventing event loss.
  3. Accurate metrics: Using the bool return from showLocalNotification at lines 413-419 provides accurate success/failure counts.
  4. Timeout protection: Per-session timeout at lines 375-381 prevents hanging on slow relays.

The implementation follows best practices with proper error handling, logging, and resource management.

Copy link
Contributor

@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: 0

♻️ Duplicate comments (1)
lib/core/app.dart (1)

49-89: Defer clearing fcm.pending_fetch until after successful processing to preserve retries.

Right now the flag is cleared before fetchAndProcessNewEvents runs:

if (hasPending) {
  logger.i('Pending events detected on resume - processing now');

  await sharedPrefs.setBool('fcm.pending_fetch', false);

  final relays = settings.settings.relays;
  ...
  await fetchAndProcessNewEvents(...);
  logger.i('Successfully processed pending events on resume');
}

If fetchAndProcessNewEvents throws (or the process is interrupted), the flag is already false and the app will not retry on future resumes, defeating this fallback path.

Consider only clearing after a successful run and leaving it set on failure so the next resume can retry:

   if (hasPending) {
     logger.i('Pending events detected on resume - processing now');
-    
-    await sharedPrefs.setBool('fcm.pending_fetch', false);
-    
     final relays = settings.settings.relays;
     if (relays.isEmpty) {
       logger.w('No relays configured - cannot fetch events');
       return;
     }
     
     logger.i('Fetching new events from ${relays.length} relays');
     await fetchAndProcessNewEvents(
       relays: relays,
       // No limits when app is active
     );
     logger.i('Successfully processed pending events on resume');
+    await sharedPrefs.setBool('fcm.pending_fetch', false);
   }

This keeps the retry signal intact if processing fails.

🧹 Nitpick comments (4)
docs/FCM_TESTING_GUIDE.md (2)

39-51: Add languages to fenced code blocks to satisfy markdownlint (MD040).

Several code/log examples use bare ``` fences; markdownlint flags these. Consider specifying explicit languages (e.g. bash, `json`, or `text`) for consistency and better rendering.

Example adjustments:

-```
+[```text]
 [FCM] === FCM BACKGROUND WAKE START ===
 ...
-[FCM] === FCM BACKGROUND WAKE END ===
-```
+[FCM] === FCM BACKGROUND WAKE END ===
+```

Apply similar changes to the other fenced blocks at the referenced lines.

Also applies to: 96-101, 125-129, 161-169, 198-205, 216-218, 223-225


268-281: Replace bold “Check N:” lines with headings to fix MD036.

Lines like **Check 1: FCM Token Registered** are used as headings and are flagged by MD036. Consider converting them to proper Markdown headings:

-**Check 1: FCM Token Registered**
+#### Check 1: FCM Token Registered

Repeat for “Check 2/3” and the similar “Check” lines in the Events Not Fetched section.

Also applies to: 283-286, 302-316

lib/main.dart (2)

31-98: Validate background handler plugin usage and workload in FCM isolate.

The background handler does full event processing (network + storage + notifications) via fetchAndProcessNewEvents and writes SharedPreferences from the FCM isolate. This is powerful but also where subtle plugin issues tend to surface (MissingPluginException, OEM background limits, long-running work).

Concrete suggestions:

  • Explicitly verify on multiple devices (debug + release) that all plugins used transitively by fetchAndProcessNewEvents (DB, notifications, crypto, etc.) are safe to call from the FCM background isolate and don’t throw in headless mode.
  • Keep the background work as short as feasible (your current maxEventsPerSession and timeoutPerSession are good guardrails; consider tightening or offloading heavy work to your existing BackgroundService when possible).
  • Monitor logs around the try { fetchAndProcessNewEvents(...) } catch block in real-world tests to ensure you’re not hitting timeouts or plugin errors silently.

If any plugin turns out not to be background-safe, you may need to reduce this handler to “set a pending flag + minimal bookkeeping” and let the foreground/lifecycle-resume path (_checkPendingEventsOnResume in MostroApp) perform the heavy lifting instead.


51-60: Confirm relays JSON shape matches Settings storage; consider reusing the Settings model.

Here you deserialize relays directly from raw JSON:

final settingsJson = await sharedPrefs.getString('mostro_settings');
...
final settingsMap = jsonDecode(settingsJson) as Map<String, dynamic>;
final relaysList = settingsMap['relays'] as List<dynamic>?;
if (relaysList != null && relaysList.isNotEmpty) {
  relays = relaysList.cast<String>();
}

Per existing guidelines, settings.relays is typically stored with full JSON metadata via toJson()/fromJson(), not as bare strings. If the stored value is actually a list of relay objects (maps), relaysList.cast<String>() will fail at runtime and/or drop needed metadata. Based on learnings, it would be safer to either:

  • Reuse the existing Settings.fromJson model to load settings and then pass settings.relays into fetchAndProcessNewEvents, or
  • Explicitly map from the stored relay object structure to the URL type that fetchAndProcessNewEvents expects.

Example sketch:

final settings = Settings.fromJson(settingsMap);
final relays = settings.relays; // or settings.relays.map((r) => r.url).toList();

Please double-check the actual JSON shape of 'mostro_settings' and adjust this parsing accordingly so it stays in sync with the main Settings model.

Based on learnings, relays/userRelays are expected to be stored with full JSON metadata.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f4d4db and 8bb5066.

📒 Files selected for processing (4)
  • docs/FCM_TESTING_GUIDE.md (1 hunks)
  • functions/src/index.ts (1 hunks)
  • lib/core/app.dart (4 hunks)
  • lib/main.dart (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • functions/src/index.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{dart,flutter}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{dart,flutter}: Run flutter analyze after any code change - Mandatory before commits to ensure zero linting issues
Run flutter test after any code change - Mandatory before commits to ensure all unit tests pass

Files:

  • lib/main.dart
  • lib/core/app.dart
**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.dart: Use Riverpod for all state management - encapsulate business logic in Notifiers and access data only through repository classes
All code comments must be in English - use clear, concise English for variable names, function names, and comments
Always check mounted before using BuildContext after async operations to prevent errors on disposed widgets
Use const constructors where possible for better performance and immutability
Remove unused imports and dependencies to maintain code cleanliness and reduce build size

**/*.dart: Application code should be organized under lib/, grouped by domain with lib/features/<feature>/ structure, shared utilities in lib/shared/, dependency wiring in lib/core/, and services in lib/services/
Persistence, APIs, and background jobs should live in lib/data/ and lib/background/; generated localization output must be in lib/generated/ and must stay untouched
Apply flutter format . to enforce canonical Dart formatting (two-space indentation, trailing commas) before committing
Resolve every analyzer warning in Dart code
Name Riverpod providers using the <Feature>Provider or <Feature>Notifier convention
Localize all user-facing strings via ARB files and access them with S.of(context) rather than hard-coded literals

Files:

  • lib/main.dart
  • lib/core/app.dart
lib/main.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Configure timeago package locales in app initialization for proper relative time formatting (e.g., 'hace X horas' vs 'hours ago')

Files:

  • lib/main.dart
🧠 Learnings (22)
📚 Learning: 2025-11-27T12:10:12.081Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.081Z
Learning: Run `flutter test integration_test/` only for significant changes affecting core services and main user flows

Applied to files:

  • lib/main.dart
📚 Learning: 2025-10-21T21:47:03.451Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:157-182
Timestamp: 2025-10-21T21:47:03.451Z
Learning: In MostroP2P/mobile, for Action.canceled handling in abstract_mostro_notifier.dart (Riverpod StateNotifier), do not add mounted checks after async sessionNotifier.deleteSession(orderId) as they break order state synchronization during app restart. The Action.canceled flow contains critical business logic that must complete fully; Riverpod handles provider disposal automatically. Mounted checks should only protect UI operations, not business logic in StateNotifiers.

Applied to files:

  • lib/main.dart
  • lib/core/app.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/notifiers/**/*.dart : Encapsulate business logic in Notifiers - Notifiers should expose state via providers and handle all complex state transitions

Applied to files:

  • lib/main.dart
  • lib/core/app.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/subscriptions/subscription_manager.dart : Use SubscriptionManager with `fireImmediately: false` during SessionNotifier initialization to prevent premature execution

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/screens/**/*.dart : Keep UI code declarative and side-effect free - use post-frame callbacks for side effects like SnackBars/dialogs

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to **/*.dart : Always check `mounted` before using BuildContext after async operations to prevent errors on disposed widgets

Applied to files:

  • lib/main.dart
📚 Learning: 2025-07-20T23:33:17.689Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 215
File: lib/features/order/notfiers/order_notifier.dart:0-0
Timestamp: 2025-07-20T23:33:17.689Z
Learning: In AbstractMostroNotifier, the handleEvent method is synchronous and returns void, not Future<void>. Do not suggest adding await to super.handleEvent() calls as this would cause compilation errors.

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/relays/**/*.dart : Use dual storage strategy: store Mostro/default relays in `settings.relays` and user relays in `settings.userRelays` with full JSON metadata via `toJson()`/`fromJson()`

Applied to files:

  • lib/main.dart
  • lib/core/app.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/settings/settings.dart : Use null-aware operators (`??`) in Settings `copyWith()` method to preserve existing values for selectedLanguage and defaultLightningAddress when not explicitly overridden

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/core/mostro_fsm.dart : Use MostroFSM for managing order state transitions - all state changes must go through FSM state methods

Applied to files:

  • lib/core/app.dart
📚 Learning: 2025-08-21T14:45:43.974Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 272
File: lib/features/relays/widgets/relay_selector.dart:13-15
Timestamp: 2025-08-21T14:45:43.974Z
Learning: In the Mostro mobile app's RelaySelector widget (lib/features/relays/widgets/relay_selector.dart), watching relaysProvider.notifier correctly triggers rebuilds because the relaysProvider itself depends on settingsProvider (line 8 in relays_provider.dart). When blacklist changes via toggleMostroRelayBlacklist(), the settingsProvider updates, causing relaysProvider to rebuild, which then notifies widgets watching the notifier. The UI correctly reflects active/inactive states in real-time through this dependency chain.

Applied to files:

  • lib/core/app.dart
📚 Learning: 2025-06-26T15:03:23.529Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 127
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:45-54
Timestamp: 2025-06-26T15:03:23.529Z
Learning: In AbstractMostroNotifier, state updates occur for all messages regardless of timestamp to hydrate the OrderNotifier from MostroStorage during sync, while handleEvent is only called for recent messages (within 60 seconds) to prevent re-triggering side effects like notifications and navigation for previously handled messages. This design prevents displaying stale notifications when the app is reopened or brought to the foreground.

Applied to files:

  • lib/core/app.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/providers/**/*.dart : Use Notifier pattern instead of simple StateNotifier for complex state logic requiring business rule encapsulation

Applied to files:

  • lib/core/app.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/core/app_routes.dart : Use GoRouter for all navigation configuration instead of Navigator API - maintain all routes in app_routes.dart

Applied to files:

  • lib/core/app.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to **/*.dart : Use Riverpod for all state management - encapsulate business logic in Notifiers and access data only through repository classes

Applied to files:

  • lib/core/app.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/providers/**/*.dart : Organize Riverpod providers by feature in `features/{feature}/providers/` using Notifier pattern for complex state logic

Applied to files:

  • lib/core/app.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, Riverpod code generation is used with `Riverpod` annotations. Providers like `eventStorageProvider` are generated in `.g.dart` files from annotated functions in the main provider files. These providers are accessible by importing the main provider file (e.g., `mostro_service_provider.dart`), not by importing a separate provider file.

Applied to files:

  • lib/core/app.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Name Riverpod providers using the `<Feature>Provider` or `<Feature>Notifier` convention

Applied to files:

  • lib/core/app.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, `eventStorageProvider` is exported from `package:mostro_mobile/shared/providers/mostro_service_provider.dart` and not from a separate `event_storage_provider.dart` file.

Applied to files:

  • lib/core/app.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/shared/**/*.dart : Follow existing feature patterns when adding new shared utilities - refer to order, chat, and auth features as implementation examples

Applied to files:

  • lib/core/app.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Persistence, APIs, and background jobs should live in `lib/data/` and `lib/background/`; generated localization output must be in `lib/generated/` and must stay untouched

Applied to files:

  • lib/core/app.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Run `flutter test` before pushing code and add targeted test scenarios when expanding complex logic or async workflows

Applied to files:

  • docs/FCM_TESTING_GUIDE.md
🪛 markdownlint-cli2 (0.18.1)
docs/FCM_TESTING_GUIDE.md

39-39: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


96-96: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


125-125: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


161-161: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


198-198: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


216-216: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


223-223: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


268-268: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


273-273: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


278-278: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


283-283: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


302-302: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


308-308: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


313-313: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

Implement privacy-preserving push notification system inspired by MIP-05:
- Add PushNotificationService for encrypted device token registration
- Encrypt FCM tokens with server's public key using ECDH + ChaCha20-Poly1305
- Register tokens per trade pubkey for targeted notifications
- Add debug notification to verify push source (cloud functions vs new server)
- Update default Mostro pubkey and add configurable push server URL
- Integrate
Copy link
Contributor

@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: 3

♻️ Duplicate comments (1)
lib/main.dart (1)

80-101: Background handler performs heavy processing; past review concerns remain.

A past review flagged that fetchAndProcessNewEvents() performs heavy operations (network calls, decryption, database access, notification display) in the background isolate, which violates best practices for FCM background handlers. While event limits and timeouts have been added, the fundamental concern about plugin safety in background isolates remains.

For production reliability, consider:

  1. Setting the fcm.pending_fetch flag and deferring processing to app resume, or
  2. Using platform background job APIs (WorkManager/BGTask) for the heavy lifting

This is flagged as a duplicate since it was raised in past reviews.

🧹 Nitpick comments (4)
lib/shared/notifiers/session_notifier.dart (2)

118-119: Push token registration is fire-and-forget; consider awaiting for reliability.

The _registerPushToken call is not awaited, meaning the session is returned before registration completes. If registration fails silently, the user won't receive push notifications for this trade. Consider awaiting to ensure registration success before returning the session, or at minimum log the registration attempt for debugging.

🔎 Proposed fix to await registration
     // Register push notification token for this trade
-    _registerPushToken(tradeKey.public);
+    await _registerPushToken(tradeKey.public);

     _emitState();

202-214: Consider awaiting unregister before storage deletion for consistency.

The _unregisterPushToken call is fire-and-forget. While this is acceptable since the session is already removed locally, awaiting would ensure the server-side cleanup completes before storage deletion. This is a minor consistency improvement.

lib/main.dart (1)

202-241: Debug notification code should be removed before production release.

The _showDebugSourceNotification function and its call at line 50 are clearly marked for removal after testing. Given the PR status mentions "needs more testing and bug fixes before production deployment," this is acceptable for now. Create a follow-up task to remove this before merging to production.

Do you want me to open an issue to track removing the debug notification code before production release?

lib/services/push_notification_service.dart (1)

250-276: HKDF implementation only supports single-block output (≤32 bytes).

The expand step produces only one block of output, which is fine since length is always 32 in current usage. However, if future use requires more than 32 bytes, this will silently produce incorrect output. Consider adding an assertion or extending to support multi-block expansion.

🔎 Proposed fix: Add assertion for single-block limit
   /// HKDF-SHA256 key derivation
   Uint8List _hkdfDerive(Uint8List ikm, int length) {
+    // This implementation only supports single-block output (≤32 bytes)
+    assert(length <= 32, 'HKDF expand supports max 32 bytes with this implementation');
+    
     const salt = 'mostro-push-v1';
     const info = 'mostro-token-encryption';
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8bb5066 and 2e9405c.

📒 Files selected for processing (5)
  • lib/core/config.dart (2 hunks)
  • lib/main.dart (3 hunks)
  • lib/services/push_notification_service.dart (1 hunks)
  • lib/shared/notifiers/session_notifier.dart (3 hunks)
  • lib/shared/providers/push_notification_provider.dart (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{dart,flutter}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{dart,flutter}: Run flutter analyze after any code change - Mandatory before commits to ensure zero linting issues
Run flutter test after any code change - Mandatory before commits to ensure all unit tests pass

Files:

  • lib/shared/providers/push_notification_provider.dart
  • lib/core/config.dart
  • lib/shared/notifiers/session_notifier.dart
  • lib/services/push_notification_service.dart
  • lib/main.dart
**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.dart: Use Riverpod for all state management - encapsulate business logic in Notifiers and access data only through repository classes
All code comments must be in English - use clear, concise English for variable names, function names, and comments
Always check mounted before using BuildContext after async operations to prevent errors on disposed widgets
Use const constructors where possible for better performance and immutability
Remove unused imports and dependencies to maintain code cleanliness and reduce build size

**/*.dart: Application code should be organized under lib/, grouped by domain with lib/features/<feature>/ structure, shared utilities in lib/shared/, dependency wiring in lib/core/, and services in lib/services/
Persistence, APIs, and background jobs should live in lib/data/ and lib/background/; generated localization output must be in lib/generated/ and must stay untouched
Apply flutter format . to enforce canonical Dart formatting (two-space indentation, trailing commas) before committing
Resolve every analyzer warning in Dart code
Name Riverpod providers using the <Feature>Provider or <Feature>Notifier convention
Localize all user-facing strings via ARB files and access them with S.of(context) rather than hard-coded literals

Files:

  • lib/shared/providers/push_notification_provider.dart
  • lib/core/config.dart
  • lib/shared/notifiers/session_notifier.dart
  • lib/services/push_notification_service.dart
  • lib/main.dart
lib/shared/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Follow existing feature patterns when adding new shared utilities - refer to order, chat, and auth features as implementation examples

Files:

  • lib/shared/providers/push_notification_provider.dart
  • lib/shared/notifiers/session_notifier.dart
lib/services/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Access NostrService for all Nostr protocol interactions - NostrService manages relay connections and messaging

Files:

  • lib/services/push_notification_service.dart
lib/main.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Configure timeago package locales in app initialization for proper relative time formatting (e.g., 'hace X horas' vs 'hours ago')

Files:

  • lib/main.dart
🧠 Learnings (24)
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Name Riverpod providers using the `<Feature>Provider` or `<Feature>Notifier` convention

Applied to files:

  • lib/shared/providers/push_notification_provider.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/providers/**/*.dart : Organize Riverpod providers by feature in `features/{feature}/providers/` using Notifier pattern for complex state logic

Applied to files:

  • lib/shared/providers/push_notification_provider.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, Riverpod code generation is used with `Riverpod` annotations. Providers like `eventStorageProvider` are generated in `.g.dart` files from annotated functions in the main provider files. These providers are accessible by importing the main provider file (e.g., `mostro_service_provider.dart`), not by importing a separate provider file.

Applied to files:

  • lib/shared/providers/push_notification_provider.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/providers/**/*.dart : Use Notifier pattern instead of simple StateNotifier for complex state logic requiring business rule encapsulation

Applied to files:

  • lib/shared/providers/push_notification_provider.dart
  • lib/shared/notifiers/session_notifier.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to **/*.dart : Use Riverpod for all state management - encapsulate business logic in Notifiers and access data only through repository classes

Applied to files:

  • lib/shared/providers/push_notification_provider.dart
📚 Learning: 2025-05-06T15:49:26.443Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/services/mostro_service.dart:70-76
Timestamp: 2025-05-06T15:49:26.443Z
Learning: In the Mostro Mobile codebase, `eventStorageProvider` is exported from `package:mostro_mobile/shared/providers/mostro_service_provider.dart` and not from a separate `event_storage_provider.dart` file.

Applied to files:

  • lib/shared/providers/push_notification_provider.dart
📚 Learning: 2025-10-14T21:12:06.887Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:141-154
Timestamp: 2025-10-14T21:12:06.887Z
Learning: In the MostroP2P mobile codebase, the notification system uses a two-layer localization pattern: providers/notifiers (without BuildContext access) call `showCustomMessage()` with string keys (e.g., 'orderTimeoutMaker', 'orderCanceled'), and the UI layer's `NotificationListenerWidget` has a switch statement that maps these keys to localized strings using `S.of(context)`. This architectural pattern properly separates concerns while maintaining full localization support for all user-facing messages.

Applied to files:

  • lib/shared/providers/push_notification_provider.dart
  • lib/core/config.dart
  • lib/shared/notifiers/session_notifier.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/auth/notifiers/abstract_mostro_notifier.dart : Start 10-second cleanup timer automatically when taking orders via `startSessionTimeoutCleanup()` to prevent orphan sessions

Applied to files:

  • lib/shared/notifiers/session_notifier.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/notifiers/**/*.dart : Encapsulate business logic in Notifiers - Notifiers should expose state via providers and handle all complex state transitions

Applied to files:

  • lib/shared/notifiers/session_notifier.dart
  • lib/main.dart
📚 Learning: 2025-10-21T21:47:03.451Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 327
File: lib/features/order/notfiers/abstract_mostro_notifier.dart:157-182
Timestamp: 2025-10-21T21:47:03.451Z
Learning: In MostroP2P/mobile, for Action.canceled handling in abstract_mostro_notifier.dart (Riverpod StateNotifier), do not add mounted checks after async sessionNotifier.deleteSession(orderId) as they break order state synchronization during app restart. The Action.canceled flow contains critical business logic that must complete fully; Riverpod handles provider disposal automatically. Mounted checks should only protect UI operations, not business logic in StateNotifiers.

Applied to files:

  • lib/shared/notifiers/session_notifier.dart
  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/orders/notifiers/add_order_notifier.dart : Start session timeout cleanup in `submitOrder()` method to prevent orphan sessions when Mostro doesn't respond within 10 seconds

Applied to files:

  • lib/shared/notifiers/session_notifier.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/subscriptions/subscription_manager.dart : Use SubscriptionManager with `fireImmediately: false` during SessionNotifier initialization to prevent premature execution

Applied to files:

  • lib/shared/notifiers/session_notifier.dart
  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/auth/notifiers/abstract_mostro_notifier.dart : Use `startSessionTimeoutCleanupForRequestId()` for order creation timeout protection and cancel timer automatically when any Mostro response received

Applied to files:

  • lib/shared/notifiers/session_notifier.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/services/nostr_service.dart : Manage all relay connections and Nostr messaging through NostrService - automatically reconnect when relay list updates

Applied to files:

  • lib/shared/notifiers/session_notifier.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/services/**/*.dart : Access NostrService for all Nostr protocol interactions - NostrService manages relay connections and messaging

Applied to files:

  • lib/shared/notifiers/session_notifier.dart
📚 Learning: 2025-06-08T23:54:09.987Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 110
File: lib/features/trades/widgets/mostro_message_detail_widget.dart:134-141
Timestamp: 2025-06-08T23:54:09.987Z
Learning: In the OrderState refactor, public keys should be accessed via `tradeState.peer?.publicKey` from the OrderState instance rather than through `session?.peer?.publicKey`, as the OrderState encapsulates peer information directly.

Applied to files:

  • lib/shared/notifiers/session_notifier.dart
📚 Learning: 2025-11-27T12:10:12.081Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.081Z
Learning: Run `flutter test integration_test/` only for significant changes affecting core services and main user flows

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:26.407Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-27T12:10:26.407Z
Learning: Applies to **/*.dart : Persistence, APIs, and background jobs should live in `lib/data/` and `lib/background/`; generated localization output must be in `lib/generated/` and must stay untouched

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/**/screens/**/*.dart : Keep UI code declarative and side-effect free - use post-frame callbacks for side effects like SnackBars/dialogs

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to **/*.dart : Always check `mounted` before using BuildContext after async operations to prevent errors on disposed widgets

Applied to files:

  • lib/main.dart
📚 Learning: 2025-07-20T23:33:17.689Z
Learnt from: Catrya
Repo: MostroP2P/mobile PR: 215
File: lib/features/order/notfiers/order_notifier.dart:0-0
Timestamp: 2025-07-20T23:33:17.689Z
Learning: In AbstractMostroNotifier, the handleEvent method is synchronous and returns void, not Future<void>. Do not suggest adding await to super.handleEvent() calls as this would cause compilation errors.

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/relays/**/*.dart : Use dual storage strategy: store Mostro/default relays in `settings.relays` and user relays in `settings.userRelays` with full JSON metadata via `toJson()`/`fromJson()`

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/features/settings/settings.dart : Use null-aware operators (`??`) in Settings `copyWith()` method to preserve existing values for selectedLanguage and defaultLightningAddress when not explicitly overridden

Applied to files:

  • lib/main.dart
📚 Learning: 2025-11-27T12:10:12.082Z
Learnt from: CR
Repo: MostroP2P/mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-27T12:10:12.082Z
Learning: Applies to lib/main.dart : Configure timeago package locales in app initialization for proper relative time formatting (e.g., 'hace X horas' vs 'hours ago')

Applied to files:

  • lib/main.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (9)
lib/core/config.dart (2)

46-51: LGTM! Push server configuration follows established patterns.

The pushServerUrl configuration uses the same String.fromEnvironment pattern as other config values, allowing environment-based overrides while providing a sensible default.


15-16: Verify the updated Mostro public key is correct.

The default mostroPubKey has been changed. Ensure this new public key (0a537332f2d5...) corresponds to the intended Mostro daemon instance for production use.

lib/shared/providers/push_notification_provider.dart (1)

1-10: LGTM! Provider follows naming conventions and properly wires the service.

The provider correctly uses the <Feature>Provider naming convention and injects the configuration dependency. Based on learnings about Riverpod provider naming.

lib/shared/notifiers/session_notifier.dart (1)

126-143: LGTM! Error handling prevents push failures from breaking session management.

The try-catch wrappers with warning logs ensure that push notification failures don't crash the core session lifecycle, which is the right trade-off for a non-critical enhancement.

lib/main.dart (2)

113-119: LGTM! Firebase initialization follows recommended patterns.

The conditional initialization on supported platforms and early registration of the background message handler follows Firebase Messaging best practices.


56-59: No issues found. The comment about SharedPreferencesAsync behavior is accurate—no reload() is needed because the API queries the platform directly on each call without a local cache.

lib/services/push_notification_service.dart (3)

12-18: Well-documented privacy-preserving design.

The class-level documentation clearly explains the MIP-05 inspired privacy approach. This helps future maintainers understand the security model.


163-228: LGTM! Cryptographic implementation follows sound practices.

The encryption implementation uses:

  • Ephemeral ECDH for forward secrecy
  • ChaCha20-Poly1305 AEAD with 128-bit auth tag
  • Proper random number generation via Random.secure()
  • secp256k1 curve (consistent with Nostr ecosystem)

71-126: LGTM! Token registration has proper validation and error handling.

The method correctly:

  • Validates preconditions (platform support, server pubkey initialized)
  • Handles null FCM token gracefully
  • Uses timeouts for HTTP requests
  • Logs truncated keys for privacy
  • Parses response for success confirmation

Comment on lines +188 to +200
/// Initialize push notification service for targeted notifications
Future<void> _initializePushService() async {
if (!_isFirebaseSupported) return;

try {
final pushService = PushNotificationService(
pushServerUrl: Config.pushServerUrl,
);
await pushService.initialize();
} catch (e) {
debugPrint('Failed to initialize push service: $e');
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Redundant PushNotificationService instantiation.

This function creates and initializes a standalone PushNotificationService instance that is immediately discarded. Meanwhile, pushNotificationServiceProvider creates a separate instance when accessed by SessionNotifier. This results in:

  1. Two separate service instances with independent state
  2. The provider's instance never has initialize() called (no server pubkey fetched)
  3. Token registration will fail since _serverPubkey is null in the provider's instance

Either use the provider here or ensure the provider's instance is initialized.

🔎 Proposed fix: Initialize the provider's instance instead
 /// Initialize push notification service for targeted notifications
 Future<void> _initializePushService() async {
   if (!_isFirebaseSupported) return;

   try {
-    final pushService = PushNotificationService(
-      pushServerUrl: Config.pushServerUrl,
-    );
-    await pushService.initialize();
+    // Note: Initialization should happen through the provider
+    // The provider instance will be initialized when first accessed
+    // Consider moving this to a proper app initialization flow with ProviderContainer
+    debugPrint('Push service will be initialized on first use');
   } catch (e) {
     debugPrint('Failed to initialize push service: $e');
   }
 }

Or better, modify the provider to auto-initialize:

final pushNotificationServiceProvider = FutureProvider<PushNotificationService>((ref) async {
  final service = PushNotificationService(pushServerUrl: Config.pushServerUrl);
  await service.initialize();
  return service;
});
🤖 Prompt for AI Agents
lib/main.dart lines 188-200: The function currently constructs a throwaway
PushNotificationService instance and initializes it, leaving the provider's
instance uninitialized which causes null server pubkey and token registration
failures; either obtain and initialize the provider's instance here (e.g., await
the existing pushNotificationServiceProvider so its service.initialize() is
called) or change the provider to auto-initialize by making it return an
already-initialized service (convert it to a FutureProvider that constructs the
service, calls initialize(), then returns it); implement one of these fixes so
only the provider's instance is used and initialized.

Comment on lines +128 to +157
/// Unregister a device token for a specific trade
Future<bool> unregisterToken(String tradePubkey) async {
if (!isSupported) {
return false;
}

try {
final response = await http
.post(
Uri.parse('$_pushServerUrl/api/unregister'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'trade_pubkey': tradePubkey,
}),
)
.timeout(const Duration(seconds: 10));

if (response.statusCode == 200) {
_logger.i(
'Token unregistered for trade ${tradePubkey.substring(0, 16)}...');
return true;
}

_logger.w('Failed to unregister token: ${response.body}');
return false;
} catch (e) {
_logger.e('Error unregistering token: $e');
return false;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the push notification service file
find . -name "push_notification_service.dart" -type f 2>/dev/null | head -20

Repository: MostroP2P/mobile

Length of output: 105


🏁 Script executed:

#!/bin/bash
# Get file size and then examine it
if [ -f "lib/services/push_notification_service.dart" ]; then
  wc -l lib/services/push_notification_service.dart
  echo "---"
  cat -n lib/services/push_notification_service.dart
else
  echo "File not found at lib/services/push_notification_service.dart"
  # Search for it
  find . -name "*push*" -type f 2>/dev/null | grep -E "\.(dart|yaml)$"
fi

Repository: MostroP2P/mobile

Length of output: 12116


🏁 Script executed:

#!/bin/bash
# Look for registerToken method to compare
rg "registerToken|unregisterToken" --type dart -B 3 -A 15

Repository: MostroP2P/mobile

Length of output: 5148


🏁 Script executed:

#!/bin/bash
# Search for authentication patterns in service files
rg "signature|signed|auth|_serverPubkey|_pushServer" lib/services/ --type dart -B 2 -A 2

Repository: MostroP2P/mobile

Length of output: 13823


Unregister endpoint lacks ownership verification - enables denial of service.

The unregisterToken method sends only the trade_pubkey without any proof of ownership. An attacker can unregister other users' trade pubkeys, preventing them from receiving push notifications. While this doesn't leak data, it's an availability risk.

The asymmetry is evident: registerToken uses encryption (via _encryptToken()) to secure the operation, but unregisterToken sends a plain request. Consider requiring the same encrypted payload or a cryptographic signature to prove ownership before allowing unregistration, consistent with the privacy-preserving model documented in the class.

🤖 Prompt for AI Agents
In lib/services/push_notification_service.dart around lines 128 to 157, the
unregisterToken call sends only the trade_pubkey allowing
unauthenticated/unverified unregistrations; update the method to include the
same ownership proof used by registerToken (e.g. call and attach _encryptToken()
output or a cryptographic signature in the request body/headers), send the
encrypted payload instead of or alongside trade_pubkey, preserve timeout and
content-type handling, and update logging/error paths to reflect failed
encryption or missing proof so the server can verify ownership before
unregistration.

Comment on lines +230 to +248
/// Create padded payload: platform_byte || token_length (2 bytes BE) || token || random_padding
Uint8List _createPaddedPayload(String deviceToken, Random random) {
final tokenBytes = utf8.encode(deviceToken);
final platformByte = Platform.isIOS ? _platformIos : _platformAndroid;

final payload = Uint8List(_paddedPayloadSize);
payload[0] = platformByte;
payload[1] = (tokenBytes.length >> 8) & 0xFF;
payload[2] = tokenBytes.length & 0xFF;
payload.setRange(3, 3 + tokenBytes.length, tokenBytes);

// Fill rest with random padding
final paddingStart = 3 + tokenBytes.length;
for (var i = paddingStart; i < _paddedPayloadSize; i++) {
payload[i] = random.nextInt(256);
}

return payload;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing validation for token length exceeding payload capacity.

If deviceToken exceeds 217 bytes (220 - 3 header bytes), setRange at line 239 will write beyond the payload buffer, causing a runtime error. FCM tokens are typically ~163 chars, but this should be validated defensively.

🔎 Proposed fix: Add length validation
   Uint8List _createPaddedPayload(String deviceToken, Random random) {
     final tokenBytes = utf8.encode(deviceToken);
+    
+    // Validate token fits in payload (220 - 3 header bytes = 217 max)
+    if (tokenBytes.length > _paddedPayloadSize - 3) {
+      throw ArgumentError('Device token too long: ${tokenBytes.length} bytes (max: ${_paddedPayloadSize - 3})');
+    }
+    
     final platformByte = Platform.isIOS ? _platformIos : _platformAndroid;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Create padded payload: platform_byte || token_length (2 bytes BE) || token || random_padding
Uint8List _createPaddedPayload(String deviceToken, Random random) {
final tokenBytes = utf8.encode(deviceToken);
final platformByte = Platform.isIOS ? _platformIos : _platformAndroid;
final payload = Uint8List(_paddedPayloadSize);
payload[0] = platformByte;
payload[1] = (tokenBytes.length >> 8) & 0xFF;
payload[2] = tokenBytes.length & 0xFF;
payload.setRange(3, 3 + tokenBytes.length, tokenBytes);
// Fill rest with random padding
final paddingStart = 3 + tokenBytes.length;
for (var i = paddingStart; i < _paddedPayloadSize; i++) {
payload[i] = random.nextInt(256);
}
return payload;
}
/// Create padded payload: platform_byte || token_length (2 bytes BE) || token || random_padding
Uint8List _createPaddedPayload(String deviceToken, Random random) {
final tokenBytes = utf8.encode(deviceToken);
// Validate token fits in payload (220 - 3 header bytes = 217 max)
if (tokenBytes.length > _paddedPayloadSize - 3) {
throw ArgumentError('Device token too long: ${tokenBytes.length} bytes (max: ${_paddedPayloadSize - 3})');
}
final platformByte = Platform.isIOS ? _platformIos : _platformAndroid;
final payload = Uint8List(_paddedPayloadSize);
payload[0] = platformByte;
payload[1] = (tokenBytes.length >> 8) & 0xFF;
payload[2] = tokenBytes.length & 0xFF;
payload.setRange(3, 3 + tokenBytes.length, tokenBytes);
// Fill rest with random padding
final paddingStart = 3 + tokenBytes.length;
for (var i = paddingStart; i < _paddedPayloadSize; i++) {
payload[i] = random.nextInt(256);
}
return payload;
}
🤖 Prompt for AI Agents
In lib/services/push_notification_service.dart around lines 230 to 248, the
method _createPaddedPayload does not validate deviceToken length and can write
past the payload buffer if tokenBytes.length > _paddedPayloadSize - 3; add a
defensive check right after computing tokenBytes (e.g., if (tokenBytes.length >
_paddedPayloadSize - 3) throw ArgumentError.value(deviceToken, 'deviceToken',
'encoded token too long for padded payload')); this prevents buffer overflow and
surfaces a clear error to callers; keep existing behavior otherwise and return
the padded payload as before.

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.

4 participants