mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 12:28:40 +00:00
240 lines
6.7 KiB
Dart
240 lines
6.7 KiB
Dart
import 'dart:convert';
|
|
import 'dart:math';
|
|
import 'package:drift/drift.dart';
|
|
import 'package:hive/hive.dart';
|
|
import 'package:logging/logging.dart';
|
|
import 'package:mutex/mutex.dart';
|
|
import 'package:twonly/globals.dart';
|
|
import 'package:twonly/src/database/twonly_database.dart';
|
|
import 'package:twonly/src/database/tables/messages_table.dart';
|
|
import 'package:twonly/src/model/json/message.dart';
|
|
import 'package:twonly/src/model/json/userdata.dart';
|
|
import 'package:twonly/src/model/protobuf/api/error.pb.dart';
|
|
import 'package:twonly/src/providers/api/api_utils.dart';
|
|
import 'package:twonly/src/providers/hive.dart';
|
|
import 'package:twonly/src/services/notification_service.dart';
|
|
// ignore: library_prefixes
|
|
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
|
import 'package:twonly/src/utils/storage.dart';
|
|
|
|
final lockSendingMessages = Mutex();
|
|
|
|
Future tryTransmitMessages() async {
|
|
lockSendingMessages.protect(() async {
|
|
Map<String, dynamic> retransmit = await getAllMessagesForRetransmitting();
|
|
|
|
if (retransmit.isEmpty) return;
|
|
|
|
Logger("api.dart").info("try sending messages: ${retransmit.length}");
|
|
|
|
Map<String, dynamic> failed = {};
|
|
|
|
for (String key in retransmit.keys) {
|
|
RetransmitMessage msg =
|
|
RetransmitMessage.fromJson(jsonDecode(retransmit[key]));
|
|
|
|
Result resp = await apiProvider.sendTextMessage(
|
|
msg.userId,
|
|
msg.bytes,
|
|
msg.pushData,
|
|
);
|
|
|
|
if (resp.isSuccess) {
|
|
if (msg.messageId != null) {
|
|
await twonlyDatabase.messagesDao.updateMessageByMessageId(
|
|
msg.messageId!,
|
|
MessagesCompanion(
|
|
acknowledgeByServer: Value(true),
|
|
),
|
|
);
|
|
}
|
|
} else {
|
|
failed[key] = retransmit[key];
|
|
}
|
|
}
|
|
Box box = await getMediaStorage();
|
|
box.put("messages-to-retransmit", jsonEncode(failed));
|
|
});
|
|
}
|
|
|
|
class RetransmitMessage {
|
|
int? messageId;
|
|
int userId;
|
|
Uint8List bytes;
|
|
List<int>? pushData;
|
|
RetransmitMessage({
|
|
this.messageId,
|
|
required this.userId,
|
|
required this.bytes,
|
|
this.pushData,
|
|
});
|
|
|
|
// From JSON constructor
|
|
factory RetransmitMessage.fromJson(Map<String, dynamic> json) {
|
|
return RetransmitMessage(
|
|
messageId: json['messageId'],
|
|
userId: json['userId'],
|
|
bytes: base64Decode(json['bytes']),
|
|
pushData: json['pushData'] == null
|
|
? null
|
|
: List<int>.from(json['pushData'].map((item) => item as int)),
|
|
);
|
|
}
|
|
|
|
// To JSON method
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'messageId': messageId,
|
|
'userId': userId,
|
|
'bytes': base64Encode(bytes),
|
|
'pushData': pushData,
|
|
};
|
|
}
|
|
}
|
|
|
|
Future<Map<String, dynamic>> getAllMessagesForRetransmitting() async {
|
|
Box box = await getMediaStorage();
|
|
String? retransmitJson = box.get("messages-to-retransmit");
|
|
Map<String, dynamic> retransmit = {};
|
|
|
|
if (retransmitJson != null) {
|
|
retransmit = jsonDecode(retransmitJson);
|
|
}
|
|
return retransmit;
|
|
}
|
|
|
|
// this functions ensures that the message is received by the server and in case of errors will try again later
|
|
Future<Result> encryptAndSendMessage(
|
|
int? messageId, int userId, MessageJson msg,
|
|
{PushKind? pushKind}) async {
|
|
return await lockSendingMessages.protect<Result>(() async {
|
|
Uint8List? bytes = await SignalHelper.encryptMessage(msg, userId);
|
|
|
|
if (bytes == null) {
|
|
Logger("api.dart").shout("Error encryption message!");
|
|
return Result.error(ErrorCode.InternalError);
|
|
}
|
|
|
|
String stateId =
|
|
(messageId ?? (60001 + Random().nextInt(100000))).toString();
|
|
Box box = await getMediaStorage();
|
|
|
|
List<int>? pushData;
|
|
if (pushKind != null) {
|
|
pushData = await getPushData(userId, pushKind);
|
|
}
|
|
|
|
{
|
|
var retransmit = await getAllMessagesForRetransmitting();
|
|
|
|
retransmit[stateId] = jsonEncode(RetransmitMessage(
|
|
messageId: messageId,
|
|
userId: userId,
|
|
bytes: bytes,
|
|
pushData: pushData,
|
|
).toJson());
|
|
|
|
box.put("messages-to-retransmit", jsonEncode(retransmit));
|
|
}
|
|
|
|
Result resp = await apiProvider.sendTextMessage(userId, bytes, pushData);
|
|
|
|
if (resp.isSuccess) {
|
|
if (messageId != null) {
|
|
await twonlyDatabase.messagesDao.updateMessageByMessageId(
|
|
messageId,
|
|
MessagesCompanion(acknowledgeByServer: Value(true)),
|
|
);
|
|
|
|
{
|
|
var retransmit = await getAllMessagesForRetransmitting();
|
|
retransmit.remove(stateId);
|
|
box.put("messages-to-retransmit", jsonEncode(retransmit));
|
|
}
|
|
|
|
box.delete("retransmit-$messageId-textmessage");
|
|
}
|
|
}
|
|
|
|
return resp;
|
|
});
|
|
}
|
|
|
|
Future sendTextMessage(
|
|
int target,
|
|
TextMessageContent content,
|
|
PushKind? pushKind, {
|
|
int? responseToMessageId,
|
|
}) async {
|
|
DateTime messageSendAt = DateTime.now();
|
|
|
|
int? messageId = await twonlyDatabase.messagesDao.insertMessage(
|
|
MessagesCompanion(
|
|
contactId: Value(target),
|
|
kind: Value(MessageKind.textMessage),
|
|
sendAt: Value(messageSendAt),
|
|
responseToOtherMessageId: Value(content.responseToMessageId),
|
|
responseToMessageId: Value(responseToMessageId),
|
|
downloadState: Value(DownloadState.downloaded),
|
|
contentJson: Value(
|
|
jsonEncode(content.toJson()),
|
|
),
|
|
),
|
|
);
|
|
|
|
if (messageId == null) return;
|
|
|
|
MessageJson msg = MessageJson(
|
|
kind: MessageKind.textMessage,
|
|
messageId: messageId,
|
|
content: content,
|
|
timestamp: messageSendAt,
|
|
);
|
|
|
|
encryptAndSendMessage(messageId, target, msg, pushKind: pushKind);
|
|
}
|
|
|
|
Future notifyContactAboutOpeningMessage(
|
|
int fromUserId, List<int> messageOtherIds) async {
|
|
for (final messageOtherId in messageOtherIds) {
|
|
await encryptAndSendMessage(
|
|
null,
|
|
fromUserId,
|
|
MessageJson(
|
|
kind: MessageKind.opened,
|
|
messageId: messageOtherId,
|
|
content: MessageContent(),
|
|
timestamp: DateTime.now(),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future notifyContactsAboutProfileChange() async {
|
|
List<Contact> contacts =
|
|
await twonlyDatabase.contactsDao.getAllNotBlockedContacts();
|
|
|
|
UserData? user = await getUser();
|
|
if (user == null) return;
|
|
if (user.avatarCounter == null) return;
|
|
if (user.avatarSvg == null) return;
|
|
|
|
for (Contact contact in contacts) {
|
|
if (contact.myAvatarCounter < user.avatarCounter!) {
|
|
twonlyDatabase.contactsDao.updateContact(contact.userId,
|
|
ContactsCompanion(myAvatarCounter: Value(user.avatarCounter!)));
|
|
encryptAndSendMessage(
|
|
null,
|
|
contact.userId,
|
|
MessageJson(
|
|
kind: MessageKind.profileChange,
|
|
content: ProfileContent(
|
|
avatarSvg: user.avatarSvg!,
|
|
displayName: user.displayName,
|
|
),
|
|
timestamp: DateTime.now(),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|