diff --git a/lib/src/services/api/server_messages.dart b/lib/src/services/api/server_messages.dart index dbe4f7b..ba89de0 100644 --- a/lib/src/services/api/server_messages.dart +++ b/lib/src/services/api/server_messages.dart @@ -35,30 +35,30 @@ import 'package:twonly/src/views/components/animate_icon.dart'; final lockHandleServerMessage = Mutex(); Future handleServerMessage(server.ServerToClient msg) async { - return lockHandleServerMessage.protect(() async { - client.Response? response; + // return lockHandleServerMessage.protect(() async { + client.Response? response; - try { - if (msg.v0.hasRequestNewPreKeys()) { - response = await handleRequestNewPreKey(); - } else if (msg.v0.hasNewMessage()) { - final body = Uint8List.fromList(msg.v0.newMessage.body); - final fromUserId = msg.v0.newMessage.fromUserId.toInt(); - response = await handleNewMessage(fromUserId, body); - } else { - Log.error('Got a unknown message from the server: $msg'); - response = client.Response()..error = ErrorCode.InternalError; - } - } catch (e) { + try { + if (msg.v0.hasRequestNewPreKeys()) { + response = await handleRequestNewPreKey(); + } else if (msg.v0.hasNewMessage()) { + final body = Uint8List.fromList(msg.v0.newMessage.body); + final fromUserId = msg.v0.newMessage.fromUserId.toInt(); + response = await handleNewMessage(fromUserId, body); + } else { + Log.error('Got a unknown message from the server: $msg'); response = client.Response()..error = ErrorCode.InternalError; } + } catch (e) { + response = client.Response()..error = ErrorCode.InternalError; + } - final v0 = client.V0() - ..seq = msg.v0.seq - ..response = response; + final v0 = client.V0() + ..seq = msg.v0.seq + ..response = response; - await apiService.sendResponse(ClientToServer()..v0 = v0); - }); + await apiService.sendResponse(ClientToServer()..v0 = v0); + // }); } DateTime lastSignalDecryptMessage = @@ -128,6 +128,8 @@ Future handleNewMessage(int fromUserId, Uint8List body) async { .getRetransmissionFromHash(fromUserId, hash); if (message != null) { unawaited(sendRetransmitMessage(message.retransmissionId)); + } else { + Log.error('Could not find message to retransmit!'); } } diff --git a/lib/src/services/signal/identity.signal.dart b/lib/src/services/signal/identity.signal.dart index 866b66c..c4554d7 100644 --- a/lib/src/services/signal/identity.signal.dart +++ b/lib/src/services/signal/identity.signal.dart @@ -63,7 +63,8 @@ Future> signalGetPreKeys() async { final start = user.currentPreKeyIndexStart; await updateUserdata((user) { - user.currentPreKeyIndexStart += 200; + user.currentPreKeyIndexStart = + (user.currentPreKeyIndexStart + 200) % maxValue; return user; }); final preKeys = generatePreKeys(start, 200); diff --git a/lib/src/views/chats/chat_messages.view.dart b/lib/src/views/chats/chat_messages.view.dart index c00c514..654bd7c 100644 --- a/lib/src/views/chats/chat_messages.view.dart +++ b/lib/src/views/chats/chat_messages.view.dart @@ -133,7 +133,8 @@ class _ChatMessagesViewState extends State { DateTime? lastDate; final tmpEmojiReactionsToMessageId = >{}; - final openedMessageOtherIds = []; + // only send openedMessage to one text message, as receiver will then set all as read... + int? openedTextMessageOtherIds; final messageOtherMessageIdToMyMessageId = {}; final messageIdToMessage = {}; @@ -150,8 +151,10 @@ class _ChatMessagesViewState extends State { for (final msg in newMessages) { if (msg.kind == MessageKind.textMessage && msg.messageOtherId != null && - msg.openedAt == null) { - openedMessageOtherIds.add(msg.messageOtherId!); + msg.openedAt == null && + (openedTextMessageOtherIds == null || + openedTextMessageOtherIds < msg.messageOtherId!)) { + openedTextMessageOtherIds = msg.messageOtherId; } Message? responseTo; @@ -207,10 +210,10 @@ class _ChatMessagesViewState extends State { } } - if (openedMessageOtherIds.isNotEmpty) { + if (openedTextMessageOtherIds != null) { await notifyContactAboutOpeningMessage( widget.contact.userId, - openedMessageOtherIds, + [openedTextMessageOtherIds], ); } diff --git a/lib/src/views/settings/developer/automated_testing.view.dart b/lib/src/views/settings/developer/automated_testing.view.dart new file mode 100644 index 0000000..8d9c415 --- /dev/null +++ b/lib/src/views/settings/developer/automated_testing.view.dart @@ -0,0 +1,66 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:twonly/globals.dart'; +import 'package:twonly/src/model/json/message.dart'; +import 'package:twonly/src/services/api/messages.dart'; + +class AutomatedTestingView extends StatefulWidget { + const AutomatedTestingView({super.key}); + + @override + State createState() => _AutomatedTestingViewState(); +} + +class _AutomatedTestingViewState extends State { + String lotsOfMessagesStatus = ''; + @override + void initState() { + super.initState(); + unawaited(initAsync()); + } + + Future initAsync() async {} + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Automated Testing'), + ), + body: ListView( + children: [ + if (kDebugMode) + ListTile( + title: const Text('Sending a lot of messages.'), + subtitle: Text(lotsOfMessagesStatus), + onTap: () async { + await twonlyDB.messageRetransmissionDao + .clearRetransmissionTable(); + + final contacts = + await twonlyDB.contactsDao.getAllNotBlockedContacts(); + + for (final contact in contacts) { + for (var i = 0; i < 200; i++) { + setState(() { + lotsOfMessagesStatus = + 'At message $i to ${contact.username}.'; + }); + await sendTextMessage( + contact.userId, + TextMessageContent( + text: 'TestMessage $i', + ), + null, + ); + } + } + }, + ), + ], + ), + ); + } +} diff --git a/lib/src/views/settings/developer/developer.view.dart b/lib/src/views/settings/developer/developer.view.dart index c2cd49c..d4df29f 100644 --- a/lib/src/views/settings/developer/developer.view.dart +++ b/lib/src/views/settings/developer/developer.view.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/services/flame.service.dart'; import 'package:twonly/src/utils/storage.dart'; +import 'package:twonly/src/views/settings/developer/automated_testing.view.dart'; import 'package:twonly/src/views/settings/developer/retransmission_data.view.dart'; class DeveloperSettingsView extends StatefulWidget { @@ -76,6 +77,20 @@ class _DeveloperSettingsViewState extends State { await syncFlameCounters(); }, ), + if (kDebugMode) + ListTile( + title: const Text('Automated Testing'), + onTap: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return const AutomatedTestingView(); + }, + ), + ); + }, + ), ], ), ); diff --git a/test/signal/key_generation.dart b/test/signal/key_generation.dart new file mode 100644 index 0000000..ae45c74 --- /dev/null +++ b/test/signal/key_generation.dart @@ -0,0 +1,25 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; + +void main() { + group('testing api', () { + test('testing api connection', () async { + const offset = 100; + const count = 400; + + var prekeys = generatePreKeys(offset, count); + expect(count, prekeys.length); + + for (var i = 0; i < prekeys.length; i++) { + expect(prekeys[i].id, offset + i); + } + + prekeys += generatePreKeys(offset + count, count); + expect(count * 2, prekeys.length); + + for (var i = 0; i < (count * 2); i++) { + expect(prekeys[i].id, offset + i); + } + }); + }); +}