Encrypted messaging app
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
5.2 KiB

  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:http/http.dart' as http;
  4. import 'package:sqflite/sqflite.dart';
  5. import 'package:uuid/uuid.dart';
  6. import '/database/repositories/conversation_users_repository.dart';
  7. import '/database/repositories/conversations_repository.dart';
  8. import '/database/repositories/device_tokens_repository.dart';
  9. import '/database/models/messages.dart';
  10. import '/database/models/image_message.dart';
  11. import '/database/models/text_messages.dart';
  12. import '/database/models/conversation_users.dart';
  13. import '/database/models/conversations.dart';
  14. import '/database/models/my_profile.dart';
  15. import '/utils/storage/database.dart';
  16. import '/utils/storage/session_cookie.dart';
  17. import '/utils/storage/write_file.dart';
  18. class MessagesService {
  19. static Future<void> sendMessage(Conversation conversation, {
  20. String? data,
  21. List<File> files = const []
  22. }) async {
  23. MyProfile profile = await MyProfile.getProfile();
  24. var uuid = const Uuid();
  25. ConversationUser currentUser = await ConversationUsersRepository.getConversationUser(conversation, profile.id);
  26. List<Message> messages = [];
  27. Map<String, dynamic> payload = {
  28. 'tokens': await DeviceTokensRepository.getDeviceTokensForConversation(conversation),
  29. 'sender': profile.username,
  30. 'conversation_id': conversation.id,
  31. 'messages': [],
  32. };
  33. final db = await getDatabaseConnection();
  34. if (data != null) {
  35. TextMessage message = TextMessage(
  36. id: uuid.v4(),
  37. symmetricKey: '',
  38. userSymmetricKey: '',
  39. senderId: currentUser.userId,
  40. senderUsername: profile.username,
  41. associationKey: currentUser.associationKey,
  42. createdAt: DateTime.now().toIso8601String(),
  43. failedToSend: false,
  44. text: data,
  45. );
  46. messages.add(message);
  47. payload['messages'].add(await message.payloadJson(
  48. conversation,
  49. ));
  50. await db.insert(
  51. 'messages',
  52. message.toMap(),
  53. conflictAlgorithm: ConflictAlgorithm.replace,
  54. );
  55. }
  56. for (File file in files) {
  57. String messageId = uuid.v4();
  58. File writtenFile = await writeImage(
  59. messageId,
  60. file.readAsBytesSync(),
  61. );
  62. ImageMessage message = ImageMessage(
  63. id: messageId,
  64. symmetricKey: '',
  65. userSymmetricKey: '',
  66. senderId: currentUser.userId,
  67. senderUsername: profile.username,
  68. associationKey: currentUser.associationKey,
  69. createdAt: DateTime.now().toIso8601String(),
  70. failedToSend: false,
  71. file: writtenFile,
  72. );
  73. messages.add(message);
  74. payload['messages'].add(await message.payloadJson(
  75. conversation,
  76. ));
  77. await db.insert(
  78. 'messages',
  79. message.toMap(),
  80. conflictAlgorithm: ConflictAlgorithm.replace,
  81. );
  82. }
  83. String sessionCookie = await getSessionCookie();
  84. return http.post(
  85. await MyProfile.getServerUrl('api/v1/auth/message'),
  86. headers: <String, String>{
  87. 'Content-Type': 'application/json; charset=UTF-8',
  88. 'cookie': sessionCookie,
  89. },
  90. body: jsonEncode(payload),
  91. )
  92. .then((resp) {
  93. if (resp.statusCode != 204) {
  94. throw Exception('Unable to send message');
  95. }
  96. })
  97. .catchError((exception) {
  98. for (Message message in messages) {
  99. message.failedToSend = true;
  100. db.update(
  101. 'messages',
  102. message.toMap(),
  103. where: 'id = ?',
  104. whereArgs: [message.id],
  105. );
  106. }
  107. throw exception;
  108. });
  109. }
  110. static Future<void> updateMessageThread(Conversation conversation, {MyProfile? profile}) async {
  111. profile ??= await MyProfile.getProfile();
  112. ConversationUser currentUser = await ConversationUsersRepository.getConversationUser(conversation, profile.id);
  113. var resp = await http.get(
  114. await MyProfile.getServerUrl('api/v1/auth/messages/${currentUser.associationKey}'),
  115. headers: {
  116. 'cookie': await getSessionCookie(),
  117. }
  118. );
  119. if (resp.statusCode != 200) {
  120. throw Exception(resp.body);
  121. }
  122. List<dynamic> messageThreadJson = jsonDecode(resp.body);
  123. final db = await getDatabaseConnection();
  124. for (var i = 0; i < messageThreadJson.length; i++) {
  125. var messageJson = messageThreadJson[i] as Map<String, dynamic>;
  126. var message = messageJson['message_data']['attachment_id'] != null ?
  127. await ImageMessage.fromJson(
  128. messageJson,
  129. profile.privateKey!,
  130. ) :
  131. TextMessage.fromJson(
  132. messageJson,
  133. profile.privateKey!,
  134. );
  135. ConversationUser messageUser = await ConversationUsersRepository.getConversationUser(conversation, message.senderId);
  136. message.senderUsername = messageUser.username;
  137. await db.insert(
  138. 'messages',
  139. message.toMap(),
  140. conflictAlgorithm: ConflictAlgorithm.replace,
  141. );
  142. }
  143. }
  144. static Future<void> updateMessageThreads({List<Conversation>? conversations}) async {
  145. MyProfile profile = await MyProfile.getProfile();
  146. conversations ??= await ConversationsRepository.getConversations();
  147. for (var i = 0; i < conversations.length; i++) {
  148. await updateMessageThread(conversations[i], profile: profile);
  149. }
  150. }
  151. }