twonly-app/lib/src/services/api/client2client/contact.c2c.dart
otsmr c3e92ac7d1
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
fix avatar render issues, fix list update issues
2025-11-18 22:56:42 +01:00

176 lines
5.8 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/twonly.db.dart' hide Message;
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/services/api/utils.dart';
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
import 'package:twonly/src/utils/avatars.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
Future<bool> handleContactRequest(
int fromUserId,
EncryptedContent_ContactRequest contactRequest,
) async {
switch (contactRequest.type) {
case EncryptedContent_ContactRequest_Type.REQUEST:
Log.info('Got a contact request from $fromUserId');
final contact = await twonlyDB.contactsDao
.getContactByUserId(fromUserId)
.getSingleOrNull();
if (contact != null) {
if (contact.accepted) {
// contact was already accepted, so just accept the request in the background.
await sendCipherText(
contact.userId,
EncryptedContent(
contactRequest: EncryptedContent_ContactRequest(
type: EncryptedContent_ContactRequest_Type.ACCEPT,
),
),
);
return true;
}
}
// Request the username by the server so an attacker can not
// forge the displayed username in the contact request
final user = await apiService.getUserById(fromUserId);
if (user == null) {
return false;
}
await twonlyDB.contactsDao.insertOnConflictUpdate(
ContactsCompanion(
username: Value(utf8.decode(user.username)),
userId: Value(fromUserId),
requested: const Value(true),
deletedByUser: const Value(false),
),
);
await setupNotificationWithUsers();
case EncryptedContent_ContactRequest_Type.ACCEPT:
Log.info('Got a contact accept from $fromUserId');
await twonlyDB.contactsDao.updateContact(
fromUserId,
const ContactsCompanion(
requested: Value(false),
accepted: Value(true),
deletedByUser: Value(false),
),
);
final contact = await twonlyDB.contactsDao
.getContactByUserId(fromUserId)
.getSingleOrNull();
await twonlyDB.groupsDao.createNewDirectChat(
fromUserId,
GroupsCompanion(
groupName: Value(getContactDisplayName(contact!)),
),
);
case EncryptedContent_ContactRequest_Type.REJECT:
Log.info('Got a contact reject from $fromUserId');
await twonlyDB.contactsDao.updateContact(
fromUserId,
const ContactsCompanion(
accepted: Value(false),
requested: Value(false),
deletedByUser: Value(true),
),
);
}
return true;
}
Future<void> handleContactUpdate(
int fromUserId,
EncryptedContent_ContactUpdate contactUpdate,
int? senderProfileCounter,
) async {
switch (contactUpdate.type) {
case EncryptedContent_ContactUpdate_Type.REQUEST:
Log.info('Got a contact update request from $fromUserId');
await notifyContactsAboutProfileChange(onlyToContact: fromUserId);
case EncryptedContent_ContactUpdate_Type.UPDATE:
Log.info('Got a contact update $fromUserId');
if (contactUpdate.hasAvatarSvgCompressed() &&
contactUpdate.hasDisplayName() &&
contactUpdate.hasUsername() &&
senderProfileCounter != null) {
await twonlyDB.contactsDao.updateContact(
fromUserId,
ContactsCompanion(
avatarSvgCompressed:
Value(Uint8List.fromList(contactUpdate.avatarSvgCompressed)),
displayName: Value(contactUpdate.displayName),
username: Value(contactUpdate.username),
senderProfileCounter: Value(senderProfileCounter),
),
);
unawaited(createPushAvatars());
}
}
}
Future<void> handleFlameSync(
int contactId,
EncryptedContent_FlameSync flameSync,
) async {
Log.info('Got a flameSync from $contactId');
final group = await twonlyDB.groupsDao.getDirectChat(contactId);
if (group == null || group.lastFlameCounterChange == null) return;
var updates = GroupsCompanion(
alsoBestFriend: Value(flameSync.bestFriend),
);
if (isToday(group.lastFlameCounterChange!) &&
isToday(fromTimestamp(flameSync.lastFlameCounterChange)) ||
flameSync.forceUpdate) {
if (flameSync.flameCounter > group.flameCounter) {
updates = updates.copyWith(
flameCounter: Value(flameSync.flameCounter.toInt()),
);
}
if (flameSync.flameCounter > group.maxFlameCounter) {
updates = updates.copyWith(
maxFlameCounter: Value(flameSync.flameCounter.toInt()),
);
}
}
await twonlyDB.groupsDao.updateGroup(group.groupId, updates);
}
Future<int?> checkForProfileUpdate(
int fromUserId,
EncryptedContent content,
) async {
int? senderProfileCounter;
if (content.hasSenderProfileCounter()) {
senderProfileCounter = content.senderProfileCounter.toInt();
if (!content.hasContactUpdate()) {
final contact = await twonlyDB.contactsDao
.getContactByUserId(fromUserId)
.getSingleOrNull();
if (contact != null) {
if (contact.senderProfileCounter < senderProfileCounter) {
await sendCipherText(
fromUserId,
EncryptedContent(
contactUpdate: EncryptedContent_ContactUpdate(
type: EncryptedContent_ContactUpdate_Type.REQUEST,
),
),
);
}
}
}
}
return senderProfileCounter;
}