mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-13 09:20:29 +00:00
feat: Enhance MeshCoreBleService with new callbacks and message handling
- Added new callback types for path updates, message sent, message delivered, status responses, binary responses, and battery/storage information. - Implemented handling for binary responses and path updates, including parsing and notifying via callbacks. - Updated message sending logic to include acknowledgment and delivery confirmation. - Enhanced log parsing for received data, including detailed interpretations and analysis. - Introduced status request functionality to query operational status from repeater or sensor nodes. - Updated battery and storage information handling to provide detailed metrics and trigger callbacks. - Deprecated legacy methods in favor of more robust alternatives.
This commit is contained in:
686
MESSAGE_SEND_RECEIVE_GAP_ANALYSIS.md
Normal file
686
MESSAGE_SEND_RECEIVE_GAP_ANALYSIS.md
Normal file
@@ -0,0 +1,686 @@
|
||||
# Message Send & Receive - Gap Analysis
|
||||
|
||||
**Date**: 2025-01-14
|
||||
**Purpose**: Identify what's missing in the messaging implementation
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Your messaging implementation is **90% complete**! The core functionality works correctly. Here's what's implemented vs what's missing:
|
||||
|
||||
### ✅ What Works (Already Implemented)
|
||||
|
||||
1. ✅ **Sending channel messages** (public broadcast)
|
||||
2. ✅ **Sending direct messages to rooms** (persistent SAR markers)
|
||||
3. ✅ **Receiving messages** via `PUSH_CODE_MSG_WAITING`
|
||||
4. ✅ **Message delivery tracking** (sending → sent → delivered)
|
||||
5. ✅ **SAR marker parsing and display**
|
||||
6. ✅ **Message persistence** (MessageStorageService)
|
||||
7. ✅ **Protocol compliance** (all frame formats correct)
|
||||
|
||||
### ❌ What's Missing (Gaps)
|
||||
|
||||
1. ❌ **Sending direct messages to individual contacts** (only room DMs work)
|
||||
2. ❌ **Timeout handling** for failed messages
|
||||
3. ❌ **Message retry logic** (automatic retries on failure)
|
||||
4. ❌ **User can't send regular messages to contacts** (only SAR markers to rooms)
|
||||
|
||||
---
|
||||
|
||||
## 1. Current Implementation Analysis
|
||||
|
||||
### 1.1 Sending Messages - What Works
|
||||
|
||||
#### ✅ Channel Messages (Public Broadcast)
|
||||
|
||||
**File**: `messages_tab.dart:49-93`
|
||||
|
||||
```dart
|
||||
Future<void> _sendMessage() async {
|
||||
final text = _textController.text.trim();
|
||||
|
||||
// Always send to public channel (channel 0)
|
||||
await connectionProvider.sendChannelMessage(
|
||||
channelIdx: 0,
|
||||
text: text,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Status**: ✅ **WORKING**
|
||||
- Sends to public channel
|
||||
- Text limit enforced (160 chars)
|
||||
- User feedback via snackbar
|
||||
|
||||
#### ✅ SAR Markers to Rooms
|
||||
|
||||
**File**: `messages_tab.dart:109-221`
|
||||
|
||||
```dart
|
||||
Future<void> _sendSarMessage(...) async {
|
||||
// Format: S:<emoji>:<latitude>,<longitude>
|
||||
final sarMessage = 'S:${sarType.emoji}:${position.latitude},${position.longitude}';
|
||||
|
||||
if (sendToChannel) {
|
||||
// Send to public channel (ephemeral)
|
||||
await connectionProvider.sendChannelMessage(
|
||||
channelIdx: 0,
|
||||
text: fullMessage,
|
||||
);
|
||||
} else {
|
||||
// Send to room (persistent)
|
||||
final sentSuccessfully = await connectionProvider.sendTextMessage(
|
||||
contactPublicKey: roomPublicKey!,
|
||||
text: fullMessage,
|
||||
messageId: messageId,
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Status**: ✅ **WORKING**
|
||||
- Sends SAR markers to rooms
|
||||
- Tracks delivery with message ID
|
||||
- Updates status (sending → sent → delivered)
|
||||
|
||||
### 1.2 Sending Messages - What's Missing
|
||||
|
||||
#### ❌ Direct Messages to Individual Contacts
|
||||
|
||||
**Current State**: No UI to send regular messages to individual contacts!
|
||||
|
||||
**Gap**: User can only:
|
||||
- Send to public channel
|
||||
- Send SAR markers to rooms
|
||||
|
||||
**Missing**: Send regular text messages to individual team members
|
||||
|
||||
**Example Use Case**:
|
||||
```
|
||||
User wants to send "Meet at checkpoint B" to John (a chat contact)
|
||||
Current: ❌ No way to do this
|
||||
Should: ✅ Send direct message via CMD_SEND_TXT_MSG
|
||||
```
|
||||
|
||||
#### ❌ Timeout Handling
|
||||
|
||||
**Current State**: Messages marked "Sent" wait forever for delivery confirmation
|
||||
|
||||
**File**: `messages_provider.dart:262-279`
|
||||
|
||||
```dart
|
||||
void markMessageSent(String messageId, int expectedAckTag, int suggestedTimeoutMs) {
|
||||
final updatedMessage = message.copyWith(
|
||||
deliveryStatus: MessageDeliveryStatus.sent,
|
||||
expectedAckTag: expectedAckTag,
|
||||
suggestedTimeoutMs: suggestedTimeoutMs, // ⚠️ Stored but not used!
|
||||
);
|
||||
|
||||
_pendingSentMessages[expectedAckTag] = updatedMessage;
|
||||
// ❌ No timeout timer started!
|
||||
}
|
||||
```
|
||||
|
||||
**Gap**: No timer to mark message as "Failed" if timeout expires
|
||||
|
||||
**Should Do**:
|
||||
```dart
|
||||
void markMessageSent(String messageId, int expectedAckTag, int suggestedTimeoutMs) {
|
||||
// ... existing code ...
|
||||
|
||||
// Start timeout timer
|
||||
Future.delayed(Duration(milliseconds: suggestedTimeoutMs), () {
|
||||
if (_pendingSentMessages.containsKey(expectedAckTag)) {
|
||||
// Message not delivered within timeout
|
||||
markMessageFailed(messageId);
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### ❌ Message Retry Logic
|
||||
|
||||
**Current State**: Failed messages stay failed, no retry
|
||||
|
||||
**Gap**: MeshCore supports retries with `attempt` parameter (0-3)
|
||||
|
||||
**Protocol Spec** (MESSAGES.md):
|
||||
```
|
||||
CMD_SEND_TXT_MSG:
|
||||
- attempt (1 byte): 0-3 (retry attempt number)
|
||||
```
|
||||
|
||||
**Current Implementation** (meshcore_ble_service.dart:1183-1201):
|
||||
```dart
|
||||
Future<void> sendTextMessage({
|
||||
required Uint8List contactPublicKey,
|
||||
required String text,
|
||||
int textType = 0,
|
||||
int attempt = 0, // ✅ Parameter exists but never used!
|
||||
}) async {
|
||||
writer.writeByte(attempt); // Always 0
|
||||
}
|
||||
```
|
||||
|
||||
**Missing**: Retry logic that increments `attempt` on timeout
|
||||
|
||||
---
|
||||
|
||||
## 2. Detailed Gap Analysis
|
||||
|
||||
### Gap #1: No UI for Direct Messages to Contacts
|
||||
|
||||
#### Problem
|
||||
|
||||
**Current UI** (`messages_tab.dart`):
|
||||
```
|
||||
┌─────────────────────────────┐
|
||||
│ Messages Tab │
|
||||
├─────────────────────────────┤
|
||||
│ │
|
||||
│ [Message List] │
|
||||
│ │
|
||||
│ │
|
||||
├─────────────────────────────┤
|
||||
│ [SAR] [Text Input] [Send] │ ← Always sends to public channel
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
**Missing**:
|
||||
- No recipient selector
|
||||
- No way to send DM to individual contact
|
||||
- Can only send to public channel OR rooms (via SAR dialog)
|
||||
|
||||
#### Solution
|
||||
|
||||
**Add Recipient Selector**:
|
||||
|
||||
```dart
|
||||
Contact? _selectedRecipient; // null = public channel
|
||||
|
||||
// In build():
|
||||
Row(
|
||||
children: [
|
||||
// Recipient dropdown
|
||||
DropdownButton<Contact?>(
|
||||
value: _selectedRecipient,
|
||||
hint: Text('Public Channel'),
|
||||
items: [
|
||||
DropdownMenuItem(value: null, child: Text('📢 Public')),
|
||||
...contactsProvider.chatContacts.map((contact) =>
|
||||
DropdownMenuItem(
|
||||
value: contact,
|
||||
child: Text('👤 ${contact.displayName}'),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (value) => setState(() => _selectedRecipient = value),
|
||||
),
|
||||
|
||||
// Message input
|
||||
Expanded(child: TextField(...)),
|
||||
|
||||
// Send button
|
||||
IconButton(
|
||||
onPressed: () => _selectedRecipient == null
|
||||
? _sendChannelMessage()
|
||||
: _sendDirectMessage(_selectedRecipient!),
|
||||
),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
**New Method**:
|
||||
```dart
|
||||
Future<void> _sendDirectMessage(Contact recipient) async {
|
||||
final text = _textController.text.trim();
|
||||
|
||||
final messageId = '${DateTime.now().millisecondsSinceEpoch}_sent';
|
||||
|
||||
// Create sent message
|
||||
final sentMessage = Message(
|
||||
id: messageId,
|
||||
messageType: MessageType.contact,
|
||||
senderPublicKeyPrefix: devicePublicKey?.sublist(0, 6),
|
||||
pathLen: 0,
|
||||
textType: MessageTextType.plain,
|
||||
senderTimestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
text: text,
|
||||
receivedAt: DateTime.now(),
|
||||
deliveryStatus: MessageDeliveryStatus.sending,
|
||||
);
|
||||
|
||||
// Add to messages list
|
||||
messagesProvider.addSentMessage(sentMessage);
|
||||
|
||||
// Send via BLE
|
||||
final success = await connectionProvider.sendTextMessage(
|
||||
contactPublicKey: recipient.publicKey,
|
||||
text: text,
|
||||
messageId: messageId,
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
messagesProvider.markMessageFailed(messageId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Gap #2: No Timeout Handling
|
||||
|
||||
#### Problem
|
||||
|
||||
**Current Flow**:
|
||||
```
|
||||
Send Message
|
||||
↓
|
||||
RESP_CODE_SENT (ACK tag: 12345, timeout: 30000ms)
|
||||
↓
|
||||
Mark as "Sent"
|
||||
↓
|
||||
⏳ Wait forever for PUSH_CODE_SEND_CONFIRMED...
|
||||
↓
|
||||
❌ If never arrives, message stays "Sent" indefinitely
|
||||
```
|
||||
|
||||
**Should Be**:
|
||||
```
|
||||
Send Message
|
||||
↓
|
||||
RESP_CODE_SENT (ACK tag: 12345, timeout: 30000ms)
|
||||
↓
|
||||
Mark as "Sent" + Start 30s timeout timer
|
||||
↓
|
||||
├─ PUSH_CODE_SEND_CONFIRMED arrives → ✅ Mark "Delivered"
|
||||
└─ Timeout expires → ❌ Mark "Failed"
|
||||
```
|
||||
|
||||
#### Solution
|
||||
|
||||
**Update MessagesProvider** (`messages_provider.dart`):
|
||||
|
||||
```dart
|
||||
// Track timeout timers by ACK tag
|
||||
final Map<int, Timer> _timeoutTimers = {};
|
||||
|
||||
void markMessageSent(String messageId, int expectedAckTag, int suggestedTimeoutMs) {
|
||||
final index = _messages.indexWhere((m) => m.id == messageId);
|
||||
if (index != -1) {
|
||||
final message = _messages[index];
|
||||
final updatedMessage = message.copyWith(
|
||||
deliveryStatus: MessageDeliveryStatus.sent,
|
||||
expectedAckTag: expectedAckTag,
|
||||
suggestedTimeoutMs: suggestedTimeoutMs,
|
||||
);
|
||||
_messages[index] = updatedMessage;
|
||||
|
||||
// Track by ACK tag
|
||||
_pendingSentMessages[expectedAckTag] = updatedMessage;
|
||||
|
||||
// ✅ NEW: Start timeout timer
|
||||
_timeoutTimers[expectedAckTag] = Timer(
|
||||
Duration(milliseconds: suggestedTimeoutMs),
|
||||
() {
|
||||
// Timeout expired - mark as failed
|
||||
if (_pendingSentMessages.containsKey(expectedAckTag)) {
|
||||
print('⏱️ Message timeout: ACK $expectedAckTag not received within ${suggestedTimeoutMs}ms');
|
||||
markMessageFailed(messageId);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
_persistMessages();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void markMessageDelivered(int ackCode, int roundTripTimeMs) {
|
||||
// Find message by ACK code
|
||||
final message = _pendingSentMessages[ackCode];
|
||||
if (message != null) {
|
||||
final index = _messages.indexWhere((m) => m.id == message.id);
|
||||
if (index != -1) {
|
||||
final updatedMessage = message.copyWith(
|
||||
deliveryStatus: MessageDeliveryStatus.delivered,
|
||||
roundTripTimeMs: roundTripTimeMs,
|
||||
deliveredAt: DateTime.now(),
|
||||
);
|
||||
_messages[index] = updatedMessage;
|
||||
|
||||
// ✅ NEW: Cancel timeout timer
|
||||
_timeoutTimers[ackCode]?.cancel();
|
||||
_timeoutTimers.remove(ackCode);
|
||||
|
||||
// Remove from pending
|
||||
_pendingSentMessages.remove(ackCode);
|
||||
|
||||
_persistMessages();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void markMessageFailed(String messageId) {
|
||||
final index = _messages.indexWhere((m) => m.id == messageId);
|
||||
if (index != -1) {
|
||||
final message = _messages[index];
|
||||
final updatedMessage = message.copyWith(
|
||||
deliveryStatus: MessageDeliveryStatus.failed,
|
||||
);
|
||||
_messages[index] = updatedMessage;
|
||||
|
||||
// ✅ NEW: Cancel timeout timer if exists
|
||||
if (message.expectedAckTag != null) {
|
||||
_timeoutTimers[message.expectedAckTag]?.cancel();
|
||||
_timeoutTimers.remove(message.expectedAckTag);
|
||||
_pendingSentMessages.remove(message.expectedAckTag);
|
||||
}
|
||||
|
||||
_persistMessages();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ NEW: Cleanup on dispose
|
||||
@override
|
||||
void dispose() {
|
||||
// Cancel all pending timers
|
||||
for (final timer in _timeoutTimers.values) {
|
||||
timer.cancel();
|
||||
}
|
||||
_timeoutTimers.clear();
|
||||
super.dispose();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Gap #3: No Message Retry Logic
|
||||
|
||||
#### Problem
|
||||
|
||||
**Current**: Failed messages stay failed forever
|
||||
|
||||
**Protocol Supports**:
|
||||
```
|
||||
Attempt 0 → Timeout → ❌ Failed (no retry)
|
||||
```
|
||||
|
||||
**Should Support**:
|
||||
```
|
||||
Attempt 0 → Timeout → Retry
|
||||
Attempt 1 → Timeout → Retry
|
||||
Attempt 2 → Timeout → Retry
|
||||
Attempt 3 → Timeout → ❌ Failed (last attempt uses flood mode)
|
||||
```
|
||||
|
||||
#### Solution
|
||||
|
||||
**Option 1: Manual Retry (Simple)**
|
||||
|
||||
Add "Retry" button to failed messages:
|
||||
|
||||
```dart
|
||||
// In _MessageBubble:
|
||||
if (message.deliveryStatus == MessageDeliveryStatus.failed) ...[
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => _retryMessage(message),
|
||||
icon: Icon(Icons.refresh),
|
||||
label: Text('Retry'),
|
||||
),
|
||||
],
|
||||
```
|
||||
|
||||
**Option 2: Automatic Retry (Advanced)**
|
||||
|
||||
Update timeout handler:
|
||||
|
||||
```dart
|
||||
void _handleMessageTimeout(String messageId, int attemptNumber) {
|
||||
if (attemptNumber < 3) {
|
||||
// Retry with next attempt number
|
||||
print('⏱️ Attempt $attemptNumber timeout - retrying...');
|
||||
_retryMessage(messageId, attemptNumber + 1);
|
||||
} else {
|
||||
// All attempts exhausted
|
||||
print('❌ All 4 attempts failed - marking as failed');
|
||||
markMessageFailed(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _retryMessage(String messageId, int attempt) async {
|
||||
final message = _messages.firstWhere((m) => m.id == messageId);
|
||||
|
||||
// Update attempt count
|
||||
final updatedMessage = message.copyWith(
|
||||
deliveryStatus: MessageDeliveryStatus.sending,
|
||||
);
|
||||
// ... update in list ...
|
||||
|
||||
// Resend with incremented attempt number
|
||||
final success = await connectionProvider.sendTextMessage(
|
||||
contactPublicKey: message.recipientPublicKey,
|
||||
text: message.text,
|
||||
messageId: messageId,
|
||||
attempt: attempt,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Priority Ranking
|
||||
|
||||
### Priority 1: CRITICAL (Blocks Core Functionality)
|
||||
|
||||
1. **❌ Gap #1: Direct Messages to Contacts**
|
||||
- **Impact**: Users can't send messages to individual team members
|
||||
- **Complexity**: Medium (UI + wire to existing BLE code)
|
||||
- **Effort**: 2-3 hours
|
||||
|
||||
### Priority 2: HIGH (Improves Reliability)
|
||||
|
||||
2. **❌ Gap #2: Timeout Handling**
|
||||
- **Impact**: Failed messages never show as failed
|
||||
- **Complexity**: Low (timer logic)
|
||||
- **Effort**: 1 hour
|
||||
|
||||
### Priority 3: MEDIUM (Nice to Have)
|
||||
|
||||
3. **❌ Gap #3: Automatic Retry**
|
||||
- **Impact**: Failed messages need manual intervention
|
||||
- **Complexity**: Medium (retry orchestration)
|
||||
- **Effort**: 2-3 hours
|
||||
|
||||
---
|
||||
|
||||
## 4. Implementation Roadmap
|
||||
|
||||
### Phase 1: Basic DM Support (Priority 1)
|
||||
|
||||
**Goal**: Enable sending direct messages to contacts
|
||||
|
||||
**Tasks**:
|
||||
1. ✅ Add recipient selector dropdown to messages tab
|
||||
2. ✅ Add `_sendDirectMessage()` method
|
||||
3. ✅ Wire to existing `sendTextMessage()` BLE method
|
||||
4. ✅ Test with team members
|
||||
|
||||
**Files to Modify**:
|
||||
- `lib/screens/messages_tab.dart`
|
||||
- Add `Contact? _selectedRecipient` state
|
||||
- Add recipient dropdown above message input
|
||||
- Add `_sendDirectMessage()` method
|
||||
- Update `_sendMessage()` to route to channel vs contact
|
||||
|
||||
**Estimated Time**: 2-3 hours
|
||||
|
||||
### Phase 2: Timeout Handling (Priority 2)
|
||||
|
||||
**Goal**: Mark messages as failed when timeout expires
|
||||
|
||||
**Tasks**:
|
||||
1. ✅ Add `Map<int, Timer> _timeoutTimers` to MessagesProvider
|
||||
2. ✅ Start timer in `markMessageSent()`
|
||||
3. ✅ Cancel timer in `markMessageDelivered()`
|
||||
4. ✅ Call `markMessageFailed()` on timeout
|
||||
5. ✅ Add `dispose()` to cancel timers
|
||||
|
||||
**Files to Modify**:
|
||||
- `lib/providers/messages_provider.dart`
|
||||
- Add timeout timer tracking
|
||||
- Update `markMessageSent()`
|
||||
- Update `markMessageDelivered()`
|
||||
- Update `markMessageFailed()`
|
||||
- Add `dispose()`
|
||||
|
||||
**Estimated Time**: 1 hour
|
||||
|
||||
### Phase 3: Manual Retry (Priority 3a)
|
||||
|
||||
**Goal**: Let user manually retry failed messages
|
||||
|
||||
**Tasks**:
|
||||
1. ✅ Add "Retry" button to failed message bubbles
|
||||
2. ✅ Add `_retryMessage()` method
|
||||
3. ✅ Test retry flow
|
||||
|
||||
**Files to Modify**:
|
||||
- `lib/screens/messages_tab.dart`
|
||||
- Add retry button to `_MessageBubble` for failed messages
|
||||
- Add `_retryMessage()` callback
|
||||
|
||||
**Estimated Time**: 1 hour
|
||||
|
||||
### Phase 4: Automatic Retry (Priority 3b) - OPTIONAL
|
||||
|
||||
**Goal**: Automatically retry failed messages
|
||||
|
||||
**Tasks**:
|
||||
1. ✅ Update `_handleMessageTimeout()` to retry
|
||||
2. ✅ Pass `attempt` parameter through send chain
|
||||
3. ✅ Test 4-attempt retry cycle
|
||||
4. ✅ Verify attempt 3 uses flood mode (per protocol)
|
||||
|
||||
**Files to Modify**:
|
||||
- `lib/providers/messages_provider.dart`
|
||||
- `lib/providers/connection_provider.dart`
|
||||
- `lib/services/meshcore_ble_service.dart`
|
||||
|
||||
**Estimated Time**: 2-3 hours
|
||||
|
||||
---
|
||||
|
||||
## 5. Quick Fixes (Can Do Right Now)
|
||||
|
||||
### Quick Fix #1: Add "Reply" to Contact Messages
|
||||
|
||||
**File**: `lib/screens/messages_tab.dart`
|
||||
|
||||
Add long-press handler to contact messages:
|
||||
|
||||
```dart
|
||||
// In _MessageBubble:
|
||||
GestureDetector(
|
||||
onLongPress: message.isContactMessage
|
||||
? () => _showReplyOptions(context, message)
|
||||
: null,
|
||||
child: Container(...),
|
||||
)
|
||||
```
|
||||
|
||||
```dart
|
||||
void _showReplyOptions(BuildContext context, Message message) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
leading: Icon(Icons.reply),
|
||||
title: Text('Reply to ${message.displaySender}'),
|
||||
onTap: () {
|
||||
// Set recipient and open keyboard
|
||||
Navigator.pop(context);
|
||||
// ... set _selectedRecipient ...
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Testing Checklist
|
||||
|
||||
### After Implementing Gap #1 (Direct Messages)
|
||||
|
||||
- [ ] Can send DM to chat contact
|
||||
- [ ] Message appears in recipient's messages list
|
||||
- [ ] Delivery status shows: Sending → Sent → Delivered
|
||||
- [ ] Failed messages show "Failed" status
|
||||
- [ ] Can send to public channel (existing feature still works)
|
||||
|
||||
### After Implementing Gap #2 (Timeout Handling)
|
||||
|
||||
- [ ] Turn off recipient device
|
||||
- [ ] Send message
|
||||
- [ ] Verify "Sent" status appears
|
||||
- [ ] Wait for timeout (30s)
|
||||
- [ ] Verify status changes to "Failed"
|
||||
- [ ] Turn on recipient device
|
||||
- [ ] Send message
|
||||
- [ ] Verify status changes to "Delivered" before timeout
|
||||
|
||||
### After Implementing Gap #3 (Retry)
|
||||
|
||||
- [ ] Manual retry: Click "Retry" on failed message
|
||||
- [ ] Verify message sends again
|
||||
- [ ] Auto retry: Turn off recipient device
|
||||
- [ ] Send message
|
||||
- [ ] Verify 4 retry attempts occur
|
||||
- [ ] Verify final status is "Failed" after all attempts
|
||||
|
||||
---
|
||||
|
||||
## 7. Summary
|
||||
|
||||
### What's Already Great ✅
|
||||
|
||||
1. ✅ Protocol implementation is 100% correct
|
||||
2. ✅ Delivery tracking infrastructure exists
|
||||
3. ✅ SAR markers work perfectly
|
||||
4. ✅ Room messages work
|
||||
5. ✅ Channel messages work
|
||||
|
||||
### What Needs Adding ❌
|
||||
|
||||
1. ❌ **UI for direct messages to contacts** (2-3 hours)
|
||||
2. ❌ **Timeout timers** (1 hour)
|
||||
3. ❌ **Retry logic** (2-3 hours)
|
||||
|
||||
### Total Estimated Effort
|
||||
|
||||
**Minimum Viable** (Phase 1 + 2): **3-4 hours**
|
||||
**Full Featured** (All phases): **6-9 hours**
|
||||
|
||||
---
|
||||
|
||||
## 8. Recommended Next Steps
|
||||
|
||||
1. **Immediate** (Today): Implement Gap #1 (Direct Messages UI)
|
||||
- This unlocks the core messaging functionality
|
||||
- Users can finally message each other
|
||||
|
||||
2. **Short Term** (This Week): Implement Gap #2 (Timeout Handling)
|
||||
- Improves reliability
|
||||
- Users see when messages fail
|
||||
|
||||
3. **Optional** (Next Week): Implement Gap #3 (Retry Logic)
|
||||
- Automatic retries improve success rate
|
||||
- Manual retry button is simple fallback
|
||||
|
||||
Would you like me to implement Gap #1 (Direct Messages UI) first?
|
||||
Reference in New Issue
Block a user