mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 07:48:40 +00:00
handle text and media server messages, update pushkeys
This commit is contained in:
parent
a4ccefec75
commit
2f3f927914
41 changed files with 1172 additions and 790 deletions
|
|
@ -128,8 +128,8 @@ struct PushNotification: Sendable {
|
|||
|
||||
var kind: PushKind = .reaction
|
||||
|
||||
var messageID: Int64 {
|
||||
get {return _messageID ?? 0}
|
||||
var messageID: String {
|
||||
get {return _messageID ?? String()}
|
||||
set {_messageID = newValue}
|
||||
}
|
||||
/// Returns true if `messageID` has been explicitly set.
|
||||
|
|
@ -150,7 +150,7 @@ struct PushNotification: Sendable {
|
|||
|
||||
init() {}
|
||||
|
||||
fileprivate var _messageID: Int64? = nil
|
||||
fileprivate var _messageID: String? = nil
|
||||
fileprivate var _reactionContent: String? = nil
|
||||
}
|
||||
|
||||
|
|
@ -177,8 +177,8 @@ struct PushUser: Sendable {
|
|||
|
||||
var blocked: Bool = false
|
||||
|
||||
var lastMessageID: Int64 {
|
||||
get {return _lastMessageID ?? 0}
|
||||
var lastMessageID: String {
|
||||
get {return _lastMessageID ?? String()}
|
||||
set {_lastMessageID = newValue}
|
||||
}
|
||||
/// Returns true if `lastMessageID` has been explicitly set.
|
||||
|
|
@ -192,7 +192,7 @@ struct PushUser: Sendable {
|
|||
|
||||
init() {}
|
||||
|
||||
fileprivate var _lastMessageID: Int64? = nil
|
||||
fileprivate var _lastMessageID: String? = nil
|
||||
}
|
||||
|
||||
struct PushKey: Sendable {
|
||||
|
|
@ -273,7 +273,7 @@ extension PushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme
|
|||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
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) }()
|
||||
default: break
|
||||
}
|
||||
|
|
@ -289,7 +289,7 @@ extension PushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme
|
|||
try visitor.visitSingularEnumField(value: self.kind, fieldNumber: 1)
|
||||
}
|
||||
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 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 2: try { try decoder.decodeSingularStringField(value: &self.displayName) }()
|
||||
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) }()
|
||||
default: break
|
||||
}
|
||||
|
|
@ -371,7 +371,7 @@ extension PushUser: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
|
|||
try visitor.visitSingularBoolField(value: self.blocked, fieldNumber: 3)
|
||||
}
|
||||
try { if let v = self._lastMessageID {
|
||||
try visitor.visitSingularInt64Field(value: v, fieldNumber: 4)
|
||||
try visitor.visitSingularStringField(value: v, fieldNumber: 4)
|
||||
} }()
|
||||
if !self.pushKeys.isEmpty {
|
||||
try visitor.visitRepeatedMessageField(value: self.pushKeys, fieldNumber: 5)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,6 @@ class SecureStorageKeys {
|
|||
static const String userData = 'userData';
|
||||
static const String twonlySafeLastBackupHash = 'twonly_safe_last_backup_hash';
|
||||
|
||||
static const String receivingPushKeys = 'receiving_push_keys';
|
||||
static const String sendingPushKeys = 'sending_push_keys';
|
||||
static const String receivingPushKeys = 'push_keys_receiving';
|
||||
static const String sendingPushKeys = 'push_keys_sending';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,10 +120,7 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
|||
Stream<List<Contact>> watchNotAcceptedContacts() {
|
||||
return (select(contacts)
|
||||
..where(
|
||||
(t) =>
|
||||
t.accepted.equals(false) &
|
||||
t.archived.equals(false) &
|
||||
t.blocked.equals(false),
|
||||
(t) => t.accepted.equals(false) & t.blocked.equals(false),
|
||||
))
|
||||
.watch();
|
||||
// return (select(contacts)).watch();
|
||||
|
|
|
|||
|
|
@ -12,10 +12,18 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
|||
GroupsDao(super.db);
|
||||
|
||||
Future<bool> isContactInGroup(int contactId, String groupId) async {
|
||||
final entry = await (select(groupMembers)
|
||||
..where(
|
||||
(t) => t.contactId.equals(contactId) & t.groupId.equals(groupId)))
|
||||
final entry = await (select(groupMembers)..where(
|
||||
// ignore: require_trailing_commas
|
||||
(t) => t.contactId.equals(contactId) & t.groupId.equals(groupId)))
|
||||
.getSingleOrNull();
|
||||
return entry != null;
|
||||
}
|
||||
|
||||
Future<void> updateGroup(
|
||||
String groupId,
|
||||
GroupsCompanion updates,
|
||||
) async {
|
||||
await (update(groups)..where((c) => c.groupId.equals(groupId)))
|
||||
.write(updates);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
54
lib/src/database/daos/mediafiles.dao.dart
Normal file
54
lib/src/database/daos/mediafiles.dao.dart
Normal 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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
8
lib/src/database/daos/mediafiles.dao.g.dart
Normal file
8
lib/src/database/daos/mediafiles.dao.g.dart
Normal 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;
|
||||
}
|
||||
|
|
@ -1,13 +1,24 @@
|
|||
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/groups.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/twonly.db.dart';
|
||||
import 'package:twonly/src/services/mediafile.service.dart';
|
||||
import 'package:twonly/src/utils/log.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 {
|
||||
// this constructor is required so that the main database can create an instance
|
||||
// of this object.
|
||||
|
|
@ -156,22 +167,6 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
// .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(
|
||||
int contactId,
|
||||
String messageId,
|
||||
|
|
@ -278,28 +273,33 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
// .write(updatedValues);
|
||||
// }
|
||||
|
||||
// Future<void> updateMessageByMessageId(
|
||||
// int messageId,
|
||||
// MessagesCompanion updatedValues,
|
||||
// ) {
|
||||
// return (update(messages)..where((c) => c.messageId.equals(messageId)))
|
||||
// .write(updatedValues);
|
||||
// }
|
||||
Future<void> updateMessageId(
|
||||
String messageId,
|
||||
MessagesCompanion updatedValues,
|
||||
) {
|
||||
return (update(messages)..where((c) => c.messageId.equals(messageId)))
|
||||
.write(updatedValues);
|
||||
}
|
||||
|
||||
// Future<int?> insertMessage(MessagesCompanion message) async {
|
||||
// try {
|
||||
// await (update(contacts)
|
||||
// ..where(
|
||||
// (c) => c.userId.equals(message.contactId.value),
|
||||
// ))
|
||||
// .write(ContactsCompanion(lastMessageExchange: Value(DateTime.now())));
|
||||
Future<Message?> insertMessage(MessagesCompanion message) async {
|
||||
try {
|
||||
final rowId = await into(messages).insert(message);
|
||||
|
||||
// return await into(messages).insert(message);
|
||||
// } catch (e) {
|
||||
// Log.error('Error while inserting message: $e');
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
await twonlyDB.groupsDao.updateGroup(
|
||||
message.groupId.value,
|
||||
GroupsCompanion(
|
||||
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) {
|
||||
// return (delete(messages)
|
||||
|
|
|
|||
|
|
@ -9,4 +9,5 @@ mixin _$MessagesDaoMixin on DatabaseAccessor<TwonlyDB> {
|
|||
$MessagesTable get messages => attachedDatabase.messages;
|
||||
$MessageHistoriesTable get messageHistories =>
|
||||
attachedDatabase.messageHistories;
|
||||
$GroupsTable get groups => attachedDatabase.groups;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,11 +31,13 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
|
|||
))
|
||||
.go();
|
||||
if (emoji != null) {
|
||||
await into(reactions).insert(ReactionsCompanion(
|
||||
messageId: Value(messageId),
|
||||
emoji: Value(emoji),
|
||||
senderId: Value(contactId),
|
||||
));
|
||||
await into(reactions).insert(
|
||||
ReactionsCompanion(
|
||||
messageId: Value(messageId),
|
||||
emoji: Value(emoji),
|
||||
senderId: Value(contactId),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
|||
|
||||
Future<void> confirmReceipt(String receiptId, int fromUserId) async {
|
||||
final receipt = await (select(receipts)
|
||||
..where((t) =>
|
||||
t.receiptId.equals(receiptId) & t.contactId.equals(fromUserId)))
|
||||
..where(
|
||||
(t) =>
|
||||
t.receiptId.equals(receiptId) & t.contactId.equals(fromUserId),
|
||||
))
|
||||
.getSingleOrNull();
|
||||
|
||||
if (receipt == null) return;
|
||||
|
|
@ -26,7 +28,7 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
|||
..where((t) => t.messageId.equals(receipt.messageId!)))
|
||||
.write(
|
||||
const MessagesCompanion(
|
||||
acknowledgeByUser: Value(true),
|
||||
ackByUser: Value(true),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -39,6 +41,14 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
|||
.go();
|
||||
}
|
||||
|
||||
Future<void> deleteReceipt(String receiptId) async {
|
||||
await (delete(receipts)
|
||||
..where(
|
||||
(t) => t.receiptId.equals(receiptId),
|
||||
))
|
||||
.go();
|
||||
}
|
||||
|
||||
Future<Receipt?> insertReceipt(ReceiptsCompanion entry) async {
|
||||
try {
|
||||
final id = await into(receipts).insert(entry);
|
||||
|
|
@ -49,4 +59,33 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ class Contacts extends Table {
|
|||
BoolColumn get hidden => boolean().withDefault(const Constant(false))();
|
||||
BoolColumn get blocked => 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 alsoBestFriend =>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class Groups extends Table {
|
|||
BoolColumn get isGroupAdmin => boolean()();
|
||||
BoolColumn get isGroupOfTwo => boolean()();
|
||||
BoolColumn get pinned => boolean().withDefault(const Constant(false))();
|
||||
BoolColumn get archived => boolean().withDefault(const Constant(false))();
|
||||
|
||||
DateTimeColumn get lastMessageExchange =>
|
||||
dateTime().withDefault(currentDateAndTime)();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:hashlib/random.dart';
|
||||
|
||||
|
|
@ -14,13 +16,11 @@ enum UploadState {
|
|||
receiverNotified,
|
||||
}
|
||||
|
||||
enum DownloadState {
|
||||
pending,
|
||||
}
|
||||
enum DownloadState { pending, downloading }
|
||||
|
||||
@DataClassName('MediaFile')
|
||||
class MediaFiles extends Table {
|
||||
TextColumn get mediaId => text().clientDefault(() => uuid.v4())();
|
||||
TextColumn get mediaId => text().clientDefault(() => uuid.v7())();
|
||||
|
||||
TextColumn get type => textEnum<MediaType>()();
|
||||
|
||||
|
|
@ -34,6 +34,9 @@ class MediaFiles extends Table {
|
|||
BoolColumn get storedByContact =>
|
||||
boolean().withDefault(const Constant(false))();
|
||||
|
||||
TextColumn get reuploadRequestedBy =>
|
||||
text().map(IntListTypeConverter()).nullable()();
|
||||
|
||||
IntColumn get displayLimitInMilliseconds => integer().nullable()();
|
||||
|
||||
BlobColumn get downloadToken => blob().nullable()();
|
||||
|
|
@ -46,3 +49,15 @@ class MediaFiles extends Table {
|
|||
@override
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
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/mediafiles.table.dart';
|
||||
|
||||
@DataClassName('Message')
|
||||
class Messages extends Table {
|
||||
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
|
||||
IntColumn get senderId =>
|
||||
|
|
@ -23,10 +24,8 @@ class Messages extends Table {
|
|||
|
||||
BoolColumn get isEdited => boolean().withDefault(const Constant(false))();
|
||||
|
||||
BoolColumn get acknowledgeByUser =>
|
||||
boolean().withDefault(const Constant(false))();
|
||||
BoolColumn get acknowledgeByServer =>
|
||||
boolean().withDefault(const Constant(false))();
|
||||
BoolColumn get ackByUser => boolean().withDefault(const Constant(false))();
|
||||
BoolColumn get ackByServer => boolean().withDefault(const Constant(false))();
|
||||
|
||||
IntColumn get openedByCounter => integer().withDefault(const Constant(0))();
|
||||
DateTimeColumn get openedAt => dateTime().nullable()();
|
||||
|
|
|
|||
|
|
@ -15,11 +15,14 @@ class Receipts extends Table {
|
|||
.nullable()
|
||||
.references(Messages, #messageId, onDelete: KeyAction.cascade)();
|
||||
|
||||
/// This is the protobuf 'Message'
|
||||
BlobColumn get message => blob()();
|
||||
|
||||
BoolColumn get contactWillSendsReceipt =>
|
||||
boolean().withDefault(const Constant(true))();
|
||||
|
||||
DateTimeColumn get ackByServerAt => dateTime().nullable()();
|
||||
|
||||
IntColumn get retryCount => integer().withDefault(const Constant(0))();
|
||||
DateTimeColumn get lastRetry => dateTime().nullable()();
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:drift_flutter/drift_flutter.dart'
|
|||
import 'package:path_provider/path_provider.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/mediafiles.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/receipts.dao.dart';
|
||||
|
|
@ -48,7 +49,8 @@ part 'twonly.db.g.dart';
|
|||
SignalDao,
|
||||
ReceiptsDao,
|
||||
GroupsDao,
|
||||
ReactionsDao
|
||||
ReactionsDao,
|
||||
MediaFilesDao,
|
||||
],
|
||||
)
|
||||
class TwonlyDB extends _$TwonlyDB {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -218,6 +218,7 @@ class EncryptedContent_TextMessage extends $pb.GeneratedMessage {
|
|||
factory EncryptedContent_TextMessage({
|
||||
$core.String? senderMessageId,
|
||||
$core.String? text,
|
||||
$fixnum.Int64? timestamp,
|
||||
$core.String? quoteMessageId,
|
||||
}) {
|
||||
final $result = create();
|
||||
|
|
@ -227,6 +228,9 @@ class EncryptedContent_TextMessage extends $pb.GeneratedMessage {
|
|||
if (text != null) {
|
||||
$result.text = text;
|
||||
}
|
||||
if (timestamp != null) {
|
||||
$result.timestamp = timestamp;
|
||||
}
|
||||
if (quoteMessageId != null) {
|
||||
$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)
|
||||
..aOS(1, _omitFieldNames ? '' : 'senderMessageId', protoName: 'senderMessageId')
|
||||
..aOS(2, _omitFieldNames ? '' : 'text')
|
||||
..aOS(3, _omitFieldNames ? '' : 'quoteMessageId', protoName: 'quoteMessageId')
|
||||
..aInt64(3, _omitFieldNames ? '' : 'timestamp')
|
||||
..aOS(4, _omitFieldNames ? '' : 'quoteMessageId', protoName: 'quoteMessageId')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
|
|
@ -283,13 +288,22 @@ class EncryptedContent_TextMessage extends $pb.GeneratedMessage {
|
|||
void clearText() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.String get quoteMessageId => $_getSZ(2);
|
||||
$fixnum.Int64 get timestamp => $_getI64(2);
|
||||
@$pb.TagNumber(3)
|
||||
set quoteMessageId($core.String v) { $_setString(2, v); }
|
||||
set timestamp($fixnum.Int64 v) { $_setInt64(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasQuoteMessageId() => $_has(2);
|
||||
$core.bool hasTimestamp() => $_has(2);
|
||||
@$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 {
|
||||
|
|
@ -374,6 +388,7 @@ class EncryptedContent_MessageUpdate extends $pb.GeneratedMessage {
|
|||
factory EncryptedContent_MessageUpdate({
|
||||
EncryptedContent_MessageUpdate_Type? type,
|
||||
$core.String? senderMessageId,
|
||||
$core.Iterable<$core.String>? multipleSenderMessageIds,
|
||||
$core.String? text,
|
||||
$fixnum.Int64? timestamp,
|
||||
}) {
|
||||
|
|
@ -384,6 +399,9 @@ class EncryptedContent_MessageUpdate extends $pb.GeneratedMessage {
|
|||
if (senderMessageId != null) {
|
||||
$result.senderMessageId = senderMessageId;
|
||||
}
|
||||
if (multipleSenderMessageIds != null) {
|
||||
$result.multipleSenderMessageIds.addAll(multipleSenderMessageIds);
|
||||
}
|
||||
if (text != null) {
|
||||
$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)
|
||||
..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(3, _omitFieldNames ? '' : 'text')
|
||||
..aInt64(4, _omitFieldNames ? '' : 'timestamp')
|
||||
..pPS(3, _omitFieldNames ? '' : 'multipleSenderMessageIds', protoName: 'multipleSenderMessageIds')
|
||||
..aOS(4, _omitFieldNames ? '' : 'text')
|
||||
..aInt64(5, _omitFieldNames ? '' : 'timestamp')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
|
|
@ -444,22 +463,25 @@ class EncryptedContent_MessageUpdate extends $pb.GeneratedMessage {
|
|||
void clearSenderMessageId() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.String get text => $_getSZ(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);
|
||||
$core.List<$core.String> get multipleSenderMessageIds => $_getList(2);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$fixnum.Int64 get timestamp => $_getI64(3);
|
||||
$core.String get text => $_getSZ(3);
|
||||
@$pb.TagNumber(4)
|
||||
set timestamp($fixnum.Int64 v) { $_setInt64(3, v); }
|
||||
set text($core.String v) { $_setString(3, v); }
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasTimestamp() => $_has(3);
|
||||
$core.bool hasText() => $_has(3);
|
||||
@$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 {
|
||||
|
|
@ -468,6 +490,8 @@ class EncryptedContent_Media extends $pb.GeneratedMessage {
|
|||
EncryptedContent_Media_Type? type,
|
||||
$fixnum.Int64? displayLimitInMilliseconds,
|
||||
$core.bool? requiresAuthentication,
|
||||
$fixnum.Int64? timestamp,
|
||||
$core.String? quoteMessageId,
|
||||
$core.List<$core.int>? downloadToken,
|
||||
$core.List<$core.int>? encryptionKey,
|
||||
$core.List<$core.int>? encryptionMac,
|
||||
|
|
@ -486,6 +510,12 @@ class EncryptedContent_Media extends $pb.GeneratedMessage {
|
|||
if (requiresAuthentication != null) {
|
||||
$result.requiresAuthentication = requiresAuthentication;
|
||||
}
|
||||
if (timestamp != null) {
|
||||
$result.timestamp = timestamp;
|
||||
}
|
||||
if (quoteMessageId != null) {
|
||||
$result.quoteMessageId = quoteMessageId;
|
||||
}
|
||||
if (downloadToken != null) {
|
||||
$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)
|
||||
..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')
|
||||
..aOB(4, _omitFieldNames ? '' : 'requiresAuthentication', protoName: 'requiresAuthentication')
|
||||
..a<$core.List<$core.int>>(5, _omitFieldNames ? '' : 'downloadToken', $pb.PbFieldType.OY, protoName: 'downloadToken')
|
||||
..a<$core.List<$core.int>>(6, _omitFieldNames ? '' : 'encryptionKey', $pb.PbFieldType.OY, protoName: 'encryptionKey')
|
||||
..a<$core.List<$core.int>>(7, _omitFieldNames ? '' : 'encryptionMac', $pb.PbFieldType.OY, protoName: 'encryptionMac')
|
||||
..a<$core.List<$core.int>>(8, _omitFieldNames ? '' : 'encryptionNonce', $pb.PbFieldType.OY, protoName: 'encryptionNonce')
|
||||
..aInt64(5, _omitFieldNames ? '' : 'timestamp')
|
||||
..aOS(6, _omitFieldNames ? '' : 'quoteMessageId', protoName: 'quoteMessageId')
|
||||
..a<$core.List<$core.int>>(7, _omitFieldNames ? '' : 'downloadToken', $pb.PbFieldType.OY, protoName: 'downloadToken')
|
||||
..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
|
||||
;
|
||||
|
||||
|
|
@ -574,40 +606,58 @@ class EncryptedContent_Media extends $pb.GeneratedMessage {
|
|||
void clearRequiresAuthentication() => clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.List<$core.int> get downloadToken => $_getN(4);
|
||||
$fixnum.Int64 get timestamp => $_getI64(4);
|
||||
@$pb.TagNumber(5)
|
||||
set downloadToken($core.List<$core.int> v) { $_setBytes(4, v); }
|
||||
set timestamp($fixnum.Int64 v) { $_setInt64(4, v); }
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasDownloadToken() => $_has(4);
|
||||
$core.bool hasTimestamp() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearDownloadToken() => clearField(5);
|
||||
void clearTimestamp() => clearField(5);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$core.List<$core.int> get encryptionKey => $_getN(5);
|
||||
$core.String get quoteMessageId => $_getSZ(5);
|
||||
@$pb.TagNumber(6)
|
||||
set encryptionKey($core.List<$core.int> v) { $_setBytes(5, v); }
|
||||
set quoteMessageId($core.String v) { $_setString(5, v); }
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasEncryptionKey() => $_has(5);
|
||||
$core.bool hasQuoteMessageId() => $_has(5);
|
||||
@$pb.TagNumber(6)
|
||||
void clearEncryptionKey() => clearField(6);
|
||||
void clearQuoteMessageId() => clearField(6);
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
$core.List<$core.int> get encryptionMac => $_getN(6);
|
||||
$core.List<$core.int> get downloadToken => $_getN(6);
|
||||
@$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)
|
||||
$core.bool hasEncryptionMac() => $_has(6);
|
||||
$core.bool hasDownloadToken() => $_has(6);
|
||||
@$pb.TagNumber(7)
|
||||
void clearEncryptionMac() => clearField(7);
|
||||
void clearDownloadToken() => clearField(7);
|
||||
|
||||
@$pb.TagNumber(8)
|
||||
$core.List<$core.int> get encryptionNonce => $_getN(7);
|
||||
$core.List<$core.int> get encryptionKey => $_getN(7);
|
||||
@$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)
|
||||
$core.bool hasEncryptionNonce() => $_has(7);
|
||||
$core.bool hasEncryptionKey() => $_has(7);
|
||||
@$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 {
|
||||
|
|
@ -807,6 +857,7 @@ class EncryptedContent_PushKeys extends $pb.GeneratedMessage {
|
|||
EncryptedContent_PushKeys_Type? type,
|
||||
$fixnum.Int64? keyId,
|
||||
$core.List<$core.int>? key,
|
||||
$fixnum.Int64? createdAt,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (type != null) {
|
||||
|
|
@ -818,6 +869,9 @@ class EncryptedContent_PushKeys extends $pb.GeneratedMessage {
|
|||
if (key != null) {
|
||||
$result.key = key;
|
||||
}
|
||||
if (createdAt != null) {
|
||||
$result.createdAt = createdAt;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
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)
|
||||
..aInt64(2, _omitFieldNames ? '' : 'keyId', protoName: 'keyId')
|
||||
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'key', $pb.PbFieldType.OY)
|
||||
..aInt64(4, _omitFieldNames ? '' : 'createdAt', protoName: 'createdAt')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
|
|
@ -878,6 +933,15 @@ class EncryptedContent_PushKeys extends $pb.GeneratedMessage {
|
|||
$core.bool hasKey() => $_has(2);
|
||||
@$pb.TagNumber(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 {
|
||||
|
|
|
|||
|
|
@ -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 CIPHERTEXT = Message_Type._(2, _omitEnumNames ? '' : 'CIPHERTEXT');
|
||||
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> [
|
||||
SENDER_DELIVERY_RECEIPT,
|
||||
PLAINTEXT_CONTENT,
|
||||
CIPHERTEXT,
|
||||
PREKEY_BUNDLE,
|
||||
TEST_NOTIFICATION,
|
||||
];
|
||||
|
||||
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 {
|
||||
static const EncryptedContent_Media_Type IMAGE = EncryptedContent_Media_Type._(0, _omitEnumNames ? '' : 'IMAGE');
|
||||
static const EncryptedContent_Media_Type VIDEO = EncryptedContent_Media_Type._(1, _omitEnumNames ? '' : 'VIDEO');
|
||||
static const EncryptedContent_Media_Type GIF = EncryptedContent_Media_Type._(2, _omitEnumNames ? '' : 'GIF');
|
||||
static const EncryptedContent_Media_Type REUPLOAD = EncryptedContent_Media_Type._(0, _omitEnumNames ? '' : 'REUPLOAD');
|
||||
static const EncryptedContent_Media_Type IMAGE = EncryptedContent_Media_Type._(1, _omitEnumNames ? '' : 'IMAGE');
|
||||
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> [
|
||||
REUPLOAD,
|
||||
IMAGE,
|
||||
VIDEO,
|
||||
GIF,
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ const Message_Type$json = {
|
|||
{'1': 'PLAINTEXT_CONTENT', '2': 1},
|
||||
{'1': 'CIPHERTEXT', '2': 2},
|
||||
{'1': 'PREKEY_BUNDLE', '2': 3},
|
||||
{'1': 'TEST_NOTIFICATION', '2': 4},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -45,9 +46,10 @@ final $typed_data.Uint8List messageDescriptor = $convert.base64Decode(
|
|||
'CgdNZXNzYWdlEiEKBHR5cGUYASABKA4yDS5NZXNzYWdlLlR5cGVSBHR5cGUSHAoJcmVjZWlwdE'
|
||||
'lkGAIgASgJUglyZWNlaXB0SWQSLwoQZW5jcnlwdGVkQ29udGVudBgDIAEoDEgAUhBlbmNyeXB0'
|
||||
'ZWRDb250ZW50iAEBEkIKEHBsYWludGV4dENvbnRlbnQYBCABKAsyES5QbGFpbnRleHRDb250ZW'
|
||||
'50SAFSEHBsYWludGV4dENvbnRlbnSIAQEiXQoEVHlwZRIbChdTRU5ERVJfREVMSVZFUllfUkVD'
|
||||
'50SAFSEHBsYWludGV4dENvbnRlbnSIAQEidAoEVHlwZRIbChdTRU5ERVJfREVMSVZFUllfUkVD'
|
||||
'RUlQVBAAEhUKEVBMQUlOVEVYVF9DT05URU5UEAESDgoKQ0lQSEVSVEVYVBACEhEKDVBSRUtFWV'
|
||||
'9CVU5ETEUQA0ITChFfZW5jcnlwdGVkQ29udGVudEITChFfcGxhaW50ZXh0Q29udGVudA==');
|
||||
'9CVU5ETEUQAxIVChFURVNUX05PVElGSUNBVElPThAEQhMKEV9lbmNyeXB0ZWRDb250ZW50QhMK'
|
||||
'EV9wbGFpbnRleHRDb250ZW50');
|
||||
|
||||
@$core.Deprecated('Use plaintextContentDescriptor instead')
|
||||
const PlaintextContent$json = {
|
||||
|
|
@ -126,7 +128,8 @@ const EncryptedContent_TextMessage$json = {
|
|||
'2': [
|
||||
{'1': 'senderMessageId', '3': 1, '4': 1, '5': 9, '10': 'senderMessageId'},
|
||||
{'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': [
|
||||
{'1': '_quoteMessageId'},
|
||||
|
|
@ -152,14 +155,15 @@ const EncryptedContent_MessageUpdate$json = {
|
|||
'1': 'MessageUpdate',
|
||||
'2': [
|
||||
{'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': 'text', '3': 3, '4': 1, '5': 9, '9': 0, '10': 'text', '17': true},
|
||||
{'1': 'timestamp', '3': 4, '4': 1, '5': 3, '9': 1, '10': 'timestamp', '17': true},
|
||||
{'1': 'senderMessageId', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'senderMessageId', '17': true},
|
||||
{'1': 'multipleSenderMessageIds', '3': 3, '4': 3, '5': 9, '10': 'multipleSenderMessageIds'},
|
||||
{'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],
|
||||
'8': [
|
||||
{'1': '_senderMessageId'},
|
||||
{'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': 'displayLimitInMilliseconds', '3': 3, '4': 1, '5': 3, '9': 0, '10': 'displayLimitInMilliseconds', '17': true},
|
||||
{'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': 'encryptionKey', '3': 6, '4': 1, '5': 12, '9': 2, '10': 'encryptionKey', '17': true},
|
||||
{'1': 'encryptionMac', '3': 7, '4': 1, '5': 12, '9': 3, '10': 'encryptionMac', '17': true},
|
||||
{'1': 'encryptionNonce', '3': 8, '4': 1, '5': 12, '9': 4, '10': 'encryptionNonce', '17': true},
|
||||
{'1': 'timestamp', '3': 5, '4': 1, '5': 3, '10': 'timestamp'},
|
||||
{'1': 'quoteMessageId', '3': 6, '4': 1, '5': 9, '9': 1, '10': 'quoteMessageId', '17': true},
|
||||
{'1': 'downloadToken', '3': 7, '4': 1, '5': 12, '9': 2, '10': 'downloadToken', '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],
|
||||
'8': [
|
||||
{'1': '_displayLimitInMilliseconds'},
|
||||
{'1': '_quoteMessageId'},
|
||||
{'1': '_downloadToken'},
|
||||
{'1': '_encryptionKey'},
|
||||
{'1': '_encryptionMac'},
|
||||
|
|
@ -200,9 +207,10 @@ const EncryptedContent_Media$json = {
|
|||
const EncryptedContent_Media_Type$json = {
|
||||
'1': 'Type',
|
||||
'2': [
|
||||
{'1': 'IMAGE', '2': 0},
|
||||
{'1': 'VIDEO', '2': 1},
|
||||
{'1': 'GIF', '2': 2},
|
||||
{'1': 'REUPLOAD', '2': 0},
|
||||
{'1': 'IMAGE', '2': 1},
|
||||
{'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': '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': 'createdAt', '3': 4, '4': 1, '5': 3, '9': 2, '10': 'createdAt', '17': true},
|
||||
],
|
||||
'4': [EncryptedContent_PushKeys_Type$json],
|
||||
'8': [
|
||||
{'1': '_keyId'},
|
||||
{'1': '_key'},
|
||||
{'1': '_createdAt'},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -317,42 +327,47 @@ final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
|
|||
'N0UmVxdWVzdEgHUg5jb250YWN0UmVxdWVzdIgBARI+CglmbGFtZVN5bmMYCiABKAsyGy5FbmNy'
|
||||
'eXB0ZWRDb250ZW50LkZsYW1lU3luY0gIUglmbGFtZVN5bmOIAQESOwoIcHVzaEtleXMYCyABKA'
|
||||
'syGi5FbmNyeXB0ZWRDb250ZW50LlB1c2hLZXlzSAlSCHB1c2hLZXlziAEBEjsKCHJlYWN0aW9u'
|
||||
'GAwgASgLMhouRW5jcnlwdGVkQ29udGVudC5SZWFjdGlvbkgKUghyZWFjdGlvbogBARqLAQoLVG'
|
||||
'GAwgASgLMhouRW5jcnlwdGVkQ29udGVudC5SZWFjdGlvbkgKUghyZWFjdGlvbogBARqpAQoLVG'
|
||||
'V4dE1lc3NhZ2USKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSEgoE'
|
||||
'dGV4dBgCIAEoCVIEdGV4dBIrCg5xdW90ZU1lc3NhZ2VJZBgDIAEoCUgAUg5xdW90ZU1lc3NhZ2'
|
||||
'VJZIgBAUIRCg9fcXVvdGVNZXNzYWdlSWQagQEKCFJlYWN0aW9uEigKD3RhcmdldE1lc3NhZ2VJ'
|
||||
'ZBgBIAEoCVIPdGFyZ2V0TWVzc2FnZUlkEhkKBWVtb2ppGAIgASgJSABSBWVtb2ppiAEBEhsKBn'
|
||||
'JlbW92ZRgDIAEoCEgBUgZyZW1vdmWIAQFCCAoGX2Vtb2ppQgkKB19yZW1vdmUa9QEKDU1lc3Nh'
|
||||
'Z2VVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuTWVzc2FnZVVwZGF0ZS'
|
||||
'5UeXBlUgR0eXBlEigKD3NlbmRlck1lc3NhZ2VJZBgCIAEoCVIPc2VuZGVyTWVzc2FnZUlkEhcK'
|
||||
'BHRleHQYAyABKAlIAFIEdGV4dIgBARIhCgl0aW1lc3RhbXAYBCABKANIAVIJdGltZXN0YW1wiA'
|
||||
'EBIi0KBFR5cGUSCgoGREVMRVRFEAASDQoJRURJVF9URVhUEAESCgoGT1BFTkVEEAJCBwoFX3Rl'
|
||||
'eHRCDAoKX3RpbWVzdGFtcBqgBAoFTWVkaWESKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW'
|
||||
'5kZXJNZXNzYWdlSWQSMAoEdHlwZRgCIAEoDjIcLkVuY3J5cHRlZENvbnRlbnQuTWVkaWEuVHlw'
|
||||
'ZVIEdHlwZRJDChpkaXNwbGF5TGltaXRJbk1pbGxpc2Vjb25kcxgDIAEoA0gAUhpkaXNwbGF5TG'
|
||||
'ltaXRJbk1pbGxpc2Vjb25kc4gBARI2ChZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uGAQgASgIUhZy'
|
||||
'ZXF1aXJlc0F1dGhlbnRpY2F0aW9uEikKDWRvd25sb2FkVG9rZW4YBSABKAxIAVINZG93bmxvYW'
|
||||
'RUb2tlbogBARIpCg1lbmNyeXB0aW9uS2V5GAYgASgMSAJSDWVuY3J5cHRpb25LZXmIAQESKQoN'
|
||||
'ZW5jcnlwdGlvbk1hYxgHIAEoDEgDUg1lbmNyeXB0aW9uTWFjiAEBEi0KD2VuY3J5cHRpb25Ob2'
|
||||
'5jZRgIIAEoDEgEUg9lbmNyeXB0aW9uTm9uY2WIAQEiJQoEVHlwZRIJCgVJTUFHRRAAEgkKBVZJ'
|
||||
'REVPEAESBwoDR0lGEAJCHQobX2Rpc3BsYXlMaW1pdEluTWlsbGlzZWNvbmRzQhAKDl9kb3dubG'
|
||||
'9hZFRva2VuQhAKDl9lbmNyeXB0aW9uS2V5QhAKDl9lbmNyeXB0aW9uTWFjQhIKEF9lbmNyeXB0'
|
||||
'aW9uTm9uY2UapwEKC01lZGlhVXBkYXRlEjYKBHR5cGUYASABKA4yIi5FbmNyeXB0ZWRDb250ZW'
|
||||
'50Lk1lZGlhVXBkYXRlLlR5cGVSBHR5cGUSKAoPdGFyZ2V0TWVzc2FnZUlkGAIgASgJUg90YXJn'
|
||||
'ZXRNZXNzYWdlSWQiNgoEVHlwZRIMCghSRU9QRU5FRBAAEgoKBlNUT1JFRBABEhQKEERFQ1JZUF'
|
||||
'RJT05fRVJST1IQAhp4Cg5Db250YWN0UmVxdWVzdBI5CgR0eXBlGAEgASgOMiUuRW5jcnlwdGVk'
|
||||
'Q29udGVudC5Db250YWN0UmVxdWVzdC5UeXBlUgR0eXBlIisKBFR5cGUSCwoHUkVRVUVTVBAAEg'
|
||||
'oKBlJFSkVDVBABEgoKBkFDQ0VQVBACGtIBCg1Db250YWN0VXBkYXRlEjgKBHR5cGUYASABKA4y'
|
||||
'JC5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RVcGRhdGUuVHlwZVIEdHlwZRIhCglhdmF0YXJTdm'
|
||||
'cYAiABKAlIAFIJYXZhdGFyU3ZniAEBEiUKC2Rpc3BsYXlOYW1lGAMgASgJSAFSC2Rpc3BsYXlO'
|
||||
'YW1liAEBIh8KBFR5cGUSCwoHUkVRVUVTVBAAEgoKBlVQREFURRABQgwKCl9hdmF0YXJTdmdCDg'
|
||||
'oMX2Rpc3BsYXlOYW1lGqQBCghQdXNoS2V5cxIzCgR0eXBlGAEgASgOMh8uRW5jcnlwdGVkQ29u'
|
||||
'dGVudC5QdXNoS2V5cy5UeXBlUgR0eXBlEhkKBWtleUlkGAIgASgDSABSBWtleUlkiAEBEhUKA2'
|
||||
'tleRgDIAEoDEgBUgNrZXmIAQEiHwoEVHlwZRILCgdSRVFVRVNUEAASCgoGVVBEQVRFEAFCCAoG'
|
||||
'X2tleUlkQgYKBF9rZXkahwEKCUZsYW1lU3luYxIiCgxmbGFtZUNvdW50ZXIYASABKANSDGZsYW'
|
||||
'1lQ291bnRlchI2ChZsYXN0RmxhbWVDb3VudGVyQ2hhbmdlGAIgASgDUhZsYXN0RmxhbWVDb3Vu'
|
||||
'dGVyQ2hhbmdlEh4KCmJlc3RGcmllbmQYAyABKAhSCmJlc3RGcmllbmRCCgoIX2dyb3VwSWRCFw'
|
||||
'oVX3NlbmRlclByb2ZpbGVDb3VudGVyQg4KDF90ZXh0TWVzc2FnZUIQCg5fbWVzc2FnZVVwZGF0'
|
||||
'ZUIICgZfbWVkaWFCDgoMX21lZGlhVXBkYXRlQhAKDl9jb250YWN0VXBkYXRlQhEKD19jb250YW'
|
||||
'N0UmVxdWVzdEIMCgpfZmxhbWVTeW5jQgsKCV9wdXNoS2V5c0ILCglfcmVhY3Rpb24=');
|
||||
'dGV4dBgCIAEoCVIEdGV4dBIcCgl0aW1lc3RhbXAYAyABKANSCXRpbWVzdGFtcBIrCg5xdW90ZU'
|
||||
'1lc3NhZ2VJZBgEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBAUIRCg9fcXVvdGVNZXNzYWdlSWQa'
|
||||
'gQEKCFJlYWN0aW9uEigKD3RhcmdldE1lc3NhZ2VJZBgBIAEoCVIPdGFyZ2V0TWVzc2FnZUlkEh'
|
||||
'kKBWVtb2ppGAIgASgJSABSBWVtb2ppiAEBEhsKBnJlbW92ZRgDIAEoCEgBUgZyZW1vdmWIAQFC'
|
||||
'CAoGX2Vtb2ppQgkKB19yZW1vdmUatwIKDU1lc3NhZ2VVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLk'
|
||||
'VuY3J5cHRlZENvbnRlbnQuTWVzc2FnZVVwZGF0ZS5UeXBlUgR0eXBlEi0KD3NlbmRlck1lc3Nh'
|
||||
'Z2VJZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSWSIAQESOgoYbXVsdGlwbGVTZW5kZXJNZXNzYW'
|
||||
'dlSWRzGAMgAygJUhhtdWx0aXBsZVNlbmRlck1lc3NhZ2VJZHMSFwoEdGV4dBgEIAEoCUgBUgR0'
|
||||
'ZXh0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wIi0KBFR5cGUSCgoGREVMRVRFEA'
|
||||
'ASDQoJRURJVF9URVhUEAESCgoGT1BFTkVEEAJCEgoQX3NlbmRlck1lc3NhZ2VJZEIHCgVfdGV4'
|
||||
'dBqMBQoFTWVkaWESKAoPc2VuZGVyTWVzc2FnZUlkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSMA'
|
||||
'oEdHlwZRgCIAEoDjIcLkVuY3J5cHRlZENvbnRlbnQuTWVkaWEuVHlwZVIEdHlwZRJDChpkaXNw'
|
||||
'bGF5TGltaXRJbk1pbGxpc2Vjb25kcxgDIAEoA0gAUhpkaXNwbGF5TGltaXRJbk1pbGxpc2Vjb2'
|
||||
'5kc4gBARI2ChZyZXF1aXJlc0F1dGhlbnRpY2F0aW9uGAQgASgIUhZyZXF1aXJlc0F1dGhlbnRp'
|
||||
'Y2F0aW9uEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1wEisKDnF1b3RlTWVzc2FnZUlkGA'
|
||||
'YgASgJSAFSDnF1b3RlTWVzc2FnZUlkiAEBEikKDWRvd25sb2FkVG9rZW4YByABKAxIAlINZG93'
|
||||
'bmxvYWRUb2tlbogBARIpCg1lbmNyeXB0aW9uS2V5GAggASgMSANSDWVuY3J5cHRpb25LZXmIAQ'
|
||||
'ESKQoNZW5jcnlwdGlvbk1hYxgJIAEoDEgEUg1lbmNyeXB0aW9uTWFjiAEBEi0KD2VuY3J5cHRp'
|
||||
'b25Ob25jZRgKIAEoDEgFUg9lbmNyeXB0aW9uTm9uY2WIAQEiMwoEVHlwZRIMCghSRVVQTE9BRB'
|
||||
'AAEgkKBUlNQUdFEAESCQoFVklERU8QAhIHCgNHSUYQA0IdChtfZGlzcGxheUxpbWl0SW5NaWxs'
|
||||
'aXNlY29uZHNCEQoPX3F1b3RlTWVzc2FnZUlkQhAKDl9kb3dubG9hZFRva2VuQhAKDl9lbmNyeX'
|
||||
'B0aW9uS2V5QhAKDl9lbmNyeXB0aW9uTWFjQhIKEF9lbmNyeXB0aW9uTm9uY2UapwEKC01lZGlh'
|
||||
'VXBkYXRlEjYKBHR5cGUYASABKA4yIi5FbmNyeXB0ZWRDb250ZW50Lk1lZGlhVXBkYXRlLlR5cG'
|
||||
'VSBHR5cGUSKAoPdGFyZ2V0TWVzc2FnZUlkGAIgASgJUg90YXJnZXRNZXNzYWdlSWQiNgoEVHlw'
|
||||
'ZRIMCghSRU9QRU5FRBAAEgoKBlNUT1JFRBABEhQKEERFQ1JZUFRJT05fRVJST1IQAhp4Cg5Db2'
|
||||
'50YWN0UmVxdWVzdBI5CgR0eXBlGAEgASgOMiUuRW5jcnlwdGVkQ29udGVudC5Db250YWN0UmVx'
|
||||
'dWVzdC5UeXBlUgR0eXBlIisKBFR5cGUSCwoHUkVRVUVTVBAAEgoKBlJFSkVDVBABEgoKBkFDQ0'
|
||||
'VQVBACGtIBCg1Db250YWN0VXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50'
|
||||
'LkNvbnRhY3RVcGRhdGUuVHlwZVIEdHlwZRIhCglhdmF0YXJTdmcYAiABKAlIAFIJYXZhdGFyU3'
|
||||
'ZniAEBEiUKC2Rpc3BsYXlOYW1lGAMgASgJSAFSC2Rpc3BsYXlOYW1liAEBIh8KBFR5cGUSCwoH'
|
||||
'UkVRVUVTVBAAEgoKBlVQREFURRABQgwKCl9hdmF0YXJTdmdCDgoMX2Rpc3BsYXlOYW1lGtUBCg'
|
||||
'hQdXNoS2V5cxIzCgR0eXBlGAEgASgOMh8uRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5cy5UeXBl'
|
||||
'UgR0eXBlEhkKBWtleUlkGAIgASgDSABSBWtleUlkiAEBEhUKA2tleRgDIAEoDEgBUgNrZXmIAQ'
|
||||
'ESIQoJY3JlYXRlZEF0GAQgASgDSAJSCWNyZWF0ZWRBdIgBASIfCgRUeXBlEgsKB1JFUVVFU1QQ'
|
||||
'ABIKCgZVUERBVEUQAUIICgZfa2V5SWRCBgoEX2tleUIMCgpfY3JlYXRlZEF0GocBCglGbGFtZV'
|
||||
'N5bmMSIgoMZmxhbWVDb3VudGVyGAEgASgDUgxmbGFtZUNvdW50ZXISNgoWbGFzdEZsYW1lQ291'
|
||||
'bnRlckNoYW5nZRgCIAEoA1IWbGFzdEZsYW1lQ291bnRlckNoYW5nZRIeCgpiZXN0RnJpZW5kGA'
|
||||
'MgASgIUgpiZXN0RnJpZW5kQgoKCF9ncm91cElkQhcKFV9zZW5kZXJQcm9maWxlQ291bnRlckIO'
|
||||
'CgxfdGV4dE1lc3NhZ2VCEAoOX21lc3NhZ2VVcGRhdGVCCAoGX21lZGlhQg4KDF9tZWRpYVVwZG'
|
||||
'F0ZUIQCg5fY29udGFjdFVwZGF0ZUIRCg9fY29udGFjdFJlcXVlc3RCDAoKX2ZsYW1lU3luY0IL'
|
||||
'CglfcHVzaEtleXNCCwoJX3JlYWN0aW9u');
|
||||
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ class EncryptedPushNotification extends $pb.GeneratedMessage {
|
|||
class PushNotification extends $pb.GeneratedMessage {
|
||||
factory PushNotification({
|
||||
PushKind? kind,
|
||||
$fixnum.Int64? messageId,
|
||||
$core.String? messageId,
|
||||
$core.String? reactionContent,
|
||||
}) {
|
||||
final $result = create();
|
||||
|
|
@ -134,7 +134,7 @@ class PushNotification extends $pb.GeneratedMessage {
|
|||
|
||||
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)
|
||||
..aInt64(2, _omitFieldNames ? '' : 'messageId', protoName: 'messageId')
|
||||
..aOS(2, _omitFieldNames ? '' : 'messageId', protoName: 'messageId')
|
||||
..aOS(3, _omitFieldNames ? '' : 'reactionContent', protoName: 'reactionContent')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
|
@ -170,9 +170,9 @@ class PushNotification extends $pb.GeneratedMessage {
|
|||
void clearKind() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$fixnum.Int64 get messageId => $_getI64(1);
|
||||
$core.String get messageId => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set messageId($fixnum.Int64 v) { $_setInt64(1, v); }
|
||||
set messageId($core.String v) { $_setString(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasMessageId() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
|
|
@ -237,7 +237,7 @@ class PushUser extends $pb.GeneratedMessage {
|
|||
$fixnum.Int64? userId,
|
||||
$core.String? displayName,
|
||||
$core.bool? blocked,
|
||||
$fixnum.Int64? lastMessageId,
|
||||
$core.String? lastMessageId,
|
||||
$core.Iterable<PushKey>? pushKeys,
|
||||
}) {
|
||||
final $result = create();
|
||||
|
|
@ -266,7 +266,7 @@ class PushUser extends $pb.GeneratedMessage {
|
|||
..aInt64(1, _omitFieldNames ? '' : 'userId', protoName: 'userId')
|
||||
..aOS(2, _omitFieldNames ? '' : 'displayName', protoName: 'displayName')
|
||||
..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)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
|
@ -320,9 +320,9 @@ class PushUser extends $pb.GeneratedMessage {
|
|||
void clearBlocked() => clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$fixnum.Int64 get lastMessageId => $_getI64(3);
|
||||
$core.String get lastMessageId => $_getSZ(3);
|
||||
@$pb.TagNumber(4)
|
||||
set lastMessageId($fixnum.Int64 v) { $_setInt64(3, v); }
|
||||
set lastMessageId($core.String v) { $_setString(3, v); }
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasLastMessageId() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ const PushNotification$json = {
|
|||
'1': 'PushNotification',
|
||||
'2': [
|
||||
{'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},
|
||||
],
|
||||
'8': [
|
||||
|
|
@ -76,7 +76,7 @@ const PushNotification$json = {
|
|||
/// Descriptor for `PushNotification`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List pushNotificationDescriptor = $convert.base64Decode(
|
||||
'ChBQdXNoTm90aWZpY2F0aW9uEh0KBGtpbmQYASABKA4yCS5QdXNoS2luZFIEa2luZBIhCgltZX'
|
||||
'NzYWdlSWQYAiABKANIAFIJbWVzc2FnZUlkiAEBEi0KD3JlYWN0aW9uQ29udGVudBgDIAEoCUgB'
|
||||
'NzYWdlSWQYAiABKAlIAFIJbWVzc2FnZUlkiAEBEi0KD3JlYWN0aW9uQ29udGVudBgDIAEoCUgB'
|
||||
'Ug9yZWFjdGlvbkNvbnRlbnSIAQFCDAoKX21lc3NhZ2VJZEISChBfcmVhY3Rpb25Db250ZW50');
|
||||
|
||||
@$core.Deprecated('Use pushUsersDescriptor instead')
|
||||
|
|
@ -98,7 +98,7 @@ const PushUser$json = {
|
|||
{'1': 'userId', '3': 1, '4': 1, '5': 3, '10': 'userId'},
|
||||
{'1': 'displayName', '3': 2, '4': 1, '5': 9, '10': 'displayName'},
|
||||
{'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'},
|
||||
],
|
||||
'8': [
|
||||
|
|
@ -110,7 +110,7 @@ const PushUser$json = {
|
|||
final $typed_data.Uint8List pushUserDescriptor = $convert.base64Decode(
|
||||
'CghQdXNoVXNlchIWCgZ1c2VySWQYASABKANSBnVzZXJJZBIgCgtkaXNwbGF5TmFtZRgCIAEoCV'
|
||||
'ILZGlzcGxheU5hbWUSGAoHYmxvY2tlZBgDIAEoCFIHYmxvY2tlZBIpCg1sYXN0TWVzc2FnZUlk'
|
||||
'GAQgASgDSABSDWxhc3RNZXNzYWdlSWSIAQESJAoIcHVzaEtleXMYBSADKAsyCC5QdXNoS2V5Ug'
|
||||
'GAQgASgJSABSDWxhc3RNZXNzYWdlSWSIAQESJAoIcHVzaEtleXMYBSADKAsyCC5QdXNoS2V5Ug'
|
||||
'hwdXNoS2V5c0IQCg5fbGFzdE1lc3NhZ2VJZA==');
|
||||
|
||||
@$core.Deprecated('Use pushKeyDescriptor instead')
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ message Message {
|
|||
PLAINTEXT_CONTENT = 1;
|
||||
CIPHERTEXT = 2;
|
||||
PREKEY_BUNDLE = 3;
|
||||
TEST_NOTIFICATION = 4;
|
||||
}
|
||||
Type type = 1;
|
||||
string receiptId = 2;
|
||||
|
|
@ -46,7 +47,8 @@ message EncryptedContent {
|
|||
message TextMessage {
|
||||
string senderMessageId = 1;
|
||||
string text = 2;
|
||||
optional string quoteMessageId = 3;
|
||||
int64 timestamp = 3;
|
||||
optional string quoteMessageId = 4;
|
||||
}
|
||||
|
||||
message Reaction {
|
||||
|
|
@ -62,27 +64,31 @@ message EncryptedContent {
|
|||
OPENED = 2;
|
||||
}
|
||||
Type type = 1;
|
||||
string senderMessageId = 2;
|
||||
optional string text = 3;
|
||||
optional int64 timestamp = 4;
|
||||
optional string senderMessageId = 2;
|
||||
repeated string multipleSenderMessageIds = 3;
|
||||
optional string text = 4;
|
||||
int64 timestamp = 5;
|
||||
}
|
||||
|
||||
message Media {
|
||||
enum Type {
|
||||
IMAGE = 0;
|
||||
VIDEO = 1;
|
||||
GIF = 2;
|
||||
REUPLOAD = 0;
|
||||
IMAGE = 1;
|
||||
VIDEO = 2;
|
||||
GIF = 3;
|
||||
}
|
||||
|
||||
string senderMessageId = 1;
|
||||
Type type = 2;
|
||||
optional int64 displayLimitInMilliseconds = 3;
|
||||
bool requiresAuthentication = 4;
|
||||
int64 timestamp = 5;
|
||||
optional string quoteMessageId = 6;
|
||||
|
||||
optional bytes downloadToken = 5;
|
||||
optional bytes encryptionKey = 6;
|
||||
optional bytes encryptionMac = 7;
|
||||
optional bytes encryptionNonce = 8;
|
||||
optional bytes downloadToken = 7;
|
||||
optional bytes encryptionKey = 8;
|
||||
optional bytes encryptionMac = 9;
|
||||
optional bytes encryptionNonce = 10;
|
||||
}
|
||||
|
||||
message MediaUpdate {
|
||||
|
|
@ -124,6 +130,7 @@ message EncryptedContent {
|
|||
Type type = 1;
|
||||
optional int64 keyId = 2;
|
||||
optional bytes key = 3;
|
||||
optional int64 createdAt = 4;
|
||||
}
|
||||
|
||||
message FlameSync {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ enum PushKind {
|
|||
|
||||
message PushNotification {
|
||||
PushKind kind = 1;
|
||||
optional int64 messageId = 2;
|
||||
optional string messageId = 2;
|
||||
optional string reactionContent = 3;
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ message PushUser {
|
|||
int64 userId = 1;
|
||||
string displayName = 2;
|
||||
bool blocked = 3;
|
||||
optional int64 lastMessageId = 4;
|
||||
optional string lastMessageId = 4;
|
||||
repeated PushKey pushKeys = 5;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ class ApiService {
|
|||
_channel = null;
|
||||
isAuthenticated = false;
|
||||
globalCallbackConnectionState(isConnected: false);
|
||||
await twonlyDB.messagesDao.resetPendingDownloadState();
|
||||
await twonlyDB.mediaFilesDao.resetPendingDownloadState();
|
||||
}
|
||||
|
||||
Future<void> startReconnectionTimer() async {
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ Future<void> handleDownloadStatusUpdateInternal(
|
|||
|
||||
Mutex protectDownload = Mutex();
|
||||
|
||||
Future<void> startDownloadMedia(Message message, bool force) async {
|
||||
Future<void> startDownloadMedia(MediaFile media, bool force) async {
|
||||
Log.info(
|
||||
'Download blocked for ${message.messageId} because of network state.',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,163 +1,153 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cryptography_plus/cryptography_plus.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:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/tables/messages_table.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/client/generated/messages.pb.dart'
|
||||
as pb;
|
||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pb.dart';
|
||||
import 'package:twonly/src/services/api/server_messages.dart'
|
||||
show messageGetsAck;
|
||||
import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.dart';
|
||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
||||
final lockRetransmission = Mutex();
|
||||
|
||||
Future<void> tryTransmitMessages() async {
|
||||
return lockRetransmission.protect(() async {
|
||||
final retransIds =
|
||||
await twonlyDB.messageRetransmissionDao.getRetransmitAbleMessages();
|
||||
final receipts = await twonlyDB.receiptsDao.getReceiptsNotAckByServer();
|
||||
|
||||
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) {
|
||||
await sendRetransmitMessage(retransId);
|
||||
for (final receipt in receipts) {
|
||||
await tryToSendCompleteMessage(receipt: receipt);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> tryToSendCompleteMessage(String receiptId) async {
|
||||
Future<void> tryToSendCompleteMessage({
|
||||
String? receiptId,
|
||||
Receipt? receipt,
|
||||
bool reupload = false,
|
||||
}) async {
|
||||
try {
|
||||
final retrans = await twonlyDB.messageRetransmissionDao
|
||||
.getRetransmissionById(retransId)
|
||||
.getSingleOrNull();
|
||||
|
||||
/// SET THE Message().receiptID !!!!!!!
|
||||
/// ALSO THE encryptedContent is NOT YET ENCRYPTED!
|
||||
|
||||
if (retrans == null) {
|
||||
Log.error('$retransId not found in database');
|
||||
return;
|
||||
}
|
||||
|
||||
if (retrans.acknowledgeByServerAt != null) {
|
||||
Log.error('$retransId message already retransmitted');
|
||||
return;
|
||||
}
|
||||
|
||||
final json = MessageJson.fromJson(
|
||||
jsonDecode(
|
||||
utf8.decode(
|
||||
gzip.decode(retrans.plaintextContent),
|
||||
),
|
||||
) 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 (receiptId == null && receipt == null) return;
|
||||
if (receipt == null) {
|
||||
receipt = await twonlyDB.receiptsDao.getReceiptById(receiptId!);
|
||||
if (receipt == null) {
|
||||
Log.error('Receipt $receiptId not found.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
receiptId = receipt.receiptId;
|
||||
|
||||
if (reupload) {
|
||||
await twonlyDB.receiptsDao.updateReceipt(
|
||||
receiptId,
|
||||
const ReceiptsCompanion(
|
||||
ackByServerAt: Value(null),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (receipt.ackByServerAt != null) {
|
||||
Log.error('$receiptId message already uploaded!');
|
||||
return;
|
||||
}
|
||||
|
||||
final encryptedBytes = await signalEncryptMessage(
|
||||
retrans.contactId,
|
||||
retrans.plaintextContent,
|
||||
Log.info('Uploading $receiptId (Message to ${receipt.contactId})');
|
||||
|
||||
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) {
|
||||
Log.error('Could not encrypt the message. Aborting and trying again.');
|
||||
return;
|
||||
if (message.type == pb.Message_Type.TEST_NOTIFICATION) {
|
||||
pushData = (PushNotification()..kind = PushKind.testNotification)
|
||||
.writeToBuffer();
|
||||
}
|
||||
|
||||
final encryptedHash = (await Sha256().hash(encryptedBytes)).bytes;
|
||||
|
||||
await twonlyDB.messageRetransmissionDao.updateRetransmission(
|
||||
retransId,
|
||||
MessageRetransmissionsCompanion(
|
||||
encryptedHash: Value(Uint8List.fromList(encryptedHash)),
|
||||
),
|
||||
);
|
||||
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.');
|
||||
return;
|
||||
}
|
||||
message.encryptedContent = cipherText.serialize();
|
||||
switch (cipherText.getType()) {
|
||||
case CiphertextMessage.prekeyType:
|
||||
message.type = pb.Message_Type.PREKEY_BUNDLE;
|
||||
case CiphertextMessage.whisperType:
|
||||
message.type = pb.Message_Type.CIPHERTEXT;
|
||||
default:
|
||||
Log.error('Invalid ciphertext type: ${cipherText.getType()}.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final resp = await apiService.sendTextMessage(
|
||||
retrans.contactId,
|
||||
encryptedBytes,
|
||||
retrans.pushData,
|
||||
receipt.contactId,
|
||||
message.writeToBuffer(),
|
||||
pushData,
|
||||
);
|
||||
|
||||
var retry = true;
|
||||
|
||||
if (resp.isError) {
|
||||
Log.error('Could not retransmit message.');
|
||||
Log.error('Could not transmit message $receiptId got ${resp.error}.');
|
||||
if (resp.error == ErrorCode.UserIdNotFound) {
|
||||
retry = false;
|
||||
if (retrans.messageId != null) {
|
||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||
retrans.messageId!,
|
||||
const MessagesCompanion(errorWhileSending: Value(true)),
|
||||
);
|
||||
}
|
||||
await twonlyDB.receiptsDao.deleteReceipt(receiptId);
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
retrans.contactId,
|
||||
receipt.contactId,
|
||||
const ContactsCompanion(deleted: Value(true)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (resp.isSuccess) {
|
||||
retry = false;
|
||||
if (retrans.messageId != null) {
|
||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||
retrans.messageId!,
|
||||
if (receipt.messageId != null) {
|
||||
await twonlyDB.messagesDao.updateMessageId(
|
||||
receipt.messageId!,
|
||||
const MessagesCompanion(
|
||||
acknowledgeByServer: Value(true),
|
||||
errorWhileSending: Value(false),
|
||||
ackByServer: Value(true),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!retry) {
|
||||
if (!messageGetsAck(json.kind)) {
|
||||
await twonlyDB.messageRetransmissionDao
|
||||
.deleteRetransmissionById(retransId);
|
||||
if (!receipt.contactWillSendsReceipt) {
|
||||
await twonlyDB.receiptsDao.deleteReceipt(receiptId);
|
||||
} else {
|
||||
await twonlyDB.messageRetransmissionDao.updateRetransmission(
|
||||
retransId,
|
||||
MessageRetransmissionsCompanion(
|
||||
acknowledgeByServerAt: Value(DateTime.now()),
|
||||
retryCount: Value(retrans.retryCount + 1),
|
||||
await twonlyDB.receiptsDao.updateReceipt(
|
||||
receiptId,
|
||||
ReceiptsCompanion(
|
||||
ackByServerAt: Value(DateTime.now()),
|
||||
retryCount: Value(receipt.retryCount + 1),
|
||||
lastRetry: Value(DateTime.now()),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error('error resending message: $e');
|
||||
await twonlyDB.messageRetransmissionDao.deleteRetransmissionById(retransId);
|
||||
Log.error('Unknown Error when sending message: $e');
|
||||
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) {
|
||||
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(
|
||||
int fromUserId,
|
||||
List<int> messageOtherIds,
|
||||
int contactId,
|
||||
List<String> messageOtherIds,
|
||||
) async {
|
||||
var biggestMessageId = messageOtherIds.first;
|
||||
|
||||
for (final messageOtherId in messageOtherIds) {
|
||||
if (messageOtherId > biggestMessageId) biggestMessageId = messageOtherId;
|
||||
await encryptAndSendMessageAsync(
|
||||
null,
|
||||
fromUserId,
|
||||
MessageJson(
|
||||
kind: MessageKind.opened,
|
||||
messageReceiverId: messageOtherId,
|
||||
content: MessageContent(),
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
);
|
||||
if (isUUIDNewer(messageOtherId, biggestMessageId)) {
|
||||
biggestMessageId = messageOtherId;
|
||||
}
|
||||
}
|
||||
await updateLastMessageId(fromUserId, biggestMessageId);
|
||||
await sendCipherText(
|
||||
contactId,
|
||||
pb.EncryptedContent(
|
||||
messageUpdate: pb.EncryptedContent_MessageUpdate(
|
||||
type: pb.EncryptedContent_MessageUpdate_Type.OPENED,
|
||||
multipleSenderMessageIds: messageOtherIds,
|
||||
),
|
||||
),
|
||||
);
|
||||
await updateLastMessageId(contactId, biggestMessageId);
|
||||
}
|
||||
|
||||
Future<void> notifyContactsAboutProfileChange({int? onlyToContact}) async {
|
||||
|
|
@ -256,11 +199,13 @@ Future<void> notifyContactsAboutProfileChange({int? onlyToContact}) async {
|
|||
if (user == null) return;
|
||||
if (user.avatarSvg == null) return;
|
||||
|
||||
final encryptedContent = pb.EncryptedContent()
|
||||
..contactUpdate = (pb.EncryptedContent_ContactUpdate()
|
||||
..type = pb.EncryptedContent_ContactUpdate_Type.UPDATE
|
||||
..avatarSvg = user.avatarSvg!
|
||||
..displayName = user.displayName);
|
||||
final encryptedContent = pb.EncryptedContent(
|
||||
contactUpdate: pb.EncryptedContent_ContactUpdate(
|
||||
type: pb.EncryptedContent_ContactUpdate_Type.UPDATE,
|
||||
avatarSvg: user.avatarSvg,
|
||||
displayName: user.displayName,
|
||||
),
|
||||
);
|
||||
|
||||
if (onlyToContact != null) {
|
||||
await sendCipherText(onlyToContact, encryptedContent);
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ Future<void> handleNewMessage(int fromUserId, Uint8List body) async {
|
|||
Log.info(
|
||||
'Got decryption error: ${message.plaintextContent.decryptionErrorMessage.type} for $receiptId',
|
||||
);
|
||||
await tryToSendCompleteMessage(receiptId);
|
||||
await tryToSendCompleteMessage(receiptId: receiptId, reupload: true);
|
||||
}
|
||||
|
||||
case Message_Type.CIPHERTEXT:
|
||||
|
|
@ -97,8 +97,10 @@ Future<void> handleNewMessage(int fromUserId, Uint8List body) async {
|
|||
contactWillSendsReceipt: const Value(false),
|
||||
),
|
||||
);
|
||||
await tryToSendCompleteMessage(receiptId);
|
||||
await tryToSendCompleteMessage(receiptId: receiptId);
|
||||
}
|
||||
case Message_Type.TEST_NOTIFICATION:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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/notifications/pushkeys.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';
|
||||
|
||||
Future<void> handleContactRequest(
|
||||
|
|
@ -17,6 +18,7 @@ Future<void> handleContactRequest(
|
|||
) async {
|
||||
switch (contactRequest.type) {
|
||||
case EncryptedContent_ContactRequest_Type.REQUEST:
|
||||
Log.info('Got a contact request from $fromUserId');
|
||||
// Request the username by the server so an attacker can not
|
||||
// forge the displayed username in the contact request
|
||||
final username = await apiService.getUsername(fromUserId);
|
||||
|
|
@ -33,6 +35,7 @@ Future<void> handleContactRequest(
|
|||
}
|
||||
await setupNotificationWithUsers();
|
||||
case EncryptedContent_ContactRequest_Type.ACCEPT:
|
||||
Log.info('Got a contact accept from $fromUserId');
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
fromUserId,
|
||||
const ContactsCompanion(
|
||||
|
|
@ -41,19 +44,23 @@ Future<void> handleContactRequest(
|
|||
),
|
||||
);
|
||||
case EncryptedContent_ContactRequest_Type.REJECT:
|
||||
Log.info('Got a contact reject from $fromUserId');
|
||||
await twonlyDB.contactsDao.deleteContactByUserId(fromUserId);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> handleContactUpdate(
|
||||
int fromUserId,
|
||||
EncryptedContent_ContactUpdate contactUpdate,
|
||||
int? senderProfileCounter) async {
|
||||
int fromUserId,
|
||||
EncryptedContent_ContactUpdate contactUpdate,
|
||||
int? senderProfileCounter,
|
||||
) async {
|
||||
switch (contactUpdate.type) {
|
||||
case EncryptedContent_ContactUpdate_Type.REQUEST:
|
||||
Log.info('Got a contact update request from $fromUserId');
|
||||
await notifyContactsAboutProfileChange(onlyToContact: fromUserId);
|
||||
|
||||
case EncryptedContent_ContactUpdate_Type.UPDATE:
|
||||
Log.info('Got a contact update $fromUserId');
|
||||
if (contactUpdate.hasAvatarSvg() &&
|
||||
contactUpdate.hasDisplayName() &&
|
||||
senderProfileCounter != null) {
|
||||
|
|
@ -74,6 +81,7 @@ Future<void> handleFlameSync(
|
|||
int contactId,
|
||||
EncryptedContent_FlameSync flameSync,
|
||||
) async {
|
||||
Log.info('Got a flameSync from $contactId');
|
||||
final contact = await twonlyDB.contactsDao
|
||||
.getContactByUserId(contactId)
|
||||
.getSingleOrNull();
|
||||
|
|
|
|||
|
|
@ -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/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 {
|
||||
TODO
|
||||
}
|
||||
Future<void> handleMedia(
|
||||
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 {
|
||||
TODO
|
||||
late MediaType mediaType;
|
||||
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) {
|
||||
// case MessageKind.receiveMediaError:
|
||||
// if (message.messageReceiverId != null) {
|
||||
// final openedMessage = await twonlyDB.messagesDao
|
||||
// .getMessageByIdAndContactId(fromUserId, message.messageReceiverId!)
|
||||
// .getSingleOrNull();
|
||||
|
||||
// if (openedMessage != null) {
|
||||
// /// 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),
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
message.mediaId!,
|
||||
MediaFilesCompanion(
|
||||
downloadState: const Value(DownloadState.pending),
|
||||
downloadToken: Value(Uint8List.fromList(media.downloadToken)),
|
||||
encryptionKey: Value(Uint8List.fromList(media.encryptionKey)),
|
||||
encryptionMac: Value(Uint8List.fromList(media.encryptionMac)),
|
||||
encryptionNonce: Value(Uint8List.fromList(media.encryptionNonce)),
|
||||
),
|
||||
);
|
||||
final msg = await twonlyDB.messagesDao
|
||||
.getMessageByIdAndContactId(
|
||||
fromUserId,
|
||||
message.messageReceiverId!,
|
||||
)
|
||||
.getSingleOrNull();
|
||||
if (msg != null && msg.mediaUploadId != null) {
|
||||
final filePath = await getMediaFilePath(msg.mediaUploadId, 'send');
|
||||
if (filePath.contains('mp4')) {
|
||||
unawaited(createThumbnailsForVideo(File(filePath)));
|
||||
} else {
|
||||
unawaited(createThumbnailsForImage(File(filePath)));
|
||||
}
|
||||
|
||||
final mediaFile =
|
||||
await twonlyDB.mediaFilesDao.getMediaFileById(message.mediaId!);
|
||||
|
||||
if (mediaFile != null) {
|
||||
unawaited(startDownloadMedia(mediaFile, false));
|
||||
}
|
||||
}
|
||||
} else if (message.content != null) {}
|
||||
|
||||
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,
|
||||
true,
|
||||
fromTimestamp(media.timestamp),
|
||||
);
|
||||
|
||||
unawaited(startDownloadMedia(mediaFile, false));
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,15 @@ Future<void> handleMessageUpdate(
|
|||
) async {
|
||||
switch (messageUpdate.type) {
|
||||
case EncryptedContent_MessageUpdate_Type.OPENED:
|
||||
Log.info('Opened message ${messageUpdate.senderMessageId}');
|
||||
await twonlyDB.messagesDao.handleMessageOpened(
|
||||
groupId,
|
||||
messageUpdate.senderMessageId,
|
||||
fromTimestamp(messageUpdate.timestamp),
|
||||
);
|
||||
Log.info(
|
||||
'Opened message ${messageUpdate.multipleSenderMessageIds.length}');
|
||||
for (final senderMessageId in messageUpdate.multipleSenderMessageIds) {
|
||||
await twonlyDB.messagesDao.handleMessageOpened(
|
||||
groupId,
|
||||
senderMessageId,
|
||||
fromTimestamp(messageUpdate.timestamp),
|
||||
);
|
||||
}
|
||||
case EncryptedContent_MessageUpdate_Type.DELETE:
|
||||
Log.info('Delete message ${messageUpdate.senderMessageId}');
|
||||
await twonlyDB.messagesDao.handleMessageDeletion(
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
|
||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.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));
|
||||
|
||||
|
|
@ -11,6 +12,7 @@ Future<void> handlePushKey(
|
|||
) async {
|
||||
switch (pushKeys.type) {
|
||||
case EncryptedContent_PushKeys_Type.REQUEST:
|
||||
Log.info('Got a pushkey request from $contactId');
|
||||
if (lastPushKeyRequest
|
||||
.isBefore(DateTime.now().subtract(const Duration(seconds: 60)))) {
|
||||
lastPushKeyRequest = DateTime.now();
|
||||
|
|
@ -18,6 +20,7 @@ Future<void> handlePushKey(
|
|||
}
|
||||
|
||||
case EncryptedContent_PushKeys_Type.UPDATE:
|
||||
Log.info('Got a pushkey update from $contactId');
|
||||
await handleNewPushKey(contactId, pushKeys.keyId.toInt(), pushKeys.key);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
Future<void> handleReaction(
|
||||
int fromUserId,
|
||||
String groupId,
|
||||
EncryptedContent_Reaction reaction,
|
||||
) async {
|
||||
Log.info('Got a reaction from $fromUserId');
|
||||
if (reaction.hasRemove()) {
|
||||
if (reaction.remove) {
|
||||
await twonlyDB.reactionsDao
|
||||
|
|
|
|||
|
|
@ -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/services/api/utils.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
Future<void> handleTextMessage(
|
||||
int fromUserId,
|
||||
String groupId,
|
||||
EncryptedContent_TextMessage textMessage,
|
||||
) async {
|
||||
TODO
|
||||
// final content = message.content!;
|
||||
// // when a message is received doubled ignore it...
|
||||
Log.info(
|
||||
'Got a text message: ${textMessage.senderMessageId} from $groupId',
|
||||
);
|
||||
|
||||
// final openedMessage = await twonlyDB.messagesDao
|
||||
// .getMessageByOtherMessageId(fromUserId, message.messageSenderId!)
|
||||
// .getSingleOrNull();
|
||||
|
||||
// if (openedMessage != null) {
|
||||
// if (openedMessage.errorWhileSending) {
|
||||
// await twonlyDB.messagesDao
|
||||
// .deleteMessagesByMessageId(openedMessage.messageId);
|
||||
// } else {
|
||||
// Log.error(
|
||||
// 'Got a duplicated message from other user: ${message.messageSenderId!}',
|
||||
// );
|
||||
// final ok = client.Response_Ok()..none = true;
|
||||
// return client.Response()..ok = ok;
|
||||
// }
|
||||
// }
|
||||
|
||||
// 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;
|
||||
final message = await twonlyDB.messagesDao.insertMessage(
|
||||
MessagesCompanion(
|
||||
messageId: Value(textMessage.senderMessageId),
|
||||
senderId: Value(fromUserId),
|
||||
groupId: Value(groupId),
|
||||
content: Value(textMessage.text),
|
||||
ackByServer: const Value(true),
|
||||
ackByUser: const Value(true),
|
||||
quotesMessageId: Value(
|
||||
textMessage.hasQuoteMessageId() ? textMessage.quoteMessageId : null,
|
||||
),
|
||||
createdAt: Value(fromTimestamp(textMessage.timestamp)),
|
||||
),
|
||||
);
|
||||
if (message != null) {
|
||||
Log.info('Inserted a new text message with ID: ${message.messageId}');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import 'package:cryptography_plus/cryptography_plus.dart';
|
|||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/src/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/utils/log.dart';
|
||||
import 'package:twonly/src/views/camera/share_image_editor_view.dart'
|
||||
|
|
|
|||
|
|
@ -7,15 +7,17 @@ import 'package:cryptography_plus/cryptography_plus.dart';
|
|||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:hashlib/random.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/constants/secure_storage_keys.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/model/json/message_old.dart' as my;
|
||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pb.dart';
|
||||
import 'package:twonly/src/model/protobuf/client/generated/messages.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/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
/// This function must be called after the database is setup
|
||||
Future<void> setupNotificationWithUsers({
|
||||
|
|
@ -104,19 +106,14 @@ Future<void> setupNotificationWithUsers({
|
|||
}
|
||||
|
||||
Future<void> sendNewPushKey(int userId, PushKey pushKey) async {
|
||||
await encryptAndSendMessageAsync(
|
||||
null,
|
||||
await sendCipherText(
|
||||
userId,
|
||||
my.MessageJson(
|
||||
kind: MessageKind.pushKey,
|
||||
content: my.PushKeyContent(
|
||||
keyId: pushKey.id.toInt(),
|
||||
key: pushKey.key,
|
||||
),
|
||||
timestamp: DateTime.fromMillisecondsSinceEpoch(
|
||||
pushKey.createdAtUnixTimestamp.toInt(),
|
||||
),
|
||||
),
|
||||
EncryptedContent()
|
||||
..pushKeys = (EncryptedContent_PushKeys()
|
||||
..type = EncryptedContent_PushKeys_Type.UPDATE
|
||||
..key = pushKey.key
|
||||
..keyId = pushKey.id
|
||||
..createdAt = pushKey.createdAtUnixTimestamp),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -132,7 +129,7 @@ Future<void> updatePushUser(Contact contact) async {
|
|||
displayName: getContactDisplayName(contact),
|
||||
pushKeys: [],
|
||||
blocked: contact.blocked,
|
||||
lastMessageId: Int64(),
|
||||
lastMessageId: uuid.v7(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
|
|
@ -160,7 +157,7 @@ Future<void> handleNewPushKey(int fromUserId, int keyId, List<int> key) async {
|
|||
displayName: getContactDisplayName(contact),
|
||||
pushKeys: [],
|
||||
blocked: contact.blocked,
|
||||
lastMessageId: Int64(),
|
||||
lastMessageId: uuid.v7(),
|
||||
),
|
||||
);
|
||||
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.add(
|
||||
PushKey(
|
||||
id: Int64(pushKey.keyId),
|
||||
key: pushKey.key,
|
||||
id: Int64(keyId),
|
||||
key: key,
|
||||
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);
|
||||
}
|
||||
|
||||
Future<void> updateLastMessageId(int fromUserId, int messageId) async {
|
||||
Future<void> updateLastMessageId(int fromUserId, String messageId) async {
|
||||
final pushUsers = await getPushKeys(SecureStorageKeys.receivingPushKeys);
|
||||
|
||||
final pushUser = pushUsers.firstWhereOrNull((x) => x.userId == fromUserId);
|
||||
|
|
@ -192,15 +189,103 @@ Future<void> updateLastMessageId(int fromUserId, int messageId) async {
|
|||
return;
|
||||
}
|
||||
|
||||
if (pushUser.lastMessageId < Int64(messageId)) {
|
||||
pushUser.lastMessageId = Int64(messageId);
|
||||
if (isUUIDNewer(messageId, pushUser.lastMessageId)) {
|
||||
pushUser.lastMessageId = messageId;
|
||||
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
|
||||
/// 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);
|
||||
|
||||
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... :/
|
||||
// return null;
|
||||
Log.error('Using insecure key as the receiver does not send a push key!');
|
||||
await encryptAndSendMessageAsync(
|
||||
null,
|
||||
|
||||
await sendCipherText(
|
||||
toUserId,
|
||||
my.MessageJson(
|
||||
kind: MessageKind.requestPushKey,
|
||||
content: my.MessageContent(),
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
EncryptedContent()
|
||||
..pushKeys = (EncryptedContent_PushKeys()
|
||||
..type = EncryptedContent_PushKeys_Type.REQUEST),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -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/utils.signal.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
/// This caused some troubles, so protection the encryption...
|
||||
final lockingSignalEncryption = Mutex();
|
||||
|
||||
Future<Uint8List?> signalEncryptMessage(
|
||||
Future<CiphertextMessage?> signalEncryptMessage(
|
||||
int target,
|
||||
Uint8List plaintextContent,
|
||||
) async {
|
||||
return lockingSignalEncryption.protect<Uint8List?>(() async {
|
||||
return lockingSignalEncryption.protect<CiphertextMessage?>(() async {
|
||||
try {
|
||||
final signalStore = (await getSignalStore())!;
|
||||
final address = SignalProtocolAddress(target.toString(), defaultDeviceId);
|
||||
|
|
@ -75,14 +74,7 @@ Future<Uint8List?> signalEncryptMessage(
|
|||
Log.error('did not get the identity of the remote address');
|
||||
}
|
||||
}
|
||||
|
||||
final ciphertext = await session.encrypt(plaintextContent);
|
||||
|
||||
final b = BytesBuilder()
|
||||
..add(ciphertext.serialize())
|
||||
..add(intToBytes(ciphertext.getType()));
|
||||
|
||||
return b.takeBytes();
|
||||
return await session.encrypt(plaintextContent);
|
||||
} catch (e) {
|
||||
Log.error(e.toString());
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,23 @@ import 'dart:io';
|
|||
|
||||
import 'package:flutter_image_compress/flutter_image_compress.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: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 {
|
||||
final fileExtension = file.path.split('.').last.toLowerCase();
|
||||
if (fileExtension != 'png') {
|
||||
|
|
|
|||
|
|
@ -267,3 +267,9 @@ MediaMessageContent? getMediaContent(Message message) {
|
|||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:hashlib/random.dart';
|
||||
import 'package:twonly/globals.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/notifications/pushkeys.notifications.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
|
@ -52,10 +51,10 @@ class NotificationView extends StatelessWidget {
|
|||
if (run) {
|
||||
final user = await getUser();
|
||||
if (user != null) {
|
||||
final pushData = await getPushData(
|
||||
final pushData = await encryptPushNotification(
|
||||
user.userId,
|
||||
PushNotification(
|
||||
messageId: Int64(),
|
||||
messageId: uuid.v4(),
|
||||
kind: PushKind.testNotification,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hashlib/random.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/views/components/animate_icon.dart';
|
||||
|
||||
|
|
@ -40,5 +42,11 @@ void main() {
|
|||
final uv4String = utf8.decode(uv4Bytes.cast<int>());
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue