Merge pull request #428 from twonlyapp/hotfix_ack

fix infinite loop issue with receipts
This commit is contained in:
Tobi 2026-08-24 02:03:24 +02:00 committed by GitHub
commit 7412daf373
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 62 additions and 7 deletions

View file

@ -1,5 +1,9 @@
# Changelog
## 0.5.2
- Hotfix: Fixed an infinite loop involving delivery receipts
## 0.5.1
- Improve: Show delivery and read receipt indicators for text messages

View file

@ -250,6 +250,25 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
null;
}
/// Claims a new delivery-receipt attempt after [cooldown] has elapsed.
///
/// Updating the timestamp before sending prevents repeated server batches from
/// starting multiple delivery-receipt attempts during the same cooldown.
Future<bool> claimDuplicateReceiptResend(
String receiptId,
Duration cooldown,
) async {
final now = clock.now();
final updated =
await (update(receivedReceipts)..where(
(t) =>
t.receiptId.equals(receiptId) &
t.createdAt.isSmallerOrEqualValue(now.subtract(cooldown)),
))
.write(ReceivedReceiptsCompanion(createdAt: Value(now)));
return updated > 0;
}
Future<void> gotReceipt(String receiptId) async {
await into(
receivedReceipts,

View file

@ -299,9 +299,17 @@ class ApiService {
return result;
}
Future<void> sendResponse(ClientToServer response) async {
if (_channel != null) {
_channel!.sink.add(response.writeToBuffer());
Future<bool> sendResponse(ClientToServer response) async {
final channel = _channel;
if (channel == null || channel.closeCode != null) {
return false;
}
try {
channel.sink.add(response.writeToBuffer());
return true;
} catch (e) {
Log.warn('Could not send response to server: $e');
return false;
}
}

View file

@ -80,9 +80,16 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
..seq = msg.v0.seq
..response = response;
await apiService.sendResponse(ClientToServer()..v0 = v0);
final responseSent = await apiService.sendResponse(ClientToServer()..v0 = v0);
if (responseSent) {
Log.info(
'Successfully queued response for server message ${msg.v0.seq}.',
);
} else {
Log.warn('Could not send response for server message ${msg.v0.seq}.');
}
AppState.gotMessageFromServer = true;
Log.info('All messages from the server proccessed.');
Log.info('All messages from the server processed.');
}
DateTime lastPushKeyRequest = clock.now().subtract(const Duration(hours: 1));
@ -141,8 +148,25 @@ Future<void> _handleClient2ClientMessage(
}
if (await twonlyDB.receiptsDao.isDuplicated(receiptId)) {
if (message.type == Message_Type.SENDER_DELIVERY_RECEIPT) {
Log.info(
'[$receiptId] Delivery receipt is a duplicate. Skipping receipt response.',
);
return;
}
const duplicateReceiptCooldown = Duration(days: 10);
final shouldResend = await twonlyDB.receiptsDao.claimDuplicateReceiptResend(
receiptId,
duplicateReceiptCooldown,
);
if (!shouldResend) {
Log.info(
'[$receiptId] Message is a duplicate. Skipping delivery receipt during cooldown.',
);
return;
}
Log.info(
'[$receiptId] Message is a duplicate. Sending delivery receipt again.',
'[$receiptId] Message is a duplicate and cooldown elapsed. Sending delivery receipt again.',
);
try {
final response = Message(type: Message_Type.SENDER_DELIVERY_RECEIPT);

View file

@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec
publish_to: 'none'
version: 0.5.1+171
version: 0.5.2+172
environment:
sdk: ^3.11.0