fixing issue with the prekey unkown error

This commit is contained in:
otsmr 2025-10-16 23:56:18 +02:00
parent 97d7281f4d
commit ba1913d737
14 changed files with 4480 additions and 197 deletions

View file

@ -3,14 +3,14 @@
## 0.0.61
- Fixing message decryption error
- Fixing issue with user deletion
- Fixing issue with flame counter sync
- Dependency and Flutter upgrade
- Developer Settings
## 0.0.60
- Improved logging to debug the 'Tap to load' issue.
==> If you encounter any issues, please send your debug log via the feedback button along with a short description of the error so that we can resolve them. :)
- Display your own avatar in the title bar of the chat list.
- Created a default avatar image in case none was set.
- Improved UI handling when requesting microphone access for the first time.

File diff suppressed because one or more lines are too long

View file

@ -195,6 +195,16 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
return select(contacts).watch();
}
Future<void> modifyFlameCounterForTesting() async {
await update(contacts).write(
ContactsCompanion(
lastFlameCounterChange: Value(DateTime.now()),
flameCounter: const Value(1337),
lastFlameSync: const Value(null),
),
);
}
Stream<int> watchFlameCounter(int userId) {
return (select(contacts)
..where(

View file

@ -93,6 +93,16 @@ class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
}
Future<void> purgeOutDatedPreKeys() async {
// Deletion is a workaround for the issue, that own pre keys where deleted after 40 days, while they could be 30days
// on the server + 25 days on the others device old, resulting in the issue that the receiver could not decrypt the
// messages...
await (delete(signalContactSignedPreKeys)
..where(
(t) => (t.createdAt.isSmallerThanValue(
DateTime(2025, 10, 10),
)),
))
.go();
// other pre keys are valid 100 days
await (delete(signalContactSignedPreKeys)
..where(

View file

@ -16,5 +16,8 @@ class MessageRetransmissions extends Table {
BlobColumn get pushData => blob().nullable()();
BlobColumn get encryptedHash => blob().nullable()();
IntColumn get retryCount => integer().withDefault(const Constant(0))();
DateTimeColumn get lastRetry => dateTime().nullable()();
DateTimeColumn get acknowledgeByServerAt => dateTime().nullable()();
}

View file

@ -54,7 +54,7 @@ class TwonlyDatabase extends _$TwonlyDatabase {
TwonlyDatabase.forTesting(DatabaseConnection super.connection);
@override
int get schemaVersion => 16;
int get schemaVersion => 17;
static QueryExecutor _openConnection() {
return driftDatabase(
@ -157,6 +157,12 @@ class TwonlyDatabase extends _$TwonlyDatabase {
from15To16: (m, schema) async {
await m.deleteTable('media_downloads');
},
from16To17: (m, schema) async {
await m.addColumn(schema.messageRetransmissions,
schema.messageRetransmissions.lastRetry);
await m.addColumn(schema.messageRetransmissions,
schema.messageRetransmissions.retryCount);
},
),
);
}

View file

@ -3983,6 +3983,20 @@ class $MessageRetransmissionsTable extends MessageRetransmissions
late final GeneratedColumn<Uint8List> encryptedHash =
GeneratedColumn<Uint8List>('encrypted_hash', aliasedName, true,
type: DriftSqlType.blob, requiredDuringInsert: false);
static const VerificationMeta _retryCountMeta =
const VerificationMeta('retryCount');
@override
late final GeneratedColumn<int> retryCount = GeneratedColumn<int>(
'retry_count', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultValue: const Constant(0));
static const VerificationMeta _lastRetryMeta =
const VerificationMeta('lastRetry');
@override
late final GeneratedColumn<DateTime> lastRetry = GeneratedColumn<DateTime>(
'last_retry', aliasedName, true,
type: DriftSqlType.dateTime, requiredDuringInsert: false);
static const VerificationMeta _acknowledgeByServerAtMeta =
const VerificationMeta('acknowledgeByServerAt');
@override
@ -3997,6 +4011,8 @@ class $MessageRetransmissionsTable extends MessageRetransmissions
plaintextContent,
pushData,
encryptedHash,
retryCount,
lastRetry,
acknowledgeByServerAt
];
@override
@ -4044,6 +4060,16 @@ class $MessageRetransmissionsTable extends MessageRetransmissions
encryptedHash.isAcceptableOrUnknown(
data['encrypted_hash']!, _encryptedHashMeta));
}
if (data.containsKey('retry_count')) {
context.handle(
_retryCountMeta,
retryCount.isAcceptableOrUnknown(
data['retry_count']!, _retryCountMeta));
}
if (data.containsKey('last_retry')) {
context.handle(_lastRetryMeta,
lastRetry.isAcceptableOrUnknown(data['last_retry']!, _lastRetryMeta));
}
if (data.containsKey('acknowledge_by_server_at')) {
context.handle(
_acknowledgeByServerAtMeta,
@ -4071,6 +4097,10 @@ class $MessageRetransmissionsTable extends MessageRetransmissions
.read(DriftSqlType.blob, data['${effectivePrefix}push_data']),
encryptedHash: attachedDatabase.typeMapping
.read(DriftSqlType.blob, data['${effectivePrefix}encrypted_hash']),
retryCount: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}retry_count'])!,
lastRetry: attachedDatabase.typeMapping
.read(DriftSqlType.dateTime, data['${effectivePrefix}last_retry']),
acknowledgeByServerAt: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}acknowledge_by_server_at']),
@ -4091,6 +4121,8 @@ class MessageRetransmission extends DataClass
final Uint8List plaintextContent;
final Uint8List? pushData;
final Uint8List? encryptedHash;
final int retryCount;
final DateTime? lastRetry;
final DateTime? acknowledgeByServerAt;
const MessageRetransmission(
{required this.retransmissionId,
@ -4099,6 +4131,8 @@ class MessageRetransmission extends DataClass
required this.plaintextContent,
this.pushData,
this.encryptedHash,
required this.retryCount,
this.lastRetry,
this.acknowledgeByServerAt});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
@ -4115,6 +4149,10 @@ class MessageRetransmission extends DataClass
if (!nullToAbsent || encryptedHash != null) {
map['encrypted_hash'] = Variable<Uint8List>(encryptedHash);
}
map['retry_count'] = Variable<int>(retryCount);
if (!nullToAbsent || lastRetry != null) {
map['last_retry'] = Variable<DateTime>(lastRetry);
}
if (!nullToAbsent || acknowledgeByServerAt != null) {
map['acknowledge_by_server_at'] =
Variable<DateTime>(acknowledgeByServerAt);
@ -4136,6 +4174,10 @@ class MessageRetransmission extends DataClass
encryptedHash: encryptedHash == null && nullToAbsent
? const Value.absent()
: Value(encryptedHash),
retryCount: Value(retryCount),
lastRetry: lastRetry == null && nullToAbsent
? const Value.absent()
: Value(lastRetry),
acknowledgeByServerAt: acknowledgeByServerAt == null && nullToAbsent
? const Value.absent()
: Value(acknowledgeByServerAt),
@ -4153,6 +4195,8 @@ class MessageRetransmission extends DataClass
serializer.fromJson<Uint8List>(json['plaintextContent']),
pushData: serializer.fromJson<Uint8List?>(json['pushData']),
encryptedHash: serializer.fromJson<Uint8List?>(json['encryptedHash']),
retryCount: serializer.fromJson<int>(json['retryCount']),
lastRetry: serializer.fromJson<DateTime?>(json['lastRetry']),
acknowledgeByServerAt:
serializer.fromJson<DateTime?>(json['acknowledgeByServerAt']),
);
@ -4167,6 +4211,8 @@ class MessageRetransmission extends DataClass
'plaintextContent': serializer.toJson<Uint8List>(plaintextContent),
'pushData': serializer.toJson<Uint8List?>(pushData),
'encryptedHash': serializer.toJson<Uint8List?>(encryptedHash),
'retryCount': serializer.toJson<int>(retryCount),
'lastRetry': serializer.toJson<DateTime?>(lastRetry),
'acknowledgeByServerAt':
serializer.toJson<DateTime?>(acknowledgeByServerAt),
};
@ -4179,6 +4225,8 @@ class MessageRetransmission extends DataClass
Uint8List? plaintextContent,
Value<Uint8List?> pushData = const Value.absent(),
Value<Uint8List?> encryptedHash = const Value.absent(),
int? retryCount,
Value<DateTime?> lastRetry = const Value.absent(),
Value<DateTime?> acknowledgeByServerAt = const Value.absent()}) =>
MessageRetransmission(
retransmissionId: retransmissionId ?? this.retransmissionId,
@ -4188,6 +4236,8 @@ class MessageRetransmission extends DataClass
pushData: pushData.present ? pushData.value : this.pushData,
encryptedHash:
encryptedHash.present ? encryptedHash.value : this.encryptedHash,
retryCount: retryCount ?? this.retryCount,
lastRetry: lastRetry.present ? lastRetry.value : this.lastRetry,
acknowledgeByServerAt: acknowledgeByServerAt.present
? acknowledgeByServerAt.value
: this.acknowledgeByServerAt,
@ -4207,6 +4257,9 @@ class MessageRetransmission extends DataClass
encryptedHash: data.encryptedHash.present
? data.encryptedHash.value
: this.encryptedHash,
retryCount:
data.retryCount.present ? data.retryCount.value : this.retryCount,
lastRetry: data.lastRetry.present ? data.lastRetry.value : this.lastRetry,
acknowledgeByServerAt: data.acknowledgeByServerAt.present
? data.acknowledgeByServerAt.value
: this.acknowledgeByServerAt,
@ -4222,6 +4275,8 @@ class MessageRetransmission extends DataClass
..write('plaintextContent: $plaintextContent, ')
..write('pushData: $pushData, ')
..write('encryptedHash: $encryptedHash, ')
..write('retryCount: $retryCount, ')
..write('lastRetry: $lastRetry, ')
..write('acknowledgeByServerAt: $acknowledgeByServerAt')
..write(')'))
.toString();
@ -4235,6 +4290,8 @@ class MessageRetransmission extends DataClass
$driftBlobEquality.hash(plaintextContent),
$driftBlobEquality.hash(pushData),
$driftBlobEquality.hash(encryptedHash),
retryCount,
lastRetry,
acknowledgeByServerAt);
@override
bool operator ==(Object other) =>
@ -4247,6 +4304,8 @@ class MessageRetransmission extends DataClass
other.plaintextContent, this.plaintextContent) &&
$driftBlobEquality.equals(other.pushData, this.pushData) &&
$driftBlobEquality.equals(other.encryptedHash, this.encryptedHash) &&
other.retryCount == this.retryCount &&
other.lastRetry == this.lastRetry &&
other.acknowledgeByServerAt == this.acknowledgeByServerAt);
}
@ -4258,6 +4317,8 @@ class MessageRetransmissionsCompanion
final Value<Uint8List> plaintextContent;
final Value<Uint8List?> pushData;
final Value<Uint8List?> encryptedHash;
final Value<int> retryCount;
final Value<DateTime?> lastRetry;
final Value<DateTime?> acknowledgeByServerAt;
const MessageRetransmissionsCompanion({
this.retransmissionId = const Value.absent(),
@ -4266,6 +4327,8 @@ class MessageRetransmissionsCompanion
this.plaintextContent = const Value.absent(),
this.pushData = const Value.absent(),
this.encryptedHash = const Value.absent(),
this.retryCount = const Value.absent(),
this.lastRetry = const Value.absent(),
this.acknowledgeByServerAt = const Value.absent(),
});
MessageRetransmissionsCompanion.insert({
@ -4275,6 +4338,8 @@ class MessageRetransmissionsCompanion
required Uint8List plaintextContent,
this.pushData = const Value.absent(),
this.encryptedHash = const Value.absent(),
this.retryCount = const Value.absent(),
this.lastRetry = const Value.absent(),
this.acknowledgeByServerAt = const Value.absent(),
}) : contactId = Value(contactId),
plaintextContent = Value(plaintextContent);
@ -4285,6 +4350,8 @@ class MessageRetransmissionsCompanion
Expression<Uint8List>? plaintextContent,
Expression<Uint8List>? pushData,
Expression<Uint8List>? encryptedHash,
Expression<int>? retryCount,
Expression<DateTime>? lastRetry,
Expression<DateTime>? acknowledgeByServerAt,
}) {
return RawValuesInsertable({
@ -4294,6 +4361,8 @@ class MessageRetransmissionsCompanion
if (plaintextContent != null) 'plaintext_content': plaintextContent,
if (pushData != null) 'push_data': pushData,
if (encryptedHash != null) 'encrypted_hash': encryptedHash,
if (retryCount != null) 'retry_count': retryCount,
if (lastRetry != null) 'last_retry': lastRetry,
if (acknowledgeByServerAt != null)
'acknowledge_by_server_at': acknowledgeByServerAt,
});
@ -4306,6 +4375,8 @@ class MessageRetransmissionsCompanion
Value<Uint8List>? plaintextContent,
Value<Uint8List?>? pushData,
Value<Uint8List?>? encryptedHash,
Value<int>? retryCount,
Value<DateTime?>? lastRetry,
Value<DateTime?>? acknowledgeByServerAt}) {
return MessageRetransmissionsCompanion(
retransmissionId: retransmissionId ?? this.retransmissionId,
@ -4314,6 +4385,8 @@ class MessageRetransmissionsCompanion
plaintextContent: plaintextContent ?? this.plaintextContent,
pushData: pushData ?? this.pushData,
encryptedHash: encryptedHash ?? this.encryptedHash,
retryCount: retryCount ?? this.retryCount,
lastRetry: lastRetry ?? this.lastRetry,
acknowledgeByServerAt:
acknowledgeByServerAt ?? this.acknowledgeByServerAt,
);
@ -4340,6 +4413,12 @@ class MessageRetransmissionsCompanion
if (encryptedHash.present) {
map['encrypted_hash'] = Variable<Uint8List>(encryptedHash.value);
}
if (retryCount.present) {
map['retry_count'] = Variable<int>(retryCount.value);
}
if (lastRetry.present) {
map['last_retry'] = Variable<DateTime>(lastRetry.value);
}
if (acknowledgeByServerAt.present) {
map['acknowledge_by_server_at'] =
Variable<DateTime>(acknowledgeByServerAt.value);
@ -4356,6 +4435,8 @@ class MessageRetransmissionsCompanion
..write('plaintextContent: $plaintextContent, ')
..write('pushData: $pushData, ')
..write('encryptedHash: $encryptedHash, ')
..write('retryCount: $retryCount, ')
..write('lastRetry: $lastRetry, ')
..write('acknowledgeByServerAt: $acknowledgeByServerAt')
..write(')'))
.toString();
@ -6752,6 +6833,8 @@ typedef $$MessageRetransmissionsTableCreateCompanionBuilder
required Uint8List plaintextContent,
Value<Uint8List?> pushData,
Value<Uint8List?> encryptedHash,
Value<int> retryCount,
Value<DateTime?> lastRetry,
Value<DateTime?> acknowledgeByServerAt,
});
typedef $$MessageRetransmissionsTableUpdateCompanionBuilder
@ -6762,6 +6845,8 @@ typedef $$MessageRetransmissionsTableUpdateCompanionBuilder
Value<Uint8List> plaintextContent,
Value<Uint8List?> pushData,
Value<Uint8List?> encryptedHash,
Value<int> retryCount,
Value<DateTime?> lastRetry,
Value<DateTime?> acknowledgeByServerAt,
});
@ -6824,6 +6909,12 @@ class $$MessageRetransmissionsTableFilterComposer
ColumnFilters<Uint8List> get encryptedHash => $composableBuilder(
column: $table.encryptedHash, builder: (column) => ColumnFilters(column));
ColumnFilters<int> get retryCount => $composableBuilder(
column: $table.retryCount, builder: (column) => ColumnFilters(column));
ColumnFilters<DateTime> get lastRetry => $composableBuilder(
column: $table.lastRetry, builder: (column) => ColumnFilters(column));
ColumnFilters<DateTime> get acknowledgeByServerAt => $composableBuilder(
column: $table.acknowledgeByServerAt,
builder: (column) => ColumnFilters(column));
@ -6893,6 +6984,12 @@ class $$MessageRetransmissionsTableOrderingComposer
column: $table.encryptedHash,
builder: (column) => ColumnOrderings(column));
ColumnOrderings<int> get retryCount => $composableBuilder(
column: $table.retryCount, builder: (column) => ColumnOrderings(column));
ColumnOrderings<DateTime> get lastRetry => $composableBuilder(
column: $table.lastRetry, builder: (column) => ColumnOrderings(column));
ColumnOrderings<DateTime> get acknowledgeByServerAt => $composableBuilder(
column: $table.acknowledgeByServerAt,
builder: (column) => ColumnOrderings(column));
@ -6959,6 +7056,12 @@ class $$MessageRetransmissionsTableAnnotationComposer
GeneratedColumn<Uint8List> get encryptedHash => $composableBuilder(
column: $table.encryptedHash, builder: (column) => column);
GeneratedColumn<int> get retryCount => $composableBuilder(
column: $table.retryCount, builder: (column) => column);
GeneratedColumn<DateTime> get lastRetry =>
$composableBuilder(column: $table.lastRetry, builder: (column) => column);
GeneratedColumn<DateTime> get acknowledgeByServerAt => $composableBuilder(
column: $table.acknowledgeByServerAt, builder: (column) => column);
@ -7036,6 +7139,8 @@ class $$MessageRetransmissionsTableTableManager extends RootTableManager<
Value<Uint8List> plaintextContent = const Value.absent(),
Value<Uint8List?> pushData = const Value.absent(),
Value<Uint8List?> encryptedHash = const Value.absent(),
Value<int> retryCount = const Value.absent(),
Value<DateTime?> lastRetry = const Value.absent(),
Value<DateTime?> acknowledgeByServerAt = const Value.absent(),
}) =>
MessageRetransmissionsCompanion(
@ -7045,6 +7150,8 @@ class $$MessageRetransmissionsTableTableManager extends RootTableManager<
plaintextContent: plaintextContent,
pushData: pushData,
encryptedHash: encryptedHash,
retryCount: retryCount,
lastRetry: lastRetry,
acknowledgeByServerAt: acknowledgeByServerAt,
),
createCompanionCallback: ({
@ -7054,6 +7161,8 @@ class $$MessageRetransmissionsTableTableManager extends RootTableManager<
required Uint8List plaintextContent,
Value<Uint8List?> pushData = const Value.absent(),
Value<Uint8List?> encryptedHash = const Value.absent(),
Value<int> retryCount = const Value.absent(),
Value<DateTime?> lastRetry = const Value.absent(),
Value<DateTime?> acknowledgeByServerAt = const Value.absent(),
}) =>
MessageRetransmissionsCompanion.insert(
@ -7063,6 +7172,8 @@ class $$MessageRetransmissionsTableTableManager extends RootTableManager<
plaintextContent: plaintextContent,
pushData: pushData,
encryptedHash: encryptedHash,
retryCount: retryCount,
lastRetry: lastRetry,
acknowledgeByServerAt: acknowledgeByServerAt,
),
withReferenceMapper: (p0) => p0

View file

@ -3713,6 +3713,253 @@ final class Schema16 extends i0.VersionedSchema {
alias: null);
}
final class Schema17 extends i0.VersionedSchema {
Schema17({required super.database}) : super(version: 17);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
contacts,
messages,
mediaUploads,
signalIdentityKeyStores,
signalPreKeyStores,
signalSenderKeyStores,
signalSessionStores,
signalContactPreKeys,
signalContactSignedPreKeys,
messageRetransmissions,
];
late final Shape13 contacts = Shape13(
source: i0.VersionedTable(
entityName: 'contacts',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(user_id)',
],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
_column_4,
_column_5,
_column_6,
_column_7,
_column_8,
_column_9,
_column_39,
_column_53,
_column_57,
_column_54,
_column_40,
_column_10,
_column_11,
_column_12,
_column_13,
_column_14,
_column_55,
_column_15,
_column_16,
],
attachedDatabase: database,
),
alias: null);
late final Shape19 messages = Shape19(
source: i0.VersionedTable(
entityName: 'messages',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_17,
_column_18,
_column_19,
_column_48,
_column_49,
_column_20,
_column_21,
_column_22,
_column_52,
_column_23,
_column_24,
_column_25,
_column_70,
_column_26,
_column_27,
_column_28,
_column_29,
_column_30,
],
attachedDatabase: database,
),
alias: null);
late final Shape20 mediaUploads = Shape20(
source: i0.VersionedTable(
entityName: 'media_uploads',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_41,
_column_42,
_column_56,
_column_44,
_column_45,
],
attachedDatabase: database,
),
alias: null);
late final Shape2 signalIdentityKeyStores = Shape2(
source: i0.VersionedTable(
entityName: 'signal_identity_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(device_id, name)',
],
columns: [
_column_31,
_column_32,
_column_33,
_column_10,
],
attachedDatabase: database,
),
alias: null);
late final Shape3 signalPreKeyStores = Shape3(
source: i0.VersionedTable(
entityName: 'signal_pre_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(pre_key_id)',
],
columns: [
_column_34,
_column_35,
_column_10,
],
attachedDatabase: database,
),
alias: null);
late final Shape4 signalSenderKeyStores = Shape4(
source: i0.VersionedTable(
entityName: 'signal_sender_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(sender_key_name)',
],
columns: [
_column_36,
_column_37,
],
attachedDatabase: database,
),
alias: null);
late final Shape5 signalSessionStores = Shape5(
source: i0.VersionedTable(
entityName: 'signal_session_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(device_id, name)',
],
columns: [
_column_31,
_column_32,
_column_38,
_column_10,
],
attachedDatabase: database,
),
alias: null);
late final Shape14 signalContactPreKeys = Shape14(
source: i0.VersionedTable(
entityName: 'signal_contact_pre_keys',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(contact_id, pre_key_id)',
],
columns: [
_column_58,
_column_34,
_column_35,
_column_10,
],
attachedDatabase: database,
),
alias: null);
late final Shape15 signalContactSignedPreKeys = Shape15(
source: i0.VersionedTable(
entityName: 'signal_contact_signed_pre_keys',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(contact_id)',
],
columns: [
_column_58,
_column_59,
_column_60,
_column_61,
_column_10,
],
attachedDatabase: database,
),
alias: null);
late final Shape21 messageRetransmissions = Shape21(
source: i0.VersionedTable(
entityName: 'message_retransmissions',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_62,
_column_63,
_column_64,
_column_65,
_column_66,
_column_69,
_column_71,
_column_72,
_column_67,
],
attachedDatabase: database,
),
alias: null);
}
class Shape21 extends i0.VersionedTable {
Shape21({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get retransmissionId =>
columnsByName['retransmission_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get contactId =>
columnsByName['contact_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<int> get messageId =>
columnsByName['message_id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<i2.Uint8List> get plaintextContent =>
columnsByName['plaintext_content']! as i1.GeneratedColumn<i2.Uint8List>;
i1.GeneratedColumn<i2.Uint8List> get pushData =>
columnsByName['push_data']! as i1.GeneratedColumn<i2.Uint8List>;
i1.GeneratedColumn<i2.Uint8List> get encryptedHash =>
columnsByName['encrypted_hash']! as i1.GeneratedColumn<i2.Uint8List>;
i1.GeneratedColumn<int> get retryCount =>
columnsByName['retry_count']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<DateTime> get lastRetry =>
columnsByName['last_retry']! as i1.GeneratedColumn<DateTime>;
i1.GeneratedColumn<DateTime> get acknowledgeByServerAt =>
columnsByName['acknowledge_by_server_at']!
as i1.GeneratedColumn<DateTime>;
}
i1.GeneratedColumn<int> _column_71(String aliasedName) =>
i1.GeneratedColumn<int>('retry_count', aliasedName, false,
type: i1.DriftSqlType.int, defaultValue: const CustomExpression('0'));
i1.GeneratedColumn<DateTime> _column_72(String aliasedName) =>
i1.GeneratedColumn<DateTime>('last_retry', aliasedName, true,
type: i1.DriftSqlType.dateTime);
i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
@ -3729,6 +3976,7 @@ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15,
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
required Future<void> Function(i1.Migrator m, Schema17 schema) from16To17,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
@ -3807,6 +4055,11 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema);
await from15To16(migrator, schema);
return 16;
case 16:
final schema = Schema17(database: database);
final migrator = i1.Migrator(database, schema);
await from16To17(migrator, schema);
return 17;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
@ -3829,6 +4082,7 @@ i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15,
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
required Future<void> Function(i1.Migrator m, Schema17 schema) from16To17,
}) =>
i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(
@ -3847,4 +4101,5 @@ i1.OnUpgrade stepByStep({
from13To14: from13To14,
from14To15: from14To15,
from15To16: from15To16,
from16To17: from16To17,
));

View file

@ -67,6 +67,8 @@ Future<void> sendRetransmitMessage(int retransId) async {
.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!,
@ -142,6 +144,8 @@ Future<void> sendRetransmitMessage(int retransId) async {
retransId,
MessageRetransmissionsCompanion(
acknowledgeByServerAt: Value(DateTime.now()),
retryCount: Value(retrans.retryCount + 1),
lastRetry: Value(DateTime.now()),
),
);
}

View file

@ -46,7 +46,7 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
final fromUserId = msg.v0.newMessage.fromUserId.toInt();
response = await handleNewMessage(fromUserId, body);
} else {
Log.error('Got a new message from the server: $msg');
Log.error('Got a unknown message from the server: $msg');
response = client.Response()..error = ErrorCode.InternalError;
}
} catch (e) {
@ -90,26 +90,10 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
return client.Response()..ok = ok;
}
client.Response? result;
Log.info('Got: ${message.kind} from $fromUserId');
if (messageGetsAck(message.kind) && message.retransId != null) {
Log.info('Sending ACK for ${message.kind}');
/// ACK every message
await encryptAndSendMessageAsync(
null,
fromUserId,
MessageJson(
kind: MessageKind.ack,
content: AckContent(
messageIdToAck: message.messageSenderId,
retransIdToAck: message.retransId!,
),
timestamp: DateTime.now(),
),
);
}
switch (message.kind) {
case MessageKind.ack:
final content = message.content;
@ -125,7 +109,6 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
update,
);
}
await twonlyDB.messageRetransmissionDao
.deleteRetransmissionById(content.retransIdToAck);
}
@ -149,7 +132,7 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
}
case MessageKind.contactRequest:
return handleContactRequest(fromUserId, message);
await handleContactRequest(fromUserId, message);
case MessageKind.flameSync:
final contact = await twonlyDB.contactsDao
@ -165,7 +148,6 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
isToday(content.lastFlameCounterChange)) {
if (content.flameCounter > contact.flameCounter) {
updates = ContactsCompanion(
alsoBestFriend: Value(content.bestFriend),
flameCounter: Value(content.flameCounter),
);
}
@ -283,14 +265,49 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
// ignore: no_default_cases
default:
if (message.kind != MessageKind.textMessage &&
message.kind != MessageKind.media &&
message.kind != MessageKind.storedMediaFile &&
message.kind != MessageKind.reopenedMedia) {
Log.error('Got unknown MessageKind $message');
} else if (message.messageSenderId == null) {
if (message.messageSenderId == null) {
Log.error('Messageid not defined $message');
} else if ([
MessageKind.textMessage,
MessageKind.media,
MessageKind.storedMediaFile,
MessageKind.reopenedMedia,
].contains(message.kind)) {
result = await handleMediaOrTextMessage(fromUserId, message);
} else {
Log.error('Got unknown MessageKind $message');
}
}
if (messageGetsAck(message.kind) && message.retransId != null) {
Log.info('Sending ACK for ${message.kind}');
/// ACK every message
await encryptAndSendMessageAsync(
null,
fromUserId,
MessageJson(
kind: MessageKind.ack,
content: AckContent(
messageIdToAck: message.messageSenderId,
retransIdToAck: message.retransId!,
),
timestamp: DateTime.now(),
),
);
}
if (result != null) {
return result;
}
final ok = client.Response_Ok()..none = true;
return client.Response()..ok = ok;
}
Future<client.Response?> handleMediaOrTextMessage(
int fromUserId,
MessageJson message,
) async {
if (message.kind == MessageKind.storedMediaFile) {
if (message.messageReceiverId != null) {
/// stored media file just updates the message
@ -309,8 +326,7 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
)
.getSingleOrNull();
if (msg != null && msg.mediaUploadId != null) {
final filePath =
await getMediaFilePath(msg.mediaUploadId, 'send');
final filePath = await getMediaFilePath(msg.mediaUploadId, 'send');
if (filePath.contains('mp4')) {
unawaited(createThumbnailsForVideo(File(filePath)));
} else {
@ -355,8 +371,7 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
responseToMessageId = content.responseToMessageId;
responseToOtherMessageId = content.responseToOtherMessageId;
if (responseToMessageId != null ||
responseToOtherMessageId != null) {
if (responseToMessageId != null || responseToOtherMessageId != null) {
// reactions are shown in the notification directly...
if (isEmoji(content.text)) {
openedAt = DateTime.now();
@ -435,10 +450,7 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
archived: Value(false),
),
);
}
}
final ok = client.Response_Ok()..none = true;
return client.Response()..ok = ok;
return null;
}
Future<client.Response> handleRequestNewPreKey() async {
@ -457,7 +469,7 @@ Future<client.Response> handleRequestNewPreKey() async {
return client.Response()..ok = ok;
}
Future<client.Response> handleContactRequest(
Future<void> handleContactRequest(
int fromUserId,
MessageJson message,
) async {
@ -475,6 +487,4 @@ Future<client.Response> handleContactRequest(
);
}
await setupNotificationWithUsers();
final ok = client.Response_Ok()..none = true;
return client.Response()..ok = ok;
}

View file

@ -1,6 +1,9 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/services/flame.service.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/settings/developer/retransmission_data.view.dart';
@ -65,6 +68,14 @@ class _DeveloperSettingsViewState extends State<DeveloperSettingsView> {
);
},
),
if (kDebugMode)
ListTile(
title: const Text('Test FlameSync'),
onTap: () async {
await twonlyDB.contactsDao.modifyFlameCounterForTesting();
await syncFlameCounters();
},
),
],
),
);

View file

@ -1,8 +1,14 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/services/api/messages.dart';
class RetransmissionDataView extends StatefulWidget {
const RetransmissionDataView({super.key});
@ -11,11 +17,48 @@ class RetransmissionDataView extends StatefulWidget {
State<RetransmissionDataView> createState() => _RetransmissionDataViewState();
}
class RetransMsg {
RetransMsg({
required this.json,
required this.retrans,
required this.contact,
});
final MessageJson json;
final MessageRetransmission retrans;
final Contact? contact;
static List<RetransMsg> fromRaw(
List<MessageRetransmission> retrans,
Map<int, Contact> contacts,
) {
final res = <RetransMsg>[];
for (final retrans in retrans) {
final json = MessageJson.fromJson(
jsonDecode(
utf8.decode(
gzip.decode(retrans.plaintextContent),
),
) as Map<String, dynamic>,
);
res.add(
RetransMsg(
json: json,
retrans: retrans,
contact: contacts[retrans.contactId],
),
);
}
return res;
}
}
class _RetransmissionDataViewState extends State<RetransmissionDataView> {
List<MessageRetransmission> retransmissions = [];
List<Contact> contacts = [];
Map<int, Contact> contacts = {};
StreamSubscription<List<MessageRetransmission>>? subscriptionRetransmission;
StreamSubscription<List<Contact>>? subscriptionContacts;
List<RetransMsg> messages = [];
@override
void initState() {
@ -35,15 +78,21 @@ class _RetransmissionDataViewState extends State<RetransmissionDataView> {
Future<void> initAsync() async {
subscriptionContacts =
twonlyDB.contactsDao.watchAllContacts().listen((updated) {
setState(() {
contacts = updated;
});
for (final contact in updated) {
contacts[contact.userId] = contact;
}
if (retransmissions.isNotEmpty) {
messages = RetransMsg.fromRaw(retransmissions, contacts);
}
setState(() {});
});
subscriptionRetransmission =
twonlyDB.messageRetransmissionDao.watchAllMessages().listen((updated) {
setState(() {
retransmissions = updated;
});
if (contacts.isNotEmpty) {
messages = RetransMsg.fromRaw(retransmissions, contacts);
}
setState(() {});
});
}
@ -55,16 +104,83 @@ class _RetransmissionDataViewState extends State<RetransmissionDataView> {
),
body: Column(
children: [
ListView(
children: retransmissions
Expanded(
child: ListView(
children: messages
.map(
(retrans) => ListTile(
title: Text(retrans.retransmissionId.toString()),
subtitle: Text('Message to ${retrans.contactId}'),
title: Text(
'${retrans.retrans.retransmissionId}: ${retrans.json.kind}',
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'To ${retrans.contact?.username}',
),
Text(
'Server-Ack: ${retrans.retrans.acknowledgeByServerAt}',
),
Text(
'Retry: ${retrans.retrans.retryCount} : ${retrans.retrans.lastRetry}',
),
],
),
trailing: SizedBox(
width: 80,
child: Row(
children: [
SizedBox(
height: 20,
width: 40,
child: Center(
child: GestureDetector(
onDoubleTap: () async {
await twonlyDB.messageRetransmissionDao
.deleteRetransmissionById(
retrans.retrans.retransmissionId);
},
child: const FaIcon(
FontAwesomeIcons.trash,
size: 15,
),
),
),
),
SizedBox(
width: 40,
child: OutlinedButton(
style: ButtonStyle(
padding: WidgetStateProperty.all<EdgeInsets>(
EdgeInsets.zero,
),
),
onPressed: () async {
await twonlyDB.messageRetransmissionDao
.updateRetransmission(
retrans.retrans.retransmissionId,
const MessageRetransmissionsCompanion(
acknowledgeByServerAt: Value(null),
),
);
await sendRetransmitMessage(
retrans.retrans.retransmissionId,
);
},
child: const FaIcon(
FontAwesomeIcons.arrowRotateLeft,
size: 15,
),
),
),
],
),
),
),
)
.toList(),
)
),
),
],
),
);

View file

@ -19,6 +19,7 @@ import 'schema_v13.dart' as v13;
import 'schema_v14.dart' as v14;
import 'schema_v15.dart' as v15;
import 'schema_v16.dart' as v16;
import 'schema_v17.dart' as v17;
class GeneratedHelper implements SchemaInstantiationHelper {
@override
@ -56,6 +57,8 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v15.DatabaseAtV15(db);
case 16:
return v16.DatabaseAtV16(db);
case 17:
return v17.DatabaseAtV17(db);
default:
throw MissingSchemaException(version, versions);
}
@ -77,6 +80,7 @@ class GeneratedHelper implements SchemaInstantiationHelper {
13,
14,
15,
16
16,
17
];
}

File diff suppressed because it is too large Load diff