handle text and media server messages, update pushkeys

This commit is contained in:
otsmr 2025-10-19 14:44:49 +02:00
parent a4ccefec75
commit 2f3f927914
41 changed files with 1172 additions and 790 deletions

View file

@ -128,8 +128,8 @@ struct PushNotification: Sendable {
var kind: PushKind = .reaction var kind: PushKind = .reaction
var messageID: Int64 { var messageID: String {
get {return _messageID ?? 0} get {return _messageID ?? String()}
set {_messageID = newValue} set {_messageID = newValue}
} }
/// Returns true if `messageID` has been explicitly set. /// Returns true if `messageID` has been explicitly set.
@ -150,7 +150,7 @@ struct PushNotification: Sendable {
init() {} init() {}
fileprivate var _messageID: Int64? = nil fileprivate var _messageID: String? = nil
fileprivate var _reactionContent: String? = nil fileprivate var _reactionContent: String? = nil
} }
@ -177,8 +177,8 @@ struct PushUser: Sendable {
var blocked: Bool = false var blocked: Bool = false
var lastMessageID: Int64 { var lastMessageID: String {
get {return _lastMessageID ?? 0} get {return _lastMessageID ?? String()}
set {_lastMessageID = newValue} set {_lastMessageID = newValue}
} }
/// Returns true if `lastMessageID` has been explicitly set. /// Returns true if `lastMessageID` has been explicitly set.
@ -192,7 +192,7 @@ struct PushUser: Sendable {
init() {} init() {}
fileprivate var _lastMessageID: Int64? = nil fileprivate var _lastMessageID: String? = nil
} }
struct PushKey: Sendable { struct PushKey: Sendable {
@ -273,7 +273,7 @@ extension PushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme
// enabled. https://github.com/apple/swift-protobuf/issues/1034 // enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber { switch fieldNumber {
case 1: try { try decoder.decodeSingularEnumField(value: &self.kind) }() case 1: try { try decoder.decodeSingularEnumField(value: &self.kind) }()
case 2: try { try decoder.decodeSingularInt64Field(value: &self._messageID) }() case 2: try { try decoder.decodeSingularStringField(value: &self._messageID) }()
case 3: try { try decoder.decodeSingularStringField(value: &self._reactionContent) }() case 3: try { try decoder.decodeSingularStringField(value: &self._reactionContent) }()
default: break default: break
} }
@ -289,7 +289,7 @@ extension PushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme
try visitor.visitSingularEnumField(value: self.kind, fieldNumber: 1) try visitor.visitSingularEnumField(value: self.kind, fieldNumber: 1)
} }
try { if let v = self._messageID { try { if let v = self._messageID {
try visitor.visitSingularInt64Field(value: v, fieldNumber: 2) try visitor.visitSingularStringField(value: v, fieldNumber: 2)
} }() } }()
try { if let v = self._reactionContent { try { if let v = self._reactionContent {
try visitor.visitSingularStringField(value: v, fieldNumber: 3) try visitor.visitSingularStringField(value: v, fieldNumber: 3)
@ -349,7 +349,7 @@ extension PushUser: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
case 1: try { try decoder.decodeSingularInt64Field(value: &self.userID) }() case 1: try { try decoder.decodeSingularInt64Field(value: &self.userID) }()
case 2: try { try decoder.decodeSingularStringField(value: &self.displayName) }() case 2: try { try decoder.decodeSingularStringField(value: &self.displayName) }()
case 3: try { try decoder.decodeSingularBoolField(value: &self.blocked) }() case 3: try { try decoder.decodeSingularBoolField(value: &self.blocked) }()
case 4: try { try decoder.decodeSingularInt64Field(value: &self._lastMessageID) }() case 4: try { try decoder.decodeSingularStringField(value: &self._lastMessageID) }()
case 5: try { try decoder.decodeRepeatedMessageField(value: &self.pushKeys) }() case 5: try { try decoder.decodeRepeatedMessageField(value: &self.pushKeys) }()
default: break default: break
} }
@ -371,7 +371,7 @@ extension PushUser: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
try visitor.visitSingularBoolField(value: self.blocked, fieldNumber: 3) try visitor.visitSingularBoolField(value: self.blocked, fieldNumber: 3)
} }
try { if let v = self._lastMessageID { try { if let v = self._lastMessageID {
try visitor.visitSingularInt64Field(value: v, fieldNumber: 4) try visitor.visitSingularStringField(value: v, fieldNumber: 4)
} }() } }()
if !self.pushKeys.isEmpty { if !self.pushKeys.isEmpty {
try visitor.visitRepeatedMessageField(value: self.pushKeys, fieldNumber: 5) try visitor.visitRepeatedMessageField(value: self.pushKeys, fieldNumber: 5)

View file

@ -6,6 +6,6 @@ class SecureStorageKeys {
static const String userData = 'userData'; static const String userData = 'userData';
static const String twonlySafeLastBackupHash = 'twonly_safe_last_backup_hash'; static const String twonlySafeLastBackupHash = 'twonly_safe_last_backup_hash';
static const String receivingPushKeys = 'receiving_push_keys'; static const String receivingPushKeys = 'push_keys_receiving';
static const String sendingPushKeys = 'sending_push_keys'; static const String sendingPushKeys = 'push_keys_sending';
} }

View file

@ -120,10 +120,7 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
Stream<List<Contact>> watchNotAcceptedContacts() { Stream<List<Contact>> watchNotAcceptedContacts() {
return (select(contacts) return (select(contacts)
..where( ..where(
(t) => (t) => t.accepted.equals(false) & t.blocked.equals(false),
t.accepted.equals(false) &
t.archived.equals(false) &
t.blocked.equals(false),
)) ))
.watch(); .watch();
// return (select(contacts)).watch(); // return (select(contacts)).watch();

View file

@ -12,10 +12,18 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
GroupsDao(super.db); GroupsDao(super.db);
Future<bool> isContactInGroup(int contactId, String groupId) async { Future<bool> isContactInGroup(int contactId, String groupId) async {
final entry = await (select(groupMembers) final entry = await (select(groupMembers)..where(
..where( // ignore: require_trailing_commas
(t) => t.contactId.equals(contactId) & t.groupId.equals(groupId))) (t) => t.contactId.equals(contactId) & t.groupId.equals(groupId)))
.getSingleOrNull(); .getSingleOrNull();
return entry != null; return entry != null;
} }
Future<void> updateGroup(
String groupId,
GroupsCompanion updates,
) async {
await (update(groups)..where((c) => c.groupId.equals(groupId)))
.write(updates);
}
} }

View file

@ -0,0 +1,54 @@
import 'package:drift/drift.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/utils/log.dart';
part 'mediafiles.dao.g.dart';
@DriftAccessor(tables: [MediaFiles])
class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
with _$MediaFilesDaoMixin {
// this constructor is required so that the main database can create an instance
// of this object.
// ignore: matching_super_parameters
MediaFilesDao(super.db);
Future<MediaFile?> insertMedia(MediaFilesCompanion mediaFile) async {
try {
final rowId = await into(mediaFiles).insert(mediaFile);
return await (select(mediaFiles)..where((t) => t.rowId.equals(rowId)))
.getSingle();
} catch (e) {
Log.error('Could not insert media file: $e');
return null;
}
}
Future<void> updateMedia(
String mediaId,
MediaFilesCompanion updates,
) async {
await (update(mediaFiles)..where((c) => c.mediaId.equals(mediaId)))
.write(updates);
}
Future<MediaFile?> getMediaFileById(String mediaId) async {
return (select(mediaFiles)..where((t) => t.mediaId.equals(mediaId)))
.getSingleOrNull();
}
Future<void> resetPendingDownloadState() async {
await (update(mediaFiles)
..where(
(c) => c.downloadState.equals(
DownloadState.downloading.name,
),
))
.write(
const MediaFilesCompanion(
downloadState: Value(DownloadState.pending),
),
);
}
}

View file

@ -0,0 +1,8 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'mediafiles.dao.dart';
// ignore_for_file: type=lint
mixin _$MediaFilesDaoMixin on DatabaseAccessor<TwonlyDB> {
$MediaFilesTable get mediaFiles => attachedDatabase.mediaFiles;
}

View file

@ -1,13 +1,24 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/tables/contacts.table.dart'; import 'package:twonly/src/database/tables/contacts.table.dart';
import 'package:twonly/src/database/tables/groups.table.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/tables/messages.table.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/mediafile.service.dart'; import 'package:twonly/src/services/mediafile.service.dart';
import 'package:twonly/src/utils/log.dart';
part 'messages.dao.g.dart'; part 'messages.dao.g.dart';
@DriftAccessor(tables: [Messages, Contacts, MediaFiles, MessageHistories]) @DriftAccessor(
tables: [
Messages,
Contacts,
MediaFiles,
MessageHistories,
Groups,
],
)
class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin { class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
// this constructor is required so that the main database can create an instance // this constructor is required so that the main database can create an instance
// of this object. // of this object.
@ -156,22 +167,6 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
// .write(updates); // .write(updates);
// } // }
// Future<void> resetPendingDownloadState() {
// // All media files in the downloading state are reset to the pending state
// // When the app is used in mobile network, they will not be downloaded at the start
// // if they are not yet downloaded...
// const updates =
// MessagesCompanion(downloadState: Value(DownloadState.pending));
// return (update(messages)
// ..where(
// (t) =>
// t.messageOtherId.isNotNull() &
// t.downloadState.equals(DownloadState.downloading.index) &
// t.kind.equals(MessageKind.media.name),
// ))
// .write(updates);
// }
Future<void> handleMessageDeletion( Future<void> handleMessageDeletion(
int contactId, int contactId,
String messageId, String messageId,
@ -278,28 +273,33 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
// .write(updatedValues); // .write(updatedValues);
// } // }
// Future<void> updateMessageByMessageId( Future<void> updateMessageId(
// int messageId, String messageId,
// MessagesCompanion updatedValues, MessagesCompanion updatedValues,
// ) { ) {
// return (update(messages)..where((c) => c.messageId.equals(messageId))) return (update(messages)..where((c) => c.messageId.equals(messageId)))
// .write(updatedValues); .write(updatedValues);
// } }
// Future<int?> insertMessage(MessagesCompanion message) async { Future<Message?> insertMessage(MessagesCompanion message) async {
// try { try {
// await (update(contacts) final rowId = await into(messages).insert(message);
// ..where(
// (c) => c.userId.equals(message.contactId.value),
// ))
// .write(ContactsCompanion(lastMessageExchange: Value(DateTime.now())));
// return await into(messages).insert(message); await twonlyDB.groupsDao.updateGroup(
// } catch (e) { message.groupId.value,
// Log.error('Error while inserting message: $e'); GroupsCompanion(
// return null; lastMessageExchange: Value(DateTime.now()),
// } archived: const Value(false),
// } ),
);
return await (select(messages)..where((t) => t.rowId.equals(rowId)))
.getSingle();
} catch (e) {
Log.error('Could not insert message: $e');
return null;
}
}
// Future<void> deleteMessagesByContactId(int contactId) { // Future<void> deleteMessagesByContactId(int contactId) {
// return (delete(messages) // return (delete(messages)

View file

@ -9,4 +9,5 @@ mixin _$MessagesDaoMixin on DatabaseAccessor<TwonlyDB> {
$MessagesTable get messages => attachedDatabase.messages; $MessagesTable get messages => attachedDatabase.messages;
$MessageHistoriesTable get messageHistories => $MessageHistoriesTable get messageHistories =>
attachedDatabase.messageHistories; attachedDatabase.messageHistories;
$GroupsTable get groups => attachedDatabase.groups;
} }

View file

@ -31,11 +31,13 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
)) ))
.go(); .go();
if (emoji != null) { if (emoji != null) {
await into(reactions).insert(ReactionsCompanion( await into(reactions).insert(
ReactionsCompanion(
messageId: Value(messageId), messageId: Value(messageId),
emoji: Value(emoji), emoji: Value(emoji),
senderId: Value(contactId), senderId: Value(contactId),
)); ),
);
} }
} catch (e) { } catch (e) {
Log.error(e); Log.error(e);

View file

@ -15,8 +15,10 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
Future<void> confirmReceipt(String receiptId, int fromUserId) async { Future<void> confirmReceipt(String receiptId, int fromUserId) async {
final receipt = await (select(receipts) final receipt = await (select(receipts)
..where((t) => ..where(
t.receiptId.equals(receiptId) & t.contactId.equals(fromUserId))) (t) =>
t.receiptId.equals(receiptId) & t.contactId.equals(fromUserId),
))
.getSingleOrNull(); .getSingleOrNull();
if (receipt == null) return; if (receipt == null) return;
@ -26,7 +28,7 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
..where((t) => t.messageId.equals(receipt.messageId!))) ..where((t) => t.messageId.equals(receipt.messageId!)))
.write( .write(
const MessagesCompanion( const MessagesCompanion(
acknowledgeByUser: Value(true), ackByUser: Value(true),
), ),
); );
} }
@ -39,6 +41,14 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
.go(); .go();
} }
Future<void> deleteReceipt(String receiptId) async {
await (delete(receipts)
..where(
(t) => t.receiptId.equals(receiptId),
))
.go();
}
Future<Receipt?> insertReceipt(ReceiptsCompanion entry) async { Future<Receipt?> insertReceipt(ReceiptsCompanion entry) async {
try { try {
final id = await into(receipts).insert(entry); final id = await into(receipts).insert(entry);
@ -49,4 +59,33 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
return null; return null;
} }
} }
Future<Receipt?> getReceiptById(String receiptId) async {
try {
return await (select(receipts)
..where(
(t) => t.receiptId.equals(receiptId),
))
.getSingleOrNull();
} catch (e) {
Log.error(e);
return null;
}
}
Future<List<Receipt>> getReceiptsNotAckByServer() async {
return (select(receipts)
..where(
(t) => t.ackByServerAt.isNull(),
))
.get();
}
Future<void> updateReceipt(
String receiptId,
ReceiptsCompanion updates,
) async {
await (update(receipts)..where((c) => c.receiptId.equals(receiptId)))
.write(updates);
}
} }

View file

@ -16,7 +16,6 @@ class Contacts extends Table {
BoolColumn get hidden => boolean().withDefault(const Constant(false))(); BoolColumn get hidden => boolean().withDefault(const Constant(false))();
BoolColumn get blocked => boolean().withDefault(const Constant(false))(); BoolColumn get blocked => boolean().withDefault(const Constant(false))();
BoolColumn get verified => boolean().withDefault(const Constant(false))(); BoolColumn get verified => boolean().withDefault(const Constant(false))();
BoolColumn get archived => boolean().withDefault(const Constant(false))();
BoolColumn get deleted => boolean().withDefault(const Constant(false))(); BoolColumn get deleted => boolean().withDefault(const Constant(false))();
BoolColumn get alsoBestFriend => BoolColumn get alsoBestFriend =>

View file

@ -9,6 +9,7 @@ class Groups extends Table {
BoolColumn get isGroupAdmin => boolean()(); BoolColumn get isGroupAdmin => boolean()();
BoolColumn get isGroupOfTwo => boolean()(); BoolColumn get isGroupOfTwo => boolean()();
BoolColumn get pinned => boolean().withDefault(const Constant(false))(); BoolColumn get pinned => boolean().withDefault(const Constant(false))();
BoolColumn get archived => boolean().withDefault(const Constant(false))();
DateTimeColumn get lastMessageExchange => DateTimeColumn get lastMessageExchange =>
dateTime().withDefault(currentDateAndTime)(); dateTime().withDefault(currentDateAndTime)();

View file

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:hashlib/random.dart'; import 'package:hashlib/random.dart';
@ -14,13 +16,11 @@ enum UploadState {
receiverNotified, receiverNotified,
} }
enum DownloadState { enum DownloadState { pending, downloading }
pending,
}
@DataClassName('MediaFile') @DataClassName('MediaFile')
class MediaFiles extends Table { class MediaFiles extends Table {
TextColumn get mediaId => text().clientDefault(() => uuid.v4())(); TextColumn get mediaId => text().clientDefault(() => uuid.v7())();
TextColumn get type => textEnum<MediaType>()(); TextColumn get type => textEnum<MediaType>()();
@ -34,6 +34,9 @@ class MediaFiles extends Table {
BoolColumn get storedByContact => BoolColumn get storedByContact =>
boolean().withDefault(const Constant(false))(); boolean().withDefault(const Constant(false))();
TextColumn get reuploadRequestedBy =>
text().map(IntListTypeConverter()).nullable()();
IntColumn get displayLimitInMilliseconds => integer().nullable()(); IntColumn get displayLimitInMilliseconds => integer().nullable()();
BlobColumn get downloadToken => blob().nullable()(); BlobColumn get downloadToken => blob().nullable()();
@ -46,3 +49,15 @@ class MediaFiles extends Table {
@override @override
Set<Column> get primaryKey => {mediaId}; Set<Column> get primaryKey => {mediaId};
} }
class IntListTypeConverter extends TypeConverter<List<int>, String> {
@override
List<int> fromSql(String fromDb) {
return List<int>.from(jsonDecode(fromDb) as Iterable<dynamic>);
}
@override
String toSql(List<int> value) {
return json.encode(value);
}
}

View file

@ -1,11 +1,12 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:hashlib/random.dart';
import 'package:twonly/src/database/tables/contacts.table.dart'; import 'package:twonly/src/database/tables/contacts.table.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart';
@DataClassName('Message') @DataClassName('Message')
class Messages extends Table { class Messages extends Table {
TextColumn get groupId => text()(); TextColumn get groupId => text()();
TextColumn get messageId => text()(); TextColumn get messageId => text().clientDefault(() => uuid.v7())();
// in case senderId is null, it was send by user itself // in case senderId is null, it was send by user itself
IntColumn get senderId => IntColumn get senderId =>
@ -23,10 +24,8 @@ class Messages extends Table {
BoolColumn get isEdited => boolean().withDefault(const Constant(false))(); BoolColumn get isEdited => boolean().withDefault(const Constant(false))();
BoolColumn get acknowledgeByUser => BoolColumn get ackByUser => boolean().withDefault(const Constant(false))();
boolean().withDefault(const Constant(false))(); BoolColumn get ackByServer => boolean().withDefault(const Constant(false))();
BoolColumn get acknowledgeByServer =>
boolean().withDefault(const Constant(false))();
IntColumn get openedByCounter => integer().withDefault(const Constant(0))(); IntColumn get openedByCounter => integer().withDefault(const Constant(0))();
DateTimeColumn get openedAt => dateTime().nullable()(); DateTimeColumn get openedAt => dateTime().nullable()();

View file

@ -15,11 +15,14 @@ class Receipts extends Table {
.nullable() .nullable()
.references(Messages, #messageId, onDelete: KeyAction.cascade)(); .references(Messages, #messageId, onDelete: KeyAction.cascade)();
/// This is the protobuf 'Message'
BlobColumn get message => blob()(); BlobColumn get message => blob()();
BoolColumn get contactWillSendsReceipt => BoolColumn get contactWillSendsReceipt =>
boolean().withDefault(const Constant(true))(); boolean().withDefault(const Constant(true))();
DateTimeColumn get ackByServerAt => dateTime().nullable()();
IntColumn get retryCount => integer().withDefault(const Constant(0))(); IntColumn get retryCount => integer().withDefault(const Constant(0))();
DateTimeColumn get lastRetry => dateTime().nullable()(); DateTimeColumn get lastRetry => dateTime().nullable()();

View file

@ -4,6 +4,7 @@ import 'package:drift_flutter/drift_flutter.dart'
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/daos/groups.dao.dart'; import 'package:twonly/src/database/daos/groups.dao.dart';
import 'package:twonly/src/database/daos/mediafiles.dao.dart';
import 'package:twonly/src/database/daos/messages.dao.dart'; import 'package:twonly/src/database/daos/messages.dao.dart';
import 'package:twonly/src/database/daos/reactions.dao.dart'; import 'package:twonly/src/database/daos/reactions.dao.dart';
import 'package:twonly/src/database/daos/receipts.dao.dart'; import 'package:twonly/src/database/daos/receipts.dao.dart';
@ -48,7 +49,8 @@ part 'twonly.db.g.dart';
SignalDao, SignalDao,
ReceiptsDao, ReceiptsDao,
GroupsDao, GroupsDao,
ReactionsDao ReactionsDao,
MediaFilesDao,
], ],
) )
class TwonlyDB extends _$TwonlyDB { class TwonlyDB extends _$TwonlyDB {

File diff suppressed because it is too large Load diff

View file

@ -218,6 +218,7 @@ class EncryptedContent_TextMessage extends $pb.GeneratedMessage {
factory EncryptedContent_TextMessage({ factory EncryptedContent_TextMessage({
$core.String? senderMessageId, $core.String? senderMessageId,
$core.String? text, $core.String? text,
$fixnum.Int64? timestamp,
$core.String? quoteMessageId, $core.String? quoteMessageId,
}) { }) {
final $result = create(); final $result = create();
@ -227,6 +228,9 @@ class EncryptedContent_TextMessage extends $pb.GeneratedMessage {
if (text != null) { if (text != null) {
$result.text = text; $result.text = text;
} }
if (timestamp != null) {
$result.timestamp = timestamp;
}
if (quoteMessageId != null) { if (quoteMessageId != null) {
$result.quoteMessageId = quoteMessageId; $result.quoteMessageId = quoteMessageId;
} }
@ -239,7 +243,8 @@ class EncryptedContent_TextMessage extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedContent.TextMessage', createEmptyInstance: create) static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedContent.TextMessage', createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'senderMessageId', protoName: 'senderMessageId') ..aOS(1, _omitFieldNames ? '' : 'senderMessageId', protoName: 'senderMessageId')
..aOS(2, _omitFieldNames ? '' : 'text') ..aOS(2, _omitFieldNames ? '' : 'text')
..aOS(3, _omitFieldNames ? '' : 'quoteMessageId', protoName: 'quoteMessageId') ..aInt64(3, _omitFieldNames ? '' : 'timestamp')
..aOS(4, _omitFieldNames ? '' : 'quoteMessageId', protoName: 'quoteMessageId')
..hasRequiredFields = false ..hasRequiredFields = false
; ;
@ -283,13 +288,22 @@ class EncryptedContent_TextMessage extends $pb.GeneratedMessage {
void clearText() => clearField(2); void clearText() => clearField(2);
@$pb.TagNumber(3) @$pb.TagNumber(3)
$core.String get quoteMessageId => $_getSZ(2); $fixnum.Int64 get timestamp => $_getI64(2);
@$pb.TagNumber(3) @$pb.TagNumber(3)
set quoteMessageId($core.String v) { $_setString(2, v); } set timestamp($fixnum.Int64 v) { $_setInt64(2, v); }
@$pb.TagNumber(3) @$pb.TagNumber(3)
$core.bool hasQuoteMessageId() => $_has(2); $core.bool hasTimestamp() => $_has(2);
@$pb.TagNumber(3) @$pb.TagNumber(3)
void clearQuoteMessageId() => clearField(3); void clearTimestamp() => clearField(3);
@$pb.TagNumber(4)
$core.String get quoteMessageId => $_getSZ(3);
@$pb.TagNumber(4)
set quoteMessageId($core.String v) { $_setString(3, v); }
@$pb.TagNumber(4)
$core.bool hasQuoteMessageId() => $_has(3);
@$pb.TagNumber(4)
void clearQuoteMessageId() => clearField(4);
} }
class EncryptedContent_Reaction extends $pb.GeneratedMessage { class EncryptedContent_Reaction extends $pb.GeneratedMessage {
@ -374,6 +388,7 @@ class EncryptedContent_MessageUpdate extends $pb.GeneratedMessage {
factory EncryptedContent_MessageUpdate({ factory EncryptedContent_MessageUpdate({
EncryptedContent_MessageUpdate_Type? type, EncryptedContent_MessageUpdate_Type? type,
$core.String? senderMessageId, $core.String? senderMessageId,
$core.Iterable<$core.String>? multipleSenderMessageIds,
$core.String? text, $core.String? text,
$fixnum.Int64? timestamp, $fixnum.Int64? timestamp,
}) { }) {
@ -384,6 +399,9 @@ class EncryptedContent_MessageUpdate extends $pb.GeneratedMessage {
if (senderMessageId != null) { if (senderMessageId != null) {
$result.senderMessageId = senderMessageId; $result.senderMessageId = senderMessageId;
} }
if (multipleSenderMessageIds != null) {
$result.multipleSenderMessageIds.addAll(multipleSenderMessageIds);
}
if (text != null) { if (text != null) {
$result.text = text; $result.text = text;
} }
@ -399,8 +417,9 @@ class EncryptedContent_MessageUpdate extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedContent.MessageUpdate', createEmptyInstance: create) static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedContent.MessageUpdate', createEmptyInstance: create)
..e<EncryptedContent_MessageUpdate_Type>(1, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, defaultOrMaker: EncryptedContent_MessageUpdate_Type.DELETE, valueOf: EncryptedContent_MessageUpdate_Type.valueOf, enumValues: EncryptedContent_MessageUpdate_Type.values) ..e<EncryptedContent_MessageUpdate_Type>(1, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, defaultOrMaker: EncryptedContent_MessageUpdate_Type.DELETE, valueOf: EncryptedContent_MessageUpdate_Type.valueOf, enumValues: EncryptedContent_MessageUpdate_Type.values)
..aOS(2, _omitFieldNames ? '' : 'senderMessageId', protoName: 'senderMessageId') ..aOS(2, _omitFieldNames ? '' : 'senderMessageId', protoName: 'senderMessageId')
..aOS(3, _omitFieldNames ? '' : 'text') ..pPS(3, _omitFieldNames ? '' : 'multipleSenderMessageIds', protoName: 'multipleSenderMessageIds')
..aInt64(4, _omitFieldNames ? '' : 'timestamp') ..aOS(4, _omitFieldNames ? '' : 'text')
..aInt64(5, _omitFieldNames ? '' : 'timestamp')
..hasRequiredFields = false ..hasRequiredFields = false
; ;
@ -444,22 +463,25 @@ class EncryptedContent_MessageUpdate extends $pb.GeneratedMessage {
void clearSenderMessageId() => clearField(2); void clearSenderMessageId() => clearField(2);
@$pb.TagNumber(3) @$pb.TagNumber(3)
$core.String get text => $_getSZ(2); $core.List<$core.String> get multipleSenderMessageIds => $_getList(2);
@$pb.TagNumber(3)
set text($core.String v) { $_setString(2, v); }
@$pb.TagNumber(3)
$core.bool hasText() => $_has(2);
@$pb.TagNumber(3)
void clearText() => clearField(3);
@$pb.TagNumber(4) @$pb.TagNumber(4)
$fixnum.Int64 get timestamp => $_getI64(3); $core.String get text => $_getSZ(3);
@$pb.TagNumber(4) @$pb.TagNumber(4)
set timestamp($fixnum.Int64 v) { $_setInt64(3, v); } set text($core.String v) { $_setString(3, v); }
@$pb.TagNumber(4) @$pb.TagNumber(4)
$core.bool hasTimestamp() => $_has(3); $core.bool hasText() => $_has(3);
@$pb.TagNumber(4) @$pb.TagNumber(4)
void clearTimestamp() => clearField(4); void clearText() => clearField(4);
@$pb.TagNumber(5)
$fixnum.Int64 get timestamp => $_getI64(4);
@$pb.TagNumber(5)
set timestamp($fixnum.Int64 v) { $_setInt64(4, v); }
@$pb.TagNumber(5)
$core.bool hasTimestamp() => $_has(4);
@$pb.TagNumber(5)
void clearTimestamp() => clearField(5);
} }
class EncryptedContent_Media extends $pb.GeneratedMessage { class EncryptedContent_Media extends $pb.GeneratedMessage {
@ -468,6 +490,8 @@ class EncryptedContent_Media extends $pb.GeneratedMessage {
EncryptedContent_Media_Type? type, EncryptedContent_Media_Type? type,
$fixnum.Int64? displayLimitInMilliseconds, $fixnum.Int64? displayLimitInMilliseconds,
$core.bool? requiresAuthentication, $core.bool? requiresAuthentication,
$fixnum.Int64? timestamp,
$core.String? quoteMessageId,
$core.List<$core.int>? downloadToken, $core.List<$core.int>? downloadToken,
$core.List<$core.int>? encryptionKey, $core.List<$core.int>? encryptionKey,
$core.List<$core.int>? encryptionMac, $core.List<$core.int>? encryptionMac,
@ -486,6 +510,12 @@ class EncryptedContent_Media extends $pb.GeneratedMessage {
if (requiresAuthentication != null) { if (requiresAuthentication != null) {
$result.requiresAuthentication = requiresAuthentication; $result.requiresAuthentication = requiresAuthentication;
} }
if (timestamp != null) {
$result.timestamp = timestamp;
}
if (quoteMessageId != null) {
$result.quoteMessageId = quoteMessageId;
}
if (downloadToken != null) { if (downloadToken != null) {
$result.downloadToken = downloadToken; $result.downloadToken = downloadToken;
} }
@ -506,13 +536,15 @@ class EncryptedContent_Media extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedContent.Media', createEmptyInstance: create) static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedContent.Media', createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'senderMessageId', protoName: 'senderMessageId') ..aOS(1, _omitFieldNames ? '' : 'senderMessageId', protoName: 'senderMessageId')
..e<EncryptedContent_Media_Type>(2, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, defaultOrMaker: EncryptedContent_Media_Type.IMAGE, valueOf: EncryptedContent_Media_Type.valueOf, enumValues: EncryptedContent_Media_Type.values) ..e<EncryptedContent_Media_Type>(2, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, defaultOrMaker: EncryptedContent_Media_Type.REUPLOAD, valueOf: EncryptedContent_Media_Type.valueOf, enumValues: EncryptedContent_Media_Type.values)
..aInt64(3, _omitFieldNames ? '' : 'displayLimitInMilliseconds', protoName: 'displayLimitInMilliseconds') ..aInt64(3, _omitFieldNames ? '' : 'displayLimitInMilliseconds', protoName: 'displayLimitInMilliseconds')
..aOB(4, _omitFieldNames ? '' : 'requiresAuthentication', protoName: 'requiresAuthentication') ..aOB(4, _omitFieldNames ? '' : 'requiresAuthentication', protoName: 'requiresAuthentication')
..a<$core.List<$core.int>>(5, _omitFieldNames ? '' : 'downloadToken', $pb.PbFieldType.OY, protoName: 'downloadToken') ..aInt64(5, _omitFieldNames ? '' : 'timestamp')
..a<$core.List<$core.int>>(6, _omitFieldNames ? '' : 'encryptionKey', $pb.PbFieldType.OY, protoName: 'encryptionKey') ..aOS(6, _omitFieldNames ? '' : 'quoteMessageId', protoName: 'quoteMessageId')
..a<$core.List<$core.int>>(7, _omitFieldNames ? '' : 'encryptionMac', $pb.PbFieldType.OY, protoName: 'encryptionMac') ..a<$core.List<$core.int>>(7, _omitFieldNames ? '' : 'downloadToken', $pb.PbFieldType.OY, protoName: 'downloadToken')
..a<$core.List<$core.int>>(8, _omitFieldNames ? '' : 'encryptionNonce', $pb.PbFieldType.OY, protoName: 'encryptionNonce') ..a<$core.List<$core.int>>(8, _omitFieldNames ? '' : 'encryptionKey', $pb.PbFieldType.OY, protoName: 'encryptionKey')
..a<$core.List<$core.int>>(9, _omitFieldNames ? '' : 'encryptionMac', $pb.PbFieldType.OY, protoName: 'encryptionMac')
..a<$core.List<$core.int>>(10, _omitFieldNames ? '' : 'encryptionNonce', $pb.PbFieldType.OY, protoName: 'encryptionNonce')
..hasRequiredFields = false ..hasRequiredFields = false
; ;
@ -574,40 +606,58 @@ class EncryptedContent_Media extends $pb.GeneratedMessage {
void clearRequiresAuthentication() => clearField(4); void clearRequiresAuthentication() => clearField(4);
@$pb.TagNumber(5) @$pb.TagNumber(5)
$core.List<$core.int> get downloadToken => $_getN(4); $fixnum.Int64 get timestamp => $_getI64(4);
@$pb.TagNumber(5) @$pb.TagNumber(5)
set downloadToken($core.List<$core.int> v) { $_setBytes(4, v); } set timestamp($fixnum.Int64 v) { $_setInt64(4, v); }
@$pb.TagNumber(5) @$pb.TagNumber(5)
$core.bool hasDownloadToken() => $_has(4); $core.bool hasTimestamp() => $_has(4);
@$pb.TagNumber(5) @$pb.TagNumber(5)
void clearDownloadToken() => clearField(5); void clearTimestamp() => clearField(5);
@$pb.TagNumber(6) @$pb.TagNumber(6)
$core.List<$core.int> get encryptionKey => $_getN(5); $core.String get quoteMessageId => $_getSZ(5);
@$pb.TagNumber(6) @$pb.TagNumber(6)
set encryptionKey($core.List<$core.int> v) { $_setBytes(5, v); } set quoteMessageId($core.String v) { $_setString(5, v); }
@$pb.TagNumber(6) @$pb.TagNumber(6)
$core.bool hasEncryptionKey() => $_has(5); $core.bool hasQuoteMessageId() => $_has(5);
@$pb.TagNumber(6) @$pb.TagNumber(6)
void clearEncryptionKey() => clearField(6); void clearQuoteMessageId() => clearField(6);
@$pb.TagNumber(7) @$pb.TagNumber(7)
$core.List<$core.int> get encryptionMac => $_getN(6); $core.List<$core.int> get downloadToken => $_getN(6);
@$pb.TagNumber(7) @$pb.TagNumber(7)
set encryptionMac($core.List<$core.int> v) { $_setBytes(6, v); } set downloadToken($core.List<$core.int> v) { $_setBytes(6, v); }
@$pb.TagNumber(7) @$pb.TagNumber(7)
$core.bool hasEncryptionMac() => $_has(6); $core.bool hasDownloadToken() => $_has(6);
@$pb.TagNumber(7) @$pb.TagNumber(7)
void clearEncryptionMac() => clearField(7); void clearDownloadToken() => clearField(7);
@$pb.TagNumber(8) @$pb.TagNumber(8)
$core.List<$core.int> get encryptionNonce => $_getN(7); $core.List<$core.int> get encryptionKey => $_getN(7);
@$pb.TagNumber(8) @$pb.TagNumber(8)
set encryptionNonce($core.List<$core.int> v) { $_setBytes(7, v); } set encryptionKey($core.List<$core.int> v) { $_setBytes(7, v); }
@$pb.TagNumber(8) @$pb.TagNumber(8)
$core.bool hasEncryptionNonce() => $_has(7); $core.bool hasEncryptionKey() => $_has(7);
@$pb.TagNumber(8) @$pb.TagNumber(8)
void clearEncryptionNonce() => clearField(8); void clearEncryptionKey() => clearField(8);
@$pb.TagNumber(9)
$core.List<$core.int> get encryptionMac => $_getN(8);
@$pb.TagNumber(9)
set encryptionMac($core.List<$core.int> v) { $_setBytes(8, v); }
@$pb.TagNumber(9)
$core.bool hasEncryptionMac() => $_has(8);
@$pb.TagNumber(9)
void clearEncryptionMac() => clearField(9);
@$pb.TagNumber(10)
$core.List<$core.int> get encryptionNonce => $_getN(9);
@$pb.TagNumber(10)
set encryptionNonce($core.List<$core.int> v) { $_setBytes(9, v); }
@$pb.TagNumber(10)
$core.bool hasEncryptionNonce() => $_has(9);
@$pb.TagNumber(10)
void clearEncryptionNonce() => clearField(10);
} }
class EncryptedContent_MediaUpdate extends $pb.GeneratedMessage { class EncryptedContent_MediaUpdate extends $pb.GeneratedMessage {
@ -807,6 +857,7 @@ class EncryptedContent_PushKeys extends $pb.GeneratedMessage {
EncryptedContent_PushKeys_Type? type, EncryptedContent_PushKeys_Type? type,
$fixnum.Int64? keyId, $fixnum.Int64? keyId,
$core.List<$core.int>? key, $core.List<$core.int>? key,
$fixnum.Int64? createdAt,
}) { }) {
final $result = create(); final $result = create();
if (type != null) { if (type != null) {
@ -818,6 +869,9 @@ class EncryptedContent_PushKeys extends $pb.GeneratedMessage {
if (key != null) { if (key != null) {
$result.key = key; $result.key = key;
} }
if (createdAt != null) {
$result.createdAt = createdAt;
}
return $result; return $result;
} }
EncryptedContent_PushKeys._() : super(); EncryptedContent_PushKeys._() : super();
@ -828,6 +882,7 @@ class EncryptedContent_PushKeys extends $pb.GeneratedMessage {
..e<EncryptedContent_PushKeys_Type>(1, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, defaultOrMaker: EncryptedContent_PushKeys_Type.REQUEST, valueOf: EncryptedContent_PushKeys_Type.valueOf, enumValues: EncryptedContent_PushKeys_Type.values) ..e<EncryptedContent_PushKeys_Type>(1, _omitFieldNames ? '' : 'type', $pb.PbFieldType.OE, defaultOrMaker: EncryptedContent_PushKeys_Type.REQUEST, valueOf: EncryptedContent_PushKeys_Type.valueOf, enumValues: EncryptedContent_PushKeys_Type.values)
..aInt64(2, _omitFieldNames ? '' : 'keyId', protoName: 'keyId') ..aInt64(2, _omitFieldNames ? '' : 'keyId', protoName: 'keyId')
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'key', $pb.PbFieldType.OY) ..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'key', $pb.PbFieldType.OY)
..aInt64(4, _omitFieldNames ? '' : 'createdAt', protoName: 'createdAt')
..hasRequiredFields = false ..hasRequiredFields = false
; ;
@ -878,6 +933,15 @@ class EncryptedContent_PushKeys extends $pb.GeneratedMessage {
$core.bool hasKey() => $_has(2); $core.bool hasKey() => $_has(2);
@$pb.TagNumber(3) @$pb.TagNumber(3)
void clearKey() => clearField(3); void clearKey() => clearField(3);
@$pb.TagNumber(4)
$fixnum.Int64 get createdAt => $_getI64(3);
@$pb.TagNumber(4)
set createdAt($fixnum.Int64 v) { $_setInt64(3, v); }
@$pb.TagNumber(4)
$core.bool hasCreatedAt() => $_has(3);
@$pb.TagNumber(4)
void clearCreatedAt() => clearField(4);
} }
class EncryptedContent_FlameSync extends $pb.GeneratedMessage { class EncryptedContent_FlameSync extends $pb.GeneratedMessage {

View file

@ -18,12 +18,14 @@ class Message_Type extends $pb.ProtobufEnum {
static const Message_Type PLAINTEXT_CONTENT = Message_Type._(1, _omitEnumNames ? '' : 'PLAINTEXT_CONTENT'); static const Message_Type PLAINTEXT_CONTENT = Message_Type._(1, _omitEnumNames ? '' : 'PLAINTEXT_CONTENT');
static const Message_Type CIPHERTEXT = Message_Type._(2, _omitEnumNames ? '' : 'CIPHERTEXT'); static const Message_Type CIPHERTEXT = Message_Type._(2, _omitEnumNames ? '' : 'CIPHERTEXT');
static const Message_Type PREKEY_BUNDLE = Message_Type._(3, _omitEnumNames ? '' : 'PREKEY_BUNDLE'); static const Message_Type PREKEY_BUNDLE = Message_Type._(3, _omitEnumNames ? '' : 'PREKEY_BUNDLE');
static const Message_Type TEST_NOTIFICATION = Message_Type._(4, _omitEnumNames ? '' : 'TEST_NOTIFICATION');
static const $core.List<Message_Type> values = <Message_Type> [ static const $core.List<Message_Type> values = <Message_Type> [
SENDER_DELIVERY_RECEIPT, SENDER_DELIVERY_RECEIPT,
PLAINTEXT_CONTENT, PLAINTEXT_CONTENT,
CIPHERTEXT, CIPHERTEXT,
PREKEY_BUNDLE, PREKEY_BUNDLE,
TEST_NOTIFICATION,
]; ];
static final $core.Map<$core.int, Message_Type> _byValue = $pb.ProtobufEnum.initByValue(values); static final $core.Map<$core.int, Message_Type> _byValue = $pb.ProtobufEnum.initByValue(values);
@ -65,11 +67,13 @@ class EncryptedContent_MessageUpdate_Type extends $pb.ProtobufEnum {
} }
class EncryptedContent_Media_Type extends $pb.ProtobufEnum { class EncryptedContent_Media_Type extends $pb.ProtobufEnum {
static const EncryptedContent_Media_Type IMAGE = EncryptedContent_Media_Type._(0, _omitEnumNames ? '' : 'IMAGE'); static const EncryptedContent_Media_Type REUPLOAD = EncryptedContent_Media_Type._(0, _omitEnumNames ? '' : 'REUPLOAD');
static const EncryptedContent_Media_Type VIDEO = EncryptedContent_Media_Type._(1, _omitEnumNames ? '' : 'VIDEO'); static const EncryptedContent_Media_Type IMAGE = EncryptedContent_Media_Type._(1, _omitEnumNames ? '' : 'IMAGE');
static const EncryptedContent_Media_Type GIF = EncryptedContent_Media_Type._(2, _omitEnumNames ? '' : 'GIF'); static const EncryptedContent_Media_Type VIDEO = EncryptedContent_Media_Type._(2, _omitEnumNames ? '' : 'VIDEO');
static const EncryptedContent_Media_Type GIF = EncryptedContent_Media_Type._(3, _omitEnumNames ? '' : 'GIF');
static const $core.List<EncryptedContent_Media_Type> values = <EncryptedContent_Media_Type> [ static const $core.List<EncryptedContent_Media_Type> values = <EncryptedContent_Media_Type> [
REUPLOAD,
IMAGE, IMAGE,
VIDEO, VIDEO,
GIF, GIF,

View file

@ -37,6 +37,7 @@ const Message_Type$json = {
{'1': 'PLAINTEXT_CONTENT', '2': 1}, {'1': 'PLAINTEXT_CONTENT', '2': 1},
{'1': 'CIPHERTEXT', '2': 2}, {'1': 'CIPHERTEXT', '2': 2},
{'1': 'PREKEY_BUNDLE', '2': 3}, {'1': 'PREKEY_BUNDLE', '2': 3},
{'1': 'TEST_NOTIFICATION', '2': 4},
], ],
}; };
@ -45,9 +46,10 @@ final $typed_data.Uint8List messageDescriptor = $convert.base64Decode(
'CgdNZXNzYWdlEiEKBHR5cGUYASABKA4yDS5NZXNzYWdlLlR5cGVSBHR5cGUSHAoJcmVjZWlwdE' 'CgdNZXNzYWdlEiEKBHR5cGUYASABKA4yDS5NZXNzYWdlLlR5cGVSBHR5cGUSHAoJcmVjZWlwdE'
'lkGAIgASgJUglyZWNlaXB0SWQSLwoQZW5jcnlwdGVkQ29udGVudBgDIAEoDEgAUhBlbmNyeXB0' 'lkGAIgASgJUglyZWNlaXB0SWQSLwoQZW5jcnlwdGVkQ29udGVudBgDIAEoDEgAUhBlbmNyeXB0'
'ZWRDb250ZW50iAEBEkIKEHBsYWludGV4dENvbnRlbnQYBCABKAsyES5QbGFpbnRleHRDb250ZW' 'ZWRDb250ZW50iAEBEkIKEHBsYWludGV4dENvbnRlbnQYBCABKAsyES5QbGFpbnRleHRDb250ZW'
'50SAFSEHBsYWludGV4dENvbnRlbnSIAQEiXQoEVHlwZRIbChdTRU5ERVJfREVMSVZFUllfUkVD' '50SAFSEHBsYWludGV4dENvbnRlbnSIAQEidAoEVHlwZRIbChdTRU5ERVJfREVMSVZFUllfUkVD'
'RUlQVBAAEhUKEVBMQUlOVEVYVF9DT05URU5UEAESDgoKQ0lQSEVSVEVYVBACEhEKDVBSRUtFWV' 'RUlQVBAAEhUKEVBMQUlOVEVYVF9DT05URU5UEAESDgoKQ0lQSEVSVEVYVBACEhEKDVBSRUtFWV'
'9CVU5ETEUQA0ITChFfZW5jcnlwdGVkQ29udGVudEITChFfcGxhaW50ZXh0Q29udGVudA=='); '9CVU5ETEUQAxIVChFURVNUX05PVElGSUNBVElPThAEQhMKEV9lbmNyeXB0ZWRDb250ZW50QhMK'
'EV9wbGFpbnRleHRDb250ZW50');
@$core.Deprecated('Use plaintextContentDescriptor instead') @$core.Deprecated('Use plaintextContentDescriptor instead')
const PlaintextContent$json = { const PlaintextContent$json = {
@ -126,7 +128,8 @@ const EncryptedContent_TextMessage$json = {
'2': [ '2': [
{'1': 'senderMessageId', '3': 1, '4': 1, '5': 9, '10': 'senderMessageId'}, {'1': 'senderMessageId', '3': 1, '4': 1, '5': 9, '10': 'senderMessageId'},
{'1': 'text', '3': 2, '4': 1, '5': 9, '10': 'text'}, {'1': 'text', '3': 2, '4': 1, '5': 9, '10': 'text'},
{'1': 'quoteMessageId', '3': 3, '4': 1, '5': 9, '9': 0, '10': 'quoteMessageId', '17': true}, {'1': 'timestamp', '3': 3, '4': 1, '5': 3, '10': 'timestamp'},
{'1': 'quoteMessageId', '3': 4, '4': 1, '5': 9, '9': 0, '10': 'quoteMessageId', '17': true},
], ],
'8': [ '8': [
{'1': '_quoteMessageId'}, {'1': '_quoteMessageId'},
@ -152,14 +155,15 @@ const EncryptedContent_MessageUpdate$json = {
'1': 'MessageUpdate', '1': 'MessageUpdate',
'2': [ '2': [
{'1': 'type', '3': 1, '4': 1, '5': 14, '6': '.EncryptedContent.MessageUpdate.Type', '10': 'type'}, {'1': 'type', '3': 1, '4': 1, '5': 14, '6': '.EncryptedContent.MessageUpdate.Type', '10': 'type'},
{'1': 'senderMessageId', '3': 2, '4': 1, '5': 9, '10': 'senderMessageId'}, {'1': 'senderMessageId', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'senderMessageId', '17': true},
{'1': 'text', '3': 3, '4': 1, '5': 9, '9': 0, '10': 'text', '17': true}, {'1': 'multipleSenderMessageIds', '3': 3, '4': 3, '5': 9, '10': 'multipleSenderMessageIds'},
{'1': 'timestamp', '3': 4, '4': 1, '5': 3, '9': 1, '10': 'timestamp', '17': true}, {'1': 'text', '3': 4, '4': 1, '5': 9, '9': 1, '10': 'text', '17': true},
{'1': 'timestamp', '3': 5, '4': 1, '5': 3, '10': 'timestamp'},
], ],
'4': [EncryptedContent_MessageUpdate_Type$json], '4': [EncryptedContent_MessageUpdate_Type$json],
'8': [ '8': [
{'1': '_senderMessageId'},
{'1': '_text'}, {'1': '_text'},
{'1': '_timestamp'},
], ],
}; };
@ -181,14 +185,17 @@ const EncryptedContent_Media$json = {
{'1': 'type', '3': 2, '4': 1, '5': 14, '6': '.EncryptedContent.Media.Type', '10': 'type'}, {'1': 'type', '3': 2, '4': 1, '5': 14, '6': '.EncryptedContent.Media.Type', '10': 'type'},
{'1': 'displayLimitInMilliseconds', '3': 3, '4': 1, '5': 3, '9': 0, '10': 'displayLimitInMilliseconds', '17': true}, {'1': 'displayLimitInMilliseconds', '3': 3, '4': 1, '5': 3, '9': 0, '10': 'displayLimitInMilliseconds', '17': true},
{'1': 'requiresAuthentication', '3': 4, '4': 1, '5': 8, '10': 'requiresAuthentication'}, {'1': 'requiresAuthentication', '3': 4, '4': 1, '5': 8, '10': 'requiresAuthentication'},
{'1': 'downloadToken', '3': 5, '4': 1, '5': 12, '9': 1, '10': 'downloadToken', '17': true}, {'1': 'timestamp', '3': 5, '4': 1, '5': 3, '10': 'timestamp'},
{'1': 'encryptionKey', '3': 6, '4': 1, '5': 12, '9': 2, '10': 'encryptionKey', '17': true}, {'1': 'quoteMessageId', '3': 6, '4': 1, '5': 9, '9': 1, '10': 'quoteMessageId', '17': true},
{'1': 'encryptionMac', '3': 7, '4': 1, '5': 12, '9': 3, '10': 'encryptionMac', '17': true}, {'1': 'downloadToken', '3': 7, '4': 1, '5': 12, '9': 2, '10': 'downloadToken', '17': true},
{'1': 'encryptionNonce', '3': 8, '4': 1, '5': 12, '9': 4, '10': 'encryptionNonce', '17': true}, {'1': 'encryptionKey', '3': 8, '4': 1, '5': 12, '9': 3, '10': 'encryptionKey', '17': true},
{'1': 'encryptionMac', '3': 9, '4': 1, '5': 12, '9': 4, '10': 'encryptionMac', '17': true},
{'1': 'encryptionNonce', '3': 10, '4': 1, '5': 12, '9': 5, '10': 'encryptionNonce', '17': true},
], ],
'4': [EncryptedContent_Media_Type$json], '4': [EncryptedContent_Media_Type$json],
'8': [ '8': [
{'1': '_displayLimitInMilliseconds'}, {'1': '_displayLimitInMilliseconds'},
{'1': '_quoteMessageId'},
{'1': '_downloadToken'}, {'1': '_downloadToken'},
{'1': '_encryptionKey'}, {'1': '_encryptionKey'},
{'1': '_encryptionMac'}, {'1': '_encryptionMac'},
@ -200,9 +207,10 @@ const EncryptedContent_Media$json = {
const EncryptedContent_Media_Type$json = { const EncryptedContent_Media_Type$json = {
'1': 'Type', '1': 'Type',
'2': [ '2': [
{'1': 'IMAGE', '2': 0}, {'1': 'REUPLOAD', '2': 0},
{'1': 'VIDEO', '2': 1}, {'1': 'IMAGE', '2': 1},
{'1': 'GIF', '2': 2}, {'1': 'VIDEO', '2': 2},
{'1': 'GIF', '2': 3},
], ],
}; };
@ -276,11 +284,13 @@ const EncryptedContent_PushKeys$json = {
{'1': 'type', '3': 1, '4': 1, '5': 14, '6': '.EncryptedContent.PushKeys.Type', '10': 'type'}, {'1': 'type', '3': 1, '4': 1, '5': 14, '6': '.EncryptedContent.PushKeys.Type', '10': 'type'},
{'1': 'keyId', '3': 2, '4': 1, '5': 3, '9': 0, '10': 'keyId', '17': true}, {'1': 'keyId', '3': 2, '4': 1, '5': 3, '9': 0, '10': 'keyId', '17': true},
{'1': 'key', '3': 3, '4': 1, '5': 12, '9': 1, '10': 'key', '17': true}, {'1': 'key', '3': 3, '4': 1, '5': 12, '9': 1, '10': 'key', '17': true},
{'1': 'createdAt', '3': 4, '4': 1, '5': 3, '9': 2, '10': 'createdAt', '17': true},
], ],
'4': [EncryptedContent_PushKeys_Type$json], '4': [EncryptedContent_PushKeys_Type$json],
'8': [ '8': [
{'1': '_keyId'}, {'1': '_keyId'},
{'1': '_key'}, {'1': '_key'},
{'1': '_createdAt'},
], ],
}; };
@ -317,42 +327,47 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
'N0UmVxdWVzdEgHUg5jb250YWN0UmVxdWVzdIgBARI+CglmbGFtZVN5bmMYCiABKAsyGy5FbmNy' 'N0UmVxdWVzdEgHUg5jb250YWN0UmVxdWVzdIgBARI+CglmbGFtZVN5bmMYCiABKAsyGy5FbmNy'
'eXB0ZWRDb250ZW50LkZsYW1lU3luY0gIUglmbGFtZVN5bmOIAQESOwoIcHVzaEtleXMYCyABKA' 'eXB0ZWRDb250ZW50LkZsYW1lU3luY0gIUglmbGFtZVN5bmOIAQESOwoIcHVzaEtleXMYCyABKA'
'syGi5FbmNyeXB0ZWRDb250ZW50LlB1c2hLZXlzSAlSCHB1c2hLZXlziAEBEjsKCHJlYWN0aW9u' 'syGi5FbmNyeXB0ZWRDb250ZW50LlB1c2hLZXlzSAlSCHB1c2hLZXlziAEBEjsKCHJlYWN0aW9u'
'GAwgASgLMhouRW5jcnlwdGVkQ29udGVudC5SZWFjdGlvbkgKUghyZWFjdGlvbogBARqLAQoLVG' 'GAwgASgLMhouRW5jcnlwdGVkQ29udGVudC5SZWFjdGlvbkgKUghyZWFjdGlvbogBARqpAQoLVG'
'V4dE1lc3NhZ2USKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSEgoE' 'V4dE1lc3NhZ2USKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSEgoE'
'dGV4dBgCIAEoCVIEdGV4dBIrCg5xdW90ZU1lc3NhZ2VJZBgDIAEoCUgAUg5xdW90ZU1lc3NhZ2' 'dGV4dBgCIAEoCVIEdGV4dBIcCgl0aW1lc3RhbXAYAyABKANSCXRpbWVzdGFtcBIrCg5xdW90ZU'
'VJZIgBAUIRCg9fcXVvdGVNZXNzYWdlSWQagQEKCFJlYWN0aW9uEigKD3RhcmdldE1lc3NhZ2VJ' '1lc3NhZ2VJZBgEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBAUIRCg9fcXVvdGVNZXNzYWdlSWQa'
'ZBgBIAEoCVIPdGFyZ2V0TWVzc2FnZUlkEhkKBWVtb2ppGAIgASgJSABSBWVtb2ppiAEBEhsKBn' 'gQEKCFJlYWN0aW9uEigKD3RhcmdldE1lc3NhZ2VJZBgBIAEoCVIPdGFyZ2V0TWVzc2FnZUlkEh'
'JlbW92ZRgDIAEoCEgBUgZyZW1vdmWIAQFCCAoGX2Vtb2ppQgkKB19yZW1vdmUa9QEKDU1lc3Nh' 'kKBWVtb2ppGAIgASgJSABSBWVtb2ppiAEBEhsKBnJlbW92ZRgDIAEoCEgBUgZyZW1vdmWIAQFC'
'Z2VVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuTWVzc2FnZVVwZGF0ZS' 'CAoGX2Vtb2ppQgkKB19yZW1vdmUatwIKDU1lc3NhZ2VVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLk'
'5UeXBlUgR0eXBlEigKD3NlbmRlck1lc3NhZ2VJZBgCIAEoCVIPc2VuZGVyTWVzc2FnZUlkEhcK' 'VuY3J5cHRlZENvbnRlbnQuTWVzc2FnZVVwZGF0ZS5UeXBlUgR0eXBlEi0KD3NlbmRlck1lc3Nh'
'BHRleHQYAyABKAlIAFIEdGV4dIgBARIhCgl0aW1lc3RhbXAYBCABKANIAVIJdGltZXN0YW1wiA' 'Z2VJZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSWSIAQESOgoYbXVsdGlwbGVTZW5kZXJNZXNzYW'
'EBIi0KBFR5cGUSCgoGREVMRVRFEAASDQoJRURJVF9URVhUEAESCgoGT1BFTkVEEAJCBwoFX3Rl' 'dlSWRzGAMgAygJUhhtdWx0aXBsZVNlbmRlck1lc3NhZ2VJZHMSFwoEdGV4dBgEIAEoCUgBUgR0'
'eHRCDAoKX3RpbWVzdGFtcBqgBAoFTWVkaWESKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW' 'ZXh0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wIi0KBFR5cGUSCgoGREVMRVRFEA'
'5kZXJNZXNzYWdlSWQSMAoEdHlwZRgCIAEoDjIcLkVuY3J5cHRlZENvbnRlbnQuTWVkaWEuVHlw' 'ASDQoJRURJVF9URVhUEAESCgoGT1BFTkVEEAJCEgoQX3NlbmRlck1lc3NhZ2VJZEIHCgVfdGV4'
'ZVIEdHlwZRJDChpkaXNwbGF5TGltaXRJbk1pbGxpc2Vjb25kcxgDIAEoA0gAUhpkaXNwbGF5TG' 'dBqMBQoFTWVkaWESKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSMA'
'ltaXRJbk1pbGxpc2Vjb25kc4gBARI2ChZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uGAQgASgIUhZy' 'oEdHlwZRgCIAEoDjIcLkVuY3J5cHRlZENvbnRlbnQuTWVkaWEuVHlwZVIEdHlwZRJDChpkaXNw'
'ZXF1aXJlc0F1dGhlbnRpY2F0aW9uEikKDWRvd25sb2FkVG9rZW4YBSABKAxIAVINZG93bmxvYW' 'bGF5TGltaXRJbk1pbGxpc2Vjb25kcxgDIAEoA0gAUhpkaXNwbGF5TGltaXRJbk1pbGxpc2Vjb2'
'RUb2tlbogBARIpCg1lbmNyeXB0aW9uS2V5GAYgASgMSAJSDWVuY3J5cHRpb25LZXmIAQESKQoN' '5kc4gBARI2ChZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uGAQgASgIUhZyZXF1aXJlc0F1dGhlbnRp'
'ZW5jcnlwdGlvbk1hYxgHIAEoDEgDUg1lbmNyeXB0aW9uTWFjiAEBEi0KD2VuY3J5cHRpb25Ob2' 'Y2F0aW9uEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTWVzc2FnZUlkGA'
'5jZRgIIAEoDEgEUg9lbmNyeXB0aW9uTm9uY2WIAQEiJQoEVHlwZRIJCgVJTUFHRRAAEgkKBVZJ' 'YgASgJSAFSDnF1b3RlTWVzc2FnZUlkiAEBEikKDWRvd25sb2FkVG9rZW4YByABKAxIAlINZG93'
'REVPEAESBwoDR0lGEAJCHQobX2Rpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRzQhAKDl9kb3dubG' 'bmxvYWRUb2tlbogBARIpCg1lbmNyeXB0aW9uS2V5GAggASgMSANSDWVuY3J5cHRpb25LZXmIAQ'
'9hZFRva2VuQhAKDl9lbmNyeXB0aW9uS2V5QhAKDl9lbmNyeXB0aW9uTWFjQhIKEF9lbmNyeXB0' 'ESKQoNZW5jcnlwdGlvbk1hYxgJIAEoDEgEUg1lbmNyeXB0aW9uTWFjiAEBEi0KD2VuY3J5cHRp'
'aW9uTm9uY2UapwEKC01lZGlhVXBkYXRlEjYKBHR5cGUYASABKA4yIi5FbmNyeXB0ZWRDb250ZW' 'b25Ob25jZRgKIAEoDEgFUg9lbmNyeXB0aW9uTm9uY2WIAQEiMwoEVHlwZRIMCghSRVVQTE9BRB'
'50Lk1lZGlhVXBkYXRlLlR5cGVSBHR5cGUSKAoPdGFyZ2V0TWVzc2FnZUlkGAIgASgJUg90YXJn' 'AAEgkKBUlNQUdFEAESCQoFVklERU8QAhIHCgNHSUYQA0IdChtfZGlzcGxheUxpbWl0SW5NaWxs'
'ZXRNZXNzYWdlSWQiNgoEVHlwZRIMCghSRU9QRU5FRBAAEgoKBlNUT1JFRBABEhQKEERFQ1JZUF' 'aXNlY29uZHNCEQoPX3F1b3RlTWVzc2FnZUlkQhAKDl9kb3dubG9hZFRva2VuQhAKDl9lbmNyeX'
'RJT05fRVJST1IQAhp4Cg5Db250YWN0UmVxdWVzdBI5CgR0eXBlGAEgASgOMiUuRW5jcnlwdGVk' 'B0aW9uS2V5QhAKDl9lbmNyeXB0aW9uTWFjQhIKEF9lbmNyeXB0aW9uTm9uY2UapwEKC01lZGlh'
'Q29udGVudC5Db250YWN0UmVxdWVzdC5UeXBlUgR0eXBlIisKBFR5cGUSCwoHUkVRVUVTVBAAEg' 'VXBkYXRlEjYKBHR5cGUYASABKA4yIi5FbmNyeXB0ZWRDb250ZW50Lk1lZGlhVXBkYXRlLlR5cG'
'oKBlJFSkVDVBABEgoKBkFDQ0VQVBACGtIBCg1Db250YWN0VXBkYXRlEjgKBHR5cGUYASABKA4y' 'VSBHR5cGUSKAoPdGFyZ2V0TWVzc2FnZUlkGAIgASgJUg90YXJnZXRNZXNzYWdlSWQiNgoEVHlw'
'JC5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RVcGRhdGUuVHlwZVIEdHlwZRIhCglhdmF0YXJTdm' 'ZRIMCghSRU9QRU5FRBAAEgoKBlNUT1JFRBABEhQKEERFQ1JZUFRJT05fRVJST1IQAhp4Cg5Db2'
'cYAiABKAlIAFIJYXZhdGFyU3ZniAEBEiUKC2Rpc3BsYXlOYW1lGAMgASgJSAFSC2Rpc3BsYXlO' '50YWN0UmVxdWVzdBI5CgR0eXBlGAEgASgOMiUuRW5jcnlwdGVkQ29udGVudC5Db250YWN0UmVx'
'YW1liAEBIh8KBFR5cGUSCwoHUkVRVUVTVBAAEgoKBlVQREFURRABQgwKCl9hdmF0YXJTdmdCDg' 'dWVzdC5UeXBlUgR0eXBlIisKBFR5cGUSCwoHUkVRVUVTVBAAEgoKBlJFSkVDVBABEgoKBkFDQ0'
'oMX2Rpc3BsYXlOYW1lGqQBCghQdXNoS2V5cxIzCgR0eXBlGAEgASgOMh8uRW5jcnlwdGVkQ29u' 'VQVBACGtIBCg1Db250YWN0VXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50'
'dGVudC5QdXNoS2V5cy5UeXBlUgR0eXBlEhkKBWtleUlkGAIgASgDSABSBWtleUlkiAEBEhUKA2' 'LkNvbnRhY3RVcGRhdGUuVHlwZVIEdHlwZRIhCglhdmF0YXJTdmcYAiABKAlIAFIJYXZhdGFyU3'
'tleRgDIAEoDEgBUgNrZXmIAQEiHwoEVHlwZRILCgdSRVFVRVNUEAASCgoGVVBEQVRFEAFCCAoG' 'ZniAEBEiUKC2Rpc3BsYXlOYW1lGAMgASgJSAFSC2Rpc3BsYXlOYW1liAEBIh8KBFR5cGUSCwoH'
'X2tleUlkQgYKBF9rZXkahwEKCUZsYW1lU3luYxIiCgxmbGFtZUNvdW50ZXIYASABKANSDGZsYW' 'UkVRVUVTVBAAEgoKBlVQREFURRABQgwKCl9hdmF0YXJTdmdCDgoMX2Rpc3BsYXlOYW1lGtUBCg'
'1lQ291bnRlchI2ChZsYXN0RmxhbWVDb3VudGVyQ2hhbmdlGAIgASgDUhZsYXN0RmxhbWVDb3Vu' 'hQdXNoS2V5cxIzCgR0eXBlGAEgASgOMh8uRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5cy5UeXBl'
'dGVyQ2hhbmdlEh4KCmJlc3RGcmllbmQYAyABKAhSCmJlc3RGcmllbmRCCgoIX2dyb3VwSWRCFw' 'UgR0eXBlEhkKBWtleUlkGAIgASgDSABSBWtleUlkiAEBEhUKA2tleRgDIAEoDEgBUgNrZXmIAQ'
'oVX3NlbmRlclByb2ZpbGVDb3VudGVyQg4KDF90ZXh0TWVzc2FnZUIQCg5fbWVzc2FnZVVwZGF0' 'ESIQoJY3JlYXRlZEF0GAQgASgDSAJSCWNyZWF0ZWRBdIgBASIfCgRUeXBlEgsKB1JFUVVFU1QQ'
'ZUIICgZfbWVkaWFCDgoMX21lZGlhVXBkYXRlQhAKDl9jb250YWN0VXBkYXRlQhEKD19jb250YW' 'ABIKCgZVUERBVEUQAUIICgZfa2V5SWRCBgoEX2tleUIMCgpfY3JlYXRlZEF0GocBCglGbGFtZV'
'N0UmVxdWVzdEIMCgpfZmxhbWVTeW5jQgsKCV9wdXNoS2V5c0ILCglfcmVhY3Rpb24='); 'N5bmMSIgoMZmxhbWVDb3VudGVyGAEgASgDUgxmbGFtZUNvdW50ZXISNgoWbGFzdEZsYW1lQ291'
'bnRlckNoYW5nZRgCIAEoA1IWbGFzdEZsYW1lQ291bnRlckNoYW5nZRIeCgpiZXN0RnJpZW5kGA'
'MgASgIUgpiZXN0RnJpZW5kQgoKCF9ncm91cElkQhcKFV9zZW5kZXJQcm9maWxlQ291bnRlckIO'
'CgxfdGV4dE1lc3NhZ2VCEAoOX21lc3NhZ2VVcGRhdGVCCAoGX21lZGlhQg4KDF9tZWRpYVVwZG'
'F0ZUIQCg5fY29udGFjdFVwZGF0ZUIRCg9fY29udGFjdFJlcXVlc3RCDAoKX2ZsYW1lU3luY0IL'
'CglfcHVzaEtleXNCCwoJX3JlYWN0aW9u');

View file

@ -113,7 +113,7 @@ class EncryptedPushNotification extends $pb.GeneratedMessage {
class PushNotification extends $pb.GeneratedMessage { class PushNotification extends $pb.GeneratedMessage {
factory PushNotification({ factory PushNotification({
PushKind? kind, PushKind? kind,
$fixnum.Int64? messageId, $core.String? messageId,
$core.String? reactionContent, $core.String? reactionContent,
}) { }) {
final $result = create(); final $result = create();
@ -134,7 +134,7 @@ class PushNotification extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PushNotification', createEmptyInstance: create) static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PushNotification', createEmptyInstance: create)
..e<PushKind>(1, _omitFieldNames ? '' : 'kind', $pb.PbFieldType.OE, defaultOrMaker: PushKind.reaction, valueOf: PushKind.valueOf, enumValues: PushKind.values) ..e<PushKind>(1, _omitFieldNames ? '' : 'kind', $pb.PbFieldType.OE, defaultOrMaker: PushKind.reaction, valueOf: PushKind.valueOf, enumValues: PushKind.values)
..aInt64(2, _omitFieldNames ? '' : 'messageId', protoName: 'messageId') ..aOS(2, _omitFieldNames ? '' : 'messageId', protoName: 'messageId')
..aOS(3, _omitFieldNames ? '' : 'reactionContent', protoName: 'reactionContent') ..aOS(3, _omitFieldNames ? '' : 'reactionContent', protoName: 'reactionContent')
..hasRequiredFields = false ..hasRequiredFields = false
; ;
@ -170,9 +170,9 @@ class PushNotification extends $pb.GeneratedMessage {
void clearKind() => clearField(1); void clearKind() => clearField(1);
@$pb.TagNumber(2) @$pb.TagNumber(2)
$fixnum.Int64 get messageId => $_getI64(1); $core.String get messageId => $_getSZ(1);
@$pb.TagNumber(2) @$pb.TagNumber(2)
set messageId($fixnum.Int64 v) { $_setInt64(1, v); } set messageId($core.String v) { $_setString(1, v); }
@$pb.TagNumber(2) @$pb.TagNumber(2)
$core.bool hasMessageId() => $_has(1); $core.bool hasMessageId() => $_has(1);
@$pb.TagNumber(2) @$pb.TagNumber(2)
@ -237,7 +237,7 @@ class PushUser extends $pb.GeneratedMessage {
$fixnum.Int64? userId, $fixnum.Int64? userId,
$core.String? displayName, $core.String? displayName,
$core.bool? blocked, $core.bool? blocked,
$fixnum.Int64? lastMessageId, $core.String? lastMessageId,
$core.Iterable<PushKey>? pushKeys, $core.Iterable<PushKey>? pushKeys,
}) { }) {
final $result = create(); final $result = create();
@ -266,7 +266,7 @@ class PushUser extends $pb.GeneratedMessage {
..aInt64(1, _omitFieldNames ? '' : 'userId', protoName: 'userId') ..aInt64(1, _omitFieldNames ? '' : 'userId', protoName: 'userId')
..aOS(2, _omitFieldNames ? '' : 'displayName', protoName: 'displayName') ..aOS(2, _omitFieldNames ? '' : 'displayName', protoName: 'displayName')
..aOB(3, _omitFieldNames ? '' : 'blocked') ..aOB(3, _omitFieldNames ? '' : 'blocked')
..aInt64(4, _omitFieldNames ? '' : 'lastMessageId', protoName: 'lastMessageId') ..aOS(4, _omitFieldNames ? '' : 'lastMessageId', protoName: 'lastMessageId')
..pc<PushKey>(5, _omitFieldNames ? '' : 'pushKeys', $pb.PbFieldType.PM, protoName: 'pushKeys', subBuilder: PushKey.create) ..pc<PushKey>(5, _omitFieldNames ? '' : 'pushKeys', $pb.PbFieldType.PM, protoName: 'pushKeys', subBuilder: PushKey.create)
..hasRequiredFields = false ..hasRequiredFields = false
; ;
@ -320,9 +320,9 @@ class PushUser extends $pb.GeneratedMessage {
void clearBlocked() => clearField(3); void clearBlocked() => clearField(3);
@$pb.TagNumber(4) @$pb.TagNumber(4)
$fixnum.Int64 get lastMessageId => $_getI64(3); $core.String get lastMessageId => $_getSZ(3);
@$pb.TagNumber(4) @$pb.TagNumber(4)
set lastMessageId($fixnum.Int64 v) { $_setInt64(3, v); } set lastMessageId($core.String v) { $_setString(3, v); }
@$pb.TagNumber(4) @$pb.TagNumber(4)
$core.bool hasLastMessageId() => $_has(3); $core.bool hasLastMessageId() => $_has(3);
@$pb.TagNumber(4) @$pb.TagNumber(4)

View file

@ -64,7 +64,7 @@ const PushNotification$json = {
'1': 'PushNotification', '1': 'PushNotification',
'2': [ '2': [
{'1': 'kind', '3': 1, '4': 1, '5': 14, '6': '.PushKind', '10': 'kind'}, {'1': 'kind', '3': 1, '4': 1, '5': 14, '6': '.PushKind', '10': 'kind'},
{'1': 'messageId', '3': 2, '4': 1, '5': 3, '9': 0, '10': 'messageId', '17': true}, {'1': 'messageId', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'messageId', '17': true},
{'1': 'reactionContent', '3': 3, '4': 1, '5': 9, '9': 1, '10': 'reactionContent', '17': true}, {'1': 'reactionContent', '3': 3, '4': 1, '5': 9, '9': 1, '10': 'reactionContent', '17': true},
], ],
'8': [ '8': [
@ -76,7 +76,7 @@ const PushNotification$json = {
/// Descriptor for `PushNotification`. Decode as a `google.protobuf.DescriptorProto`. /// Descriptor for `PushNotification`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List pushNotificationDescriptor = $convert.base64Decode( final $typed_data.Uint8List pushNotificationDescriptor = $convert.base64Decode(
'ChBQdXNoTm90aWZpY2F0aW9uEh0KBGtpbmQYASABKA4yCS5QdXNoS2luZFIEa2luZBIhCgltZX' 'ChBQdXNoTm90aWZpY2F0aW9uEh0KBGtpbmQYASABKA4yCS5QdXNoS2luZFIEa2luZBIhCgltZX'
'NzYWdlSWQYAiABKANIAFIJbWVzc2FnZUlkiAEBEi0KD3JlYWN0aW9uQ29udGVudBgDIAEoCUgB' 'NzYWdlSWQYAiABKAlIAFIJbWVzc2FnZUlkiAEBEi0KD3JlYWN0aW9uQ29udGVudBgDIAEoCUgB'
'Ug9yZWFjdGlvbkNvbnRlbnSIAQFCDAoKX21lc3NhZ2VJZEISChBfcmVhY3Rpb25Db250ZW50'); 'Ug9yZWFjdGlvbkNvbnRlbnSIAQFCDAoKX21lc3NhZ2VJZEISChBfcmVhY3Rpb25Db250ZW50');
@$core.Deprecated('Use pushUsersDescriptor instead') @$core.Deprecated('Use pushUsersDescriptor instead')
@ -98,7 +98,7 @@ const PushUser$json = {
{'1': 'userId', '3': 1, '4': 1, '5': 3, '10': 'userId'}, {'1': 'userId', '3': 1, '4': 1, '5': 3, '10': 'userId'},
{'1': 'displayName', '3': 2, '4': 1, '5': 9, '10': 'displayName'}, {'1': 'displayName', '3': 2, '4': 1, '5': 9, '10': 'displayName'},
{'1': 'blocked', '3': 3, '4': 1, '5': 8, '10': 'blocked'}, {'1': 'blocked', '3': 3, '4': 1, '5': 8, '10': 'blocked'},
{'1': 'lastMessageId', '3': 4, '4': 1, '5': 3, '9': 0, '10': 'lastMessageId', '17': true}, {'1': 'lastMessageId', '3': 4, '4': 1, '5': 9, '9': 0, '10': 'lastMessageId', '17': true},
{'1': 'pushKeys', '3': 5, '4': 3, '5': 11, '6': '.PushKey', '10': 'pushKeys'}, {'1': 'pushKeys', '3': 5, '4': 3, '5': 11, '6': '.PushKey', '10': 'pushKeys'},
], ],
'8': [ '8': [
@ -110,7 +110,7 @@ const PushUser$json = {
final $typed_data.Uint8List pushUserDescriptor = $convert.base64Decode( final $typed_data.Uint8List pushUserDescriptor = $convert.base64Decode(
'CghQdXNoVXNlchIWCgZ1c2VySWQYASABKANSBnVzZXJJZBIgCgtkaXNwbGF5TmFtZRgCIAEoCV' 'CghQdXNoVXNlchIWCgZ1c2VySWQYASABKANSBnVzZXJJZBIgCgtkaXNwbGF5TmFtZRgCIAEoCV'
'ILZGlzcGxheU5hbWUSGAoHYmxvY2tlZBgDIAEoCFIHYmxvY2tlZBIpCg1sYXN0TWVzc2FnZUlk' 'ILZGlzcGxheU5hbWUSGAoHYmxvY2tlZBgDIAEoCFIHYmxvY2tlZBIpCg1sYXN0TWVzc2FnZUlk'
'GAQgASgDSABSDWxhc3RNZXNzYWdlSWSIAQESJAoIcHVzaEtleXMYBSADKAsyCC5QdXNoS2V5Ug' 'GAQgASgJSABSDWxhc3RNZXNzYWdlSWSIAQESJAoIcHVzaEtleXMYBSADKAsyCC5QdXNoS2V5Ug'
'hwdXNoS2V5c0IQCg5fbGFzdE1lc3NhZ2VJZA=='); 'hwdXNoS2V5c0IQCg5fbGFzdE1lc3NhZ2VJZA==');
@$core.Deprecated('Use pushKeyDescriptor instead') @$core.Deprecated('Use pushKeyDescriptor instead')

View file

@ -6,6 +6,7 @@ message Message {
PLAINTEXT_CONTENT = 1; PLAINTEXT_CONTENT = 1;
CIPHERTEXT = 2; CIPHERTEXT = 2;
PREKEY_BUNDLE = 3; PREKEY_BUNDLE = 3;
TEST_NOTIFICATION = 4;
} }
Type type = 1; Type type = 1;
string receiptId = 2; string receiptId = 2;
@ -46,7 +47,8 @@ message EncryptedContent {
message TextMessage { message TextMessage {
string senderMessageId = 1; string senderMessageId = 1;
string text = 2; string text = 2;
optional string quoteMessageId = 3; int64 timestamp = 3;
optional string quoteMessageId = 4;
} }
message Reaction { message Reaction {
@ -62,27 +64,31 @@ message EncryptedContent {
OPENED = 2; OPENED = 2;
} }
Type type = 1; Type type = 1;
string senderMessageId = 2; optional string senderMessageId = 2;
optional string text = 3; repeated string multipleSenderMessageIds = 3;
optional int64 timestamp = 4; optional string text = 4;
int64 timestamp = 5;
} }
message Media { message Media {
enum Type { enum Type {
IMAGE = 0; REUPLOAD = 0;
VIDEO = 1; IMAGE = 1;
GIF = 2; VIDEO = 2;
GIF = 3;
} }
string senderMessageId = 1; string senderMessageId = 1;
Type type = 2; Type type = 2;
optional int64 displayLimitInMilliseconds = 3; optional int64 displayLimitInMilliseconds = 3;
bool requiresAuthentication = 4; bool requiresAuthentication = 4;
int64 timestamp = 5;
optional string quoteMessageId = 6;
optional bytes downloadToken = 5; optional bytes downloadToken = 7;
optional bytes encryptionKey = 6; optional bytes encryptionKey = 8;
optional bytes encryptionMac = 7; optional bytes encryptionMac = 9;
optional bytes encryptionNonce = 8; optional bytes encryptionNonce = 10;
} }
message MediaUpdate { message MediaUpdate {
@ -124,6 +130,7 @@ message EncryptedContent {
Type type = 1; Type type = 1;
optional int64 keyId = 2; optional int64 keyId = 2;
optional bytes key = 3; optional bytes key = 3;
optional int64 createdAt = 4;
} }
message FlameSync { message FlameSync {

View file

@ -26,7 +26,7 @@ enum PushKind {
message PushNotification { message PushNotification {
PushKind kind = 1; PushKind kind = 1;
optional int64 messageId = 2; optional string messageId = 2;
optional string reactionContent = 3; optional string reactionContent = 3;
} }
@ -39,7 +39,7 @@ message PushUser {
int64 userId = 1; int64 userId = 1;
string displayName = 2; string displayName = 2;
bool blocked = 3; bool blocked = 3;
optional int64 lastMessageId = 4; optional string lastMessageId = 4;
repeated PushKey pushKeys = 5; repeated PushKey pushKeys = 5;
} }

View file

@ -114,7 +114,7 @@ class ApiService {
_channel = null; _channel = null;
isAuthenticated = false; isAuthenticated = false;
globalCallbackConnectionState(isConnected: false); globalCallbackConnectionState(isConnected: false);
await twonlyDB.messagesDao.resetPendingDownloadState(); await twonlyDB.mediaFilesDao.resetPendingDownloadState();
} }
Future<void> startReconnectionTimer() async { Future<void> startReconnectionTimer() async {

View file

@ -119,7 +119,7 @@ Future<void> handleDownloadStatusUpdateInternal(
Mutex protectDownload = Mutex(); Mutex protectDownload = Mutex();
Future<void> startDownloadMedia(Message message, bool force) async { Future<void> startDownloadMedia(MediaFile media, bool force) async {
Log.info( Log.info(
'Download blocked for ${message.messageId} because of network state.', 'Download blocked for ${message.messageId} because of network state.',
); );

View file

@ -1,163 +1,153 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:cryptography_plus/cryptography_plus.dart';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:fixnum/fixnum.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:mutex/mutex.dart'; import 'package:mutex/mutex.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/json/message_old.dart';
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart' import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
as pb; as pb;
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.dart';
import 'package:twonly/src/services/api/server_messages.dart'
show messageGetsAck;
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
import 'package:twonly/src/services/signal/encryption.signal.dart'; import 'package:twonly/src/services/signal/encryption.signal.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/utils/storage.dart';
final lockRetransmission = Mutex(); final lockRetransmission = Mutex();
Future<void> tryTransmitMessages() async { Future<void> tryTransmitMessages() async {
return lockRetransmission.protect(() async { return lockRetransmission.protect(() async {
final retransIds = final receipts = await twonlyDB.receiptsDao.getReceiptsNotAckByServer();
await twonlyDB.messageRetransmissionDao.getRetransmitAbleMessages();
Log.info('Retransmitting ${retransIds.length} text messages'); if (receipts.isEmpty) return;
if (retransIds.isEmpty) return; Log.info('Reuploading ${receipts.length} messages to the server.');
for (final retransId in retransIds) { for (final receipt in receipts) {
await sendRetransmitMessage(retransId); await tryToSendCompleteMessage(receipt: receipt);
} }
}); });
} }
Future<void> tryToSendCompleteMessage(String receiptId) async { Future<void> tryToSendCompleteMessage({
String? receiptId,
Receipt? receipt,
bool reupload = false,
}) async {
try { try {
final retrans = await twonlyDB.messageRetransmissionDao if (receiptId == null && receipt == null) return;
.getRetransmissionById(retransId) if (receipt == null) {
.getSingleOrNull(); receipt = await twonlyDB.receiptsDao.getReceiptById(receiptId!);
if (receipt == null) {
/// SET THE Message().receiptID !!!!!!! Log.error('Receipt $receiptId not found.');
/// ALSO THE encryptedContent is NOT YET ENCRYPTED!
if (retrans == null) {
Log.error('$retransId not found in database');
return; return;
} }
if (retrans.acknowledgeByServerAt != null) {
Log.error('$retransId message already retransmitted');
return;
} }
receiptId = receipt.receiptId;
final json = MessageJson.fromJson( if (reupload) {
jsonDecode( await twonlyDB.receiptsDao.updateReceipt(
utf8.decode( receiptId,
gzip.decode(retrans.plaintextContent), const ReceiptsCompanion(
ackByServerAt: Value(null),
), ),
) as Map<String, dynamic>,
);
Log.info('Retransmitting $retransId: ${json.kind} to ${retrans.contactId}');
final contact = await twonlyDB.contactsDao
.getContactByUserId(retrans.contactId)
.getSingleOrNull();
if (contact == null || contact.deleted) {
Log.warn('Contact deleted $retransId or not found in database.');
await twonlyDB.messageRetransmissionDao
.deleteRetransmissionById(retransId);
if (retrans.messageId != null) {
await twonlyDB.messagesDao.updateMessageByMessageId(
retrans.messageId!,
const MessagesCompanion(errorWhileSending: Value(true)),
); );
} }
if (receipt.ackByServerAt != null) {
Log.error('$receiptId message already uploaded!');
return; return;
} }
final encryptedBytes = await signalEncryptMessage( Log.info('Uploading $receiptId (Message to ${receipt.contactId})');
retrans.contactId,
retrans.plaintextContent, final message = pb.Message.fromBuffer(receipt.message)
..receiptId = receiptId;
final encryptedContent =
pb.EncryptedContent.fromBuffer(message.encryptedContent);
var pushData = await getPushDataFromEncryptedContent(
receipt.contactId,
receipt.messageId,
encryptedContent,
); );
if (encryptedBytes == null) { if (message.type == pb.Message_Type.TEST_NOTIFICATION) {
pushData = (PushNotification()..kind = PushKind.testNotification)
.writeToBuffer();
}
if (message.type == pb.Message_Type.CIPHERTEXT) {
final cipherText = await signalEncryptMessage(
receipt.contactId,
Uint8List.fromList(message.encryptedContent),
);
if (cipherText == null) {
Log.error('Could not encrypt the message. Aborting and trying again.'); Log.error('Could not encrypt the message. Aborting and trying again.');
return; return;
} }
message.encryptedContent = cipherText.serialize();
final encryptedHash = (await Sha256().hash(encryptedBytes)).bytes; switch (cipherText.getType()) {
case CiphertextMessage.prekeyType:
await twonlyDB.messageRetransmissionDao.updateRetransmission( message.type = pb.Message_Type.PREKEY_BUNDLE;
retransId, case CiphertextMessage.whisperType:
MessageRetransmissionsCompanion( message.type = pb.Message_Type.CIPHERTEXT;
encryptedHash: Value(Uint8List.fromList(encryptedHash)), default:
), Log.error('Invalid ciphertext type: ${cipherText.getType()}.');
); return;
}
}
final resp = await apiService.sendTextMessage( final resp = await apiService.sendTextMessage(
retrans.contactId, receipt.contactId,
encryptedBytes, message.writeToBuffer(),
retrans.pushData, pushData,
); );
var retry = true;
if (resp.isError) { if (resp.isError) {
Log.error('Could not retransmit message.'); Log.error('Could not transmit message $receiptId got ${resp.error}.');
if (resp.error == ErrorCode.UserIdNotFound) { if (resp.error == ErrorCode.UserIdNotFound) {
retry = false; await twonlyDB.receiptsDao.deleteReceipt(receiptId);
if (retrans.messageId != null) {
await twonlyDB.messagesDao.updateMessageByMessageId(
retrans.messageId!,
const MessagesCompanion(errorWhileSending: Value(true)),
);
}
await twonlyDB.contactsDao.updateContact( await twonlyDB.contactsDao.updateContact(
retrans.contactId, receipt.contactId,
const ContactsCompanion(deleted: Value(true)), const ContactsCompanion(deleted: Value(true)),
); );
return;
} }
} }
if (resp.isSuccess) { if (resp.isSuccess) {
retry = false; if (receipt.messageId != null) {
if (retrans.messageId != null) { await twonlyDB.messagesDao.updateMessageId(
await twonlyDB.messagesDao.updateMessageByMessageId( receipt.messageId!,
retrans.messageId!,
const MessagesCompanion( const MessagesCompanion(
acknowledgeByServer: Value(true), ackByServer: Value(true),
errorWhileSending: Value(false),
), ),
); );
} }
} if (!receipt.contactWillSendsReceipt) {
await twonlyDB.receiptsDao.deleteReceipt(receiptId);
if (!retry) {
if (!messageGetsAck(json.kind)) {
await twonlyDB.messageRetransmissionDao
.deleteRetransmissionById(retransId);
} else { } else {
await twonlyDB.messageRetransmissionDao.updateRetransmission( await twonlyDB.receiptsDao.updateReceipt(
retransId, receiptId,
MessageRetransmissionsCompanion( ReceiptsCompanion(
acknowledgeByServerAt: Value(DateTime.now()), ackByServerAt: Value(DateTime.now()),
retryCount: Value(retrans.retryCount + 1), retryCount: Value(receipt.retryCount + 1),
lastRetry: Value(DateTime.now()), lastRetry: Value(DateTime.now()),
), ),
); );
} }
} }
} catch (e) { } catch (e) {
Log.error('error resending message: $e'); Log.error('Unknown Error when sending message: $e');
await twonlyDB.messageRetransmissionDao.deleteRetransmissionById(retransId); if (receiptId != null) {
await twonlyDB.receiptsDao.deleteReceipt(receiptId);
}
if (receipt != null) {
await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId);
}
} }
} }
@ -177,78 +167,31 @@ Future<void> sendCipherText(
); );
if (receipt != null) { if (receipt != null) {
await tryToSendCompleteMessage(receipt.receiptId); await tryToSendCompleteMessage(receipt: receipt);
} }
} }
// Future<void> sendTextMessage(
// int target,
// TextMessageContent content,
// PushNotification? pushNotification,
// ) async {
// final messageSendAt = DateTime.now();
// DateTime? openedAt;
// if (pushNotification != null && pushNotification.hasReactionContent()) {
// openedAt = DateTime.now();
// }
// final messageId = await twonlyDB.messagesDao.insertMessage(
// MessagesCompanion(
// contactId: Value(target),
// kind: const Value(MessageKind.textMessage),
// sendAt: Value(messageSendAt),
// responseToOtherMessageId: Value(content.responseToMessageId),
// responseToMessageId: Value(content.responseToOtherMessageId),
// downloadState: const Value(DownloadState.downloaded),
// openedAt: Value(openedAt),
// contentJson: Value(
// jsonEncode(content.toJson()),
// ),
// ),
// );
// if (messageId == null) return;
// if (pushNotification != null && !pushNotification.hasReactionContent()) {
// pushNotification.messageId = Int64(messageId);
// }
// final msg = MessageJson(
// kind: MessageKind.textMessage,
// messageSenderId: messageId,
// content: content,
// timestamp: messageSendAt,
// );
// await encryptAndSendMessageAsync(
// messageId,
// target,
// msg,
// pushNotification: pushNotification,
// );
// }
Future<void> notifyContactAboutOpeningMessage( Future<void> notifyContactAboutOpeningMessage(
int fromUserId, int contactId,
List<int> messageOtherIds, List<String> messageOtherIds,
) async { ) async {
var biggestMessageId = messageOtherIds.first; var biggestMessageId = messageOtherIds.first;
for (final messageOtherId in messageOtherIds) { for (final messageOtherId in messageOtherIds) {
if (messageOtherId > biggestMessageId) biggestMessageId = messageOtherId; if (isUUIDNewer(messageOtherId, biggestMessageId)) {
await encryptAndSendMessageAsync( biggestMessageId = messageOtherId;
null, }
fromUserId, }
MessageJson( await sendCipherText(
kind: MessageKind.opened, contactId,
messageReceiverId: messageOtherId, pb.EncryptedContent(
content: MessageContent(), messageUpdate: pb.EncryptedContent_MessageUpdate(
timestamp: DateTime.now(), type: pb.EncryptedContent_MessageUpdate_Type.OPENED,
multipleSenderMessageIds: messageOtherIds,
),
), ),
); );
} await updateLastMessageId(contactId, biggestMessageId);
await updateLastMessageId(fromUserId, biggestMessageId);
} }
Future<void> notifyContactsAboutProfileChange({int? onlyToContact}) async { Future<void> notifyContactsAboutProfileChange({int? onlyToContact}) async {
@ -256,11 +199,13 @@ Future<void> notifyContactsAboutProfileChange({int? onlyToContact}) async {
if (user == null) return; if (user == null) return;
if (user.avatarSvg == null) return; if (user.avatarSvg == null) return;
final encryptedContent = pb.EncryptedContent() final encryptedContent = pb.EncryptedContent(
..contactUpdate = (pb.EncryptedContent_ContactUpdate() contactUpdate: pb.EncryptedContent_ContactUpdate(
..type = pb.EncryptedContent_ContactUpdate_Type.UPDATE type: pb.EncryptedContent_ContactUpdate_Type.UPDATE,
..avatarSvg = user.avatarSvg! avatarSvg: user.avatarSvg,
..displayName = user.displayName); displayName: user.displayName,
),
);
if (onlyToContact != null) { if (onlyToContact != null) {
await sendCipherText(onlyToContact, encryptedContent); await sendCipherText(onlyToContact, encryptedContent);

View file

@ -65,7 +65,7 @@ Future<void> handleNewMessage(int fromUserId, Uint8List body) async {
Log.info( Log.info(
'Got decryption error: ${message.plaintextContent.decryptionErrorMessage.type} for $receiptId', 'Got decryption error: ${message.plaintextContent.decryptionErrorMessage.type} for $receiptId',
); );
await tryToSendCompleteMessage(receiptId); await tryToSendCompleteMessage(receiptId: receiptId, reupload: true);
} }
case Message_Type.CIPHERTEXT: case Message_Type.CIPHERTEXT:
@ -97,8 +97,10 @@ Future<void> handleNewMessage(int fromUserId, Uint8List body) async {
contactWillSendsReceipt: const Value(false), contactWillSendsReceipt: const Value(false),
), ),
); );
await tryToSendCompleteMessage(receiptId); await tryToSendCompleteMessage(receiptId: receiptId);
} }
case Message_Type.TEST_NOTIFICATION:
return;
} }
} }

View file

@ -9,6 +9,7 @@ import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/services/api/utils.dart'; import 'package:twonly/src/services/api/utils.dart';
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
import 'package:twonly/src/services/notifications/setup.notifications.dart'; import 'package:twonly/src/services/notifications/setup.notifications.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
Future<void> handleContactRequest( Future<void> handleContactRequest(
@ -17,6 +18,7 @@ Future<void> handleContactRequest(
) async { ) async {
switch (contactRequest.type) { switch (contactRequest.type) {
case EncryptedContent_ContactRequest_Type.REQUEST: case EncryptedContent_ContactRequest_Type.REQUEST:
Log.info('Got a contact request from $fromUserId');
// Request the username by the server so an attacker can not // Request the username by the server so an attacker can not
// forge the displayed username in the contact request // forge the displayed username in the contact request
final username = await apiService.getUsername(fromUserId); final username = await apiService.getUsername(fromUserId);
@ -33,6 +35,7 @@ Future<void> handleContactRequest(
} }
await setupNotificationWithUsers(); await setupNotificationWithUsers();
case EncryptedContent_ContactRequest_Type.ACCEPT: case EncryptedContent_ContactRequest_Type.ACCEPT:
Log.info('Got a contact accept from $fromUserId');
await twonlyDB.contactsDao.updateContact( await twonlyDB.contactsDao.updateContact(
fromUserId, fromUserId,
const ContactsCompanion( const ContactsCompanion(
@ -41,6 +44,7 @@ Future<void> handleContactRequest(
), ),
); );
case EncryptedContent_ContactRequest_Type.REJECT: case EncryptedContent_ContactRequest_Type.REJECT:
Log.info('Got a contact reject from $fromUserId');
await twonlyDB.contactsDao.deleteContactByUserId(fromUserId); await twonlyDB.contactsDao.deleteContactByUserId(fromUserId);
} }
} }
@ -48,12 +52,15 @@ Future<void> handleContactRequest(
Future<void> handleContactUpdate( Future<void> handleContactUpdate(
int fromUserId, int fromUserId,
EncryptedContent_ContactUpdate contactUpdate, EncryptedContent_ContactUpdate contactUpdate,
int? senderProfileCounter) async { int? senderProfileCounter,
) async {
switch (contactUpdate.type) { switch (contactUpdate.type) {
case EncryptedContent_ContactUpdate_Type.REQUEST: case EncryptedContent_ContactUpdate_Type.REQUEST:
Log.info('Got a contact update request from $fromUserId');
await notifyContactsAboutProfileChange(onlyToContact: fromUserId); await notifyContactsAboutProfileChange(onlyToContact: fromUserId);
case EncryptedContent_ContactUpdate_Type.UPDATE: case EncryptedContent_ContactUpdate_Type.UPDATE:
Log.info('Got a contact update $fromUserId');
if (contactUpdate.hasAvatarSvg() && if (contactUpdate.hasAvatarSvg() &&
contactUpdate.hasDisplayName() && contactUpdate.hasDisplayName() &&
senderProfileCounter != null) { senderProfileCounter != null) {
@ -74,6 +81,7 @@ Future<void> handleFlameSync(
int contactId, int contactId,
EncryptedContent_FlameSync flameSync, EncryptedContent_FlameSync flameSync,
) async { ) async {
Log.info('Got a flameSync from $contactId');
final contact = await twonlyDB.contactsDao final contact = await twonlyDB.contactsDao
.getContactByUserId(contactId) .getContactByUserId(contactId)
.getSingleOrNull(); .getSingleOrNull();

View file

@ -1,92 +1,158 @@
import 'dart:async';
import 'package:drift/drift.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/api/media_download.dart';
import 'package:twonly/src/services/api/utils.dart';
import 'package:twonly/src/services/mediafile.service.dart';
import 'package:twonly/src/services/thumbnail.service.dart';
import 'package:twonly/src/utils/log.dart';
Future<void> handleMedia(int fromUserId, String groupId, EncryptedContent_Media media) async { Future<void> handleMedia(
TODO int fromUserId,
} String groupId,
EncryptedContent_Media media,
) async {
Log.info(
'Got a media message: ${media.senderMessageId} from $groupId with type ${media.type}',
);
Future<void> handleMediaUpdate(int fromUserId, String groupId, EncryptedContent_MediaUpdate mediaUpdate) async { late MediaType mediaType;
TODO switch (media.type) {
case EncryptedContent_Media_Type.REUPLOAD:
final message = await twonlyDB.messagesDao
.getMessageById(media.senderMessageId)
.getSingleOrNull();
if (message == null ||
message.senderId != fromUserId ||
message.mediaId == null) {
return;
}
// in case there was already a downloaded file delete it...
await removeMediaFile(message.mediaId!);
// switch (message.kind) { await twonlyDB.mediaFilesDao.updateMedia(
// case MessageKind.receiveMediaError: message.mediaId!,
// if (message.messageReceiverId != null) { MediaFilesCompanion(
// final openedMessage = await twonlyDB.messagesDao downloadState: const Value(DownloadState.pending),
// .getMessageByIdAndContactId(fromUserId, message.messageReceiverId!) downloadToken: Value(Uint8List.fromList(media.downloadToken)),
// .getSingleOrNull(); encryptionKey: Value(Uint8List.fromList(media.encryptionKey)),
encryptionMac: Value(Uint8List.fromList(media.encryptionMac)),
// if (openedMessage != null) { encryptionNonce: Value(Uint8List.fromList(media.encryptionNonce)),
// /// message found
// /// checks if
// /// 1. this was a media upload
// /// 2. the media was not already retransmitted
// /// 3. the media was send in the last two days
// if (openedMessage.mediaUploadId != null &&
// openedMessage.mediaRetransmissionState ==
// MediaRetransmitting.none &&
// openedMessage.sendAt
// .isAfter(DateTime.now().subtract(const Duration(days: 2)))) {
// // reset the media upload state to pending,
// // this will cause the media to be re-encrypted again
// await twonlyDB.mediaUploadsDao.updateMediaUpload(
// openedMessage.mediaUploadId!,
// const MediaUploadsCompanion(
// state: Value(
// UploadState.pending,
// ),
// ),
// );
// // reset the message upload so the upload will be done again
// await twonlyDB.messagesDao.updateMessageByOtherUser(
// fromUserId,
// message.messageReceiverId!,
// const MessagesCompanion(
// downloadState: Value(DownloadState.pending),
// mediaRetransmissionState:
// Value(MediaRetransmitting.retransmitted),
// ),
// );
// unawaited(retryMediaUpload(false));
// } else {
// await twonlyDB.messagesDao.updateMessageByOtherUser(
// fromUserId,
// message.messageReceiverId!,
// const MessagesCompanion(
// errorWhileSending: Value(true),
// ),
// );
// }
// }
// }
if (message.kind == MessageKind.storedMediaFile) {
if (message.messageReceiverId != null) {
/// stored media file just updates the message
await twonlyDB.messagesDao.updateMessageByOtherUser(
fromUserId,
message.messageReceiverId!,
const MessagesCompanion(
mediaStored: Value(true),
errorWhileSending: Value(false),
), ),
); );
final msg = await twonlyDB.messagesDao
.getMessageByIdAndContactId( final mediaFile =
await twonlyDB.mediaFilesDao.getMediaFileById(message.mediaId!);
if (mediaFile != null) {
unawaited(startDownloadMedia(mediaFile, false));
}
return;
case EncryptedContent_Media_Type.IMAGE:
mediaType = MediaType.image;
case EncryptedContent_Media_Type.VIDEO:
mediaType = MediaType.video;
case EncryptedContent_Media_Type.GIF:
mediaType = MediaType.gif;
}
final mediaFile = await twonlyDB.mediaFilesDao.insertMedia(
MediaFilesCompanion(
downloadState: const Value(DownloadState.pending),
type: Value(mediaType),
requiresAuthentication: Value(media.requiresAuthentication),
displayLimitInMilliseconds: Value(
media.hasDisplayLimitInMilliseconds()
? media.displayLimitInMilliseconds.toInt()
: null,
),
downloadToken: Value(Uint8List.fromList(media.downloadToken)),
encryptionKey: Value(Uint8List.fromList(media.encryptionKey)),
encryptionMac: Value(Uint8List.fromList(media.encryptionMac)),
encryptionNonce: Value(Uint8List.fromList(media.encryptionNonce)),
createdAt: Value(fromTimestamp(media.timestamp)),
),
);
if (mediaFile == null) {
return;
}
final message = await twonlyDB.messagesDao.insertMessage(
MessagesCompanion(
messageId: Value(media.senderMessageId),
senderId: Value(fromUserId),
groupId: Value(groupId),
mediaId: Value(mediaFile.mediaId),
ackByServer: const Value(true),
ackByUser: const Value(true),
quotesMessageId: Value(
media.hasQuoteMessageId() ? media.quoteMessageId : null,
),
createdAt: Value(fromTimestamp(media.timestamp)),
),
);
if (message != null) {
Log.info('Inserted a new media message with ID: ${message.messageId}');
await twonlyDB.contactsDao.incFlameCounter(
fromUserId, fromUserId,
message.messageReceiverId!, true,
) fromTimestamp(media.timestamp),
.getSingleOrNull(); );
if (msg != null && msg.mediaUploadId != null) {
final filePath = await getMediaFilePath(msg.mediaUploadId, 'send'); unawaited(startDownloadMedia(mediaFile, false));
if (filePath.contains('mp4')) { }
unawaited(createThumbnailsForVideo(File(filePath))); }
} else {
unawaited(createThumbnailsForImage(File(filePath))); Future<void> handleMediaUpdate(
int fromUserId,
String groupId,
EncryptedContent_MediaUpdate mediaUpdate,
) async {
final message = await twonlyDB.messagesDao
.getMessageById(mediaUpdate.targetMessageId)
.getSingleOrNull();
if (message == null || message.mediaId == null) return;
final mediaFile =
await twonlyDB.mediaFilesDao.getMediaFileById(message.mediaId!);
if (mediaFile == null) return;
switch (mediaUpdate.type) {
case EncryptedContent_MediaUpdate_Type.REOPENED:
Log.info('Got media file reopened ${mediaFile.mediaId}');
await twonlyDB.mediaFilesDao.updateMedia(
mediaFile.mediaId,
const MediaFilesCompanion(
reopenByContact: Value(true),
),
);
case EncryptedContent_MediaUpdate_Type.STORED:
Log.info('Got media file stored ${mediaFile.mediaId}');
await twonlyDB.mediaFilesDao.updateMedia(
mediaFile.mediaId,
const MediaFilesCompanion(
storedByContact: Value(true),
),
);
unawaited(createThumbnailForMediaFile(mediaFile));
case EncryptedContent_MediaUpdate_Type.DECRYPTION_ERROR:
Log.info('Got media file decryption error ${mediaFile.mediaId}');
final reuploadRequestedBy = mediaFile.reuploadRequestedBy ?? [];
reuploadRequestedBy.add(fromUserId);
await twonlyDB.mediaFilesDao.updateMedia(
mediaFile.mediaId,
MediaFilesCompanion(
uploadState: const Value(UploadState.pending),
reuploadRequestedBy: Value(reuploadRequestedBy),
),
);
} }
}
}
} else if (message.content != null) {}
} }

View file

@ -10,12 +10,15 @@ Future<void> handleMessageUpdate(
) async { ) async {
switch (messageUpdate.type) { switch (messageUpdate.type) {
case EncryptedContent_MessageUpdate_Type.OPENED: case EncryptedContent_MessageUpdate_Type.OPENED:
Log.info('Opened message ${messageUpdate.senderMessageId}'); Log.info(
'Opened message ${messageUpdate.multipleSenderMessageIds.length}');
for (final senderMessageId in messageUpdate.multipleSenderMessageIds) {
await twonlyDB.messagesDao.handleMessageOpened( await twonlyDB.messagesDao.handleMessageOpened(
groupId, groupId,
messageUpdate.senderMessageId, senderMessageId,
fromTimestamp(messageUpdate.timestamp), fromTimestamp(messageUpdate.timestamp),
); );
}
case EncryptedContent_MessageUpdate_Type.DELETE: case EncryptedContent_MessageUpdate_Type.DELETE:
Log.info('Delete message ${messageUpdate.senderMessageId}'); Log.info('Delete message ${messageUpdate.senderMessageId}');
await twonlyDB.messagesDao.handleMessageDeletion( await twonlyDB.messagesDao.handleMessageDeletion(

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
import 'package:twonly/src/utils/log.dart';
DateTime lastPushKeyRequest = DateTime.now().subtract(const Duration(hours: 1)); DateTime lastPushKeyRequest = DateTime.now().subtract(const Duration(hours: 1));
@ -11,6 +12,7 @@ Future<void> handlePushKey(
) async { ) async {
switch (pushKeys.type) { switch (pushKeys.type) {
case EncryptedContent_PushKeys_Type.REQUEST: case EncryptedContent_PushKeys_Type.REQUEST:
Log.info('Got a pushkey request from $contactId');
if (lastPushKeyRequest if (lastPushKeyRequest
.isBefore(DateTime.now().subtract(const Duration(seconds: 60)))) { .isBefore(DateTime.now().subtract(const Duration(seconds: 60)))) {
lastPushKeyRequest = DateTime.now(); lastPushKeyRequest = DateTime.now();
@ -18,6 +20,7 @@ Future<void> handlePushKey(
} }
case EncryptedContent_PushKeys_Type.UPDATE: case EncryptedContent_PushKeys_Type.UPDATE:
Log.info('Got a pushkey update from $contactId');
await handleNewPushKey(contactId, pushKeys.keyId.toInt(), pushKeys.key); await handleNewPushKey(contactId, pushKeys.keyId.toInt(), pushKeys.key);
} }
} }

View file

@ -1,11 +1,13 @@
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/utils/log.dart';
Future<void> handleReaction( Future<void> handleReaction(
int fromUserId, int fromUserId,
String groupId, String groupId,
EncryptedContent_Reaction reaction, EncryptedContent_Reaction reaction,
) async { ) async {
Log.info('Got a reaction from $fromUserId');
if (reaction.hasRemove()) { if (reaction.hasRemove()) {
if (reaction.remove) { if (reaction.remove) {
await twonlyDB.reactionsDao await twonlyDB.reactionsDao

View file

@ -1,125 +1,34 @@
import 'package:drift/drift.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/api/utils.dart';
import 'package:twonly/src/utils/log.dart';
Future<void> handleTextMessage( Future<void> handleTextMessage(
int fromUserId, int fromUserId,
String groupId, String groupId,
EncryptedContent_TextMessage textMessage, EncryptedContent_TextMessage textMessage,
) async { ) async {
TODO Log.info(
// final content = message.content!; 'Got a text message: ${textMessage.senderMessageId} from $groupId',
// // when a message is received doubled ignore it... );
// final openedMessage = await twonlyDB.messagesDao final message = await twonlyDB.messagesDao.insertMessage(
// .getMessageByOtherMessageId(fromUserId, message.messageSenderId!) MessagesCompanion(
// .getSingleOrNull(); messageId: Value(textMessage.senderMessageId),
senderId: Value(fromUserId),
// if (openedMessage != null) { groupId: Value(groupId),
// if (openedMessage.errorWhileSending) { content: Value(textMessage.text),
// await twonlyDB.messagesDao ackByServer: const Value(true),
// .deleteMessagesByMessageId(openedMessage.messageId); ackByUser: const Value(true),
// } else { quotesMessageId: Value(
// Log.error( textMessage.hasQuoteMessageId() ? textMessage.quoteMessageId : null,
// 'Got a duplicated message from other user: ${message.messageSenderId!}', ),
// ); createdAt: Value(fromTimestamp(textMessage.timestamp)),
// final ok = client.Response_Ok()..none = true; ),
// return client.Response()..ok = ok; );
// } if (message != null) {
// } Log.info('Inserted a new text message with ID: ${message.messageId}');
}
// int? responseToMessageId;
// int? responseToOtherMessageId;
// int? messageId;
// var acknowledgeByUser = false;
// DateTime? openedAt;
// if (message.kind == MessageKind.reopenedMedia) {
// acknowledgeByUser = true;
// openedAt = DateTime.now();
// }
// if (content is TextMessageContent) {
// responseToMessageId = content.responseToMessageId;
// responseToOtherMessageId = content.responseToOtherMessageId;
// if (responseToMessageId != null || responseToOtherMessageId != null) {
// // reactions are shown in the notification directly...
// if (isEmoji(content.text)) {
// openedAt = DateTime.now();
// }
// }
// }
// if (content is ReopenedMediaFileContent) {
// responseToMessageId = content.messageId;
// }
// if (responseToMessageId != null) {
// await twonlyDB.messagesDao.updateMessageByOtherUser(
// fromUserId,
// responseToMessageId,
// MessagesCompanion(
// errorWhileSending: const Value(false),
// openedAt: Value(
// DateTime.now(),
// ), // when a user reacted to the media file, it should be marked as opened
// ),
// );
// }
// final contentJson = jsonEncode(content.toJson());
// final update = MessagesCompanion(
// contactId: Value(fromUserId),
// kind: Value(message.kind),
// messageOtherId: Value(message.messageSenderId),
// contentJson: Value(contentJson),
// acknowledgeByServer: const Value(true),
// acknowledgeByUser: Value(acknowledgeByUser),
// responseToMessageId: Value(responseToMessageId),
// responseToOtherMessageId: Value(responseToOtherMessageId),
// openedAt: Value(openedAt),
// downloadState: Value(
// message.kind == MessageKind.media
// ? DownloadState.pending
// : DownloadState.downloaded,
// ),
// sendAt: Value(message.timestamp),
// );
// messageId = await twonlyDB.messagesDao.insertMessage(
// update,
// );
// if (messageId == null) {
// Log.error('could not insert message into db');
// return client.Response()..error = ErrorCode.InternalError;
// }
// Log.info('Inserted a new message with id: $messageId');
// if (message.kind == MessageKind.media) {
// await twonlyDB.contactsDao.incFlameCounter(
// fromUserId,
// true,
// message.timestamp,
// );
// final msg = await twonlyDB.messagesDao
// .getMessageByMessageId(messageId)
// .getSingleOrNull();
// if (msg != null) {
// unawaited(startDownloadMedia(msg, false));
// }
// }
// } else {
// Log.error('Content is not defined $message');
// }
// // unarchive contact when receiving a new message
// await twonlyDB.contactsDao.updateContact(
// fromUserId,
// const ContactsCompanion(
// archived: Value(false),
// ),
// );
// return null;
} }

View file

@ -7,7 +7,7 @@ import 'package:cryptography_plus/cryptography_plus.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:twonly/src/constants/secure_storage_keys.dart'; import 'package:twonly/src/constants/secure_storage_keys.dart';
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/push_notification.pbenum.dart';
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/views/camera/share_image_editor_view.dart' import 'package:twonly/src/views/camera/share_image_editor_view.dart'

View file

@ -7,15 +7,17 @@ import 'package:cryptography_plus/cryptography_plus.dart';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hashlib/random.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/daos/contacts.dao.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/tables/messages_table.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/json/message_old.dart' as my; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.dart';
import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
/// This function must be called after the database is setup /// This function must be called after the database is setup
Future<void> setupNotificationWithUsers({ Future<void> setupNotificationWithUsers({
@ -104,19 +106,14 @@ Future<void> setupNotificationWithUsers({
} }
Future<void> sendNewPushKey(int userId, PushKey pushKey) async { Future<void> sendNewPushKey(int userId, PushKey pushKey) async {
await encryptAndSendMessageAsync( await sendCipherText(
null,
userId, userId,
my.MessageJson( EncryptedContent()
kind: MessageKind.pushKey, ..pushKeys = (EncryptedContent_PushKeys()
content: my.PushKeyContent( ..type = EncryptedContent_PushKeys_Type.UPDATE
keyId: pushKey.id.toInt(), ..key = pushKey.key
key: pushKey.key, ..keyId = pushKey.id
), ..createdAt = pushKey.createdAtUnixTimestamp),
timestamp: DateTime.fromMillisecondsSinceEpoch(
pushKey.createdAtUnixTimestamp.toInt(),
),
),
); );
} }
@ -132,7 +129,7 @@ Future<void> updatePushUser(Contact contact) async {
displayName: getContactDisplayName(contact), displayName: getContactDisplayName(contact),
pushKeys: [], pushKeys: [],
blocked: contact.blocked, blocked: contact.blocked,
lastMessageId: Int64(), lastMessageId: uuid.v7(),
), ),
); );
} else { } else {
@ -160,7 +157,7 @@ Future<void> handleNewPushKey(int fromUserId, int keyId, List<int> key) async {
displayName: getContactDisplayName(contact), displayName: getContactDisplayName(contact),
pushKeys: [], pushKeys: [],
blocked: contact.blocked, blocked: contact.blocked,
lastMessageId: Int64(), lastMessageId: uuid.v7(),
), ),
); );
pushUser = pushKeys.firstWhereOrNull((x) => x.userId == fromUserId); pushUser = pushKeys.firstWhereOrNull((x) => x.userId == fromUserId);
@ -174,8 +171,8 @@ Future<void> handleNewPushKey(int fromUserId, int keyId, List<int> key) async {
pushUser!.pushKeys.clear(); pushUser!.pushKeys.clear();
pushUser.pushKeys.add( pushUser.pushKeys.add(
PushKey( PushKey(
id: Int64(pushKey.keyId), id: Int64(keyId),
key: pushKey.key, key: key,
createdAtUnixTimestamp: Int64(DateTime.now().millisecondsSinceEpoch), createdAtUnixTimestamp: Int64(DateTime.now().millisecondsSinceEpoch),
), ),
); );
@ -183,7 +180,7 @@ Future<void> handleNewPushKey(int fromUserId, int keyId, List<int> key) async {
await setPushKeys(SecureStorageKeys.sendingPushKeys, pushKeys); await setPushKeys(SecureStorageKeys.sendingPushKeys, pushKeys);
} }
Future<void> updateLastMessageId(int fromUserId, int messageId) async { Future<void> updateLastMessageId(int fromUserId, String messageId) async {
final pushUsers = await getPushKeys(SecureStorageKeys.receivingPushKeys); final pushUsers = await getPushKeys(SecureStorageKeys.receivingPushKeys);
final pushUser = pushUsers.firstWhereOrNull((x) => x.userId == fromUserId); final pushUser = pushUsers.firstWhereOrNull((x) => x.userId == fromUserId);
@ -192,15 +189,103 @@ Future<void> updateLastMessageId(int fromUserId, int messageId) async {
return; return;
} }
if (pushUser.lastMessageId < Int64(messageId)) { if (isUUIDNewer(messageId, pushUser.lastMessageId)) {
pushUser.lastMessageId = Int64(messageId); pushUser.lastMessageId = messageId;
await setPushKeys(SecureStorageKeys.receivingPushKeys, pushUsers); await setPushKeys(SecureStorageKeys.receivingPushKeys, pushUsers);
} }
} }
Future<Uint8List?> getPushDataFromEncryptedContent(
int toUserId,
String? messageId,
EncryptedContent content,
) async {
late PushKind kind;
String? reactionContent;
if (content.hasReaction()) {
if (content.reaction.remove) return null;
final msg = await twonlyDB.messagesDao
.getMessageById(content.reaction.targetMessageId)
.getSingleOrNull();
if (msg == null) return null;
if (msg.content != null) {
kind = PushKind.reactionToText;
} else if (msg.mediaId != null) {
final media = await twonlyDB.mediaFilesDao.getMediaFileById(msg.mediaId!);
if (media == null) return null;
switch (media.type) {
case MediaType.image:
kind = PushKind.reactionToImage;
case MediaType.video:
kind = PushKind.reactionToVideo;
case MediaType.gif:
kind = PushKind.reaction;
}
}
reactionContent = content.reaction.emoji;
}
if (content.hasTextMessage()) {
kind = PushKind.text;
if (content.textMessage.hasQuoteMessageId()) {
kind = PushKind.response;
}
}
if (content.hasMedia()) {
switch (content.media.type) {
case EncryptedContent_Media_Type.IMAGE:
kind = PushKind.image;
case EncryptedContent_Media_Type.VIDEO:
kind = PushKind.video;
// ignore: no_default_cases
default:
return null;
}
if (content.media.requiresAuthentication) {
kind = PushKind.twonly;
}
}
if (content.hasContactRequest()) {
switch (content.contactRequest.type) {
case EncryptedContent_ContactRequest_Type.REQUEST:
kind = PushKind.contactRequest;
case EncryptedContent_ContactRequest_Type.ACCEPT:
kind = PushKind.acceptRequest;
case EncryptedContent_ContactRequest_Type.REJECT:
return null;
}
}
if (content.hasMediaUpdate()) {
switch (content.mediaUpdate.type) {
case EncryptedContent_MediaUpdate_Type.REOPENED:
kind = PushKind.reopenedMedia;
case EncryptedContent_MediaUpdate_Type.STORED:
kind = PushKind.storedMediaFile;
case EncryptedContent_MediaUpdate_Type.DECRYPTION_ERROR:
return null;
}
}
final pushNotification = PushNotification()..kind = kind;
if (reactionContent != null) {
pushNotification.reactionContent = reactionContent;
}
if (messageId != null) {
pushNotification.messageId = messageId;
}
return encryptPushNotification(toUserId, pushNotification);
}
/// this will trigger a push notification /// this will trigger a push notification
/// push notification only containing the message kind and username /// push notification only containing the message kind and username
Future<Uint8List?> getPushData(int toUserId, PushNotification content) async { Future<Uint8List?> encryptPushNotification(
int toUserId,
PushNotification content,
) async {
final pushKeys = await getPushKeys(SecureStorageKeys.sendingPushKeys); final pushKeys = await getPushKeys(SecureStorageKeys.sendingPushKeys);
var key = 'InsecureOnlyUsedForAddingContact'.codeUnits; var key = 'InsecureOnlyUsedForAddingContact'.codeUnits;
@ -218,14 +303,12 @@ Future<Uint8List?> getPushData(int toUserId, PushNotification content) async {
// this will be enforced after every app uses this system... :/ // this will be enforced after every app uses this system... :/
// return null; // return null;
Log.error('Using insecure key as the receiver does not send a push key!'); Log.error('Using insecure key as the receiver does not send a push key!');
await encryptAndSendMessageAsync(
null, await sendCipherText(
toUserId, toUserId,
my.MessageJson( EncryptedContent()
kind: MessageKind.requestPushKey, ..pushKeys = (EncryptedContent_PushKeys()
content: my.MessageContent(), ..type = EncryptedContent_PushKeys_Type.REQUEST),
timestamp: DateTime.now(),
),
); );
} }
} else { } else {

View file

@ -7,16 +7,15 @@ import 'package:twonly/src/services/signal/consts.signal.dart';
import 'package:twonly/src/services/signal/prekeys.signal.dart'; import 'package:twonly/src/services/signal/prekeys.signal.dart';
import 'package:twonly/src/services/signal/utils.signal.dart'; import 'package:twonly/src/services/signal/utils.signal.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
/// This caused some troubles, so protection the encryption... /// This caused some troubles, so protection the encryption...
final lockingSignalEncryption = Mutex(); final lockingSignalEncryption = Mutex();
Future<Uint8List?> signalEncryptMessage( Future<CiphertextMessage?> signalEncryptMessage(
int target, int target,
Uint8List plaintextContent, Uint8List plaintextContent,
) async { ) async {
return lockingSignalEncryption.protect<Uint8List?>(() async { return lockingSignalEncryption.protect<CiphertextMessage?>(() async {
try { try {
final signalStore = (await getSignalStore())!; final signalStore = (await getSignalStore())!;
final address = SignalProtocolAddress(target.toString(), defaultDeviceId); final address = SignalProtocolAddress(target.toString(), defaultDeviceId);
@ -75,14 +74,7 @@ Future<Uint8List?> signalEncryptMessage(
Log.error('did not get the identity of the remote address'); Log.error('did not get the identity of the remote address');
} }
} }
return await session.encrypt(plaintextContent);
final ciphertext = await session.encrypt(plaintextContent);
final b = BytesBuilder()
..add(ciphertext.serialize())
..add(intToBytes(ciphertext.getType()));
return b.takeBytes();
} catch (e) { } catch (e) {
Log.error(e.toString()); Log.error(e.toString());
return null; return null;

View file

@ -2,9 +2,23 @@ import 'dart:io';
import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:video_thumbnail/video_thumbnail.dart'; import 'package:video_thumbnail/video_thumbnail.dart';
Future<void> createThumbnailForMediaFile(MediaFile media) async {
switch (media.type) {
case MediaType.image:
TODO
break;
default:
}
}
Future<void> createThumbnailsForImage(File file) async { Future<void> createThumbnailsForImage(File file) async {
final fileExtension = file.path.split('.').last.toLowerCase(); final fileExtension = file.path.split('.').last.toLowerCase();
if (fileExtension != 'png') { if (fileExtension != 'png') {

View file

@ -267,3 +267,9 @@ MediaMessageContent? getMediaContent(Message message) {
return null; return null;
} }
} }
bool isUUIDNewer(String uuid1, String uuid2) {
final timestamp1 = int.parse(uuid1.substring(0, 8), radix: 16);
final timestamp2 = int.parse(uuid2.substring(0, 8), radix: 16);
return timestamp1 > timestamp2;
}

View file

@ -1,13 +1,12 @@
import 'dart:io'; import 'dart:io';
import 'package:fixnum/fixnum.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hashlib/random.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/model/protobuf/push_notification/push_notification.pbserver.dart'; import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.dart';
import 'package:twonly/src/services/fcm.service.dart'; import 'package:twonly/src/services/fcm.service.dart';
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
@ -52,10 +51,10 @@ class NotificationView extends StatelessWidget {
if (run) { if (run) {
final user = await getUser(); final user = await getUser();
if (user != null) { if (user != null) {
final pushData = await getPushData( final pushData = await encryptPushNotification(
user.userId, user.userId,
PushNotification( PushNotification(
messageId: Int64(), messageId: uuid.v4(),
kind: PushKind.testNotification, kind: PushKind.testNotification,
), ),
); );

View file

@ -1,9 +1,11 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:hashlib/random.dart'; import 'package:hashlib/random.dart';
import 'package:twonly/src/services/api/media_upload.dart'; import 'package:twonly/src/services/api/media_upload.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/pow.dart'; import 'package:twonly/src/utils/pow.dart';
import 'package:twonly/src/views/components/animate_icon.dart'; import 'package:twonly/src/views/components/animate_icon.dart';
@ -40,5 +42,11 @@ void main() {
final uv4String = utf8.decode(uv4Bytes.cast<int>()); final uv4String = utf8.decode(uv4Bytes.cast<int>());
expect(uv4String, uv4); expect(uv4String, uv4);
}); });
test('comparing uui7', () async {
final uv7Old = uuid.v7();
sleep(const Duration(milliseconds: 1000));
final uv7New = uuid.v7();
expect(isUUIDNewer(uv7New, uv7Old), true);
});
}); });
} }