mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 14:48:41 +00:00
implement retransmission for encrypted text messages
This commit is contained in:
parent
dd31f18e07
commit
57309ae775
8 changed files with 110 additions and 63 deletions
|
|
@ -72,10 +72,9 @@ class MessageSendStateIcon extends StatelessWidget {
|
||||||
bool isDownloading = false;
|
bool isDownloading = false;
|
||||||
if (message.messageContent != null &&
|
if (message.messageContent != null &&
|
||||||
message.messageContent!.downloadToken != null) {
|
message.messageContent!.downloadToken != null) {
|
||||||
isDownloading = context
|
final test = context.watch<DownloadChangeProvider>().currentlyDownloading;
|
||||||
.watch<DownloadChangeProvider>()
|
isDownloading =
|
||||||
.currentlyDownloading
|
test.contains(message.messageContent!.downloadToken.toString());
|
||||||
.contains(message.messageContent!.downloadToken!);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDownloading) {
|
if (isDownloading) {
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,15 @@ class DbMessages extends CvModelBase {
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<List<DbMessage>> getAllMessagesForRetransmitting() async {
|
||||||
|
var rows = await dbProvider.db!.query(
|
||||||
|
tableName,
|
||||||
|
where: "$columnMessageAcknowledgeByServer = 0",
|
||||||
|
);
|
||||||
|
List<DbMessage> messages = await convertToDbMessage(rows);
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
static Future<List<DbMessage>> getAllMessagesForUser(int otherUserId) async {
|
static Future<List<DbMessage>> getAllMessagesForUser(int otherUserId) async {
|
||||||
var rows = await dbProvider.db!.query(
|
var rows = await dbProvider.db!.query(
|
||||||
tableName,
|
tableName,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,33 @@ import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
||||||
|
|
||||||
// this functions ensures that the message is received by the server and in case of errors will try again later
|
// this functions ensures that the message is received by the server and in case of errors will try again later
|
||||||
|
|
||||||
|
Future tryTransmitMessages() async {
|
||||||
|
List<DbMessage> retransmit =
|
||||||
|
await DbMessages.getAllMessagesForRetransmitting();
|
||||||
|
|
||||||
|
debugPrint("tryTransmitMessages: ${retransmit.length}");
|
||||||
|
|
||||||
|
Box box = await getMediaStorage();
|
||||||
|
for (int i = 0; i < retransmit.length; i++) {
|
||||||
|
int msgId = retransmit[i].messageId;
|
||||||
|
debugPrint("msgId=$msgId");
|
||||||
|
Uint8List? bytes = box.get("retransmit-$msgId");
|
||||||
|
debugPrint("bytes == null =${bytes == null}");
|
||||||
|
if (bytes != null) {
|
||||||
|
Result resp = await apiProvider.sendTextMessage(
|
||||||
|
Int64(retransmit[i].otherUserId), bytes);
|
||||||
|
|
||||||
|
if (resp.isSuccess) {
|
||||||
|
DbMessages.acknowledgeMessageByServer(msgId);
|
||||||
|
box.delete("retransmit-$msgId");
|
||||||
|
} else {
|
||||||
|
// in case of error do nothing. As the message is not removed the app will try again when relaunched
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Result> encryptAndSendMessage(Int64 userId, Message msg) async {
|
Future<Result> encryptAndSendMessage(Int64 userId, Message msg) async {
|
||||||
Uint8List? bytes = await SignalHelper.encryptMessage(msg, userId);
|
Uint8List? bytes = await SignalHelper.encryptMessage(msg, userId);
|
||||||
|
|
||||||
|
|
@ -25,18 +52,19 @@ Future<Result> encryptAndSendMessage(Int64 userId, Message msg) async {
|
||||||
return Result.error(ErrorCode.InternalError);
|
return Result.error(ErrorCode.InternalError);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger("api.dart").shout(
|
Box box = await getMediaStorage();
|
||||||
"TODO: store encrypted message and send later again. STORE: userId, bytes and messageId");
|
if (msg.messageId != null) {
|
||||||
|
debugPrint("putting=${msg.messageId}");
|
||||||
|
box.put("retransmit-${msg.messageId}", bytes);
|
||||||
|
}
|
||||||
|
|
||||||
Result resp = await apiProvider.sendTextMessage(userId, bytes);
|
Result resp = await apiProvider.sendTextMessage(userId, bytes);
|
||||||
|
|
||||||
if (resp.isSuccess) {
|
if (resp.isSuccess) {
|
||||||
if (msg.messageId != null) {
|
if (msg.messageId != null) {
|
||||||
DbMessages.acknowledgeMessageByServer(msg.messageId!);
|
DbMessages.acknowledgeMessageByServer(msg.messageId!);
|
||||||
|
box.delete("retransmit-${msg.messageId}");
|
||||||
}
|
}
|
||||||
// TODO: remove encrypted tmp file
|
|
||||||
} else {
|
|
||||||
// in case of error do nothing. As the message is not removed the app will try again when relaunched
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
|
|
@ -65,22 +93,6 @@ Future sendImageToSingleTarget(Int64 target, Uint8List imageBytes) async {
|
||||||
await DbMessages.insertMyMessage(target.toInt(), MessageKind.image);
|
await DbMessages.insertMyMessage(target.toInt(), MessageKind.image);
|
||||||
if (messageId == null) return;
|
if (messageId == null) return;
|
||||||
|
|
||||||
Result res = await apiProvider.getUploadToken();
|
|
||||||
|
|
||||||
if (res.isError || !res.value.hasUploadtoken()) {
|
|
||||||
Logger("api.dart").shout("Error getting upload token!");
|
|
||||||
|
|
||||||
// TODO store message for later and try again
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<int> uploadToken = res.value.uploadtoken;
|
|
||||||
Logger("sendImageToSingleTarget").fine("Got token: $uploadToken");
|
|
||||||
|
|
||||||
MessageContent content =
|
|
||||||
MessageContent(text: null, downloadToken: uploadToken);
|
|
||||||
|
|
||||||
Uint8List? encryptBytes = await SignalHelper.encryptBytes(imageBytes, target);
|
Uint8List? encryptBytes = await SignalHelper.encryptBytes(imageBytes, target);
|
||||||
if (encryptBytes == null) {
|
if (encryptBytes == null) {
|
||||||
await DbMessages.deleteMessageById(messageId);
|
await DbMessages.deleteMessageById(messageId);
|
||||||
|
|
@ -88,14 +100,25 @@ Future sendImageToSingleTarget(Int64 target, Uint8List imageBytes) async {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<int>? imageToken =
|
Result res = await apiProvider.getUploadToken();
|
||||||
await apiProvider.uploadData(uploadToken, encryptBytes);
|
|
||||||
if (imageToken == null) {
|
if (res.isError || !res.value.hasUploadtoken()) {
|
||||||
Logger("api.dart").shout("handle error uploading like saving...");
|
print("store encryptBytes in box to retransmit without an upload token");
|
||||||
return;
|
Logger("api.dart").shout("Error getting upload token!");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
print("TODO: insert into DB and then create this MESSAGE");
|
List<int> uploadToken = res.value.uploadtoken;
|
||||||
|
|
||||||
|
MessageContent content =
|
||||||
|
MessageContent(text: null, downloadToken: uploadToken);
|
||||||
|
|
||||||
|
print("fragmentate the data");
|
||||||
|
|
||||||
|
if (!await apiProvider.uploadData(uploadToken, encryptBytes, 0)) {
|
||||||
|
Logger("api.dart").shout("error while uploading image");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Message msg = Message(
|
Message msg = Message(
|
||||||
kind: MessageKind.image,
|
kind: MessageKind.image,
|
||||||
|
|
@ -140,7 +163,7 @@ Future tryDownloadMedia(List<int> mediaToken, {bool force = false}) async {
|
||||||
if (media != null && media.isNotEmpty) {
|
if (media != null && media.isNotEmpty) {
|
||||||
offset = media.length;
|
offset = media.length;
|
||||||
}
|
}
|
||||||
globalCallBackOnDownloadChange(mediaToken, true);
|
//globalCallBackOnDownloadChange(mediaToken, true);
|
||||||
apiProvider.triggerDownload(mediaToken, offset);
|
apiProvider.triggerDownload(mediaToken, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import 'package:twonly/src/app.dart';
|
||||||
import 'package:twonly/src/proto/api/client_to_server.pbserver.dart';
|
import 'package:twonly/src/proto/api/client_to_server.pbserver.dart';
|
||||||
import 'package:twonly/src/proto/api/error.pb.dart';
|
import 'package:twonly/src/proto/api/error.pb.dart';
|
||||||
import 'package:twonly/src/proto/api/server_to_client.pb.dart' as server;
|
import 'package:twonly/src/proto/api/server_to_client.pb.dart' as server;
|
||||||
|
import 'package:twonly/src/providers/api/api.dart';
|
||||||
import 'package:twonly/src/providers/api/api_utils.dart';
|
import 'package:twonly/src/providers/api/api_utils.dart';
|
||||||
import 'package:twonly/src/providers/api/server_messages.dart';
|
import 'package:twonly/src/providers/api/server_messages.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
@ -52,6 +53,14 @@ class ApiProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future onConnected() async {
|
||||||
|
await authenticate();
|
||||||
|
globalCallbackConnectionState(true);
|
||||||
|
_reconnectionDelay = 5;
|
||||||
|
|
||||||
|
tryTransmitMessages();
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> connect() async {
|
Future<bool> connect() async {
|
||||||
if (_channel != null && _channel!.closeCode != null) {
|
if (_channel != null && _channel!.closeCode != null) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -63,17 +72,13 @@ class ApiProvider {
|
||||||
|
|
||||||
log.info("Trying to connect to the backend $apiUrl!");
|
log.info("Trying to connect to the backend $apiUrl!");
|
||||||
if (await _connectTo(apiUrl)) {
|
if (await _connectTo(apiUrl)) {
|
||||||
await authenticate();
|
onConnected();
|
||||||
globalCallbackConnectionState(true);
|
|
||||||
_reconnectionDelay = 5;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (backupApiUrl != null) {
|
if (backupApiUrl != null) {
|
||||||
log.info("Trying to connect to the backup backend $backupApiUrl!");
|
log.info("Trying to connect to the backup backend $backupApiUrl!");
|
||||||
if (await _connectTo(backupApiUrl!)) {
|
if (await _connectTo(backupApiUrl!)) {
|
||||||
globalCallbackConnectionState(true);
|
onConnected();
|
||||||
await authenticate();
|
|
||||||
_reconnectionDelay = 5;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -265,18 +270,16 @@ class ApiProvider {
|
||||||
return await _sendRequestV0(req);
|
return await _sendRequestV0(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<int>?> uploadData(List<int> uploadToken, Uint8List data) async {
|
Future<bool> uploadData(
|
||||||
log.shout("fragmentate the data");
|
List<int> uploadToken, Uint8List data, int offset) async {
|
||||||
|
|
||||||
var get = ApplicationData_UploadData()
|
var get = ApplicationData_UploadData()
|
||||||
..uploadToken = uploadToken
|
..uploadToken = uploadToken
|
||||||
..data = data
|
..data = data
|
||||||
..offset = 0;
|
..offset = offset;
|
||||||
|
|
||||||
var appData = ApplicationData()..uploaddata = get;
|
var appData = ApplicationData()..uploaddata = get;
|
||||||
var req = createClientToServerFromApplicationData(appData);
|
var req = createClientToServerFromApplicationData(appData);
|
||||||
final result = await _sendRequestV0(req);
|
final result = await _sendRequestV0(req);
|
||||||
return result.isSuccess ? uploadToken : null;
|
return result.isSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result> getUserData(String username) async {
|
Future<Result> getUserData(String username) async {
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,19 @@ import 'dart:collection';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
class DownloadChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
class DownloadChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
final HashSet<List<int>> _currentlyDownloading = HashSet<List<int>>();
|
final HashSet<String> _currentlyDownloading = HashSet<String>();
|
||||||
|
|
||||||
HashSet<List<int>> get currentlyDownloading => _currentlyDownloading;
|
HashSet<String> get currentlyDownloading => _currentlyDownloading;
|
||||||
|
|
||||||
void update(List<int> token, bool add) {
|
void update(List<int> token, bool add) {
|
||||||
|
debugPrint("Downloading: $add : $token");
|
||||||
|
|
||||||
if (add) {
|
if (add) {
|
||||||
_currentlyDownloading.add(token);
|
_currentlyDownloading.add(token.toString());
|
||||||
} else {
|
} else {
|
||||||
_currentlyDownloading.remove(token);
|
_currentlyDownloading.remove(token.toString());
|
||||||
}
|
}
|
||||||
|
debugPrint("Downloading: $add : ${_currentlyDownloading.toList()}");
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ class MessagesChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
changeCounter[targetUserId] = 0;
|
changeCounter[targetUserId] = 0;
|
||||||
}
|
}
|
||||||
changeCounter[targetUserId] = changeCounter[targetUserId]! + 1;
|
changeCounter[targetUserId] = changeCounter[targetUserId]! + 1;
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void init() async {
|
void init() async {
|
||||||
|
|
@ -33,5 +34,6 @@ class MessagesChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
_lastMessage[last.otherUserId] = last;
|
_lastMessage[last.otherUserId] = last;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,13 @@ class ChatListEntry extends StatelessWidget {
|
||||||
MessageSendState state = message.getSendState();
|
MessageSendState state = message.getSendState();
|
||||||
|
|
||||||
bool isDownloading = false;
|
bool isDownloading = false;
|
||||||
if (message.messageContent != null &&
|
// if (message.messageContent != null &&
|
||||||
message.messageContent!.downloadToken != null) {
|
// message.messageContent!.downloadToken != null) {
|
||||||
isDownloading = context
|
// isDownloading = context
|
||||||
.watch<DownloadChangeProvider>()
|
// .watch<DownloadChangeProvider>()
|
||||||
.currentlyDownloading
|
// .currentlyDownloading
|
||||||
.contains(message.messageContent!.downloadToken!);
|
// .contains(message.messageContent!.downloadToken!);
|
||||||
}
|
// }
|
||||||
|
|
||||||
Widget child = Container();
|
Widget child = Container();
|
||||||
|
|
||||||
|
|
@ -148,7 +148,14 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
||||||
_messages.insertAll(0, toAppend);
|
_messages.insertAll(0, toAppend);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (context.mounted) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// state should be disposed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (updateOpenStatus) {
|
if (updateOpenStatus) {
|
||||||
_messages.where((x) => x.messageOpenedAt == null).forEach((message) {
|
_messages.where((x) => x.messageOpenedAt == null).forEach((message) {
|
||||||
|
|
|
||||||
|
|
@ -151,13 +151,13 @@ class _UserListItem extends State<UserListItem> {
|
||||||
|
|
||||||
MessageSendState state = widget.lastMessage.getSendState();
|
MessageSendState state = widget.lastMessage.getSendState();
|
||||||
bool isDownloading = false;
|
bool isDownloading = false;
|
||||||
if (widget.lastMessage.messageContent != null &&
|
// if (widget.lastMessage.messageContent != null &&
|
||||||
widget.lastMessage.messageContent!.downloadToken != null) {
|
// widget.lastMessage.messageContent!.downloadToken != null) {
|
||||||
isDownloading = context
|
// isDownloading = context
|
||||||
.watch<DownloadChangeProvider>()
|
// .watch<DownloadChangeProvider>()
|
||||||
.currentlyDownloading
|
// .currentlyDownloading
|
||||||
.contains(widget.lastMessage.messageContent!.downloadToken!);
|
// .contains(widget.lastMessage.messageContent!.downloadToken!);
|
||||||
}
|
// }
|
||||||
|
|
||||||
return UserContextMenu(
|
return UserContextMenu(
|
||||||
user: widget.user,
|
user: widget.user,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue