feat(dm): add delivery confirmation, retry, and receiver-side dedup

- Fix ACK handler bug: read 'code' field instead of 'expected_ack'
- Add DM retry (up to 3 attempts) with same timestamp for receiver dedup
- Add receiver-side dedup in _on_dm_received() (sender_timestamp or time-window)
- Add PATH_UPDATE as backup delivery signal for flood DMs
- Track pending acks with dm_id for proper ACK→DM linkage
- Return dm_id and expected_ack from POST /dm/messages API
- Add find_dm_duplicate() and get_dm_by_id() database helpers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-03-07 07:02:58 +01:00
parent c1b0085710
commit 5b757e9548
4 changed files with 258 additions and 40 deletions
+38
View File
@@ -7,6 +7,7 @@ Synchronous wrapper with WAL mode. Thread-safe via connection-per-call pattern.
import sqlite3
import shutil
import logging
import time
from pathlib import Path
from contextlib import contextmanager
from datetime import datetime, timedelta
@@ -355,6 +356,43 @@ class Database:
).fetchone()
return dict(row) if row else None
def get_dm_by_id(self, dm_id: int) -> Optional[Dict]:
"""Fetch a direct message by its ID."""
with self._connect() as conn:
row = conn.execute(
"SELECT * FROM direct_messages WHERE id = ?", (dm_id,)
).fetchone()
return dict(row) if row else None
def find_dm_duplicate(self, contact_pubkey: str, content: str,
sender_timestamp: int = None,
window_seconds: int = 300) -> Optional[Dict]:
"""Check for duplicate incoming DM (for receiver-side dedup).
If sender_timestamp is provided, matches exact (sender, timestamp, text).
Otherwise falls back to time-window match (same sender + text within window).
"""
contact_pubkey = contact_pubkey.lower()
with self._connect() as conn:
if sender_timestamp is not None:
row = conn.execute(
"""SELECT id FROM direct_messages
WHERE contact_pubkey = ? AND direction = 'in'
AND content = ? AND sender_timestamp = ?
LIMIT 1""",
(contact_pubkey, content, sender_timestamp)
).fetchone()
else:
cutoff = int(time.time()) - window_seconds
row = conn.execute(
"""SELECT id FROM direct_messages
WHERE contact_pubkey = ? AND direction = 'in'
AND content = ? AND timestamp > ?
LIMIT 1""",
(contact_pubkey, content, cutoff)
).fetchone()
return dict(row) if row else None
# ================================================================
# Echoes
# ================================================================