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.

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