Skip to content

Conversation

@Catrya
Copy link
Member

@Catrya Catrya commented Nov 26, 2025

to fix #351

image image
  • Add file picker integration for PDFs, documents, videos and images
  • Implement ChaCha20-Poly1305 encryption for all file types
  • Upload encrypted files to Blossom servers with fallback support
  • Send file metadata via NIP-59 gift wrap messages
  • Add download-on-demand functionality for received files
  • Display files with type-specific icons and metadata
  • Auto-preview images, manual download for other file types
  • Integrate system file opening with native apps
  • Add file validation service with size limits

Summary by CodeRabbit

  • New Features

    • Encrypted file/image messaging: client-side encryption for uploads, background download/decrypt with retries, local caching, image previews, open/download UX, and chat UI integration.
  • Bug Fixes

    • Android build: targeted AndroidX resolution added to fix a build error.
  • Documentation

    • Added comprehensive architecture/implementation guide for encrypted image/file messaging.
  • Localization

    • Added file-messaging, confirmation, and decryption/status strings across locales.
  • Tests

    • Added end-to-end file-messaging tests and updated test mocks.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 26, 2025

Caution

Review failed

An error occurred during the review process. Please try again later.

Walkthrough

Adds end-to-end encrypted image/file messaging: encryption primitives and blob format, Blossom upload/download clients with retries, validation/sanitization, notifier shared-key access and in-memory caches, UI widgets and upload/upload flows, tests, localization entries, dependencies, and a targeted AndroidX Gradle resolution block.

Changes

Cohort / File(s) Summary
Build & Docs
android/app/build.gradle, docs/architecture/ENCRYPTED_IMAGE_MESSAGING_IMPLEMENTATION.md
Adds a targeted AndroidX resolutionStrategy forcing two androidx.activity artifacts and a comprehensive design/implementation document for encrypted image/file messaging.
Encryption Core
lib/services/encryption_service.dart
Implements ChaCha20-Poly1305 encrypt/decrypt, blob serialization (nonce + ciphertext + tag), secure RNG, EncryptionResult container, and EncryptionException.
Blossom Integration & Config
lib/services/blossom_client.dart, lib/services/blossom_download_service.dart, lib/core/config/blossom_config.dart, lib/services/blossom_upload_helper.dart
Adds BlossomClient for authenticated uploads, BlossomDownloadService with retry/backoff/progress, BlossomUploadHelper to retry across servers, and BlossomConfig constants for servers/timeouts/retries.
Encrypted Upload Services
lib/services/encrypted_image_upload_service.dart, lib/services/encrypted_file_upload_service.dart, lib/services/image_upload_service.dart
New services to validate, encrypt, upload encrypted blobs to Blossom (multi-server retry) and to download+decrypt blobs; JSON-serializable result models and BlossomException handling.
Validation & Sanitization
lib/services/file_validation_service.dart, lib/services/media_validation_service.dart
File/image validation and sanitization (light/heavy), MIME/type detection, size limits, and type-specific validators.
Chat Notifier & Caching
lib/features/chat/notifiers/chat_room_notifier.dart
Adds getSharedKey() API, encrypted message detection/processing, background download/decrypt handlers, and in-memory caches for decrypted images/files and metadata; integrates into event processing.
Message Type Helpers
lib/features/chat/utils/message_type_helpers.dart
Adds MessageTypeUtils with encrypted message detectors and MessageContentType enum.
Chat UI Wiring
lib/features/chat/screens/chat_room_screen.dart, lib/features/chat/widgets/chat_messages_list.dart, lib/features/chat/widgets/message_bubble.dart
Threads orderId through widgets, converts MessageBubble to Riverpod ConsumerWidget (build signature changed), and adds rendering branches for encrypted image/file messages.
Encrypted Message Widgets
lib/features/chat/widgets/encrypted_image_message.dart, lib/features/chat/widgets/encrypted_file_message.dart
New Consumer widgets to load, decrypt, cache, and display encrypted images/files; handle loading/error states, previews, download/open actions, and filename sanitization.
Message Input & Upload UI
lib/features/chat/widgets/message_input.dart
Adds attach button, file picker, MIME detection, shared-key retrieval, encrypted upload flows, upload state and confirmation dialog, and constructor updates to accept orderId and info-type props.
Localization & Dependencies
pubspec.yaml, lib/l10n/intl_en.arb, lib/l10n/intl_es.arb, lib/l10n/intl_it.arb
Adds dependencies (image_picker, mime, image, pointycastle, file_picker, open_file, diacritic), adds file-messaging localization keys, and minor ARB text tweaks.
Tests & Mocks
test/features/chat/file_messaging_test.dart, test/mocks.dart, test/mocks.mocks.dart
Adds comprehensive tests for encryption/validation/upload/download flows and extends generated test mocks for Blossom and upload/download services and the chat notifier.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as EncryptedImageMessage
    participant Notifier as ChatRoomNotifier
    participant ImgSvc as EncryptedImageUploadService
    participant DL as BlossomDownloadService
    participant Enc as EncryptionService

    User->>UI: open message
    UI->>Notifier: getCachedImage(messageId)
    alt cached
        Notifier-->>UI: image bytes
        UI->>User: render image
    else not cached
        UI->>UI: parse metadata (blossomUrl, nonce)
        UI->>Notifier: getSharedKey()
        Notifier-->>UI: sharedKey
        UI->>ImgSvc: downloadAndDecryptImage(blossomUrl, sharedKey)
        ImgSvc->>DL: downloadWithRetry(blossomUrl)
        DL-->>ImgSvc: encrypted blob
        ImgSvc->>Enc: decryptFromBlob(blob, sharedKey)
        Enc-->>ImgSvc: plaintext bytes
        ImgSvc-->>UI: image bytes
        UI->>Notifier: cacheDecryptedImage(messageId, bytes, metadata)
        UI->>User: render image
    end
Loading
sequenceDiagram
    actor User
    participant Input as MessageInput
    participant Picker as FilePicker
    participant FileVal as FileValidationService
    participant EncSvc as EncryptedFileUploadService
    participant Blossom as BlossomClient
    participant Notifier as ChatRoomNotifier

    User->>Input: tap attach
    Input->>Picker: pickFile()
    Picker-->>Input: File
    Input->>Notifier: getSharedKey()
    Notifier-->>Input: sharedKey
    Input->>FileVal: validateFile(file)
    FileVal-->>Input: FileValidationResult
    Input->>EncSvc: uploadEncryptedFile(file, sharedKey)
    loop try blossom servers
        EncSvc->>Blossom: uploadImage(encryptedData)
        Blossom-->>EncSvc: blossomUrl / error
    end
    EncSvc-->>Input: EncryptedFileUploadResult
    Input->>Notifier: sendEncryptedFileMessage(result)
    Notifier-->>User: message sent
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Focus areas:
    • lib/services/encryption_service.dart — blob layout, nonce/auth-tag sizes, RNG seeding, and error paths.
    • lib/services/*_upload_service.dart, blossom helpers — multi-server retry, timeout/headers, error propagation.
    • lib/features/chat/notifiers/chat_room_notifier.dart — shared-key derivation, cache concurrency and lifecycle.
    • Widgets & Riverpod changes: message_input, message_bubble, encrypted_* widgets — async lifecycle, orderId propagation, UI state handling.
    • Tests/mocks: ensure generated mocks align with updated public interfaces and deterministic crypto test setup.

Possibly related PRs

Suggested reviewers

  • grunch

Poem

🐰 I munched on nonces, snug and bright,

hid bytes in blossoms out of sight.
ChaCha hummed a secret tune,
pixels bloomed beneath the moon.
Caches warm — the bunny hops with delight.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Add encrypted file messaging support' accurately reflects the main objective of implementing end-to-end encrypted file/image messaging with ChaCha20-Poly1305, Blossom uploads, and NIP-59 integration.
Linked Issues check ✅ Passed The PR implements the core requirement from issue #351 (enabling users to send images in chat) and extends it to support PDFs, videos, and documents with encryption, validation, and download-on-demand functionality.
Out of Scope Changes check ✅ Passed All changes directly support encrypted file messaging: encryption services (ChaCha20-Poly1305), Blossom upload/download clients, file validation, UI widgets, localization strings, and configuration. No unrelated changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

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: 12

🧹 Nitpick comments (24)
docs/architecture/ENCRYPTED_IMAGE_MESSAGING_IMPLEMENTATION.md (3)

31-45: Add language specifier to fenced code blocks for consistency.

Static analysis flagged missing language specifiers. For ASCII diagrams, use text or plaintext.

-```
+```text
 ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐

145-149: Add language specifier for blob structure diagram.

-```
+```text
 ┌─────────────┬──────────────────┬─────────────────┐

151-156: Clarify forward secrecy claim.

Line 155 states "Forward Secrecy: Compromised key doesn't affect past messages." However, based on line 140-142, the same shared_key is used for the entire chat session. True forward secrecy typically requires per-message or periodic key rotation. Consider clarifying this is session-level isolation rather than per-message forward secrecy.

lib/services/blossom_download_service.dart (1)

62-67: Avoid mutating parameter; use a local variable for backoff.

Reassigning retryDelay mutates the parameter. While Duration is immutable and this doesn't affect the caller, mutating parameters is a code smell and can confuse readers.

   static Future<Uint8List> downloadWithRetry(
     String blossomUrl, {
     int maxRetries = 3,
-    Duration retryDelay = const Duration(seconds: 1),
+    Duration initialRetryDelay = const Duration(seconds: 1),
   }) async {
     _logger.i('📥 Download with retry: $blossomUrl (max $maxRetries attempts)');
 
     Exception? lastException;
+    var currentDelay = initialRetryDelay;
     
     for (int attempt = 1; attempt <= maxRetries; attempt++) {
       try {
         _logger.d('Attempt $attempt/$maxRetries');
         return await downloadFromBlossom(blossomUrl);
       } catch (e) {
         lastException = e is Exception ? e : Exception(e.toString());
         _logger.w('❌ Attempt $attempt failed: $e');
         
         if (attempt < maxRetries) {
-          _logger.d('⏳ Waiting ${retryDelay.inMilliseconds}ms before retry...');
-          await Future.delayed(retryDelay);
+          _logger.d('⏳ Waiting ${currentDelay.inMilliseconds}ms before retry...');
+          await Future.delayed(currentDelay);
           // Exponential backoff
-          retryDelay = Duration(milliseconds: (retryDelay.inMilliseconds * 1.5).round());
+          currentDelay = Duration(milliseconds: (currentDelay.inMilliseconds * 1.5).round());
         }
       }
     }
lib/services/image_upload_service.dart (2)

63-94: Remove unreachable code at line 93.

The throw BlossomException('No Blossom servers available') at line 93 is unreachable. The loop either returns on success (line 78) or throws on the last iteration (line 85). If servers.length is 0, the loop body never executes, and we'd still reach line 93—but that would mean _blossomServers is empty, which is a static configuration issue.

       // Continue with next server
       continue;
     }
   }
   
-  throw BlossomException('No Blossom servers available');
+  // This is only reachable if _blossomServers is empty
+  throw BlossomException('No Blossom servers configured');
 }

Alternatively, add an early guard:

 Future<String> _uploadWithRetry(Uint8List imageData, String mimeType) async {
   final servers = _blossomServers;
+  
+  if (servers.isEmpty) {
+    throw BlossomException('No Blossom servers configured');
+  }
   
   for (int i = 0; i < servers.length; i++) {

11-21: Consider extracting shared Blossom server list.

The server list is duplicated across ImageUploadService, EncryptedImageUploadService, and EncryptedFileUploadService. This could be centralized in a shared configuration or constant file to avoid drift.

lib/features/chat/widgets/message_input.dart (1)

126-140: Avoid losing stack trace when re-throwing exceptions.

Wrapping the exception with throw Exception(...) loses the original stack trace. Consider using rethrow or Error.throwWithStackTrace.

   Future<void> _sendEncryptedFileMessage(EncryptedFileUploadResult result) async {
-    try {
-      // Create JSON content for the rumor
-      final fileMessageJson = jsonEncode(result.toJson());
-      
-      // Send via existing chat system (will be wrapped in NIP-59)
-      await ref
-          .read(chatRoomsProvider(widget.orderId).notifier)
-          .sendMessage(fileMessageJson);
-          
-    } catch (e) {
-      throw Exception('Failed to send encrypted file message: $e');
-    }
+    // Create JSON content for the rumor
+    final fileMessageJson = jsonEncode(result.toJson());
+    
+    // Send via existing chat system (will be wrapped in NIP-59)
+    // Exceptions will propagate with original stack trace
+    await ref
+        .read(chatRoomsProvider(widget.orderId).notifier)
+        .sendMessage(fileMessageJson);
   }

The caller at line 98 already catches and handles exceptions, so this inner try-catch is redundant.

lib/services/blossom_client.dart (1)

68-72: Consider parsing the actual URL from the response body.

The Blossom server typically returns JSON with the blob descriptor including the actual URL. Constructing the URL from the hash assumes a specific server structure. If the server returns the URL differently (e.g., with a file extension or different path), this could break.

       if (response.statusCode == 200 || response.statusCode == 201) {
-        // Blossom returns the file URL in the response, construct from hash
-        final blossomUrl = '$serverUrl/$hashHex';
+        // Parse the response to get the actual URL from Blossom
+        try {
+          final responseData = jsonDecode(response.body) as Map<String, dynamic>;
+          final blossomUrl = responseData['url'] as String? ?? '$serverUrl/$hashHex';
+          _logger.i('✅ Image uploaded successfully to Blossom: $blossomUrl');
+          return blossomUrl;
+        } catch (_) {
+          // Fallback to constructed URL if response parsing fails
+          final blossomUrl = '$serverUrl/$hashHex';
+          _logger.i('✅ Image uploaded successfully to Blossom: $blossomUrl');
+          return blossomUrl;
+        }
-        _logger.i('✅ Image uploaded successfully to Blossom: $blossomUrl');
-        return blossomUrl;
lib/services/media_validation_service.dart (1)

102-139: Consider offloading image processing to an isolate.

Image decoding and re-encoding are CPU-intensive operations that can freeze the UI for large images. Use Flutter's compute() function to run this work in a background isolate.

// Example approach:
static Future<MediaValidationResult> validateAndSanitizeImage(
  Uint8List imageData,
) async {
  // ... validation checks ...
  
  // Offload heavy work to isolate
  return await compute(_processImage, imageData);
}

static MediaValidationResult _processImage(Uint8List imageData) {
  // Move decoding and encoding logic here
}
lib/features/chat/widgets/message_bubble.dart (1)

62-134: Extract common bubble container to reduce duplication.

The container, constraints, decoration, and Row structure are nearly identical for encrypted images, encrypted files, and text messages. Consider extracting a shared _buildBubbleContainer method.

Widget _buildBubbleContainer({
  required BuildContext context,
  required bool isFromPeer,
  required Widget child,
  EdgeInsets padding = const EdgeInsets.all(8),
}) {
  return Container(
    padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
    alignment: isFromPeer ? Alignment.centerLeft : Alignment.centerRight,
    child: Row(
      mainAxisAlignment: isFromPeer ? MainAxisAlignment.start : MainAxisAlignment.end,
      children: [
        Flexible(
          child: ConstrainedBox(
            constraints: BoxConstraints(
              maxWidth: MediaQuery.of(context).size.width * 0.75,
              minWidth: 0,
            ),
            child: Container(
              padding: padding,
              decoration: BoxDecoration(
                color: isFromPeer ? _getPeerMessageColor(peerPubkey) : AppTheme.purpleAccent,
                borderRadius: BorderRadius.only(
                  topLeft: const Radius.circular(16),
                  topRight: const Radius.circular(16),
                  bottomLeft: Radius.circular(isFromPeer ? 4 : 16),
                  bottomRight: Radius.circular(isFromPeer ? 16 : 4),
                ),
              ),
              child: child,
            ),
          ),
        ),
      ],
    ),
  );
}
lib/services/file_validation_service.dart (1)

77-78: Use the path package for cross-platform filename extraction.

file.path.split('/').last assumes Unix-style paths. For cross-platform compatibility, use the path package.

+import 'package:path/path.dart' as p;

-    final filename = file.path.split('/').last;
+    final filename = p.basename(file.path);
lib/features/chat/widgets/encrypted_file_message.dart (2)

36-40: Use ref.watch instead of ref.read for reactive state in build().

Using ref.read in build() means the widget won't rebuild when the chat notifier's state changes (e.g., when cached files are updated). This could cause stale UI.

   @override
   Widget build(BuildContext context) {
-    final chatNotifier = ref.read(chatRoomsProvider(widget.orderId).notifier);
+    final chatNotifier = ref.watch(chatRoomsProvider(widget.orderId).notifier);
     
     // Check if file is already cached
-    final cachedFile = chatNotifier.getCachedFile(widget.message.id!);
-    final fileMetadata = chatNotifier.getFileMetadata(widget.message.id!);
+    final messageId = widget.message.id;
+    if (messageId == null) {
+      return _buildErrorWidget();
+    }
+    final cachedFile = chatNotifier.getCachedFile(messageId);
+    final fileMetadata = chatNotifier.getFileMetadata(messageId);

62-70: Potential infinite rebuild loop on image auto-download.

If _downloadFile() fails and sets _errorMessage, the widget rebuilds. Since _errorMessage != null is checked before reaching line 63, this specific path won't loop. However, if the metadata parsing succeeds but download keeps failing transiently without setting error state, this could cause repeated download attempts on each rebuild.

Consider adding a flag to track that auto-download was already attempted.

 class _EncryptedFileMessageState extends ConsumerState<EncryptedFileMessage> {
   bool _isLoading = false;
   String? _errorMessage;
+  bool _autoDownloadAttempted = false;
   
   ...
   
       // For images, try to download automatically for preview
       if (_isImage(metadata.fileType)) {
         WidgetsBinding.instance.addPostFrameCallback((_) {
-          if (!_isLoading) {
+          if (!_isLoading && !_autoDownloadAttempted) {
+            _autoDownloadAttempted = true;
             _downloadFile();
           }
         });
       }
lib/services/encrypted_image_upload_service.dart (3)

46-58: Add null safety checks in fromJson factory.

Direct map access without null checks will throw if any field is missing, making debugging difficult. Consider explicit checks or using null-aware operators with defaults.

   factory EncryptedImageUploadResult.fromJson(Map<String, dynamic> json) {
+    final blossomUrl = json['blossom_url'];
+    final nonce = json['nonce'];
+    if (blossomUrl == null || nonce == null) {
+      throw FormatException('Missing required fields in encrypted image metadata');
+    }
     return EncryptedImageUploadResult(
-      blossomUrl: json['blossom_url'],
-      nonce: json['nonce'],
-      mimeType: json['mime_type'],
-      originalSize: json['original_size'],
-      width: json['width'],
-      height: json['height'],
-      filename: json['filename'],
-      encryptedSize: json['encrypted_size'],
+      blossomUrl: blossomUrl as String,
+      nonce: nonce as String,
+      mimeType: json['mime_type'] as String? ?? 'application/octet-stream',
+      originalSize: json['original_size'] as int? ?? 0,
+      width: json['width'] as int? ?? 0,
+      height: json['height'] as int? ?? 0,
+      filename: json['filename'] as String? ?? 'unknown',
+      encryptedSize: json['encrypted_size'] as int? ?? 0,
     );
   }

3-3: Remove unused import.

flutter/foundation.dart is imported but not used in this file.

 import 'dart:io';
 import 'dart:typed_data';
-import 'package:flutter/foundation.dart';
 import 'package:logger/logger.dart';

226-231: Duplicate BlossomException class definition.

This exception class is also defined in encrypted_file_upload_service.dart. Consider consolidating into a shared location to avoid confusion and ensure consistent behavior.

Consider moving BlossomException to a shared file like lib/services/blossom_exceptions.dart and importing it in both services.

lib/features/chat/notifiers/chat_room_notifier.dart (4)

478-484: In-memory caches grow unbounded - potential memory leak.

The _imageCache and _fileCache maps store decrypted file data indefinitely. For long chat sessions with many media files, this could consume significant memory.

Consider implementing cache eviction (e.g., LRU with size limit) or clearing caches on dispose.

+  static const int _maxCacheEntries = 50;
+
   // Simple cache for decrypted images
   final Map<String, Uint8List> _imageCache = {};
   final Map<String, EncryptedImageUploadResult> _imageMetadata = {};
   
   // Simple cache for decrypted files
   final Map<String, Uint8List> _fileCache = {};
   final Map<String, EncryptedFileUploadResult> _fileMetadata = {};

+  void _evictOldestCacheEntries() {
+    while (_imageCache.length > _maxCacheEntries) {
+      _imageCache.remove(_imageCache.keys.first);
+      _imageMetadata.remove(_imageMetadata.keys.first);
+    }
+    while (_fileCache.length > _maxCacheEntries) {
+      _fileCache.remove(_fileCache.keys.first);
+      _fileMetadata.remove(_fileMetadata.keys.first);
+    }
+  }

Then call _evictOldestCacheEntries() in cacheDecryptedImage and cacheDecryptedFile.


442-476: Pre-downloading blocks message processing - consider fire-and-forget.

_processEncryptedImageMessage is awaited during _onChatEvent, which means message display is delayed until the image downloads. For slow networks, this could significantly delay showing new messages.

Consider using fire-and-forget for pre-downloads:

-      await _processMessageContent(chat);
+      // Process in background - don't block message display
+      _processMessageContent(chat);

The UI widgets already handle the case where cached data isn't available.


558-569: Clarify API: cacheDecryptedFile accepts nullable fileData.

The method signature allows null for fileData, which is used to store metadata without file data (lines 544, 549). This is a valid pattern but could be confusing. Consider documenting this behavior or using a separate method.

-  /// Cache a decrypted file for quick display
+  /// Cache a decrypted file for quick display.
+  /// Pass null for [fileData] to cache only metadata (for on-demand download).
   void cacheDecryptedFile(
     String messageId, 
     Uint8List? fileData, 
     EncryptedFileUploadResult metadata
   ) {

607-613: Clear caches on dispose to free memory.

The caches should be cleared when the notifier is disposed to ensure memory is released.

   @override
   void dispose() {
     _subscription?.cancel();
     _sessionListener?.close();
+    _imageCache.clear();
+    _imageMetadata.clear();
+    _fileCache.clear();
+    _fileMetadata.clear();
     _logger.i('Disposed chat room notifier for orderId: $orderId');
     super.dispose();
   }
lib/services/encrypted_file_upload_service.dart (4)

43-54: Add null safety checks in fromJson factory.

Same issue as EncryptedImageUploadResult - direct map access without null checks will throw unhelpful errors if fields are missing.

   factory EncryptedFileUploadResult.fromJson(Map<String, dynamic> json) {
+    final blossomUrl = json['blossom_url'];
+    final nonce = json['nonce'];
+    final fileType = json['file_type'];
+    if (blossomUrl == null || nonce == null || fileType == null) {
+      throw FormatException('Missing required fields in encrypted file metadata');
+    }
     return EncryptedFileUploadResult(
-      blossomUrl: json['blossom_url'],
-      nonce: json['nonce'],
-      mimeType: json['mime_type'],
-      fileType: json['file_type'],
-      originalSize: json['original_size'],
-      filename: json['filename'],
-      encryptedSize: json['encrypted_size'],
+      blossomUrl: blossomUrl as String,
+      nonce: nonce as String,
+      mimeType: json['mime_type'] as String? ?? 'application/octet-stream',
+      fileType: fileType as String,
+      originalSize: json['original_size'] as int? ?? 0,
+      filename: json['filename'] as String? ?? 'unknown',
+      encryptedSize: json['encrypted_size'] as int? ?? 0,
     );
   }

60-71: Duplicate Blossom server list - consider centralizing.

The same server list exists in both EncryptedFileUploadService and EncryptedImageUploadService. This duplication could lead to inconsistencies if servers are added/removed.

Consider creating a shared configuration:

// lib/services/blossom_config.dart
class BlossomConfig {
  static const List<String> servers = [
    'https://blossom.primal.net',
    'https://blossom.band',
    // ... etc
  ];
}

3-3: Remove unused import.

flutter/foundation.dart is imported but not used.

 import 'dart:io';
 import 'dart:typed_data';
-import 'package:flutter/foundation.dart';
 import 'package:logger/logger.dart';

216-222: Duplicate BlossomException class.

This is defined identically in encrypted_image_upload_service.dart. Consolidate to avoid potential import conflicts.

Move to a shared location like lib/services/blossom_exceptions.dart.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4c49bc and 5b41d14.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • android/app/build.gradle (1 hunks)
  • docs/architecture/ENCRYPTED_IMAGE_MESSAGING_IMPLEMENTATION.md (1 hunks)
  • lib/features/chat/notifiers/chat_room_notifier.dart (3 hunks)
  • lib/features/chat/screens/chat_room_screen.dart (1 hunks)
  • lib/features/chat/widgets/chat_messages_list.dart (2 hunks)
  • lib/features/chat/widgets/encrypted_file_message.dart (1 hunks)
  • lib/features/chat/widgets/encrypted_image_message.dart (1 hunks)
  • lib/features/chat/widgets/message_bubble.dart (2 hunks)
  • lib/features/chat/widgets/message_input.dart (5 hunks)
  • lib/services/blossom_client.dart (1 hunks)
  • lib/services/blossom_download_service.dart (1 hunks)
  • lib/services/encrypted_file_upload_service.dart (1 hunks)
  • lib/services/encrypted_image_upload_service.dart (1 hunks)
  • lib/services/encryption_service.dart (1 hunks)
  • lib/services/file_validation_service.dart (1 hunks)
  • lib/services/image_upload_service.dart (1 hunks)
  • lib/services/media_validation_service.dart (1 hunks)
  • pubspec.yaml (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 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/architecture/ENCRYPTED_IMAGE_MESSAGING_IMPLEMENTATION.md
📚 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/chat/widgets/message_bubble.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/chat/notifiers/chat_room_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, `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/chat/notifiers/chat_room_notifier.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/chat/notifiers/chat_room_notifier.dart
🪛 markdownlint-cli2 (0.18.1)
docs/architecture/ENCRYPTED_IMAGE_MESSAGING_IMPLEMENTATION.md

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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)

⏰ 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 (21)
android/app/build.gradle (1)

115-122: Targeted AndroidX conflict resolution looks appropriate.

The minimal approach of forcing only the problematic dependencies rather than all AndroidX artifacts is a good practice. The comment clearly documents the intent.

Consider documenting which specific dependency introduced this conflict (likely file_picker or open_file) in case future package updates resolve it naturally.

lib/services/blossom_download_service.dart (1)

10-42: Download implementation looks solid.

Good use of timeout, proper error handling that distinguishes domain exceptions from network errors, and appropriate logging.

lib/features/chat/screens/chat_room_screen.dart (1)

157-162: LGTM!

Clean propagation of orderId to ChatMessagesList enabling encrypted media components to access order context.

lib/features/chat/widgets/chat_messages_list.dart (2)

10-19: LGTM!

Clean addition of orderId field and constructor requirement.


131-138: LGTM!

Correct propagation of orderId to each MessageBubble for encrypted media context.

lib/services/image_upload_service.dart (1)

26-60: Upload workflow looks solid.

Good separation of concerns: read → validate/sanitize → upload with retry. Logging provides visibility into the process.

lib/features/chat/widgets/message_input.dart (2)

69-99: File selection and upload flow is well-structured.

Good use of loading state, mounted checks, and finally block for cleanup. The flow correctly chains file picking → key retrieval → encryption/upload → message sending.


169-200: Attach button UI implementation is clean.

Good handling of loading state with disabled button and spinner indicator. Consistent styling with the existing send button.

lib/services/blossom_client.dart (1)

9-17: LGTM!

The client structure is clean with configurable timeout and proper dependency injection of the server URL.

lib/services/media_validation_service.dart (1)

5-45: LGTM!

The enum design with extension methods for mime type and extension mappings is clean and maintainable.

lib/features/chat/widgets/message_bubble.dart (1)

38-53: LGTM!

The migration to ConsumerWidget and addition of orderId for threading context through to child widgets is clean.

lib/features/chat/widgets/encrypted_image_message.dart (1)

55-139: LGTM!

The image display widget with proper constraints, error handling in Image.memory, and metadata display is well-implemented.

lib/services/file_validation_service.dart (2)

24-28: LGTM!

The service structure with clear constants for size limits and supported types is clean and maintainable.


76-120: Validation logic is sound.

The multi-step validation (size check → MIME detection → fallback to extension → type verification) is a robust approach for file validation.

lib/features/chat/widgets/encrypted_file_message.dart (1)

511-522: LGTM - helper function correctly detects encrypted file messages.

The isEncryptedFileMessage helper properly handles null content and JSON parsing exceptions, returning false on any failure.

lib/services/encryption_service.dart (4)

55-67: LGTM - Secure random initialization is correct.

The SecureRandom is seeded with cryptographically secure bytes from Random.secure(). The Fortuna CSPRNG is appropriate for generating nonces.


79-132: LGTM - ChaCha20-Poly1305 encryption implementation is correct.

Key and nonce length validation is properly enforced. The cipher is correctly initialized with AEAD parameters, and the authentication tag is properly extracted from the output.


134-187: LGTM - Decryption with proper authentication tag verification.

The implementation correctly validates input lengths and combines encrypted data with auth tag for AEAD decryption. PointyCastle's doFinal will throw if authentication fails.


34-52: LGTM - Blob serialization is well-structured.

The fromBlob method correctly validates minimum blob size (28 bytes for nonce + auth tag) and extracts components in the documented order: [nonce][encrypted_data][auth_tag].

lib/services/encrypted_image_upload_service.dart (1)

181-213: LGTM - Upload retry logic with server fallback is well implemented.

The implementation correctly iterates through multiple Blossom servers and only throws after all servers have been exhausted.

lib/services/encrypted_file_upload_service.dart (1)

75-131: LGTM - Upload flow is well-structured.

The upload flow correctly validates, encrypts, and uploads with proper error handling and logging.

@Catrya Catrya marked this pull request as draft November 26, 2025 23:36
@Catrya Catrya marked this pull request as ready for review December 1, 2025 23:06
@Catrya
Copy link
Member Author

Catrya commented Dec 1, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 1, 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: 3

Caution

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

⚠️ Outside diff range comments (1)
lib/features/chat/notifiers/chat_room_notifier.dart (1)

103-107: Don't block chat event processing on Blossom downloads/decryptions.

_onChatEvent awaits _processMessageContent(chat), and the processing path can perform multi-minute network operations (Blossom downloadWithRetry + decryption). If Blossom is slow or unreachable, chat message delivery and event handling for this room are effectively stalled until those calls finish.

Consider kicking off the encrypted-content prefetch in the background instead of blocking the event pipeline, e.g.:

-      // Process special message types (e.g., encrypted images)
-      await _processMessageContent(chat);
+      // Process special message types (e.g., encrypted images) in background
+      // so message delivery isn't blocked by Blossom downloads/decryptions.
+      unawaited(_processMessageContent(chat));

_processMessageContent already catches and logs its own errors, so running it unawaited should be safe while significantly improving responsiveness.

Also applies to: 151-155, 415-451, 453-487, 518-566

♻️ Duplicate comments (3)
lib/features/chat/widgets/message_input.dart (1)

121-130: Avoid exposing raw exception details to users.

The error message 'Error uploading file: $e' may expose internal details (file paths, network errors, stack traces) to users.

Apply the previously suggested fix:

     } catch (e) {
       // Show error to user
       if (mounted) {
+        debugPrint('File upload error: $e');
         ScaffoldMessenger.of(context).showSnackBar(
           SnackBar(
-            content: Text('Error uploading file: $e'),
+            content: Text('Failed to upload file. Please try again.'),
             backgroundColor: Colors.red,
           ),
         );
       }
     } finally {

As per coding guidelines, user-facing strings should be localized via ARB files.

lib/features/chat/widgets/encrypted_file_message.dart (1)

35-43: Handle potential null message.id defensively before cache/file operations.

widget.message.id! is force-unwrapped in build(), _downloadFile(), and _openFile(). If an event ever arrives without an ID (or with a partially constructed model), this will throw and break the whole message list.

Mirror the defensive pattern used in EncryptedImageMessage by reading the ID into a local, checking for null once, and then using the non-null local everywhere:

  @override
  Widget build(BuildContext context) {
    final chatNotifier = ref.read(chatRoomsProvider(widget.orderId).notifier);
-    
-    // Check if file is already cached
-    final cachedFile = chatNotifier.getCachedFile(widget.message.id!);
-    final fileMetadata = chatNotifier.getFileMetadata(widget.message.id!);
+    
+    // Handle null message ID defensively
+    final messageId = widget.message.id;
+    if (messageId == null) {
+      return _buildErrorWidget();
+    }
+
+    // Check if file is already cached
+    final cachedFile = chatNotifier.getCachedFile(messageId);
+    final fileMetadata = chatNotifier.getFileMetadata(messageId);

And in the async methods:

  Future<void> _downloadFile() async {
    if (_isLoading) return;
 
+    final messageId = widget.message.id;
+    if (messageId == null) {
+      setState(() {
+        _errorMessage = 'Invalid message: missing ID';
+      });
+      return;
+    }
+
@@
-      // Cache the file
-      chatNotifier.cacheDecryptedFile(widget.message.id!, decryptedFile, metadata);
+      // Cache the file
+      chatNotifier.cacheDecryptedFile(messageId, decryptedFile, metadata);
@@
   Future<void> _openFile() async {
     try {
       final chatNotifier = ref.read(chatRoomsProvider(widget.orderId).notifier);
-      final cachedFile = chatNotifier.getCachedFile(widget.message.id!);
-      final metadata = chatNotifier.getFileMetadata(widget.message.id!);
+      final messageId = widget.message.id;
+      if (messageId == null) {
+        throw Exception('Invalid message: missing ID');
+      }
+      final cachedFile = chatNotifier.getCachedFile(messageId);
+      final metadata = chatNotifier.getFileMetadata(messageId);

This keeps the widget robust even if upstream models change.

Also applies to: 407-435, 451-456

lib/services/encrypted_image_upload_service.dart (1)

150-179: Clarify or remove unused nonceHex parameter in downloadAndDecryptImage.

downloadAndDecryptImage accepts nonceHex and logs it, but the actual decryption delegates to EncryptionService.decryptFromBlob which derives the nonce from the blob itself. As a result, nonceHex has no effect on correctness and can be misleading to readers/callers.

Either:

  • Remove nonceHex from the method signature and all call sites (e.g., in EncryptedImageMessage and any other users), updating docs/comments to state that the nonce is read from the blob; or
  • Use nonceHex for validation (e.g., decode it and verify the nonce extracted from the blob matches the expected value, failing fast with a clear error if it doesn’t).

Keeping the API and its behaviour aligned will reduce confusion and potential future misuse.

Check how `EncryptionService.decryptFromBlob` for ChaCha20-Poly1305 in Flutter/Dart typically handles nonces (does it always embed the nonce in the blob, and is it standard to ignore an external nonce parameter in such APIs?).
🧹 Nitpick comments (14)
lib/services/media_validation_service.dart (3)

61-65: Consider removing unnecessary async keyword.

Both validateAndSanitizeImage, validateAndSanitizeImageLight, and _sanitizeImageHeavy are marked as async but contain no await expressions. This adds unnecessary overhead by wrapping results in Futures.

If async is intentional for future extensibility, consider documenting why; otherwise:

-  static Future<MediaValidationResult> validateAndSanitizeImage(
+  static MediaValidationResult validateAndSanitizeImage(
     Uint8List imageData,
-  ) async {
-    return _sanitizeImageHeavy(imageData);
+  ) {
+    return _sanitizeImageHeavySync(imageData);
   }

Also applies to: 72-75, 145-148


72-142: Consider extracting common validation logic.

The light and heavy sanitization methods share nearly identical structure (MIME detection, decoding, re-encoding). The only differences are JPEG quality (95 vs 90) and log messages. Consider extracting to a private helper with a quality parameter.

Also applies to: 145-214


30-34: Static constant in extension is non-idiomatic.

Consider moving all to the enum itself using SupportedImageType.values which is auto-generated, or define it outside the extension.

 enum SupportedImageType {
   jpeg,
   png,
+  
+  static const List<SupportedImageType> all = values;
 }
 
 extension SupportedImageTypeExtension on SupportedImageType {
   // ... mimeType and extension getters only
-  static const List<SupportedImageType> all = [
-    SupportedImageType.jpeg,
-    SupportedImageType.png,
-  ];
 }

Or simply use SupportedImageType.values directly at call sites.

lib/services/file_validation_service.dart (2)

280-287: AVI header validation is slightly incomplete.

The AVI format technically has "AVI " (with a trailing space) or "AVIX" for extended AVI. The current check for just "AVI" (3 chars) works but may miss edge cases.

       case 'video/x-msvideo':
         // AVI files start with RIFF header followed by AVI 
         final riff = String.fromCharCodes(fileData.take(4));
-        final avi = String.fromCharCodes(fileData.skip(8).take(3));
-        if (riff != 'RIFF' || avi != 'AVI') {
+        final avi = String.fromCharCodes(fileData.skip(8).take(4));
+        if (riff != 'RIFF' || !avi.startsWith('AVI')) {
           throw FileValidationException('Invalid AVI file structure');
         }
         break;

180-201: Validation helper methods are unnecessarily async.

_validatePdfStructure, _validateDocumentStructure, and _validateVideoStructure are marked async but contain no await expressions. This adds overhead by wrapping synchronous operations in Futures.

-  static Future<void> _validatePdfStructure(Uint8List fileData) async {
+  static void _validatePdfStructure(Uint8List fileData) {

Update the caller in _performTypeSpecificValidation accordingly.

Also applies to: 204-254, 257-291

lib/features/chat/widgets/message_input.dart (2)

97-118: File is read twice - consider passing bytes to upload services.

The file bytes are read at line 97 for MIME type detection, then the file is passed to upload services which read it again internally. For large files (up to 25MB), this doubles memory usage and I/O.

Consider modifying the upload services to accept Uint8List directly, or cache the bytes:

         if (isImage) {
-          final uploadResult = await _imageUploadService.uploadEncryptedImage(
-            imageFile: file,
+          final uploadResult = await _imageUploadService.uploadEncryptedImageFromBytes(
+            imageData: fileData,
             sharedKey: sharedKey,
             filename: filename,
           );

35-36: Consider using Riverpod providers for services.

The upload services are instantiated as instance fields. Per coding guidelines, consider encapsulating service access through Riverpod providers for consistency.

// Define providers in a providers file
final encryptedFileUploadServiceProvider = Provider((ref) => EncryptedFileUploadService());
final encryptedImageUploadServiceProvider = Provider((ref) => EncryptedImageUploadService());

// Then in the widget:
final fileService = ref.read(encryptedFileUploadServiceProvider);
lib/features/chat/utils/message_type_helpers.dart (1)

33-37: Minor: getMessageType may parse JSON twice.

When the message is an encrypted file, getMessageType parses the JSON content once in isEncryptedImageMessage (returns false) and again in isEncryptedFileMessage. For frequently called code paths, consider parsing once:

static MessageContentType getMessageType(NostrEvent message) {
  try {
    final content = message.content;
    if (content == null || !content.startsWith('{')) {
      return MessageContentType.text;
    }
    
    final jsonContent = jsonDecode(content) as Map<String, dynamic>;
    final type = jsonContent['type'];
    
    if (type == 'image_encrypted') return MessageContentType.encryptedImage;
    if (type == 'file_encrypted') return MessageContentType.encryptedFile;
    return MessageContentType.text;
  } catch (e) {
    return MessageContentType.text;
  }
}
lib/features/chat/widgets/message_bubble.dart (2)

12-12: ConsumerWidget not utilized - ref parameter is unused.

The widget was changed to ConsumerWidget but the WidgetRef ref parameter is never used in the build method. If Riverpod access isn't needed here, consider reverting to StatelessWidget to avoid unnecessary overhead.

-class MessageBubble extends ConsumerWidget {
+class MessageBubble extends StatelessWidget {
   // ...
   @override
-  Widget build(BuildContext context, WidgetRef ref) {
+  Widget build(BuildContext context) {

Or if ref will be needed for future features, add a TODO comment explaining the intent.

Also applies to: 24-25


36-108: Consider extracting common bubble container logic.

The encrypted image (lines 36-70) and encrypted file (lines 73-108) branches share identical container/decoration logic. A private helper could reduce duplication:

Widget _buildEncryptedMessageBubble({
  required BuildContext context,
  required bool isFromPeer,
  required Widget child,
}) {
  return Container(
    padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
    alignment: isFromPeer ? Alignment.centerLeft : Alignment.centerRight,
    child: Row(
      mainAxisAlignment: isFromPeer ? MainAxisAlignment.start : MainAxisAlignment.end,
      children: [
        Flexible(
          child: ConstrainedBox(
            constraints: BoxConstraints(
              maxWidth: MediaQuery.of(context).size.width * 0.75,
              minWidth: 0,
            ),
            child: Container(
              padding: const EdgeInsets.all(8),
              decoration: BoxDecoration(
                color: isFromPeer ? _getPeerMessageColor(peerPubkey) : AppTheme.purpleAccent,
                borderRadius: BorderRadius.only(
                  topLeft: const Radius.circular(16),
                  topRight: const Radius.circular(16),
                  bottomLeft: Radius.circular(isFromPeer ? 4 : 16),
                  bottomRight: Radius.circular(isFromPeer ? 16 : 4),
                ),
              ),
              child: child,
            ),
          ),
        ),
      ],
    ),
  );
}
lib/features/chat/widgets/encrypted_image_message.dart (1)

174-217: Localize all user-facing strings in loading/error states.

_buildLoadingWidget, _buildErrorWidget, and the _errorMessage values use hard‑coded English text ('Decrypting image...', 'Failed to load image', 'Invalid message: missing ID', and raw exception messages). Per project guidelines, these should be ARB-backed and accessed via S.of(context) (and, for internal errors, consider mapping exceptions to user-friendly, localized copies rather than exposing raw toString()).

Also applies to: 219-273, 275-283

lib/features/chat/notifiers/chat_room_notifier.dart (1)

489-505: Add basic bounds/eviction for decrypted file/image caches.

_imageCache / _fileCache and their metadata maps grow monotonically for the lifetime of a ChatRoomNotifier. With many large images/files in a busy chat, this can accumulate substantial memory.

Consider adding at least a simple bound or eviction strategy (e.g., LRU with max count or total bytes per chat room) or clearing caches when navigating away/closing the chat, while keeping the current API surface (getCached*/cacheDecrypted*) intact.

Also applies to: 518-589, 592-598

lib/features/chat/widgets/encrypted_file_message.dart (1)

81-119: Localize all user-facing strings in previews, file cards, and SnackBars.

This widget contains several hard-coded English strings ('Failed to load image', 'Failed to load file', 'File not available', 'Security error: Invalid characters in sanitized filename', 'Opening ...', 'Could not open file: ...', 'Error opening file: ...'). Project guidelines require routing user-visible text through ARB + S.of(context).

Recommend:

  • Add appropriate keys to your ARB files.
  • Replace these literals with S.of(context)!.<key> (or map internal exception messages to localized, user-friendly summaries before displaying).

Also applies to: 186-289, 291-386, 407-422, 451-505

lib/services/encrypted_file_upload_service.dart (1)

61-71: Deduplicate Blossom server configuration and exception type across services.

Both EncryptedFileUploadService and EncryptedImageUploadService embed their own _blossomServers list and define a BlossomException type. This increases the risk of drift (e.g., one server list updated, the other not).

Consider:

  • Extracting the Blossom server list into a shared config (e.g., blossom_config.dart or a shared service) used by both upload services.
  • Defining BlossomException once in a shared module and reusing it instead of redefining it in multiple files.

This keeps Blossom behaviour consistent and simplifies future changes.

Also applies to: 163-195, 215-221

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b41d14 and 5a42c47.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • lib/features/chat/notifiers/chat_room_notifier.dart (3 hunks)
  • lib/features/chat/utils/message_type_helpers.dart (1 hunks)
  • lib/features/chat/widgets/encrypted_file_message.dart (1 hunks)
  • lib/features/chat/widgets/encrypted_image_message.dart (1 hunks)
  • lib/features/chat/widgets/message_bubble.dart (2 hunks)
  • lib/features/chat/widgets/message_input.dart (5 hunks)
  • lib/l10n/intl_en.arb (1 hunks)
  • lib/l10n/intl_es.arb (1 hunks)
  • lib/l10n/intl_it.arb (1 hunks)
  • lib/services/blossom_download_service.dart (1 hunks)
  • lib/services/encrypted_file_upload_service.dart (1 hunks)
  • lib/services/encrypted_image_upload_service.dart (1 hunks)
  • lib/services/file_validation_service.dart (1 hunks)
  • lib/services/media_validation_service.dart (1 hunks)
  • pubspec.yaml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • lib/l10n/intl_it.arb
🚧 Files skipped from review as they are similar to previous changes (1)
  • pubspec.yaml
🧰 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/features/chat/utils/message_type_helpers.dart
  • lib/services/blossom_download_service.dart
  • lib/services/file_validation_service.dart
  • lib/features/chat/widgets/encrypted_image_message.dart
  • lib/services/encrypted_file_upload_service.dart
  • lib/features/chat/widgets/message_input.dart
  • lib/features/chat/widgets/encrypted_file_message.dart
  • lib/services/encrypted_image_upload_service.dart
  • lib/services/media_validation_service.dart
  • lib/features/chat/widgets/message_bubble.dart
  • lib/features/chat/notifiers/chat_room_notifier.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/chat/utils/message_type_helpers.dart
  • lib/services/blossom_download_service.dart
  • lib/services/file_validation_service.dart
  • lib/features/chat/widgets/encrypted_image_message.dart
  • lib/services/encrypted_file_upload_service.dart
  • lib/features/chat/widgets/message_input.dart
  • lib/features/chat/widgets/encrypted_file_message.dart
  • lib/services/encrypted_image_upload_service.dart
  • lib/services/media_validation_service.dart
  • lib/features/chat/widgets/message_bubble.dart
  • lib/features/chat/notifiers/chat_room_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/blossom_download_service.dart
  • lib/services/file_validation_service.dart
  • lib/services/encrypted_file_upload_service.dart
  • lib/services/encrypted_image_upload_service.dart
  • lib/services/media_validation_service.dart
lib/l10n/**/*.arb

📄 CodeRabbit inference engine (CLAUDE.md)

Add new localization keys to all three ARB files (en, es, it) - use S.of(context)!.keyName for all user-facing strings

Files:

  • lib/l10n/intl_en.arb
  • lib/l10n/intl_es.arb
lib/features/**/notifiers/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Encapsulate business logic in Notifiers - Notifiers should expose state via providers and handle all complex state transitions

Files:

  • lib/features/chat/notifiers/chat_room_notifier.dart
🧠 Learnings (15)
📚 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/chat/utils/message_type_helpers.dart
  • lib/features/chat/widgets/encrypted_image_message.dart
  • lib/features/chat/widgets/message_bubble.dart
  • lib/features/chat/notifiers/chat_room_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/features/chat/utils/message_type_helpers.dart
  • lib/features/chat/notifiers/chat_room_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/l10n/**/*.arb : Add new localization keys to all three ARB files (en, es, it) - use `S.of(context)!.keyName` for all user-facing strings

Applied to files:

  • lib/l10n/intl_en.arb
📚 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/features/chat/widgets/encrypted_image_message.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/features/chat/widgets/encrypted_image_message.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/chat/widgets/message_bubble.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/chat/notifiers/chat_room_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/features/chat/notifiers/chat_room_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/**/providers/**/*.dart : Use Notifier pattern instead of simple StateNotifier for complex state logic requiring business rule encapsulation

Applied to files:

  • lib/features/chat/notifiers/chat_room_notifier.dart
📚 Learning: 2025-05-06T22:23:06.684Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/features/chat/notifiers/chat_rooms_notifier.dart:0-0
Timestamp: 2025-05-06T22:23:06.684Z
Learning: The `reload()` method in the chat_room_notifier is synchronous, so await is not required or possible.

Applied to files:

  • lib/features/chat/notifiers/chat_room_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/features/chat/notifiers/chat_room_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/features/chat/notifiers/chat_room_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/**/providers/**/*.dart : Organize Riverpod providers by feature in `features/{feature}/providers/` using Notifier pattern for complex state logic

Applied to files:

  • lib/features/chat/notifiers/chat_room_notifier.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/chat/notifiers/chat_room_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/features/chat/notifiers/chat_room_notifier.dart
🔇 Additional comments (9)
lib/l10n/intl_es.arb (2)

1172-1172: LGTM - Trailing comma addition.

The trailing comma on restoreErrorMessage is a correct JSON formatting change that allows the subsequent entries to be added cleanly.


1174-1178: All file messaging localization keys are correctly added to all three ARB files.

The new file messaging keys (download, openFile, encrypted, downloadingFile, and the section comment) have been successfully added to the Italian localization file (lib/l10n/intl_it.arb) along with the Spanish and English files, meeting the coding guideline requirement.

lib/l10n/intl_en.arb (2)

1194-1194: LGTM - Trailing comma addition.

The trailing comma on restoreErrorMessage correctly enables the subsequent file messaging entries.


1196-1200: LGTM - File messaging localization keys added.

The English file messaging localization keys have been correctly added and the translations are clear and appropriate for the file messaging feature.

Note: Verification of Italian translations was requested in the review of the Spanish localization file.

lib/services/file_validation_service.dart (1)

6-22: LGTM - Well-structured result class.

The FileValidationResult class provides comprehensive metadata for validated files.

lib/features/chat/widgets/message_input.dart (1)

229-260: Good implementation of upload button with loading state.

The loading indicator and disabled state during upload provide good UX feedback. The mounted checks before setState are properly placed.

lib/features/chat/utils/message_type_helpers.dart (1)

5-17: Good extraction of message type utilities.

This properly centralizes the message type detection logic that was previously duplicated across files, following the past review suggestion.

lib/features/chat/widgets/message_bubble.dart (1)

8-10: Good refactoring - duplicate helpers consolidated.

The message type helper functions have been moved to message_type_helpers.dart as suggested in past reviews, eliminating code duplication.

lib/services/blossom_download_service.dart (1)

110-158: HTTP client lifecycle handled correctly in streaming download.

downloadWithProgress now creates a single http.Client and closes it in a finally block, ensuring no client leak even when exceptions occur during send or stream consumption. The progress callback and error wrapping via BlossomDownloadException also look solid.

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

🧹 Nitpick comments (7)
lib/services/media_validation_service.dart (2)

61-65: Consider using compute() for CPU-bound image operations.

Both validateAndSanitizeImage and validateAndSanitizeImageLight are marked async but perform synchronous CPU-bound operations (decodeImage, encodeJpg, encodePng). For large images, these operations could block the UI thread and cause jank.

Consider offloading heavy work to an isolate:

import 'package:flutter/foundation.dart';

static Future<MediaValidationResult> validateAndSanitizeImageLight(
  Uint8List imageData,
) async {
  return compute(_sanitizeImageLightIsolate, imageData);
}

static MediaValidationResult _sanitizeImageLightIsolate(Uint8List imageData) {
  // Move sanitization logic here
}

Also applies to: 147-148


220-223: Consider adding dimension limits to prevent memory exhaustion.

The service validates file size but not image dimensions. A maliciously crafted image could have small file size but extremely large dimensions (e.g., 50000×50000 pixels), potentially exhausting memory during decode.

static bool isImageTypeSupported(String mimeType) {
  return mimeType == 'image/jpeg' || mimeType == 'image/png';
}

/// Maximum allowed image dimension (width or height)
static const int maxImageDimension = 8192;

Then add a check after decoding in both sanitization methods:

if (decodedImage.width > maxImageDimension || decodedImage.height > maxImageDimension) {
  throw MediaValidationException(
    'Image dimensions exceed maximum allowed (${maxImageDimension}x$maxImageDimension)'
  );
}
lib/features/chat/widgets/encrypted_file_message.dart (3)

109-115: Localize hard-coded error strings.

'Failed to load image' (line 110) and 'Failed to load file' (line 362) should use localized strings via S.of(context) for consistency with the rest of the UI. As per coding guidelines, all user-facing strings should be localized via ARB files.

-                      Text(
-                        'Failed to load image',
+                      Text(
+                        S.of(context)!.failedToLoadImage,
-          Text(
-            'Failed to load file',
+          Text(
+            S.of(context)!.failedToLoadFile,

Also applies to: 362-367


62-71: Auto-download in build() may trigger multiple times.

Using addPostFrameCallback inside build() to trigger _downloadFile() can cause repeated download attempts if the widget rebuilds frequently. The _isLoading guard helps, but there's still a brief window where multiple callbacks could be scheduled.

Consider moving this to initState or using a flag to ensure single execution:

+  bool _hasTriggeredAutoDownload = false;
+
   @override
   Widget build(BuildContext context) {
     // ... existing code ...
     
     if (metadata != null) {
       // For images, try to download automatically for preview
-      if (_isImage(metadata.fileType)) {
+      if (_isImage(metadata.fileType) && !_hasTriggeredAutoDownload) {
         WidgetsBinding.instance.addPostFrameCallback((_) {
           if (!_isLoading) {
+            _hasTriggeredAutoDownload = true;
             _downloadFile();
           }
         });
       }

519-567: Sanitization covers common cases but has limited i18n support.

The _sanitizeFilename method provides solid security but the accent normalization (lines 525-530) only covers Spanish characters. Other accented characters (German umlauts, French accents, etc.) will pass through unchanged, which could cause issues on some filesystems.

Consider using a more comprehensive approach or simply replacing all non-ASCII with underscores for maximum compatibility:

// Alternative: Replace all non-ASCII characters
String normalized = basename.replaceAll(RegExp(r'[^\x00-\x7F]'), '_');
lib/services/file_validation_service.dart (2)

66-68: Cross-platform path handling issue.

Using file.path.split('/').last won't correctly extract the filename on Windows, where paths use backslashes. Consider using path package or a cross-platform approach:

+import 'package:path/path.dart' as p;
+
 static Future<FileValidationResult> validateFile(File file) async {
-  final filename = file.path.split('/').last;
+  final filename = p.basename(file.path);
   final fileData = await file.readAsBytes();

Alternatively, use a regex that handles both separators:

final filename = file.path.split(RegExp(r'[/\\]')).last;

70-70: Consider sanitizing filename in logs.

The log statement includes the raw filename which could contain PII or sensitive information. Consider truncating or hashing the filename for production logs.

-_logger.i('🔍 Validating file: $filename (${fileData.length} bytes)');
+_logger.i('🔍 Validating file: ${filename.length > 20 ? '${filename.substring(0, 20)}...' : filename} (${fileData.length} bytes)');
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a42c47 and 71b6717.

📒 Files selected for processing (3)
  • lib/features/chat/widgets/encrypted_file_message.dart (1 hunks)
  • lib/services/file_validation_service.dart (1 hunks)
  • lib/services/media_validation_service.dart (1 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/services/file_validation_service.dart
  • lib/features/chat/widgets/encrypted_file_message.dart
  • lib/services/media_validation_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/services/file_validation_service.dart
  • lib/features/chat/widgets/encrypted_file_message.dart
  • lib/services/media_validation_service.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/file_validation_service.dart
  • lib/services/media_validation_service.dart
🧠 Learnings (1)
📚 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/chat/widgets/encrypted_file_message.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/services/media_validation_service.dart (3)

1-52: LGTM!

The enum, extension, and data class are well-structured. The SupportedImageType enum with its extension provides clean access to MIME types and extensions.


100-115: LGTM – Exception handling fix properly applied.

The on MediaValidationException { rethrow; } block at lines 109-110 correctly prevents double-wrapping of MediaValidationException, addressing the previous review feedback.


175-191: LGTM – Heavy sanitization exception handling also fixed.

The same exception handling pattern is correctly applied here at lines 185-186.

lib/features/chat/widgets/encrypted_file_message.dart (2)

461-471: LGTM – Path traversal vulnerability properly mitigated.

The implementation now uses _sanitizeFilename() at line 463 with defense-in-depth validation at lines 468-471. The _sanitizeFilename method (lines 519-567) comprehensively handles:

  • Directory traversal patterns
  • Dangerous characters
  • Windows reserved names
  • Length limits

This addresses the previous security concern.


96-100: LGTM – Color.withValues alpha values corrected.

All withValues(alpha:) calls now use the correct 0.0-1.0 range (e.g., alpha: 0.2, alpha: 0.7, alpha: 0.098), fixing the previous transparency issue.

Also applies to: 133-136, 344-349

lib/services/file_validation_service.dart (4)

31-35: LGTM – Invalid MIME type removed.

The supportedTypes map now correctly uses only image/jpeg and image/png, removing the non-standard image/jpg as flagged in the previous review.


291-328: LGTM – Efficient byte pattern search implemented.

The macro detection now uses an efficient sliding-window byte pattern search (_containsPattern) instead of converting the entire file to a string. This addresses the memory efficiency and false-positive concerns from the previous review.


179-201: PDF validation is basic but appropriate for this use case.

The PDF validation checks header (%PDF-) and warns on missing trailer (%%EOF). While this doesn't catch all malformed PDFs, it's reasonable for proof-of-payment exchange validation without adding heavyweight PDF parsing dependencies.


254-289: LGTM – Video structure validation.

The video validation correctly checks magic bytes for MP4, MOV, and AVI formats. The ftyp box check for MP4/MOV and RIFF/AVI header checks are appropriate structural validations.

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/chat/widgets/message_input.dart (1)

127-136: Avoid exposing raw exception details to users.

The error message still exposes raw exception details that could reveal internal information (file paths, network errors, stack traces).

Apply this diff as suggested in the previous review:

     } catch (e) {
       // Show error to user
       if (mounted) {
+        // Log the full error for debugging
+        debugPrint('File upload error: $e');
         ScaffoldMessenger.of(context).showSnackBar(
           SnackBar(
-            content: Text('Error uploading file: $e'),
+            content: Text(S.of(context)!.failedToUploadFile),
             backgroundColor: Colors.red,
           ),
         );
       }
     } finally {

Note: You'll need to add the failedToUploadFile localization key to your ARB files.

🧹 Nitpick comments (1)
lib/features/chat/widgets/message_input.dart (1)

103-104: Consider optimizing memory usage for large files.

Reading the entire file into memory with readAsBytes() before MIME detection could cause memory pressure for large files (especially videos). Consider reading only the header bytes needed for MIME detection first.

-      final fileData = await file.readAsBytes();
-      final isImage = await _isImageFile(fileData, filename);
+      // Read only first few KB for MIME detection to avoid loading large files
+      final headerSize = 8192; // 8KB should be enough for MIME detection
+      final fileHandle = await file.open();
+      final headerBytes = await fileHandle.read(headerSize);
+      await fileHandle.close();
+      final isImage = await _isImageFile(headerBytes, filename);

Note: The upload services will still read the full file, but this avoids loading it twice in memory.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 71b6717 and 0f87a23.

📒 Files selected for processing (4)
  • lib/features/chat/widgets/message_input.dart (5 hunks)
  • lib/l10n/intl_en.arb (1 hunks)
  • lib/l10n/intl_es.arb (1 hunks)
  • lib/l10n/intl_it.arb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/l10n/intl_it.arb
  • lib/l10n/intl_es.arb
🧰 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/features/chat/widgets/message_input.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/chat/widgets/message_input.dart
lib/l10n/**/*.arb

📄 CodeRabbit inference engine (CLAUDE.md)

Add new localization keys to all three ARB files (en, es, it) - use S.of(context)!.keyName for all user-facing strings

Files:

  • lib/l10n/intl_en.arb
🧠 Learnings (1)
📚 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/l10n/**/*.arb : Add new localization keys to all three ARB files (en, es, it) - use `S.of(context)!.keyName` for all user-facing strings

Applied to files:

  • lib/l10n/intl_en.arb
⏰ 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/l10n/intl_en.arb (2)

1194-1194: LGTM: Trailing comma addition is correct.

The trailing comma added to restoreErrorMessage is proper JSON formatting for a non-final entry.


1196-1205: Localization keys properly added to all three ARB files.

All seven new keys (download, openFile, encrypted, downloadingFile, confirmFileUpload, sendThisFile, send) are correctly present in the English, Spanish, and Italian ARB files. The comment markers (@_comment_file_messaging and @_comment_file_confirmation) are also consistently added across all three locales.

lib/features/chat/widgets/message_input.dart (4)

158-167: MIME detection logic is correct.

The approach of checking magic bytes first, then falling back to extension-based detection is solid and follows best practices.


209-285: Well-implemented confirmation dialog.

The file upload confirmation provides clear user feedback with proper localization, theming, and null handling. Good UX pattern to confirm before uploading.


313-343: Clean UI implementation with proper loading states.

The file attachment button provides good visual feedback during uploads with the loading indicator and properly disables interaction while uploading.


81-87: The substring(1) operation is correct. FileValidationService.getSupportedExtensions() returns extensions with leading dots (e.g., ".pdf", ".jpg") from the extensionToMime map (lines 48–63). The substring(1) call properly removes the dot to match FilePicker's expected format of ['jpg', 'pdf', 'png'] without dots. No issue exists in this code.

Likely an incorrect or invalid review comment.

@grunch
Copy link
Member

grunch commented Dec 2, 2025

I uploaded a jpeg file and it gets uploaded correctly, the counterpart can see the preview but non of them can download the entire file

image

@Catrya
Copy link
Member Author

Catrya commented Dec 2, 2025

I uploaded a jpeg file and it gets uploaded correctly, the counterpart can see the preview but non of them can download the entire file

ok I'll make sure the image opens properly, because it downloads but only shows a preview.

@Catrya
Copy link
Member Author

Catrya commented Dec 2, 2025

@grunch voy a hacer en otro PR que los archivos se guarden en memoria mientras dure la sesion (ahora 72 horas) para que no tengan que volver a descargarlos cada vez que abran el chat. Tambien que el user pueda cambiar los servidores blossom que usará porque ahora tengo varios harcodeados, pero serán en otros PRs para no hacer tan grande este, te parece bien?

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

🧹 Nitpick comments (5)
lib/features/chat/widgets/encrypted_file_message.dart (5)

15-29: Unused parameter isOwnMessage.

The isOwnMessage parameter is defined but never referenced in the widget implementation. Per coding guidelines, remove unused dependencies to maintain code cleanliness.

 class EncryptedFileMessage extends ConsumerStatefulWidget {
   final NostrEvent message;
   final String orderId;
-  final bool isOwnMessage;

   const EncryptedFileMessage({
     super.key,
     required this.message,
     required this.orderId,
-    required this.isOwnMessage,
   });

Note: Verify all call sites are updated if you remove this parameter.


116-122: Localize user-facing string.

The hard-coded 'Failed to load image' violates coding guidelines requiring localization via ARB files. As per coding guidelines, access user-facing strings with S.of(context) rather than hard-coded literals.

                       Text(
-                        'Failed to load image',
+                        S.of(context)!.failedToLoadImage,
                         style: TextStyle(
                           color: AppTheme.textSecondary,
                           fontSize: 12,
                         ),
                       ),

Add the corresponding key to your ARB files.


368-374: Localize user-facing string.

The hard-coded 'Failed to load file' should use localization. As per coding guidelines, access user-facing strings with S.of(context) rather than hard-coded literals.

           Text(
-            'Failed to load file',
+            S.of(context)!.failedToLoadFile,
             style: TextStyle(
               fontSize: 12,
               color: Colors.red,
             ),

Add the corresponding key to your ARB files.


496-514: Localize user-facing error messages in SnackBars.

The hard-coded error messages violate coding guidelines. As per coding guidelines, localize all user-facing strings via ARB files.

           ScaffoldMessenger.of(context).showSnackBar(
             SnackBar(
-              content: Text('Could not open file: ${result.message}'),
+              content: Text(S.of(context)!.couldNotOpenFile(result.message)),
               backgroundColor: Colors.orange,
               duration: const Duration(seconds: 3),
             ),
           );
         }
       }
     } catch (e) {
       if (mounted) {
         ScaffoldMessenger.of(context).showSnackBar(
           SnackBar(
-            content: Text('Error opening file: $e'),
+            content: Text(S.of(context)!.errorOpeningFile(e.toString())),
             backgroundColor: Colors.red,
           ),
         );

Add corresponding parameterized ARB keys.


528-576: Good security implementation with a note on accent handling.

The sanitization properly addresses path traversal concerns from the previous review. The implementation handles:

  • Path separator extraction (basename)
  • Dangerous character removal
  • Length limits
  • Windows reserved names
  • Fallback for invalid filenames

The accent normalization (lines 534-539) only covers Spanish characters. Consider using a more comprehensive Unicode normalization approach in a future iteration if international filenames are expected:

// Alternative: Use Unicode normalization + ASCII-only filtering
// import 'package:diacritic/diacritic.dart'; // or similar
// String normalized = removeDiacritics(basename);

This is a nice-to-have improvement for broader language support but not blocking for this PR.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbf2ab5 and b7a90e7.

📒 Files selected for processing (1)
  • lib/features/chat/widgets/encrypted_file_message.dart (1 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/chat/widgets/encrypted_file_message.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/chat/widgets/encrypted_file_message.dart
🧠 Learnings (3)
📚 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/chat/widgets/encrypted_file_message.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/chat/widgets/encrypted_file_message.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/features/chat/widgets/encrypted_file_message.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/features/chat/widgets/encrypted_file_message.dart (9)

1-13: LGTM!

Imports are well-organized and all appear to be necessary for the widget functionality.


31-82: LGTM!

The build method properly addresses previous review concerns:

  • Null check on messageId prevents null assertion crashes.
  • Race condition fix by setting _isLoading = true synchronously before scheduling _downloadFile.

The control flow is clear and handles all states appropriately.


84-86: LGTM!

Simple and effective type check.


193-296: LGTM!

Good use of localized strings (S.of(context)!.encrypted, S.of(context)!.openFile, S.of(context)!.download) and correct Color.withValues alpha values in the 0.0–1.0 range.


298-339: LGTM!

Properly localized loading message and correct alpha values.


395-412: LGTM!

Defensive JSON parsing with null return on failure. Consider adding debug logging in the catch block for troubleshooting, but not critical for this PR.


414-461: LGTM!

Proper use of mounted checks before setState after async operations, as per coding guidelines. The download flow correctly handles loading states and errors.


518-520: LGTM!

Clean delegation to the service layer.


522-526: LGTM!

Simple and effective file size formatting. The unit abbreviations (B, KB, MB) are universally recognized and typically not localized.

  - Add 31 tests covering encryption, validation, upload/download
  - Include edge cases and security validation
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

🧹 Nitpick comments (10)
test/features/chat/file_messaging_test.dart (10)

16-17: Consider centralizing BlossomException definition.

The hide BlossomException suggests this exception is defined in multiple places (likely in both upload services and BlossomClient). This creates maintenance overhead and potential inconsistencies.

Consider moving BlossomException to a shared location (e.g., lib/services/blossom_exceptions.dart) and importing it from there in all services that need it.


27-34: Consider extracting shared key derivation to test helper.

This key derivation setup is duplicated from mostro_service_test.dart (per the comment). Extracting it to a shared test helper (e.g., test/helpers/key_test_helpers.dart) would reduce duplication and ensure consistency across tests.

Example helper:

class KeyTestHelpers {
  static const validMnemonic = 'leader monkey parrot ring guide accident before fence cannon height naive bean';
  
  static DerivedTestKeys deriveTestKeys() {
    final keyDerivator = KeyDerivator(Config.keyDerivationPath);
    // ... derivation logic
    return DerivedTestKeys(...);
  }
}

88-194: Consider moving EncryptionService unit tests to dedicated test file.

These tests comprehensively cover EncryptionService static methods (shared key derivation, encryption/decryption, nonce generation, blob serialization). However, they belong in test/services/encryption_service_test.dart rather than the file messaging integration test.

Keep only the integration-focused tests here (e.g., testing that ChatRoomNotifier correctly uses encryption in the file messaging flow). Move pure EncryptionService unit tests to a dedicated test/services/encryption_service_test.dart file for better organization and maintainability.


196-269: Move validation service unit tests to dedicated test files.

These tests cover FileValidationService and MediaValidationService static methods. They should be in:

  • test/services/file_validation_service_test.dart (lines 196-240)
  • test/services/media_validation_service_test.dart (lines 242-269)

This file should focus on integration testing: verifying that file messaging correctly uses these validation services, handles validation failures, and propagates errors appropriately.


293-300: Remove or improve weak BlossomClient test.

This test creates a real BlossomClient instance but only verifies the serverUrl property, which is trivial. The comment acknowledges the limitation. Either:

  1. Remove this test entirely (the URL construction is too simple to warrant testing)
  2. Or mock http.Client to properly test the upload flow

The other tests in this group (lines 272-291, 302-317) properly use mocks and are more valuable.


377-402: Download integrity test duplicates earlier encryption tests.

Lines 383-401 repeat the encryption/decryption round-trip test from lines 117-138. Since this is primarily testing EncryptionService rather than download integration, consider removing this duplication.


474-500: Use flutter_test exception matchers instead of manual try-catch.

Lines 487-499 manually catch exceptions and set a boolean flag. This is less idiomatic than using expect(..., throwsA(isA<EncryptionException>())) as done elsewhere (e.g., lines 150-158).

Apply this pattern:

-        bool threwEncryptionException = false;
-        try {
-          EncryptionService.decryptChaCha20Poly1305(
-            key: key,
-            nonce: encrypted.nonce,
-            encryptedData: encrypted.encryptedData,
-            authTag: corruptedAuthTag,
-          );
-        } catch (e) {
-          threwEncryptionException = e is EncryptionException;
-        }
-        
-        expect(threwEncryptionException, isTrue);
+        expect(
+          () => EncryptionService.decryptChaCha20Poly1305(
+            key: key,
+            nonce: encrypted.nonce,
+            encryptedData: encrypted.encryptedData,
+            authTag: corruptedAuthTag,
+          ),
+          throwsA(isA<EncryptionException>()),
+        );

520-546: Use flutter_test exception matchers instead of manual try-catch.

Lines 534-545 use manual exception handling similar to lines 474-500. Use expect(..., throwsA(isA<ArgumentError>())) for consistency with flutter_test conventions.


453-472: Duplicate test: cross-session decryption already tested.

This test duplicates the "wrong key" test from lines 140-159. Both verify that decryption fails with an incorrect key. Consider removing this duplication or consolidating into a single test with clearer naming.


24-549: Add integration test coverage for file messaging flows.

This PR introduces file messaging with networking (Blossom uploads/downloads), background work (retries), and end-to-end encryption. Per coding guidelines and learnings, flows touching networking require integration test coverage in integration_test/.

Current unit tests mock all network interactions, which won't catch real-world issues like the download bug reported by grunch (uploaded files not downloadable by counterpart).

Suggested integration test scenarios for integration_test/file_messaging_integration_test.dart:

  1. Upload an image to real Blossom server (or test server), verify URL returned
  2. Download and decrypt a file from Blossom server
  3. End-to-end flow: User A uploads encrypted file, User B receives message and downloads
  4. Retry logic when Blossom server is temporarily unavailable
  5. Error handling for oversized files, unsupported formats

Based on coding guidelines: "Ensure integration test coverage for flows touching networking, background work, or session recovery in integration_test/"

Would you like me to help draft an integration test file for these scenarios?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7a90e7 and 32f37ab.

📒 Files selected for processing (2)
  • test/features/chat/file_messaging_test.dart (1 hunks)
  • test/mocks.dart (2 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:

  • test/features/chat/file_messaging_test.dart
  • test/mocks.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:

  • test/features/chat/file_messaging_test.dart
  • test/mocks.dart
**/*_test.dart

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.dart: Tests should mirror the feature layout under test/ with the *_test.dart suffix; end-to-end flows should run from integration_test/
Place unit tests beside their feature counterparts using the *_test.dart suffix and prefer Mockito fakes generated via build_runner

Files:

  • test/features/chat/file_messaging_test.dart
🧠 Learnings (19)
📚 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 integration_test/**/*.dart : Ensure integration test coverage for flows touching networking, background work, or session recovery in `integration_test/`

Applied to files:

  • test/features/chat/file_messaging_test.dart
  • test/mocks.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:

  • test/features/chat/file_messaging_test.dart
  • test/mocks.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 **/*_test.dart : Tests should mirror the feature layout under `test/` with the `*_test.dart` suffix; end-to-end flows should run from `integration_test/`

Applied to files:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.dart
  • test/mocks.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:

  • test/features/chat/file_messaging_test.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 **/*_test.dart : Place unit tests beside their feature counterparts using the `*_test.dart` suffix and prefer Mockito fakes generated via `build_runner`

Applied to files:

  • test/features/chat/file_messaging_test.dart
  • test/mocks.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:

  • test/features/chat/file_messaging_test.dart
  • test/mocks.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:

  • test/features/chat/file_messaging_test.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,flutter} : Run `flutter test` after any code change - Mandatory before commits to ensure all unit tests pass

Applied to files:

  • test/features/chat/file_messaging_test.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 **/*.{g.dart,mocks.dart} : Never manually edit generated files (`.g.dart`, `.mocks.dart`) or add individual ignore comments - regenerate with `dart run build_runner build -d` instead

Applied to files:

  • test/mocks.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:

  • test/mocks.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:

  • test/mocks.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:

  • test/mocks.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:

  • test/mocks.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:

  • test/mocks.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:

  • test/mocks.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:

  • test/mocks.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:

  • test/mocks.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:

  • test/mocks.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 (3)
test/mocks.dart (2)

22-29: LGTM! Service imports support new file messaging test infrastructure.

The imported services (BlossomClient, EncryptionService, upload/download services, validation services, ChatRoomNotifier) correspond to the file messaging feature. Note that EncryptionService, FileValidationService, and MediaValidationService are imported but not added to @GenerateMocks—this is correct if they are static utility classes.


51-55: Mock generation correctly configured for file messaging services.

The new mocks (BlossomClient, EncryptedFileUploadService, EncryptedImageUploadService, BlossomDownloadService, ChatRoomNotifier) enable comprehensive testing of the file messaging feature. These will be generated in mocks.mocks.dart via dart run build_runner build -d.

test/features/chat/file_messaging_test.dart (1)

415-449: Good security test for cross-session isolation.

This test verifies that different sessions derive different shared keys, ensuring files encrypted for one session cannot be decrypted by another. This is critical for the security model.

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

🧹 Nitpick comments (6)
test/features/chat/file_messaging_test.dart (6)

62-87: ProviderContainer is created but never used

container is instantiated and disposed, but not used to read or override any Riverpod providers. This is likely to trigger an “unused variable” analyzer warning and adds noise to the setup.

You can either:

  • Remove ProviderContainer entirely for now, or
  • Wire it to the file-messaging providers (e.g., overriding the ChatRoomNotifier / upload/download services) so tests exercise real Riverpod state.

This keeps tests focused and avoids analyzer noise, as required by the guidelines.


102-115: shared key used for both text and files test is tautological with a mock

This test stubs MockChatRoomNotifier.getSharedKey() to return expectedBytes and then asserts that getSharedKey() returns those same bytes. It doesn’t touch production code that actually derives or reuses the shared key for text vs. files, so it provides little real coverage.

Consider rewriting this test to:

  • Exercise the real ChatRoomNotifier (or whatever orchestrates text/file encryption) against testSession.sharedKey, or
  • Remove the test to avoid a false sense of coverage.

161-176: Nonce uniqueness and wrong-key tests are good but a bit redundant

  • The nonce-uniqueness test is useful, but with 100 iterations you still have a (tiny) theoretical chance of random collision. It’s probably fine in practice, but if flakiness ever appears you could reduce the loop count or assert nonce length/format in addition.
  • prevents cross-session file decryption effectively duplicates fails decryption with wrong shared key using different hard-coded keys. To better differentiate it, you could tie it to Session.sharedKey for two sessions and show that cross-session decrypt fails, rather than re-testing the same key-mismatch property already covered above.

These are non-blocking refinements to keep the suite lean and focused.

Also applies to: 451-471


196-240: Avoid allocating 24–26MB buffers in tests; assert on sizes instead

The file-size tests currently allocate 24MB, 25MB, and 26MB Uint8Lists just to compare .length against FileValidationService.maxFileSize. That adds unnecessary memory pressure in the test runner.

You can keep the same assertions using plain integers, e.g.:

-      test('accepts files within 25MB limit', () {
-        final file24MB = Uint8List(24 * 1024 * 1024);
-        expect(file24MB.length, lessThanOrEqualTo(FileValidationService.maxFileSize));
-        
-        final file25MB = Uint8List(25 * 1024 * 1024);
-        expect(file25MB.length, lessThanOrEqualTo(FileValidationService.maxFileSize));
-      });
+      test('accepts files within 25MB limit', () {
+        final size24MB = 24 * 1024 * 1024;
+        final size25MB = 25 * 1024 * 1024;
+        expect(size24MB, lessThanOrEqualTo(FileValidationService.maxFileSize));
+        expect(size25MB, lessThanOrEqualTo(FileValidationService.maxFileSize));
+      });
@@
-      test('rejects oversized files', () {
-        final file26MB = Uint8List(26 * 1024 * 1024);
-        expect(file26MB.length, greaterThan(FileValidationService.maxFileSize));
-      });
+      test('rejects oversized files', () {
+        final size26MB = 26 * 1024 * 1024;
+        expect(size26MB, greaterThan(FileValidationService.maxFileSize));
+      });

Similarly, the zero-byte test currently only checks length < maxFileSize without going through a validation API. Once FileValidationService exposes a “validate size” entry point, it’s worth asserting that behavior directly instead of only comparing raw lengths.


376-381: Tests that only assert mocks are non-null add little value

The tests:

  • download service can be mocked
  • image upload service can be mocked
  • file upload service can be mocked

only check that the mock instances are non-null and of the expected mock types. Mockito already guarantees that, so these tests don’t verify any production behavior and can inflate perceived coverage.

Consider either:

  • Removing these tests, or
  • Replacing them with flow tests that assert real orchestration, e.g., a higher-level “file share” API calling into EncryptedImageUploadService / EncryptedFileUploadService / BlossomDownloadService with the right arguments and error handling.

This will keep the suite focused on behavior rather than on Mockito’s own guarantees.

Also applies to: 403-412


414-448: Session isolation test could tie more directly into encryption

handles file sharing between different sessions correctly asserts that two Session instances derived from different key indices produce different sharedKey values. That’s a good invariant.

To fully validate cross-session isolation, you might extend this by:

  • Encrypting data with session1.sharedKey, and
  • Asserting that decryption with session2.sharedKey fails via EncryptionService.decryptChaCha20Poly1305.

That would bridge the Session model to the encryption layer and avoid relying only on the separate raw-key wrong-decryption tests.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 32f37ab and 375ea01.

📒 Files selected for processing (2)
  • test/features/chat/file_messaging_test.dart (1 hunks)
  • test/mocks.dart (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/mocks.dart
🧰 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:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.dart
**/*_test.dart

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.dart: Tests should mirror the feature layout under test/ with the *_test.dart suffix; end-to-end flows should run from integration_test/
Place unit tests beside their feature counterparts using the *_test.dart suffix and prefer Mockito fakes generated via build_runner

Files:

  • test/features/chat/file_messaging_test.dart
🧠 Learnings (8)
📚 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 **/*_test.dart : Tests should mirror the feature layout under `test/` with the `*_test.dart` suffix; end-to-end flows should run from `integration_test/`

Applied to files:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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 integration_test/**/*.dart : Ensure integration test coverage for flows touching networking, background work, or session recovery in `integration_test/`

Applied to files:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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 **/*_test.dart : Place unit tests beside their feature counterparts using the `*_test.dart` suffix and prefer Mockito fakes generated via `build_runner`

Applied to files:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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,flutter} : Run `flutter test` after any code change - Mandatory before commits to ensure all unit tests pass

Applied to files:

  • test/features/chat/file_messaging_test.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 (3)
test/features/chat/file_messaging_test.dart (3)

24-52: Key derivation and Mockito dummy setup look solid

Using a NIP-06-style mnemonic with KeyDerivator plus provideDummy<T> registrations gives realistic keys and proper Mockito fallbacks for Session, Blossom services, and ChatRoomNotifier. No changes needed here.


473-545: Security edge-case coverage looks strong

The tests around corrupted auth tags, malformed blobs, and invalid key lengths (handles corrupted encryption data, handles malformed blob data, validates key format and length) provide good defensive coverage of the crypto API’s failure modes and error types. The manual try/catch checks for specific exception classes are clear and adequate here.


550-576: Helper image generators are appropriate and minimal

The _createTestJpeg and _createTestPng helpers use compact, header-only byte sequences to identify JPEG/PNG signatures, which keeps tests fast and avoids bundling real fixture files. This is a good trade-off for the current assertions that only inspect magic bytes.

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)
test/features/chat/file_messaging_test.dart (1)

300-315: Use expectLater for async error handling.

The synchronous expect(() => ..., throwsA(...)) pattern doesn't properly await the Future. When uploadImage throws asynchronously, this test may pass incorrectly or produce misleading results.

This was flagged in a previous review. Apply the suggested fix:

-      test('handles upload error responses', () async {
+      test('handles upload error responses', () async {
         const mimeType = 'image/jpeg';

         when(mockBlossomClient.uploadImage(
           imageData: anyNamed('imageData'),
           mimeType: mimeType,
         )).thenThrow(BlossomException('Upload failed: 413 - File too large'));

-        expect(
-          () => mockBlossomClient.uploadImage(
+        await expectLater(
+          mockBlossomClient.uploadImage(
             imageData: Uint8List.fromList([1, 2, 3, 4, 5]),
             mimeType: mimeType,
           ),
           throwsA(isA<BlossomException>()),
         );
       });
🧹 Nitpick comments (8)
test/features/chat/file_messaging_test.dart (8)

16-17: Clarify which BlossomException is used in tests.

Both imports hide BlossomException, but line 306 throws BlossomException from blossom_client.dart. This works but the hide directives suggest potential naming conflicts between the services. Verify this is intentional and consider documenting which exception class is being used.


102-115: Test verifies mock stub, not production behavior.

This test stubs mockChatRoomNotifier.getSharedKey() then immediately calls it and asserts the stubbed value. This only confirms Mockito works—it doesn't validate that the shared key is actually used for both text and file encryption in production code.

Consider either:

  1. Removing this test (the encryption round-trip tests already validate key usage)
  2. Testing the actual ChatRoomNotifier.getSharedKey() implementation

197-208: Tests don't exercise actual validation methods.

These tests create Uint8List arrays and compare their lengths against FileValidationService.maxFileSize, but they never call the actual validation method (e.g., FileValidationService.validateFile()). If the service has validation logic beyond size checks, it's untested.

Consider calling the actual validation methods:

 test('accepts files within 25MB limit', () {
   final file24MB = Uint8List(24 * 1024 * 1024);
-  expect(file24MB.length, lessThanOrEqualTo(FileValidationService.maxFileSize));
+  final result = FileValidationService.validateFileSize(file24MB);
+  expect(result.isValid, isTrue);
   
   final file25MB = Uint8List(25 * 1024 * 1024);
-  expect(file25MB.length, lessThanOrEqualTo(FileValidationService.maxFileSize));
+  final result25 = FileValidationService.validateFileSize(file25MB);
+  expect(result25.isValid, isTrue);
 });

 test('rejects oversized files', () {
   final file26MB = Uint8List(26 * 1024 * 1024);
-  expect(file26MB.length, greaterThan(FileValidationService.maxFileSize));
+  final result = FileValidationService.validateFileSize(file26MB);
+  expect(result.isValid, isFalse);
 });

253-268: Tests validate helper functions, not MediaValidationService.

These tests verify that _createTestJpeg() and _createTestPng() produce correct magic bytes, but they don't exercise any MediaValidationService methods. If the service has image validation logic (e.g., validateImageFormat()), it remains untested.


375-399: Trivial mock check and duplicate encryption test.

  • Lines 376-379: Testing that mockDownloadService is not null and is a MockBlossomDownloadService provides no value—Mockito always returns valid mocks.
  • Lines 381-399: This test is nearly identical to 'encrypts and decrypts files correctly' at lines 117-138.

Consider removing the mock existence check and either consolidating the duplicate encryption tests or differentiating this one to test actual download+decrypt flow.


403-411: Remove trivial mock existence checks.

These tests only verify that mock objects exist and have the correct type. Mockito guarantees this—these tests cannot fail and provide no coverage value.

-    test('image upload service can be mocked', () {
-      expect(mockImageUploadService, isNotNull);
-      expect(mockImageUploadService, isA<MockEncryptedImageUploadService>());
-    });
-
-    test('file upload service can be mocked', () {
-      expect(mockFileUploadService, isNotNull);
-      expect(mockFileUploadService, isA<MockEncryptedFileUploadService>());
-    });

500-507: Test validates string inequality, not session isolation.

This test creates two string constants and asserts they're not equal, then checks testSession.orderId against those constants. This doesn't test any session isolation logic—it confirms that different strings are different.

Either remove this test or make it meaningful by testing actual session isolation behavior (e.g., that a session cannot access data from another session's orderId).


53-86: Consider consolidating test setup with provider overrides.

The ProviderContainer (line 81) is created and disposed but never used for provider overrides. The tests use mocks directly instead of through Riverpod's override mechanism. This is fine for unit testing encryption utilities, but if you want to test Riverpod integration (e.g., notifier behavior), consider:

container = ProviderContainer(
  overrides: [
    blossomClientProvider.overrideWithValue(mockBlossomClient),
    // ... other overrides
  ],
);

As per coding guidelines, Riverpod should be used for state management with business logic in Notifiers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 375ea01 and 46c0668.

📒 Files selected for processing (2)
  • test/features/chat/file_messaging_test.dart (1 hunks)
  • test/mocks.mocks.dart (36 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:

  • test/mocks.mocks.dart
  • test/features/chat/file_messaging_test.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:

  • test/mocks.mocks.dart
  • test/features/chat/file_messaging_test.dart
**/*.{g.dart,mocks.dart}

📄 CodeRabbit inference engine (CLAUDE.md)

Never manually edit generated files (.g.dart, .mocks.dart) or add individual ignore comments - regenerate with dart run build_runner build -d instead

Files:

  • test/mocks.mocks.dart
test/mocks.mocks.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Never manually modify test/mocks.mocks.dart - it already has file-level // ignore_for_file: must_be_immutable ignore directives

Files:

  • test/mocks.mocks.dart
**/*_test.dart

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.dart: Tests should mirror the feature layout under test/ with the *_test.dart suffix; end-to-end flows should run from integration_test/
Place unit tests beside their feature counterparts using the *_test.dart suffix and prefer Mockito fakes generated via build_runner

Files:

  • test/features/chat/file_messaging_test.dart
🧠 Learnings (28)
📚 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:

  • test/mocks.mocks.dart
  • test/features/chat/file_messaging_test.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 **/*_test.dart : Place unit tests beside their feature counterparts using the `*_test.dart` suffix and prefer Mockito fakes generated via `build_runner`

Applied to files:

  • test/mocks.mocks.dart
  • test/features/chat/file_messaging_test.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:

  • test/mocks.mocks.dart
  • test/features/chat/file_messaging_test.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:

  • test/mocks.mocks.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 **/*.{g.dart,mocks.dart} : Never manually edit generated files (`.g.dart`, `.mocks.dart`) or add individual ignore comments - regenerate with `dart run build_runner build -d` instead

Applied to files:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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:

  • test/mocks.mocks.dart
  • test/features/chat/file_messaging_test.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 : Track relay sources (user, mostro, default) using RelaySource enum for appropriate handling and storage strategy

Applied to files:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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/models/relay_list_event.dart : Parse NIP-65 (kind 10002) events with robust handling of different timestamp formats and null-safe parsing for malformed events

Applied to files:

  • test/mocks.mocks.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/data/repositories/**/*.dart : Implement Repository pattern for all data access - all data operations must go through repository classes

Applied to files:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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/relays_notifier.dart : Use `removeRelayWithBlacklist()` for Mostro/default relays (adds to blacklist) and `removeRelay()` for permanent user relay deletion

Applied to files:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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 relay URL normalization by removing trailing slashes in all blacklist operations to ensure consistent matching

Applied to files:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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 integration_test/**/*.dart : Ensure integration test coverage for flows touching networking, background work, or session recovery in `integration_test/`

Applied to files:

  • test/features/chat/file_messaging_test.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 **/*_test.dart : Tests should mirror the feature layout under `test/` with the `*_test.dart` suffix; end-to-end flows should run from `integration_test/`

Applied to files:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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)
test/features/chat/file_messaging_test.dart (1)

549-575: LGTM!

The helper functions create properly structured minimal JPEG and PNG test data with correct magic bytes and headers. These are useful for testing image format validation.

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)
test/features/chat/file_messaging_test.dart (1)

270-315: Blossom upload tests still operate on mocks only, and error test uses a non-async-safe pattern

  • delegates to BlossomClient uploadImage method stubs and calls MockBlossomClient.uploadImage directly, so it only verifies Mockito behavior, not that any higher-level upload service or UI flow delegates correctly.
  • exposes configured server URL is fine but very shallow.
  • handles upload error responses stubs uploadImage to throw and then uses expect(() => mockBlossomClient.uploadImage(...), throwsA(...)) on a Future<String> API, which is brittle and doesn’t assert on how your code under test reacts to failed uploads.

To make these tests more meaningful and future-proof:

  • Test a real component that depends on BlossomClient (e.g., EncryptedImageUploadService or the file-messaging flow) by injecting mockBlossomClient and asserting it calls uploadImage with the correct bytes/MIME and propagates or wraps BlossomException.
  • For the error case, prefer stubbing the async failure and asserting on the returned Future:
-      test('handles upload error responses', () async {
+      test('handles upload error responses', () async {
@@
-        when(mockBlossomClient.uploadImage(
-          imageData: anyNamed('imageData'),
-          mimeType: mimeType,
-        )).thenThrow(BlossomException('Upload failed: 413 - File too large'));
-
-        expect(
-          () => mockBlossomClient.uploadImage(
-            imageData: Uint8List.fromList([1, 2, 3, 4, 5]),
-            mimeType: mimeType,
-          ),
-          throwsA(isA<BlossomException>()),
-        );
+        when(
+          mockBlossomClient.uploadImage(
+            imageData: anyNamed('imageData'),
+            mimeType: mimeType,
+          ),
+        ).thenAnswer(
+          (_) async => throw BlossomException('Upload failed: 413 - File too large'),
+        );
+
+        await expectLater(
+          mockBlossomClient.uploadImage(
+            imageData: Uint8List.fromList([1, 2, 3, 4, 5]),
+            mimeType: mimeType,
+          ),
+          throwsA(isA<BlossomException>()),
+        );
       });

This both uses Mockito’s async stubbing conventions and checks the Future behavior directly instead of relying on synchronous throws.

To confirm behavior after refactoring, run:

#!/bin/bash
# Run only this suite while iterating on the Blossom tests
flutter test test/features/chat/file_messaging_test.dart
🧹 Nitpick comments (6)
test/features/chat/file_messaging_test.dart (6)

101-114: shared key used for both text and files test only validates a Mockito stub

This test stubs MockChatRoomNotifier.getSharedKey() and then asserts that the stubbed value is returned, without exercising real ChatRoomNotifier or any text/file pipeline. To actually prove that the same shared key is used for both text and file encryption, consider:

  • Instantiating a real ChatRoomNotifier (via its provider and ProviderContainer) with testSession, and
  • Asserting that getSharedKey() (and any file-encryption entrypoint) derive the same bytes from testSession.sharedKey.

195-239: File-size tests assert on constants rather than validation behavior

The size tests currently only compare Uint8List.length against FileValidationService.maxFileSize. They don’t call any actual validation API, so they’ll still pass if validation logic is broken but the constant remains unchanged. If FileValidationService exposes methods like validateFileSize or a higher-level validateFile(...), consider invoking those here and asserting success/failure outcomes instead of (or in addition to) the raw length comparisons.


241-268: Media validation tests only assert on handcrafted headers, not service logic

_createTestJpeg / _createTestPng and the associated tests validate the synthetic headers themselves (length and magic bytes) but don’t exercise MediaValidationService or any real image validation behavior. To better cover the media validation path, consider:

  • Adding tests that feed these buffers into the real media/file-validation APIs and assert acceptance or rejection per MIME/extension, and
  • Keeping the header checks as helper assertions if you want, but making them secondary to the production API tests.

Also applies to: 573-599


399-424: Download/decrypt tests are correct but partially redundant

verifies file integrity after decryption essentially duplicates the earlier “encrypts and decrypts files correctly” test with a shorter payload, while download service can be mocked only asserts that the mock exists. Consider:

  • Dropping or strengthening the “can be mocked” test by wiring a real BlossomDownloadService that depends on MockBlossomClient / HTTP stubs, and
  • Either merging the integrity check with the main encryption test or tweaking it to model a more realistic downloaded blob path (e.g., using EncryptionResult.toBlob/fromBlob).

426-471: “Can be mocked” upload service tests provide little value

The image upload service can be mocked and file upload service can be mocked tests only assert that the mocks are non-null and of the right type. They don’t exercise any upload flow, encryption, or messaging behavior, so they are unlikely to catch regressions.

If you keep this group, consider replacing them with an end-to-end style test that:

  • Uses MockEncryptedFileUploadService / MockEncryptedImageUploadService injected into the chat/file flow, and
  • Asserts that sending a file/image triggers the appropriate encrypted upload call and produces the expected gift-wrap metadata.

This would align better with the “Complete File Sharing Flows” group name.


474-569: Security & edge-case coverage is strong, but some tests are misnamed / low-signal

Positives:

  • Cross-key decryption failure, corrupted auth-tag handling, malformed blobs, and key-length validation all give solid safety coverage around EncryptionService and EncryptionResult.
  • The explicit check for EncryptionException on corrupted tags is especially useful.

Less strong:

  • validates session isolation only compares hard-coded order IDs and testSession.orderId, not actual session-related cryptographic state or isolation guarantees. The name overpromises.

If you want this group to focus on isolation, consider adjusting that test to:

  • Create two distinct Sessions and assert that blobs encrypted with one session’s shared key cannot be decrypted with the other (similar to your other cross-session tests), or
  • Assert on whatever session-bound state actually governs decryption access in ChatRoomNotifier / related services.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46c0668 and b16a8ed.

📒 Files selected for processing (1)
  • test/features/chat/file_messaging_test.dart (1 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:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.dart
**/*_test.dart

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.dart: Tests should mirror the feature layout under test/ with the *_test.dart suffix; end-to-end flows should run from integration_test/
Place unit tests beside their feature counterparts using the *_test.dart suffix and prefer Mockito fakes generated via build_runner

Files:

  • test/features/chat/file_messaging_test.dart
🧠 Learnings (9)
📚 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:

  • test/features/chat/file_messaging_test.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 **/*_test.dart : Tests should mirror the feature layout under `test/` with the `*_test.dart` suffix; end-to-end flows should run from `integration_test/`

Applied to files:

  • test/features/chat/file_messaging_test.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 integration_test/**/*.dart : Ensure integration test coverage for flows touching networking, background work, or session recovery in `integration_test/`

Applied to files:

  • test/features/chat/file_messaging_test.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 **/*_test.dart : Place unit tests beside their feature counterparts using the `*_test.dart` suffix and prefer Mockito fakes generated via `build_runner`

Applied to files:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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,flutter} : Run `flutter test` after any code change - Mandatory before commits to ensure all unit tests pass

Applied to files:

  • test/features/chat/file_messaging_test.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 (4)
test/features/chat/file_messaging_test.dart (4)

23-51: Test bootstrap and key derivation match production patterns

Using TestWidgetsFlutterBinding.ensureInitialized(), NIP-06-style mnemonic derivation, and provideDummy for Session and services mirrors the app’s setup and should keep tests aligned with real key material and non-nullable mocks. No issues here.


116-193: Encryption tests give solid coverage of ChaCha20-Poly1305 behavior

These tests nicely cover round-trip encryption/decryption, nonce/auth-tag lengths, failure on wrong keys, nonce uniqueness, and blob toBlob/fromBlob serialization. Assuming EncryptionService is the single entrypoint for ChaCha20-Poly1305, this is good defensive coverage for the new encrypted file format.


317-397: Gift-wrap JSON tests now exercise real result types

These tests now construct EncryptedFileUploadResult / EncryptedImageUploadResult and assert on the toJson() payload, including type tags, sizes, dimensions, and absence of raw key fields. This addresses the prior issue where tests only checked hand-built maps, and it gives good regression coverage for the NIP-59 metadata structure.


573-599: Helper image generators are fine and self-contained

The minimal JPEG/PNG helpers accurately encode the expected magic numbers and header structure for 1×1 images. They’re appropriate as test fixtures and can be reused if you later add real media-validation or thumbnailing tests.

@grunch
Copy link
Member

grunch commented Dec 8, 2025

Hi @Catrya did you applied nitpick rabbit suggestions?

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)
test/features/chat/file_messaging_test.dart (1)

441-486: Blossom upload tests now reflect behavior; consider deeper HTTP/path coverage

Renaming to delegates to BlossomClient uploadImage method / exposes configured server URL and explicitly checking the delegated call and serverUrl makes the tests honest about what they cover, and handles upload error responses correctly asserts a BlossomException.

If you later want to verify HTTP headers/status handling in BlossomClient itself, you’ll likely need to:

  • Inject an http.Client into BlossomClient, and
  • Assert on the constructed request and response handling,

or complement these unit tests with an integration test that runs the full upload path under integration_test/, as suggested in earlier feedback and project guidelines for networking-heavy flows. Based on learnings, integration tests are recommended for such paths.

🧹 Nitpick comments (4)
test/features/chat/file_messaging_test.dart (4)

64-100: ProviderContainer override is currently unused in tests

ProviderContainer with the sessionNotifierProvider override and MockSessionNotifier isn’t read or used anywhere in this file. That makes the setup a bit noisy and potentially confusing.

Either:

  • Add at least one test that exercises provider-based behavior (e.g., ChatRoomNotifier/session-dependent flows) through this container, or
  • Drop the container + override from setUp to keep the test harness minimal.

230-274: Simplify size-limit tests and avoid large allocations

accepts files within 25MB limit, rejects oversized files, and handles zero-byte files allocate 24–26 MB Uint8Lists but only assert on length vs FileValidationService.maxFileSize.

You can get the same signal with simpler, cheaper assertions, e.g.:

test('uses 25MB max file size', () {
  expect(FileValidationService.maxFileSize, equals(25 * 1024 * 1024));
});

and keep zero-byte checks if you want, without large buffers. That makes intent clearer and reduces memory use in tests.


570-606: Mock-existence tests provide little signal—consider tightening or removing

download service can be mocked, image upload service can be mocked, and file upload service can be mocked only assert that the mocks are non-null/expected types, which is almost guaranteed by the setup.

Either:

  • Extend these to assert on real interactions (e.g., that upload/download services call BlossomClient and EncryptionService as expected), or
  • Drop them to avoid low-value tests.

667-739: Prefer matcher-based exception assertions over manual flags

In handles corrupted encryption data and validates key format and length, you manually flip booleans inside try/catch to assert exceptions:

bool threwEncryptionException = false;
try {
  EncryptionService.decryptChaCha20Poly1305(...);
} catch (e) {
  threwEncryptionException = e is EncryptionException;
}
expect(threwEncryptionException, isTrue);

This can be simplified and made more expressive with expect + throwsA, e.g.:

expect(
  () => EncryptionService.decryptChaCha20Poly1305(
    key: key,
    nonce: encrypted.nonce,
    encryptedData: encrypted.encryptedData,
    authTag: corruptedAuthTag,
  ),
  throwsA(isA<EncryptionException>()),
);

expect(
  () => EncryptionService.encryptChaCha20Poly1305(
    key: invalidKey,
    plaintext: testData,
  ),
  throwsArgumentError,
);

This reduces boilerplate and yields clearer failure messages when expectations aren’t met.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b16a8ed and b98964b.

📒 Files selected for processing (1)
  • test/features/chat/file_messaging_test.dart (1 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:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.dart
**/*_test.dart

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.dart: Tests should mirror the feature layout under test/ with the *_test.dart suffix; end-to-end flows should run from integration_test/
Place unit tests beside their feature counterparts using the *_test.dart suffix and prefer Mockito fakes generated via build_runner

Files:

  • test/features/chat/file_messaging_test.dart
🧠 Learnings (9)
📚 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 integration_test/**/*.dart : Ensure integration test coverage for flows touching networking, background work, or session recovery in `integration_test/`

Applied to files:

  • test/features/chat/file_messaging_test.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 **/*_test.dart : Tests should mirror the feature layout under `test/` with the `*_test.dart` suffix; end-to-end flows should run from `integration_test/`

Applied to files:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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 **/*_test.dart : Place unit tests beside their feature counterparts using the `*_test.dart` suffix and prefer Mockito fakes generated via `build_runner`

Applied to files:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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,flutter} : Run `flutter test` after any code change - Mandatory before commits to ensure all unit tests pass

Applied to files:

  • test/features/chat/file_messaging_test.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 (6)
test/features/chat/file_messaging_test.dart (6)

27-55: Solid deterministic key setup and Mockito dummy configuration

Using a fixed mnemonic + KeyDerivator and provideDummy for Session and Blossom-related services gives you stable, realistic crypto context for all tests. This harness looks coherent and matches the rest of the codebase patterns.


102-228: Encryption & key-management coverage looks strong

These tests cover shared-key symmetry, using the same shared key for text/files, ChaCha20-Poly1305 round-trips, wrong-key failures, nonce uniqueness, and blob encoding/decoding. This gives very good safety nets around the crypto primitives and serialization.


275-342: Good API-level file validation tests

The JPEG/PNG/oversized/unsupported-type API tests exercise FileValidationService.validateFile end to end with real File instances and expected exceptions. This is the right level for catching regressions in the validation pipeline.


344-439: MediaValidationService tests are comprehensive and realistic

The combination of header-based checks, “real” 10x10 JPEG/PNG generation using the image package, and both light/heavy sanitization paths gives strong coverage of media validation behavior, including invalid/empty data and MIME-type support. Nicely done.


488-568: Gift-wrap tests now exercise production JSON structure

Switching to EncryptedFileUploadResult / EncryptedImageUploadResult and asserting on toJson() output (types, sizes, dimensions, absence of encryption_key/shared_key, etc.) directly tests the real gift-wrap message format rather than hand-crafted maps. This closes the earlier gap and should catch breaking changes in the serialized structure.


608-642: Nice explicit session-isolation check for shared keys

handles file sharing between different sessions derives separate trade/peer keys and asserts that session1.sharedKey!.private differs from session2.sharedKey!.private. This is a clear, valuable assertion about session isolation and will guard against accidental key reuse across orders.

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)
test/features/chat/file_messaging_test.dart (1)

401-446: Blossom upload tests remain unchanged from previous review.

The concerns raised in the previous review about these tests still apply:

  • Test at lines 402-421 only verifies the mock was called once; it doesn't assert on HTTP headers or authorization behavior
  • Test at lines 423-428 only checks the serverUrl property, not upload behavior
  • Test at lines 430-445 correctly uses mock setup but doesn't validate the HTTP error response details

The previous review suggested either:

  1. Renaming tests to match what they actually verify (e.g., "delegates to BlossomClient.uploadImage")
  2. Extending BlossomClient to accept injectable http.Client for deeper HTTP testing

Additionally, as noted in the previous review, flows involving networking should ideally have integration test coverage under integration_test/ per project guidelines. Based on learnings, integration tests are recommended for networking-heavy flows.

🧹 Nitpick comments (1)
test/features/chat/file_messaging_test.dart (1)

16-17: Consider centralizing BlossomException to avoid multiple definitions.

The hide BlossomException directives suggest this exception type is defined in both encrypted_file_upload_service.dart and encrypted_image_upload_service.dart. This duplication creates maintenance overhead and potential inconsistencies.

Consider moving BlossomException to a shared location (e.g., lib/services/blossom_client.dart or a dedicated exceptions file) and having both upload services import it from there.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b98964b and 771074c.

📒 Files selected for processing (1)
  • test/features/chat/file_messaging_test.dart (1 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:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.dart
**/*_test.dart

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.dart: Tests should mirror the feature layout under test/ with the *_test.dart suffix; end-to-end flows should run from integration_test/
Place unit tests beside their feature counterparts using the *_test.dart suffix and prefer Mockito fakes generated via build_runner

Files:

  • test/features/chat/file_messaging_test.dart
🧠 Learnings (9)
📚 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 integration_test/**/*.dart : Ensure integration test coverage for flows touching networking, background work, or session recovery in `integration_test/`

Applied to files:

  • test/features/chat/file_messaging_test.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 **/*_test.dart : Tests should mirror the feature layout under `test/` with the `*_test.dart` suffix; end-to-end flows should run from `integration_test/`

Applied to files:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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 **/*_test.dart : Place unit tests beside their feature counterparts using the `*_test.dart` suffix and prefer Mockito fakes generated via `build_runner`

Applied to files:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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,flutter} : Run `flutter test` after any code change - Mandatory before commits to ensure all unit tests pass

Applied to files:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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 (6)
test/features/chat/file_messaging_test.dart (6)

73-199: Excellent encryption test coverage!

This test group provides comprehensive coverage of the encryption system:

  • Validates shared key derivation correctness between parties
  • Confirms the same shared key works for both text and file encryption
  • Tests ChaCha20-Poly1305 encryption/decryption round-trips
  • Ensures decryption fails with wrong keys (security critical)
  • Verifies nonce uniqueness across encryptions (replay attack prevention)
  • Validates blob serialization format

201-302: Thorough file validation test coverage with proper I/O cleanup.

The tests comprehensively cover:

  • Size limit enforcement (25MB)
  • Extension and MIME type validation
  • Real file I/O with proper temp directory cleanup
  • Both supported and unsupported file types
  • Edge cases like zero-byte and oversized files

304-399: Comprehensive media validation test coverage.

The tests validate:

  • Supported image type definitions
  • JPEG and PNG header format requirements
  • API-based validation with real image data
  • Both light and heavy sanitization paths
  • Proper error handling for invalid and empty data

448-528: Excellent improvement! Tests now use production code instead of hand-crafted maps.

These tests have been significantly improved since the last review. They now:

  • Use actual EncryptedFileUploadResult and EncryptedImageUploadResult instances
  • Test the real toJson() serialization logic
  • Validate proper message type discrimination (file_encrypted vs image_encrypted)
  • Ensure encryption keys are not exposed in the JSON output
  • Verify all required metadata fields are present

This provides much more valuable test coverage than the previous hand-crafted map approach.


552-677: Strong security and edge case test coverage.

These test groups provide excellent coverage of critical security concerns:

  • Session isolation: Verifies different sessions derive different shared keys
  • Cross-session prevention: Ensures files encrypted in one session can't be decrypted in another
  • Data integrity: Validates that corrupted auth tags are detected and rejected
  • Input validation: Tests malformed blob handling and key length requirements

This comprehensive security testing helps ensure the encryption system is robust against common attack vectors.


681-726: Helper function documentation is now accurate and clear.

The doc comments have been improved since the last review:

  • _createTestJpeg() and _createTestPng() are correctly described as creating "minimal byte sequences with valid header markers for header validation tests"
  • _createRealJpeg() and _createRealPng() are clearly marked as creating "real valid images for MediaValidationService API tests"

The distinction between minimal header-only fixtures and full images is now crystal clear, which will help future maintainers understand the test structure.

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 (3)
lib/services/encrypted_image_upload_service.dart (2)

65-75: Same note as file service: dedupe Blossom server list/retry helper

This service repeats the same _blossomServers list and _uploadWithRetry implementation as the file service; centralizing this in a shared helper or config will make future server list changes less error‑prone.

Also applies to: 181-224


150-179: nonceHex parameter is unused in decryption and should be removed or validated

downloadAndDecryptImage accepts nonceHex but only logs it; actual decryption uses decryptFromBlob, which extracts the nonce from the blob. This mirrors the earlier situation you already fixed in EncryptedFileUploadService.

To avoid a misleading API surface, either:

  • Drop nonceHex from the signature and all callers (preferred, consistent with file service), or
  • Parse the blob first and assert that its nonce matches nonceHex, throwing if it doesn’t.

Right now it’s dead weight and may confuse future callers.

test/features/chat/file_messaging_test.dart (1)

401-446: Blossom upload tests still don’t match names and use a non‑idiomatic async error pattern

  • delegates to BlossomClient uploadImage method is fine as a delegation test.
  • exposes configured server URL only asserts the stored URL, not any upload behavior (name suggests more).
  • handles upload error responses stubs uploadImage on a Future<String> API but uses expect(() => mockBlossomClient.uploadImage(...), throwsA(...)), which treats the failure as a synchronous throw rather than a failing Future. The more idiomatic pattern in package:test is to assert on the returned Future itself, e.g.:
when(mockBlossomClient.uploadImage(
  imageData: anyNamed('imageData'),
  mimeType: mimeType,
)).thenThrow(BlossomException('Upload failed: 413 - File too large'));

await expectLater(
  mockBlossomClient.uploadImage(
    imageData: Uint8List.fromList([1, 2, 3, 4, 5]),
    mimeType: mimeType,
  ),
  throwsA(isA<BlossomException>()),
);

You might also rename the tests if you decide not to inspect HTTP/auth details here.

In Dart's `package:test`, what is the recommended pattern for asserting that an async function (returning `Future<T>`) throws—`expect(() => asyncFn(), throwsA(...))` with an async closure, or `await expectLater(asyncFn(), throwsA(...))`? Provide guidance specifically for mocked methods returning `Future`s.
🧹 Nitpick comments (1)
lib/services/encrypted_file_upload_service.dart (1)

60-71: Consider centralizing Blossom server config and retry logic

_blossomServers and _uploadWithRetry here are effectively identical to the image service; in a follow‑up it would be cleaner to share a single configurable Blossom host list + upload helper to avoid drift when servers change.

Also applies to: 163-212

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 771074c and 7fb86b5.

📒 Files selected for processing (3)
  • lib/services/encrypted_file_upload_service.dart (1 hunks)
  • lib/services/encrypted_image_upload_service.dart (1 hunks)
  • test/features/chat/file_messaging_test.dart (1 hunks)
🧰 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:

  • test/features/chat/file_messaging_test.dart
  • lib/services/encrypted_image_upload_service.dart
  • lib/services/encrypted_file_upload_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:

  • test/features/chat/file_messaging_test.dart
  • lib/services/encrypted_image_upload_service.dart
  • lib/services/encrypted_file_upload_service.dart
**/*_test.dart

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.dart: Tests should mirror the feature layout under test/ with the *_test.dart suffix; end-to-end flows should run from integration_test/
Place unit tests beside their feature counterparts using the *_test.dart suffix and prefer Mockito fakes generated via build_runner

Files:

  • test/features/chat/file_messaging_test.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/encrypted_image_upload_service.dart
  • lib/services/encrypted_file_upload_service.dart
🧠 Learnings (9)
📚 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 **/*_test.dart : Tests should mirror the feature layout under `test/` with the `*_test.dart` suffix; end-to-end flows should run from `integration_test/`

Applied to files:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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 integration_test/**/*.dart : Ensure integration test coverage for flows touching networking, background work, or session recovery in `integration_test/`

Applied to files:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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 **/*_test.dart : Place unit tests beside their feature counterparts using the `*_test.dart` suffix and prefer Mockito fakes generated via `build_runner`

Applied to files:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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,flutter} : Run `flutter test` after any code change - Mandatory before commits to ensure all unit tests pass

Applied to files:

  • test/features/chat/file_messaging_test.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:

  • test/features/chat/file_messaging_test.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 (8)
lib/services/encrypted_file_upload_service.dart (2)

10-55: EncryptedFileUploadResult JSON shape looks solid

Fields and toJson/fromJson are consistent and carry the right metadata for NIP‑59 file messages; no changes needed here.


75-161: End‑to‑end upload/download flow is correct and side‑effect safe

File validation → ChaCha20‑Poly1305 encryption → blob upload → download + decryptFromBlob all line up cleanly, with errors logged and rethrown; this looks good and matches the intended blob format.

lib/services/encrypted_image_upload_service.dart (2)

10-59: EncryptedImageUploadResult structure is appropriate

Model cleanly captures Blossom URL, nonce, image dimensions, sizes, and MIME type with consistent JSON mapping for NIP‑59; looks good.


79-148: Image upload pipeline is well‑structured

Read → validate/sanitize (light) → encrypt → upload with retry → filename generation is straightforward and matches the media validation tests; no issues spotted here.

test/features/chat/file_messaging_test.dart (4)

73-199: Strong coverage of encryption and key‑management behavior

These tests exercise shared‑key derivation, AEAD correctness, nonce uniqueness, blob round‑tripping, and key‑length validation; this gives good confidence in the ChaCha20‑Poly1305 plumbing.


201-399: File/media validation tests look thorough and realistic

You cover supported types, zero‑byte and oversized files, header‑only fixtures plus real encoded JPEG/PNG images via MediaValidationService, and the helper comments now match their implementations. Nicely comprehensive.


448-528: Gift‑wrap JSON tests now exercise real models

These tests construct EncryptedFileUploadResult / EncryptedImageUploadResult and assert on their toJson() output, which directly validates the production metadata shape and fixes the earlier issue of only testing hand‑crafted maps.


552-676: Security and cross‑session edge‑case coverage is solid

The tests for cross‑session key mismatch, corrupted auth tags, malformed blobs, and key‑length validation provide good defense‑in‑depth checks around the crypto and session model.

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

🧹 Nitpick comments (2)
lib/services/encrypted_file_upload_service.dart (2)

11-56: Tighten JSON typing and make the result model constructor const.

The model looks good and matches the NIP‑59 payload needs, but you can make it a bit safer and more in line with the “use const where possible” guideline:

  • Add a const constructor (call sites don’t need to change, but you gain the option of const instances where applicable).
  • Add explicit casts in fromJson to avoid silent type drift and give clearer failures if the JSON shape is wrong (e.g. num vs int, missing keys).

Example refactor:

-  EncryptedFileUploadResult({
+  const EncryptedFileUploadResult({
     required this.blossomUrl,
     required this.nonce,
     required this.mimeType,
     required this.fileType,
     required this.originalSize,
     required this.filename,
     required this.encryptedSize,
   });
@@
   /// Create from JSON (for receiving messages)
   factory EncryptedFileUploadResult.fromJson(Map<String, dynamic> json) {
     return EncryptedFileUploadResult(
-      blossomUrl: json['blossom_url'],
-      nonce: json['nonce'],
-      mimeType: json['mime_type'],
-      fileType: json['file_type'],
-      originalSize: json['original_size'],
-      filename: json['filename'],
-      encryptedSize: json['encrypted_size'],
+      blossomUrl: json['blossom_url'] as String,
+      nonce: json['nonce'] as String,
+      mimeType: json['mime_type'] as String,
+      fileType: json['file_type'] as String,
+      originalSize: (json['original_size'] as num).toInt(),
+      filename: json['filename'] as String,
+      encryptedSize: (json['encrypted_size'] as num).toInt(),
     );
   }

This keeps behavior the same for well‑formed payloads but makes deserialization failures more predictable and easier to debug. As per coding guidelines, this also leverages const constructors where possible.


64-120: Capture and log stack traces for upload/download failures.

The overall flow in uploadEncryptedFile and downloadAndDecryptFile looks solid (validate/encrypt/upload and download/decrypt), and the updated nonce handling in downloadAndDecryptFile now clearly matches the blob format, addressing the earlier unused-nonceHex concern.

Right now both methods do:

} catch (e) {
  _logger.e('❌ ... failed: $e');
  rethrow;
}

which logs only the error message and discards the stack trace in logs. For crypto and network issues, the stack trace is usually critical when diagnosing failures in the field.

Consider:

  • Using catch (e, stackTrace) in both methods.
  • Passing both e and stackTrace to _logger.e(...) according to the logger package version you’re on (either positional or named error/stackTrace parameters, depending on API).

This keeps the rethrow behavior (so upstream callers still see the original exception) but makes production debugging a lot easier.

Also applies to: 122-150

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7fb86b5 and 916d3d2.

📒 Files selected for processing (3)
  • lib/services/encrypted_file_upload_service.dart (1 hunks)
  • lib/services/encrypted_image_upload_service.dart (1 hunks)
  • lib/services/image_upload_service.dart (1 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/services/encrypted_file_upload_service.dart
  • lib/services/encrypted_image_upload_service.dart
  • lib/services/image_upload_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/services/encrypted_file_upload_service.dart
  • lib/services/encrypted_image_upload_service.dart
  • lib/services/image_upload_service.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/encrypted_file_upload_service.dart
  • lib/services/encrypted_image_upload_service.dart
  • lib/services/image_upload_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 (4)
lib/services/encrypted_image_upload_service.dart (2)

68-137: LGTM: Comprehensive encrypted upload flow.

The encryption and upload logic is well-structured with proper sanitization, encryption, and metadata capture. The auto-generated filename fallback on line 113-114 is a good defensive practice.


209-212: LGTM: Clean hex conversion implementation.

The _bytesToHex helper correctly converts bytes to a zero-padded hex string.

lib/services/image_upload_service.dart (1)

1-7: No action needed. BlossomException is properly available through the existing import of blossom_client.dart on line 5. In Dart, all public classes defined in an imported module are automatically accessible without requiring a separate import statement.

lib/services/encrypted_file_upload_service.dart (1)

152-184: The review comment is incorrect. BlossomException is already defined in lib/services/blossom_client.dart (line 86) and properly imported in the file via import 'package:mostro_mobile/services/blossom_client.dart'. No action is needed; the analyzer will not flag this as an undefined identifier.

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

Caution

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

⚠️ Outside diff range comments (1)
lib/features/chat/notifiers/chat_room_notifier.dart (1)

151-166: Remove the blocking await _processMessageContent(chat) call to improve message appearance latency.

The await at line 154 blocks the state update while downloading and decrypting images, delaying when the message appears in the chat list. Since the EncryptedImageMessage widget already has a fallback _loadImage() method that downloads images on-demand and displays a loading placeholder, the pre-download in the notifier is redundant. Move this to a non-blocking operation using unawaited() or Future.microtask() (patterns already used in abstract_mostro_notifier.dart and relays_notifier.dart) to allow the message to appear immediately with a loading indicator for the image.

♻️ Duplicate comments (5)
lib/services/encrypted_image_upload_service.dart (2)

47-62: Previous feedback addressed.

The fromJson factory now includes proper try/catch with a descriptive FormatException. This addresses the prior review comment about null safety checks.


142-169: Previous feedback addressed.

The downloadAndDecryptImage method no longer has the unused nonceHex parameter. The nonce is correctly extracted from the blob via decryptFromBlob.

lib/services/image_upload_service.dart (1)

50-53: Previous feedback addressed.

The _uploadWithRetry method now correctly delegates to BlossomUploadHelper.uploadWithRetry, eliminating the code duplication that was previously flagged.

lib/features/chat/widgets/encrypted_image_message.dart (1)

33-59: Previous feedback addressed - proper lifecycle handling.

The widget now correctly triggers image loading in initState via addPostFrameCallback instead of from build(). The mounted checks are properly placed before setState calls, and null message ID is handled defensively.

lib/features/chat/notifiers/chat_room_notifier.dart (1)

385-413: Previous feedback addressed - hex key validation added.

The getSharedKey method now validates hex key length (line 395-397) and wraps the parsing in try/catch with descriptive error messages (lines 402-410).

🧹 Nitpick comments (8)
docs/architecture/ENCRYPTED_IMAGE_MESSAGING_IMPLEMENTATION.md (2)

31-45: Add language specifier to fenced code block.

The ASCII diagram code block is missing a language specifier. Use text or plaintext to satisfy markdownlint (MD040).

-```
+```text
 ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐

145-149: Add language specifier to fenced code block.

Similar to the architecture diagram above, this blob structure diagram needs a language specifier.

-```
+```text
 ┌─────────────┬──────────────────┬─────────────────┐
lib/services/encrypted_image_upload_service.dart (1)

66-69: Minor: Remove extra blank line.

There's an extra blank line after the Logger declaration.

 class EncryptedImageUploadService {
   final Logger _logger = Logger();
-  
-  
+
   EncryptedImageUploadService();
lib/services/image_upload_service.dart (1)

7-11: Minor: Remove extra blank line.

Same pattern as the encrypted service - extra blank line after Logger declaration.

 class ImageUploadService {
   final Logger _logger = Logger();
-  
-  
+
   ImageUploadService();
lib/services/blossom_upload_helper.dart (1)

55-58: Unreachable code - dead throw statement.

Line 57 is unreachable. When i == servers.length - 1 (last server), line 49 throws, so the loop never exits normally when all servers fail. This throw will only execute if servers is empty.

Consider restructuring to handle the empty servers case explicitly:

   static Future<String> uploadWithRetry(
     Uint8List data,
     String mimeType,
   ) async {
     final servers = BlossomConfig.defaultServers;
+    
+    if (servers.isEmpty) {
+      throw BlossomException('No Blossom servers configured');
+    }
     
     for (int i = 0; i < servers.length; i++) {
       final serverUrl = servers[i];
       _logger.d('Attempting upload to server ${i + 1}/${servers.length}: $serverUrl');
       
       try {
         final client = BlossomClient(serverUrl: serverUrl);
         final blossomUrl = await client.uploadImage(
           imageData: data,
           mimeType: mimeType,
         );
         
         _logger.i('✅ Upload successful to: $serverUrl');
         return blossomUrl;
         
       } catch (e) {
         _logger.w('❌ Upload failed to $serverUrl: $e');
         
         // If it's the last server, re-throw the error
         if (i == servers.length - 1) {
           throw BlossomException('All Blossom servers failed. Last error: $e');
         }
-        
-        // Continue with next server
-        continue;
       }
     }
-    
-    throw BlossomException('No Blossom servers available');
+    
+    // This should never be reached due to the throw on last server failure
+    throw StateError('Unreachable: upload loop exited unexpectedly');
   }
lib/features/chat/widgets/encrypted_image_message.dart (1)

209-219: Localize user-facing strings.

Per coding guidelines, user-facing strings should use S.of(context) rather than hardcoded literals. This applies to "Decrypting image..." and similar strings in _buildErrorWidget and _openImage.

           Padding(
             padding: const EdgeInsets.symmetric(horizontal: 16),
             child: Text(
-              'Decrypting image...',
+              S.of(context).decryptingImage,
               style: TextStyle(

Also update the error messages at lines 254, 371, and 382.

lib/features/chat/notifiers/chat_room_notifier.dart (2)

488-494: Consider cache cleanup on dispose.

The in-memory caches (_imageCache, _imageMetadata, _fileCache, _fileMetadata) are not cleared in dispose(). While the references will be garbage collected when the notifier is disposed, explicitly clearing large binary data may help with memory pressure in long chat sessions.

 @override
 void dispose() {
   _subscription?.cancel();
   _sessionListener?.close();
+  _imageCache.clear();
+  _fileCache.clear();
   _logger.i('Disposed chat room notifier for orderId: $orderId');
   super.dispose();
 }

469-474: Consider reusing service instances.

New EncryptedImageUploadService instances are created for each message. Since the service appears stateless, consider making it a field or using dependency injection to avoid repeated instantiation.

+ final EncryptedImageUploadService _imageUploadService = EncryptedImageUploadService();
+ final EncryptedFileUploadService _fileUploadService = EncryptedFileUploadService();

  // In _processEncryptedImageMessage:
- final uploadService = EncryptedImageUploadService();
- final decryptedImage = await uploadService.downloadAndDecryptImage(
+ final decryptedImage = await _imageUploadService.downloadAndDecryptImage(
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0ba869 and def9ef1.

📒 Files selected for processing (8)
  • docs/architecture/ENCRYPTED_IMAGE_MESSAGING_IMPLEMENTATION.md (1 hunks)
  • lib/features/chat/notifiers/chat_room_notifier.dart (3 hunks)
  • lib/features/chat/widgets/encrypted_image_message.dart (1 hunks)
  • lib/services/blossom_upload_helper.dart (1 hunks)
  • lib/services/encrypted_file_upload_service.dart (1 hunks)
  • lib/services/encrypted_image_upload_service.dart (1 hunks)
  • lib/services/image_upload_service.dart (1 hunks)
  • test/mocks.mocks.dart (36 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{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/services/image_upload_service.dart
  • lib/services/blossom_upload_helper.dart
  • lib/services/encrypted_file_upload_service.dart
  • lib/features/chat/widgets/encrypted_image_message.dart
  • lib/features/chat/notifiers/chat_room_notifier.dart
  • lib/services/encrypted_image_upload_service.dart
  • test/mocks.mocks.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/services/image_upload_service.dart
  • lib/services/blossom_upload_helper.dart
  • lib/services/encrypted_file_upload_service.dart
  • lib/features/chat/widgets/encrypted_image_message.dart
  • lib/features/chat/notifiers/chat_room_notifier.dart
  • lib/services/encrypted_image_upload_service.dart
  • test/mocks.mocks.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/image_upload_service.dart
  • lib/services/blossom_upload_helper.dart
  • lib/services/encrypted_file_upload_service.dart
  • lib/services/encrypted_image_upload_service.dart
lib/features/**/notifiers/**/*.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Encapsulate business logic in Notifiers - Notifiers should expose state via providers and handle all complex state transitions

Files:

  • lib/features/chat/notifiers/chat_room_notifier.dart
**/*.{g.dart,mocks.dart}

📄 CodeRabbit inference engine (CLAUDE.md)

Never manually edit generated files (.g.dart, .mocks.dart) or add individual ignore comments - regenerate with dart run build_runner build -d instead

Files:

  • test/mocks.mocks.dart
test/mocks.mocks.dart

📄 CodeRabbit inference engine (CLAUDE.md)

Never manually modify test/mocks.mocks.dart - it already has file-level // ignore_for_file: must_be_immutable ignore directives

Files:

  • test/mocks.mocks.dart
🧠 Learnings (33)
📚 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/features/chat/widgets/encrypted_image_message.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/features/chat/widgets/encrypted_image_message.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/chat/widgets/encrypted_image_message.dart
  • lib/features/chat/notifiers/chat_room_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/features/chat/notifiers/chat_room_notifier.dart
  • test/mocks.mocks.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/features/chat/notifiers/chat_room_notifier.dart
  • test/mocks.mocks.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/chat/notifiers/chat_room_notifier.dart
  • test/mocks.mocks.dart
📚 Learning: 2025-05-06T22:23:06.684Z
Learnt from: chebizarro
Repo: MostroP2P/mobile PR: 74
File: lib/features/chat/notifiers/chat_rooms_notifier.dart:0-0
Timestamp: 2025-05-06T22:23:06.684Z
Learning: The `reload()` method in the chat_room_notifier is synchronous, so await is not required or possible.

Applied to files:

  • lib/features/chat/notifiers/chat_room_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/features/chat/notifiers/chat_room_notifier.dart
  • test/mocks.mocks.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/chat/notifiers/chat_room_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/**/providers/**/*.dart : Organize Riverpod providers by feature in `features/{feature}/providers/` using Notifier pattern for complex state logic

Applied to files:

  • lib/features/chat/notifiers/chat_room_notifier.dart
  • test/mocks.mocks.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/chat/notifiers/chat_room_notifier.dart
  • test/mocks.mocks.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/chat/notifiers/chat_room_notifier.dart
  • test/mocks.mocks.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/chat/notifiers/chat_room_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 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:

  • test/mocks.mocks.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 **/*_test.dart : Place unit tests beside their feature counterparts using the `*_test.dart` suffix and prefer Mockito fakes generated via `build_runner`

Applied to files:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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 integration_test/**/*.dart : Ensure integration test coverage for flows touching networking, background work, or session recovery in `integration_test/`

Applied to files:

  • test/mocks.mocks.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 **/*_test.dart : Tests should mirror the feature layout under `test/` with the `*_test.dart` suffix; end-to-end flows should run from `integration_test/`

Applied to files:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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 : Track relay sources (user, mostro, default) using RelaySource enum for appropriate handling and storage strategy

Applied to files:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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/models/relay_list_event.dart : Parse NIP-65 (kind 10002) events with robust handling of different timestamp formats and null-safe parsing for malformed events

Applied to files:

  • test/mocks.mocks.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/data/repositories/**/*.dart : Implement Repository pattern for all data access - all data operations must go through repository classes

Applied to files:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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/relays_notifier.dart : Use `removeRelayWithBlacklist()` for Mostro/default relays (adds to blacklist) and `removeRelay()` for permanent user relay deletion

Applied to files:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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 relay URL normalization by removing trailing slashes in all blacklist operations to ensure consistent matching

Applied to files:

  • test/mocks.mocks.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:

  • test/mocks.mocks.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/architecture/ENCRYPTED_IMAGE_MESSAGING_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/architecture/ENCRYPTED_IMAGE_MESSAGING_IMPLEMENTATION.md
🪛 markdownlint-cli2 (0.18.1)
docs/architecture/ENCRYPTED_IMAGE_MESSAGING_IMPLEMENTATION.md

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

(MD040, fenced-code-language)


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

(MD040, fenced-code-language)

⏰ 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 (15)
docs/architecture/ENCRYPTED_IMAGE_MESSAGING_IMPLEMENTATION.md (1)

1-805: Comprehensive and well-structured documentation.

This architecture document provides excellent coverage of the encrypted file messaging implementation, including security considerations, message flows, and future improvements. The documentation aligns well with the code implementation.

lib/services/encrypted_image_upload_service.dart (1)

71-140: Well-structured upload flow.

The uploadEncryptedImage method follows a clean pipeline: read → validate/sanitize → encrypt → upload → return metadata. Proper error handling with informative logging at each step.

lib/services/image_upload_service.dart (1)

13-48: Clean implementation.

The upload flow is straightforward: read → validate/sanitize → upload. Uses validateAndSanitizeImage (full sanitization) appropriately for non-encrypted uploads.

lib/services/blossom_upload_helper.dart (1)

6-42: Good extraction of retry logic.

This helper properly consolidates the retry-across-servers pattern that was previously duplicated. Documentation is clear about behavior, parameters, and exceptions.

lib/features/chat/widgets/encrypted_image_message.dart (3)

61-89: Clean build method.

The build() method is now side-effect free, correctly delegating to the appropriate widget based on cache state, loading status, or error condition.


390-437: Thorough filename sanitization.

The _sanitizeFilename function provides good security coverage: path traversal prevention, Windows reserved name handling, and character normalization. The post-sanitization check at lines 357-360 provides defense in depth.


281-341: Good async error handling in _loadImage.

Proper use of mounted checks after async operations, defensive null ID handling, and error state management. The error message is preserved for display in the error widget.

lib/features/chat/notifiers/chat_room_notifier.dart (2)

415-451: Good message content processing flow.

The _processMessageContent method correctly parses JSON content and delegates to type-specific handlers. Errors are caught and logged without disrupting message display.


517-565: Smart selective pre-download strategy.

Auto-downloading images for preview while deferring other file types is a good UX decision. The fallback to storing metadata-only on download failure ensures the message remains displayable.

lib/services/encrypted_file_upload_service.dart (5)

10-59: LGTM! Well-structured data class with proper serialization.

The EncryptedFileUploadResult class correctly implements immutability, complete JSON serialization for NIP-59 messages, and safe deserialization with appropriate error handling.


68-123: LGTM! Clean encryption and upload flow.

The method correctly orchestrates validation, ChaCha20-Poly1305 encryption, and upload with retry logic. Using application/octet-stream for encrypted data while preserving original MIME type in metadata is the right approach.


165-175: LGTM! Utility helpers are correct.

Both _bytesToHex and _formatFileSize are correctly implemented for their purposes: hex encoding for nonces and human-readable size formatting for logs.


125-153: LGTM! Past review feedback successfully addressed.

The nonceHex parameter has been correctly removed from the method signature, and the documentation now clearly states that the nonce is automatically extracted from the encrypted blob. All callers in the codebase (chat_room_notifier.dart and encrypted_file_message.dart) have been updated to use the new signature with only blossomUrl and sharedKey parameters.


155-163: External service dependencies verified.

The methods correctly delegate to BlossomUploadHelper.uploadWithRetry(Uint8List data, String mimeType) → Future<String> and BlossomDownloadService.downloadWithRetry(String blossomUrl, ...) → Future<Uint8List>. Both classes exist and are properly imported.

test/mocks.mocks.dart (1)

1-3556: LGTM! Generated mocks are correct.

The Mockito-generated mocks properly reflect the new services introduced for encrypted file messaging:

  • MockBlossomClient, MockEncryptedFileUploadService, MockEncryptedImageUploadService, MockBlossomDownloadService, and MockChatRoomNotifier are correctly generated
  • Mock method signatures align with the service interfaces, including the corrected downloadAndDecryptFile signature without the nonceHex parameter
  • Supporting fake classes provide appropriate return values

As per coding guidelines: never manually edit this file. Regenerate with dart run build_runner build -d if changes are needed.

Based on learnings, this file already has the required file-level ignore directives.

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

🧹 Nitpick comments (4)
lib/features/chat/widgets/encrypted_image_message.dart (3)

364-367: Consider removing redundant security check.

The security validation here duplicates checks already performed by _sanitizeFilename (lines 400, 414, 434-441). While defensive programming is valuable, the sanitization function already handles path traversal, empty filenames, and dangerous characters.

You could simplify by relying on the sanitization result:

       final tempDir = await getTemporaryDirectory();
       final sanitizedFilename = _sanitizeFilename(metadata.filename);
-      final tempFile = File('${tempDir.path}/$sanitizedFilename');
-      
-      // Security check
-      if (sanitizedFilename.contains('/') || sanitizedFilename.contains('\\') || 
-          sanitizedFilename.contains('..') || sanitizedFilename.trim().isEmpty) {
-        throw Exception(securityErrorMsg);
-      }
-      
+      final tempFile = File('${tempDir.path}/$sanitizedFilename');
+      
       await tempFile.writeAsBytes(imageData);

402-408: Consider using a Unicode normalization package.

The manual accent replacement handles common Spanish characters but won't cover other languages. For better i18n support, consider using a package like diacritic or unorm_dart for comprehensive Unicode normalization.

Example with the diacritic package:

import 'package:diacritic/diacritic.dart';

String _sanitizeFilename(String filename) {
  final basename = filename.split(RegExp(r'[/\\]')).last;
  
  // Normalize using package
  String normalized = removeDiacritics(basename);
  
  // Rest of sanitization logic...
}

304-314: JSON parsing could be more robust.

The simple check !content.startsWith('{') at line 305 is fragile. Consider validating that the parsed JSON contains expected fields before passing to fromJson.

       final content = widget.message.content;
-      if (content == null || !content.startsWith('{')) {
+      if (content == null || content.trim().isEmpty) {
         throw Exception(S.of(context)!.invalidImageMessageFormat);
       }

-      final imageData = EncryptedImageUploadResult.fromJson(
-        Map<String, dynamic>.from(
-          // ignore: avoid_dynamic_calls
-          jsonDecode(content) as Map
-        )
-      );
+      final parsedJson = jsonDecode(content);
+      if (parsedJson is! Map<String, dynamic>) {
+        throw Exception(S.of(context)!.invalidImageMessageFormat);
+      }
+      final imageData = EncryptedImageUploadResult.fromJson(parsedJson);
lib/services/encrypted_image_upload_service.dart (1)

47-62: Consider more detailed error messages in fromJson.

While the try-catch addresses type safety, the generic error message doesn't indicate which field is missing or invalid. For better debuggability, consider catching specific field errors.

   factory EncryptedImageUploadResult.fromJson(Map<String, dynamic> json) {
     try {
+      if (!json.containsKey('blossom_url')) {
+        throw FormatException('Missing required field: blossom_url');
+      }
       return EncryptedImageUploadResult(
         blossomUrl: json['blossom_url'] as String,
         nonce: json['nonce'] as String,
         mimeType: json['mime_type'] as String,
         originalSize: json['original_size'] as int,
         width: json['width'] as int,
         height: json['height'] as int,
         filename: json['filename'] as String,
         encryptedSize: json['encrypted_size'] as int,
       );
-    } catch (e) {
-      throw FormatException('Invalid EncryptedImageUploadResult JSON: $e');
+    } on TypeError catch (e) {
+      throw FormatException('Invalid field type in JSON: $e');
+    } on FormatException {
+      rethrow;
     }
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between def9ef1 and 7176d07.

📒 Files selected for processing (8)
  • docs/architecture/ENCRYPTED_IMAGE_MESSAGING_IMPLEMENTATION.md (1 hunks)
  • lib/features/chat/widgets/encrypted_image_message.dart (1 hunks)
  • lib/l10n/intl_en.arb (2 hunks)
  • lib/l10n/intl_es.arb (2 hunks)
  • lib/l10n/intl_it.arb (2 hunks)
  • lib/services/blossom_upload_helper.dart (1 hunks)
  • lib/services/encrypted_image_upload_service.dart (1 hunks)
  • lib/services/image_upload_service.dart (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/architecture/ENCRYPTED_IMAGE_MESSAGING_IMPLEMENTATION.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/services/blossom_upload_helper.dart
  • lib/services/image_upload_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/services/encrypted_image_upload_service.dart
  • lib/features/chat/widgets/encrypted_image_message.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/services/encrypted_image_upload_service.dart
  • lib/features/chat/widgets/encrypted_image_message.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/encrypted_image_upload_service.dart
lib/l10n/**/*.arb

📄 CodeRabbit inference engine (CLAUDE.md)

Add new localization keys to all three ARB files (en, es, it) - use S.of(context)!.keyName for all user-facing strings

Files:

  • lib/l10n/intl_en.arb
  • lib/l10n/intl_it.arb
  • lib/l10n/intl_es.arb
🧠 Learnings (4)
📚 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/features/chat/widgets/encrypted_image_message.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/features/chat/widgets/encrypted_image_message.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/chat/widgets/encrypted_image_message.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/l10n/**/*.arb : Add new localization keys to all three ARB files (en, es, it) - use `S.of(context)!.keyName` for all user-facing strings

Applied to files:

  • lib/l10n/intl_en.arb
  • lib/l10n/intl_it.arb
  • lib/l10n/intl_es.arb
⏰ 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 (4)
lib/l10n/intl_en.arb (1)

310-312: LGTM! Localization keys properly added across all three locale files.

The new localization keys for file messaging, file confirmation, and image decryption have been correctly added to all three ARB files (English, Spanish, and Italian) as required by the coding guidelines. The 16 new keys are:

  • Validation/Security errors: invalidMessageMissingId, invalidImageMessageFormat, securityErrorInvalidChars
  • File messaging: download, openFile, encrypted, downloadingFile
  • File confirmation: confirmFileUpload, sendThisFile, send
  • Image decryption: decryptingImage, failedToLoadImage, imageDecryptionError, imageNotAvailable, couldNotOpenFile, errorOpeningFile

All keys follow consistent naming conventions, are properly organized with comment sections, and the translations appear appropriate for each locale. This implementation correctly follows the guideline to add new localization keys to all three ARB files.

Based on coding guidelines.

Also applies to: 1197-1216

lib/features/chat/widgets/encrypted_image_message.dart (1)

34-60: LGTM! Previous review concerns addressed.

The initialization logic correctly uses a post-frame callback to trigger image loading, avoiding side effects in build(). The defensive null check for messageId with proper error handling addresses previous review concerns.

lib/services/encrypted_image_upload_service.dart (2)

141-168: LGTM! Previous review concern addressed.

The unused nonceHex parameter has been correctly removed from the method signature. The nonce is now properly read from the encrypted blob by decryptFromBlob, eliminating the redundancy.


115-116: Filename generation is acceptable.

Using millisecondsSinceEpoch for filename generation is appropriate for this use case. While theoretical collisions are possible with rapid uploads, the simplicity and chronological ordering benefits outweigh the negligible risk.

@Catrya Catrya requested a review from grunch December 9, 2025 01:13
Copy link
Member

@grunch grunch left a comment

Choose a reason for hiding this comment

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

tACK

@grunch grunch merged commit 3c257dc into main Dec 10, 2025
2 checks passed
@grunch grunch deleted the blossom-up-media branch December 10, 2025 18:21
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.

Allow sending of images in the chat between users and with the admin

3 participants