mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 14:48:41 +00:00
fix: self-dos because of Skill Issues
This commit is contained in:
parent
385cf11654
commit
1f51d3174f
7 changed files with 330 additions and 305 deletions
|
|
@ -12,6 +12,7 @@ enum UploadState {
|
||||||
|
|
||||||
pending,
|
pending,
|
||||||
readyToUpload,
|
readyToUpload,
|
||||||
|
uploadTaskStarted,
|
||||||
receiverNotified,
|
receiverNotified,
|
||||||
// after all users notified all media files that are not storable by the other person will be deleted
|
// after all users notified all media files that are not storable by the other person will be deleted
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -357,13 +357,14 @@ Future finalizeUpload(int mediaUploadId, List<int> contactIds,
|
||||||
|
|
||||||
final lockingHandleNextMediaUploadStep = Mutex();
|
final lockingHandleNextMediaUploadStep = Mutex();
|
||||||
Future handleNextMediaUploadSteps(int mediaUploadId) async {
|
Future handleNextMediaUploadSteps(int mediaUploadId) async {
|
||||||
bool rerun = await lockingHandleNextMediaUploadStep.protect<bool>(() async {
|
await lockingHandleNextMediaUploadStep.protect(() async {
|
||||||
var mediaUpload = await twonlyDB.mediaUploadsDao
|
var mediaUpload = await twonlyDB.mediaUploadsDao
|
||||||
.getMediaUploadById(mediaUploadId)
|
.getMediaUploadById(mediaUploadId)
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
|
|
||||||
if (mediaUpload == null) return false;
|
if (mediaUpload == null) return false;
|
||||||
if (mediaUpload.state == UploadState.receiverNotified) {
|
if (mediaUpload.state == UploadState.receiverNotified ||
|
||||||
|
mediaUpload.state == UploadState.uploadTaskStarted) {
|
||||||
/// Upload done and all users are notified :)
|
/// Upload done and all users are notified :)
|
||||||
Log.info("$mediaUploadId is already done");
|
Log.info("$mediaUploadId is already done");
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -380,16 +381,13 @@ Future handleNextMediaUploadSteps(int mediaUploadId) async {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await handleMediaUpload(mediaUpload);
|
await handleMediaUpload(mediaUpload);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error("Non recoverable error while sending media file: $e");
|
Log.error("Non recoverable error while sending media file: $e");
|
||||||
await handleUploadError(mediaUpload);
|
await handleUploadError(mediaUpload);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
if (rerun) {
|
|
||||||
handleNextMediaUploadSteps(mediaUploadId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
@ -402,6 +400,8 @@ Future handleUploadStatusUpdate(TaskStatusUpdate update) async {
|
||||||
bool failed = false;
|
bool failed = false;
|
||||||
int mediaUploadId = int.parse(update.task.taskId.replaceAll("upload_", ""));
|
int mediaUploadId = int.parse(update.task.taskId.replaceAll("upload_", ""));
|
||||||
|
|
||||||
|
Log.info("Upload $mediaUploadId done.");
|
||||||
|
|
||||||
MediaUpload? media = await twonlyDB.mediaUploadsDao
|
MediaUpload? media = await twonlyDB.mediaUploadsDao
|
||||||
.getMediaUploadById(mediaUploadId)
|
.getMediaUploadById(mediaUploadId)
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
|
|
@ -478,11 +478,11 @@ Future handleUploadError(MediaUpload mediaUpload) async {
|
||||||
await twonlyDB.mediaUploadsDao.deleteMediaUpload(mediaUpload.mediaUploadId);
|
await twonlyDB.mediaUploadsDao.deleteMediaUpload(mediaUpload.mediaUploadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> handleMediaUpload(MediaUpload media) async {
|
Future handleMediaUpload(MediaUpload media) async {
|
||||||
Uint8List bytesToUpload =
|
Uint8List bytesToUpload =
|
||||||
await readSendMediaFile(media.mediaUploadId, "encrypted");
|
await readSendMediaFile(media.mediaUploadId, "encrypted");
|
||||||
|
|
||||||
if (media.messageIds == null) return false;
|
if (media.messageIds == null) return;
|
||||||
|
|
||||||
List<Uint8List> downloadTokens =
|
List<Uint8List> downloadTokens =
|
||||||
createDownloadTokens(media.messageIds!.length);
|
createDownloadTokens(media.messageIds!.length);
|
||||||
|
|
@ -571,7 +571,7 @@ Future<bool> handleMediaUpload(MediaUpload media) async {
|
||||||
await FlutterSecureStorage().read(key: SecureStorageKeys.apiAuthToken);
|
await FlutterSecureStorage().read(key: SecureStorageKeys.apiAuthToken);
|
||||||
if (apiAuthToken == null) {
|
if (apiAuthToken == null) {
|
||||||
Log.error("api auth token not defined.");
|
Log.error("api auth token not defined.");
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
File uploadRequestFile = await writeSendMediaFile(
|
File uploadRequestFile = await writeSendMediaFile(
|
||||||
|
|
@ -600,11 +600,17 @@ Future<bool> handleMediaUpload(MediaUpload media) async {
|
||||||
|
|
||||||
final result = await FileDownloader().enqueue(task);
|
final result = await FileDownloader().enqueue(task);
|
||||||
|
|
||||||
return result;
|
if (result) {
|
||||||
|
await twonlyDB.mediaUploadsDao.updateMediaUpload(
|
||||||
|
media.mediaUploadId,
|
||||||
|
MediaUploadsCompanion(
|
||||||
|
state: Value(UploadState.uploadTaskStarted),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error("Exception during upload: $e");
|
Log.error("Exception during upload: $e");
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> compressVideoIfExists(int mediaUploadId) async {
|
Future<bool> compressVideoIfExists(int mediaUploadId) async {
|
||||||
|
|
|
||||||
|
|
@ -206,8 +206,9 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||||
avatarSvg: Value(content.avatarSvg),
|
avatarSvg: Value(content.avatarSvg),
|
||||||
displayName: Value(content.displayName),
|
displayName: Value(content.displayName),
|
||||||
);
|
);
|
||||||
twonlyDB.contactsDao.updateContact(fromUserId, update);
|
await twonlyDB.contactsDao.updateContact(fromUserId, update);
|
||||||
}
|
}
|
||||||
|
createPushAvatars();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MessageKind.requestPushKey:
|
case MessageKind.requestPushKey:
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@ import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/services/notification.background.service.dart';
|
||||||
import 'package:twonly/src/services/notification.service.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform;
|
||||||
import '../../firebase_options.dart';
|
import '../../firebase_options.dart';
|
||||||
|
|
@ -79,9 +78,7 @@ Future initFCMService() async {
|
||||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||||
initLogger();
|
initLogger();
|
||||||
Log.info('Handling a background message: ${message.messageId}');
|
Log.info('Handling a background message: ${message.messageId}');
|
||||||
twonlyDB = TwonlyDatabase();
|
|
||||||
await handleRemoteMessage(message);
|
await handleRemoteMessage(message);
|
||||||
|
|
||||||
// make sure every thing run...
|
// make sure every thing run...
|
||||||
await Future.delayed(Duration(milliseconds: 2000));
|
await Future.delayed(Duration(milliseconds: 2000));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
282
lib/src/services/notification.background.service.dart
Normal file
282
lib/src/services/notification.background.service.dart
Normal file
|
|
@ -0,0 +1,282 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:twonly/src/services/notification.service.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
|
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||||
|
FlutterLocalNotificationsPlugin();
|
||||||
|
|
||||||
|
Future customLocalPushNotification(String title, String msg) async {
|
||||||
|
const AndroidNotificationDetails androidNotificationDetails =
|
||||||
|
AndroidNotificationDetails(
|
||||||
|
'1',
|
||||||
|
'System',
|
||||||
|
channelDescription: 'System messages.',
|
||||||
|
importance: Importance.max,
|
||||||
|
priority: Priority.max,
|
||||||
|
);
|
||||||
|
|
||||||
|
const DarwinNotificationDetails darwinNotificationDetails =
|
||||||
|
DarwinNotificationDetails();
|
||||||
|
const NotificationDetails notificationDetails = NotificationDetails(
|
||||||
|
android: androidNotificationDetails, iOS: darwinNotificationDetails);
|
||||||
|
|
||||||
|
await flutterLocalNotificationsPlugin.show(
|
||||||
|
999999 + Random.secure().nextInt(9999),
|
||||||
|
title,
|
||||||
|
msg,
|
||||||
|
notificationDetails,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future handlePushData(String pushDataJson) async {
|
||||||
|
try {
|
||||||
|
String jsonString = utf8.decode(base64.decode(pushDataJson));
|
||||||
|
final pushData = PushNotification.fromJson(jsonDecode(jsonString));
|
||||||
|
|
||||||
|
PushKind? pushKind;
|
||||||
|
PushUser? pushUser;
|
||||||
|
int? fromUserId;
|
||||||
|
|
||||||
|
if (pushData.keyId == 0) {
|
||||||
|
List<int> key = "InsecureOnlyUsedForAddingContact".codeUnits;
|
||||||
|
pushKind = await tryDecryptMessage(key, pushData);
|
||||||
|
} else {
|
||||||
|
var pushKeys = await getPushKeys("receivingPushKeys");
|
||||||
|
for (final userId in pushKeys.keys) {
|
||||||
|
for (final key in pushKeys[userId]!.keys) {
|
||||||
|
if (key.id == pushData.keyId) {
|
||||||
|
pushKind = await tryDecryptMessage(key.key, pushData);
|
||||||
|
if (pushKind != null) {
|
||||||
|
pushUser = pushKeys[userId]!;
|
||||||
|
fromUserId = userId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// found correct key and user
|
||||||
|
if (pushUser != null) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pushKind != null) {
|
||||||
|
if (pushKind == PushKind.testNotification) {
|
||||||
|
await customLocalPushNotification(
|
||||||
|
"Test notification", "This is a test notification.");
|
||||||
|
} else if (pushUser != null && fromUserId != null) {
|
||||||
|
await showLocalPushNotification(pushUser, fromUserId, pushKind);
|
||||||
|
} else {
|
||||||
|
await showLocalPushNotificationWithoutUserId(pushKind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PushKind?> tryDecryptMessage(
|
||||||
|
List<int> key, PushNotification noti) async {
|
||||||
|
try {
|
||||||
|
final chacha20 = Chacha20.poly1305Aead();
|
||||||
|
SecretKeyData secretKeyData = SecretKeyData(key);
|
||||||
|
|
||||||
|
SecretBox secretBox = SecretBox(
|
||||||
|
noti.cipherText,
|
||||||
|
nonce: noti.nonce,
|
||||||
|
mac: Mac(noti.mac),
|
||||||
|
);
|
||||||
|
|
||||||
|
final plaintext =
|
||||||
|
await chacha20.decrypt(secretBox, secretKey: secretKeyData);
|
||||||
|
final plaintextString = utf8.decode(plaintext);
|
||||||
|
return PushKindExtension.fromString(plaintextString);
|
||||||
|
} catch (e) {
|
||||||
|
// this error is allowed to happen...
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future showLocalPushNotification(
|
||||||
|
PushUser pushUser,
|
||||||
|
int fromUserId,
|
||||||
|
PushKind pushKind,
|
||||||
|
) async {
|
||||||
|
String? title;
|
||||||
|
String? body;
|
||||||
|
|
||||||
|
// do not show notification for blocked users...
|
||||||
|
if (pushUser.blocked) {
|
||||||
|
Log.info("Blocked a message from a blocked user!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
title = pushUser.displayName;
|
||||||
|
body = getPushNotificationText(pushKind);
|
||||||
|
if (body == "") {
|
||||||
|
Log.error("No push notification type defined!");
|
||||||
|
}
|
||||||
|
|
||||||
|
FilePathAndroidBitmap? styleInformation;
|
||||||
|
String? avatarPath = await getAvatarIcon(fromUserId);
|
||||||
|
if (avatarPath != null) {
|
||||||
|
styleInformation = FilePathAndroidBitmap(avatarPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidNotificationDetails androidNotificationDetails =
|
||||||
|
AndroidNotificationDetails('0', 'Messages',
|
||||||
|
channelDescription: 'Messages from other users.',
|
||||||
|
importance: Importance.max,
|
||||||
|
priority: Priority.max,
|
||||||
|
ticker: 'You got a new message.',
|
||||||
|
largeIcon: styleInformation);
|
||||||
|
|
||||||
|
const DarwinNotificationDetails darwinNotificationDetails =
|
||||||
|
DarwinNotificationDetails();
|
||||||
|
NotificationDetails notificationDetails = NotificationDetails(
|
||||||
|
android: androidNotificationDetails, iOS: darwinNotificationDetails);
|
||||||
|
|
||||||
|
await flutterLocalNotificationsPlugin.show(
|
||||||
|
fromUserId,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
notificationDetails,
|
||||||
|
payload: pushKind.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future showLocalPushNotificationWithoutUserId(
|
||||||
|
PushKind pushKind,
|
||||||
|
) async {
|
||||||
|
String? title;
|
||||||
|
String? body;
|
||||||
|
|
||||||
|
body = getPushNotificationTextWithoutUserId(pushKind);
|
||||||
|
if (body == "") {
|
||||||
|
Log.error("No push notification type defined!");
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidNotificationDetails androidNotificationDetails =
|
||||||
|
AndroidNotificationDetails('0', 'Messages',
|
||||||
|
channelDescription: 'Messages from other users.',
|
||||||
|
importance: Importance.max,
|
||||||
|
priority: Priority.max,
|
||||||
|
ticker: 'You got a new message.');
|
||||||
|
|
||||||
|
const DarwinNotificationDetails darwinNotificationDetails =
|
||||||
|
DarwinNotificationDetails();
|
||||||
|
NotificationDetails notificationDetails = NotificationDetails(
|
||||||
|
android: androidNotificationDetails, iOS: darwinNotificationDetails);
|
||||||
|
|
||||||
|
await flutterLocalNotificationsPlugin.show(
|
||||||
|
2,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
notificationDetails,
|
||||||
|
payload: pushKind.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getAvatarIcon(int contactId) async {
|
||||||
|
final directory = await getApplicationCacheDirectory();
|
||||||
|
final avatarsDirectory = Directory('${directory.path}/avatars');
|
||||||
|
final filePath = '${avatarsDirectory.path}/$contactId.png';
|
||||||
|
final file = File(filePath);
|
||||||
|
if (file.existsSync()) {
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getPushNotificationTextWithoutUserId(PushKind pushKind) {
|
||||||
|
Map<String, String> pushNotificationText;
|
||||||
|
|
||||||
|
String systemLanguage = Platform.localeName;
|
||||||
|
|
||||||
|
if (systemLanguage.contains("de")) {
|
||||||
|
pushNotificationText = {
|
||||||
|
PushKind.text.name: "Du hast eine neue Nachricht erhalten.",
|
||||||
|
PushKind.twonly.name: "Du hast ein neues twonly erhalten.",
|
||||||
|
PushKind.video.name: "Du hast ein neues Video erhalten.",
|
||||||
|
PushKind.image.name: "Du hast ein neues Bild erhalten.",
|
||||||
|
PushKind.contactRequest.name:
|
||||||
|
"Du hast eine neue Kontaktanfrage erhalten.",
|
||||||
|
PushKind.acceptRequest.name: "Deine Kontaktanfrage wurde angenommen.",
|
||||||
|
PushKind.storedMediaFile.name: "Dein Bild wurde gespeichert.",
|
||||||
|
PushKind.reaction.name: "Du hast eine Reaktion auf dein Bild erhalten.",
|
||||||
|
PushKind.reopenedMedia.name: "Dein Bild wurde erneut geöffnet.",
|
||||||
|
PushKind.reactionToVideo.name:
|
||||||
|
"Du hast eine Reaktion auf dein Video erhalten.",
|
||||||
|
PushKind.reactionToText.name:
|
||||||
|
"Du hast eine Reaktion auf deinen Text erhalten.",
|
||||||
|
PushKind.reactionToImage.name:
|
||||||
|
"Du hast eine Reaktion auf dein Bild erhalten.",
|
||||||
|
PushKind.response.name: "Du hast eine Antwort erhalten.",
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
pushNotificationText = {
|
||||||
|
PushKind.text.name: "You have received a new message.",
|
||||||
|
PushKind.twonly.name: "You have received a new twonly.",
|
||||||
|
PushKind.video.name: "You have received a new video.",
|
||||||
|
PushKind.image.name: "You have received a new image.",
|
||||||
|
PushKind.contactRequest.name: "You have received a new contact request.",
|
||||||
|
PushKind.acceptRequest.name: "Your contact request has been accepted.",
|
||||||
|
PushKind.storedMediaFile.name: "Your image has been saved.",
|
||||||
|
PushKind.reaction.name: "You have received a reaction to your image.",
|
||||||
|
PushKind.reopenedMedia.name: "Your image has been reopened.",
|
||||||
|
PushKind.reactionToVideo.name:
|
||||||
|
"You have received a reaction to your video.",
|
||||||
|
PushKind.reactionToText.name:
|
||||||
|
"You have received a reaction to your text.",
|
||||||
|
PushKind.reactionToImage.name:
|
||||||
|
"You have received a reaction to your image.",
|
||||||
|
PushKind.response.name: "You have received a response.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return pushNotificationText[pushKind.name] ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String getPushNotificationText(PushKind pushKind) {
|
||||||
|
String systemLanguage = Platform.localeName;
|
||||||
|
|
||||||
|
Map<String, String> pushNotificationText;
|
||||||
|
|
||||||
|
if (systemLanguage.contains("de")) {
|
||||||
|
pushNotificationText = {
|
||||||
|
PushKind.text.name: "hat dir eine Nachricht gesendet.",
|
||||||
|
PushKind.twonly.name: "hat dir ein twonly gesendet.",
|
||||||
|
PushKind.video.name: "hat dir ein Video gesendet.",
|
||||||
|
PushKind.image.name: "hat dir ein Bild gesendet.",
|
||||||
|
PushKind.contactRequest.name: "möchte sich mit dir vernetzen.",
|
||||||
|
PushKind.acceptRequest.name: "ist jetzt mit dir vernetzt.",
|
||||||
|
PushKind.storedMediaFile.name: "hat dein Bild gespeichert.",
|
||||||
|
PushKind.reaction.name: "hat auf dein Bild reagiert.",
|
||||||
|
PushKind.reopenedMedia.name: "hat dein Bild erneut geöffnet.",
|
||||||
|
PushKind.reactionToVideo.name: "hat auf dein Video reagiert.",
|
||||||
|
PushKind.reactionToText.name: "hat auf deinen Text reagiert.",
|
||||||
|
PushKind.reactionToImage.name: "hat auf dein Bild reagiert.",
|
||||||
|
PushKind.response.name: "hat dir geantwortet.",
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
pushNotificationText = {
|
||||||
|
PushKind.text.name: "has sent you a message.",
|
||||||
|
PushKind.twonly.name: "has sent you a twonly.",
|
||||||
|
PushKind.video.name: "has sent you a video.",
|
||||||
|
PushKind.image.name: "has sent you an image.",
|
||||||
|
PushKind.contactRequest.name: "wants to connect with you.",
|
||||||
|
PushKind.acceptRequest.name: "is now connected with you.",
|
||||||
|
PushKind.storedMediaFile.name: "has stored your image.",
|
||||||
|
PushKind.reaction.name: "has reacted to your image.",
|
||||||
|
PushKind.reopenedMedia.name: "has reopened your image.",
|
||||||
|
PushKind.reactionToVideo.name: "has reacted to your video.",
|
||||||
|
PushKind.reactionToText.name: "has reacted to your text.",
|
||||||
|
PushKind.reactionToImage.name: "has reacted to your image.",
|
||||||
|
PushKind.response.name: "has responded.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return pushNotificationText[pushKind.name] ?? "";
|
||||||
|
}
|
||||||
|
|
@ -290,72 +290,6 @@ Future<Uint8List?> getPushData(int toUserId, PushKind kind) async {
|
||||||
return Utf8Encoder().convert(jsonEncode(res.toJson()));
|
return Utf8Encoder().convert(jsonEncode(res.toJson()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<PushKind?> tryDecryptMessage(
|
|
||||||
List<int> key, PushNotification noti) async {
|
|
||||||
try {
|
|
||||||
final chacha20 = Chacha20.poly1305Aead();
|
|
||||||
SecretKeyData secretKeyData = SecretKeyData(key);
|
|
||||||
|
|
||||||
SecretBox secretBox = SecretBox(
|
|
||||||
noti.cipherText,
|
|
||||||
nonce: noti.nonce,
|
|
||||||
mac: Mac(noti.mac),
|
|
||||||
);
|
|
||||||
|
|
||||||
final plaintext =
|
|
||||||
await chacha20.decrypt(secretBox, secretKey: secretKeyData);
|
|
||||||
final plaintextString = utf8.decode(plaintext);
|
|
||||||
return PushKindExtension.fromString(plaintextString);
|
|
||||||
} catch (e) {
|
|
||||||
// this error is allowed to happen...
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future handlePushData(String pushDataJson) async {
|
|
||||||
try {
|
|
||||||
String jsonString = utf8.decode(base64.decode(pushDataJson));
|
|
||||||
final pushData = PushNotification.fromJson(jsonDecode(jsonString));
|
|
||||||
|
|
||||||
PushKind? pushKind;
|
|
||||||
int? fromUserId;
|
|
||||||
|
|
||||||
if (pushData.keyId == 0) {
|
|
||||||
List<int> key = "InsecureOnlyUsedForAddingContact".codeUnits;
|
|
||||||
pushKind = await tryDecryptMessage(key, pushData);
|
|
||||||
} else {
|
|
||||||
var pushKeys = await getPushKeys("receivingPushKeys");
|
|
||||||
for (final userId in pushKeys.keys) {
|
|
||||||
for (final key in pushKeys[userId]!.keys) {
|
|
||||||
if (key.id == pushData.keyId) {
|
|
||||||
pushKind = await tryDecryptMessage(key.key, pushData);
|
|
||||||
if (pushKind != null) {
|
|
||||||
fromUserId = userId;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// found correct key and user
|
|
||||||
if (fromUserId != null) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pushKind != null) {
|
|
||||||
if (pushKind == PushKind.testNotification) {
|
|
||||||
await customLocalPushNotification(
|
|
||||||
"Test notification", "This is a test notification.");
|
|
||||||
} else if (fromUserId != null) {
|
|
||||||
await showLocalPushNotification(fromUserId, pushKind);
|
|
||||||
} else {
|
|
||||||
await showLocalPushNotificationWithoutUserId(pushKind);
|
|
||||||
await setupNotificationWithUsers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Map<int, PushUser>> getPushKeys(String storageKey) async {
|
Future<Map<int, PushUser>> getPushKeys(String storageKey) async {
|
||||||
var storage = FlutterSecureStorage();
|
var storage = FlutterSecureStorage();
|
||||||
String? pushKeysJson = await storage.read(
|
String? pushKeysJson = await storage.read(
|
||||||
|
|
@ -455,208 +389,17 @@ Future<void> setupPushNotification() async {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future showLocalPushNotification(
|
Future createPushAvatars() async {
|
||||||
int fromUserId,
|
if (!Platform.isAndroid) {
|
||||||
PushKind pushKind,
|
return; // avatars currently only shown in Android...
|
||||||
) async {
|
|
||||||
String? title;
|
|
||||||
String? body;
|
|
||||||
|
|
||||||
Contact? user = await twonlyDB.contactsDao
|
|
||||||
.getContactByUserId(fromUserId)
|
|
||||||
.getSingleOrNull();
|
|
||||||
|
|
||||||
if (user == null) return;
|
|
||||||
|
|
||||||
// do not show notification for blocked users...
|
|
||||||
if (user.blocked) {
|
|
||||||
Log.info("Blocked a message from a blocked user!");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
|
||||||
|
|
||||||
title = getContactDisplayName(user);
|
for (final contact in contacts) {
|
||||||
body = getPushNotificationText(pushKind);
|
if (contact.avatarSvg == null) return null;
|
||||||
if (body == "") {
|
|
||||||
Log.error("No push notification type defined!");
|
|
||||||
}
|
|
||||||
|
|
||||||
FilePathAndroidBitmap? styleInformation;
|
|
||||||
String? avatarPath = await getAvatarIcon(user);
|
|
||||||
if (avatarPath != null) {
|
|
||||||
styleInformation = FilePathAndroidBitmap(avatarPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
AndroidNotificationDetails androidNotificationDetails =
|
|
||||||
AndroidNotificationDetails('0', 'Messages',
|
|
||||||
channelDescription: 'Messages from other users.',
|
|
||||||
importance: Importance.max,
|
|
||||||
priority: Priority.max,
|
|
||||||
ticker: 'You got a new message.',
|
|
||||||
largeIcon: styleInformation);
|
|
||||||
|
|
||||||
const DarwinNotificationDetails darwinNotificationDetails =
|
|
||||||
DarwinNotificationDetails();
|
|
||||||
NotificationDetails notificationDetails = NotificationDetails(
|
|
||||||
android: androidNotificationDetails, iOS: darwinNotificationDetails);
|
|
||||||
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
|
||||||
fromUserId,
|
|
||||||
title,
|
|
||||||
body,
|
|
||||||
notificationDetails,
|
|
||||||
payload: pushKind.name,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future showLocalPushNotificationWithoutUserId(
|
|
||||||
PushKind pushKind,
|
|
||||||
) async {
|
|
||||||
String? title;
|
|
||||||
String? body;
|
|
||||||
|
|
||||||
body = getPushNotificationTextWithoutUserId(pushKind);
|
|
||||||
if (body == "") {
|
|
||||||
Log.error("No push notification type defined!");
|
|
||||||
}
|
|
||||||
|
|
||||||
AndroidNotificationDetails androidNotificationDetails =
|
|
||||||
AndroidNotificationDetails('0', 'Messages',
|
|
||||||
channelDescription: 'Messages from other users.',
|
|
||||||
importance: Importance.max,
|
|
||||||
priority: Priority.max,
|
|
||||||
ticker: 'You got a new message.');
|
|
||||||
|
|
||||||
const DarwinNotificationDetails darwinNotificationDetails =
|
|
||||||
DarwinNotificationDetails();
|
|
||||||
NotificationDetails notificationDetails = NotificationDetails(
|
|
||||||
android: androidNotificationDetails, iOS: darwinNotificationDetails);
|
|
||||||
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
|
||||||
2,
|
|
||||||
title,
|
|
||||||
body,
|
|
||||||
notificationDetails,
|
|
||||||
payload: pushKind.name,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future customLocalPushNotification(String title, String msg) async {
|
|
||||||
const AndroidNotificationDetails androidNotificationDetails =
|
|
||||||
AndroidNotificationDetails(
|
|
||||||
'1',
|
|
||||||
'System',
|
|
||||||
channelDescription: 'System messages.',
|
|
||||||
importance: Importance.max,
|
|
||||||
priority: Priority.max,
|
|
||||||
);
|
|
||||||
|
|
||||||
const DarwinNotificationDetails darwinNotificationDetails =
|
|
||||||
DarwinNotificationDetails();
|
|
||||||
const NotificationDetails notificationDetails = NotificationDetails(
|
|
||||||
android: androidNotificationDetails, iOS: darwinNotificationDetails);
|
|
||||||
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
|
||||||
999999 + Random.secure().nextInt(9999),
|
|
||||||
title,
|
|
||||||
msg,
|
|
||||||
notificationDetails,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String getPushNotificationTextWithoutUserId(PushKind pushKind) {
|
|
||||||
Map<String, String> pushNotificationText;
|
|
||||||
|
|
||||||
String systemLanguage = Platform.localeName;
|
|
||||||
|
|
||||||
if (systemLanguage.contains("de")) {
|
|
||||||
pushNotificationText = {
|
|
||||||
PushKind.text.name: "Du hast eine neue Nachricht erhalten.",
|
|
||||||
PushKind.twonly.name: "Du hast ein neues twonly erhalten.",
|
|
||||||
PushKind.video.name: "Du hast ein neues Video erhalten.",
|
|
||||||
PushKind.image.name: "Du hast ein neues Bild erhalten.",
|
|
||||||
PushKind.contactRequest.name:
|
|
||||||
"Du hast eine neue Kontaktanfrage erhalten.",
|
|
||||||
PushKind.acceptRequest.name: "Deine Kontaktanfrage wurde angenommen.",
|
|
||||||
PushKind.storedMediaFile.name: "Dein Bild wurde gespeichert.",
|
|
||||||
PushKind.reaction.name: "Du hast eine Reaktion auf dein Bild erhalten.",
|
|
||||||
PushKind.reopenedMedia.name: "Dein Bild wurde erneut geöffnet.",
|
|
||||||
PushKind.reactionToVideo.name:
|
|
||||||
"Du hast eine Reaktion auf dein Video erhalten.",
|
|
||||||
PushKind.reactionToText.name:
|
|
||||||
"Du hast eine Reaktion auf deinen Text erhalten.",
|
|
||||||
PushKind.reactionToImage.name:
|
|
||||||
"Du hast eine Reaktion auf dein Bild erhalten.",
|
|
||||||
PushKind.response.name: "Du hast eine Antwort erhalten.",
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
pushNotificationText = {
|
|
||||||
PushKind.text.name: "You have received a new message.",
|
|
||||||
PushKind.twonly.name: "You have received a new twonly.",
|
|
||||||
PushKind.video.name: "You have received a new video.",
|
|
||||||
PushKind.image.name: "You have received a new image.",
|
|
||||||
PushKind.contactRequest.name: "You have received a new contact request.",
|
|
||||||
PushKind.acceptRequest.name: "Your contact request has been accepted.",
|
|
||||||
PushKind.storedMediaFile.name: "Your image has been saved.",
|
|
||||||
PushKind.reaction.name: "You have received a reaction to your image.",
|
|
||||||
PushKind.reopenedMedia.name: "Your image has been reopened.",
|
|
||||||
PushKind.reactionToVideo.name:
|
|
||||||
"You have received a reaction to your video.",
|
|
||||||
PushKind.reactionToText.name:
|
|
||||||
"You have received a reaction to your text.",
|
|
||||||
PushKind.reactionToImage.name:
|
|
||||||
"You have received a reaction to your image.",
|
|
||||||
PushKind.response.name: "You have received a response.",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return pushNotificationText[pushKind.name] ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
String getPushNotificationText(PushKind pushKind) {
|
|
||||||
String systemLanguage = Platform.localeName;
|
|
||||||
|
|
||||||
Map<String, String> pushNotificationText;
|
|
||||||
|
|
||||||
if (systemLanguage.contains("de")) {
|
|
||||||
pushNotificationText = {
|
|
||||||
PushKind.text.name: "hat dir eine Nachricht gesendet.",
|
|
||||||
PushKind.twonly.name: "hat dir ein twonly gesendet.",
|
|
||||||
PushKind.video.name: "hat dir ein Video gesendet.",
|
|
||||||
PushKind.image.name: "hat dir ein Bild gesendet.",
|
|
||||||
PushKind.contactRequest.name: "möchte sich mit dir vernetzen.",
|
|
||||||
PushKind.acceptRequest.name: "ist jetzt mit dir vernetzt.",
|
|
||||||
PushKind.storedMediaFile.name: "hat dein Bild gespeichert.",
|
|
||||||
PushKind.reaction.name: "hat auf dein Bild reagiert.",
|
|
||||||
PushKind.reopenedMedia.name: "hat dein Bild erneut geöffnet.",
|
|
||||||
PushKind.reactionToVideo.name: "hat auf dein Video reagiert.",
|
|
||||||
PushKind.reactionToText.name: "hat auf deinen Text reagiert.",
|
|
||||||
PushKind.reactionToImage.name: "hat auf dein Bild reagiert.",
|
|
||||||
PushKind.response.name: "hat dir geantwortet.",
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
pushNotificationText = {
|
|
||||||
PushKind.text.name: "has sent you a message.",
|
|
||||||
PushKind.twonly.name: "has sent you a twonly.",
|
|
||||||
PushKind.video.name: "has sent you a video.",
|
|
||||||
PushKind.image.name: "has sent you an image.",
|
|
||||||
PushKind.contactRequest.name: "wants to connect with you.",
|
|
||||||
PushKind.acceptRequest.name: "is now connected with you.",
|
|
||||||
PushKind.storedMediaFile.name: "has stored your image.",
|
|
||||||
PushKind.reaction.name: "has reacted to your image.",
|
|
||||||
PushKind.reopenedMedia.name: "has reopened your image.",
|
|
||||||
PushKind.reactionToVideo.name: "has reacted to your video.",
|
|
||||||
PushKind.reactionToText.name: "has reacted to your text.",
|
|
||||||
PushKind.reactionToImage.name: "has reacted to your image.",
|
|
||||||
PushKind.response.name: "has responded.",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return pushNotificationText[pushKind.name] ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String?> getAvatarIcon(Contact user) async {
|
|
||||||
if (user.avatarSvg == null) return null;
|
|
||||||
|
|
||||||
final PictureInfo pictureInfo =
|
final PictureInfo pictureInfo =
|
||||||
await vg.loadPicture(SvgStringLoader(user.avatarSvg!), null);
|
await vg.loadPicture(SvgStringLoader(contact.avatarSvg!), null);
|
||||||
|
|
||||||
final ui.Image image = await pictureInfo.picture.toImage(300, 300);
|
final ui.Image image = await pictureInfo.picture.toImage(300, 300);
|
||||||
|
|
||||||
|
|
@ -672,12 +415,8 @@ Future<String?> getAvatarIcon(Contact user) async {
|
||||||
if (!await avatarsDirectory.exists()) {
|
if (!await avatarsDirectory.exists()) {
|
||||||
await avatarsDirectory.create(recursive: true);
|
await avatarsDirectory.create(recursive: true);
|
||||||
}
|
}
|
||||||
|
final filePath = '${avatarsDirectory.path}/${contact.userId}.png';
|
||||||
final filePath = '${avatarsDirectory.path}/${user.userId}.png';
|
await File(filePath).writeAsBytes(pngBytes);
|
||||||
final file = File(filePath);
|
|
||||||
await file.writeAsBytes(pngBytes);
|
|
||||||
|
|
||||||
pictureInfo.picture.dispose();
|
pictureInfo.picture.dispose();
|
||||||
|
}
|
||||||
return filePath;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ Future performTwonlySafeBackup({bool force = false}) async {
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
|
|
||||||
if (user == null || user.twonlySafeBackup == null || user.isDemoUser) {
|
if (user == null || user.twonlySafeBackup == null || user.isDemoUser) {
|
||||||
Log.warn("perform twonly safe backup was called while it is disabled");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue