moving files

This commit is contained in:
otsmr 2026-08-29 01:30:23 +02:00
parent 4557919065
commit da9752d317
51 changed files with 459 additions and 915 deletions

View file

@ -7,7 +7,7 @@ import 'frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
// These functions are ignored because they are not marked as `pub`: `get_twonly_flutter` // These functions are ignored because they are not marked as `pub`: `get_twonly_flutter`
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `AnnouncedUser`, `OtherPromotion`, `TwonlyFlutter` // These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `AnnouncedUser`, `OtherPromotion`
Future<void> initializeTwonlyFlutter({required InitConfig config}) => Future<void> initializeTwonlyFlutter({required InitConfig config}) =>
RustLib.instance.api.crateBridgeInitializeTwonlyFlutter(config: config); RustLib.instance.api.crateBridgeInitializeTwonlyFlutter(config: config);

View file

@ -56,27 +56,6 @@ enum ApiEventKind {
loginTokenMigrated, loginTokenMigrated,
} }
class PreparedOutgoingMessage {
final Uint8List message;
final Uint8List? pushData;
const PreparedOutgoingMessage({
required this.message,
this.pushData,
});
@override
int get hashCode => message.hashCode ^ pushData.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is PreparedOutgoingMessage &&
runtimeType == other.runtimeType &&
message == other.message &&
pushData == other.pushData;
}
/// Flutter-facing facade for the Rust-owned API runtime. /// Flutter-facing facade for the Rust-owned API runtime.
class RustApi { class RustApi {
const RustApi(); const RustApi();
@ -255,9 +234,8 @@ class RustApi {
.api .api
.crateBridgeApiRustApiPerformPasswordlessRecoveryHeartbeat(); .crateBridgeApiRustApiPerformPasswordlessRecoveryHeartbeat();
static Future<PreparedOutgoingMessage?> prepareQueuedMessage({ static Future<Uint8List?> prepareQueuedMessage({required String receiptId}) =>
required String receiptId, RustLib.instance.api.crateBridgeApiRustApiPrepareQueuedMessage(
}) => RustLib.instance.api.crateBridgeApiRustApiPrepareQueuedMessage(
receiptId: receiptId, receiptId: receiptId,
); );
@ -348,7 +326,7 @@ class RustApi {
contactId: contactId, contactId: contactId,
); );
static Future<PreparedOutgoingMessage?> sendEncryptedContent({ static Future<Uint8List?> sendEncryptedContent({
required PlatformInt64 contactId, required PlatformInt64 contactId,
required List<int> content, required List<int> content,
String? messageId, String? messageId,
@ -384,11 +362,9 @@ class RustApi {
static Future<void> sendTextMessage({ static Future<void> sendTextMessage({
required PlatformInt64 userId, required PlatformInt64 userId,
required List<int> body, required List<int> body,
Uint8List? pushData,
}) => RustLib.instance.api.crateBridgeApiRustApiSendTextMessage( }) => RustLib.instance.api.crateBridgeApiRustApiSendTextMessage(
userId: userId, userId: userId,
body: body, body: body,
pushData: pushData,
); );
static Future<void> sendTyping({ static Future<void> sendTyping({

View file

@ -292,7 +292,7 @@ abstract class RustLibApi extends BaseApi {
Future<void> crateBridgeApiRustApiPerformPasswordlessRecoveryHeartbeat(); Future<void> crateBridgeApiRustApiPerformPasswordlessRecoveryHeartbeat();
Future<PreparedOutgoingMessage?> crateBridgeApiRustApiPrepareQueuedMessage({ Future<Uint8List?> crateBridgeApiRustApiPrepareQueuedMessage({
required String receiptId, required String receiptId,
}); });
@ -352,7 +352,7 @@ abstract class RustLibApi extends BaseApi {
required PlatformInt64 contactId, required PlatformInt64 contactId,
}); });
Future<PreparedOutgoingMessage?> crateBridgeApiRustApiSendEncryptedContent({ Future<Uint8List?> crateBridgeApiRustApiSendEncryptedContent({
required PlatformInt64 contactId, required PlatformInt64 contactId,
required List<int> content, required List<int> content,
String? messageId, String? messageId,
@ -375,7 +375,6 @@ abstract class RustLibApi extends BaseApi {
Future<void> crateBridgeApiRustApiSendTextMessage({ Future<void> crateBridgeApiRustApiSendTextMessage({
required PlatformInt64 userId, required PlatformInt64 userId,
required List<int> body, required List<int> body,
Uint8List? pushData,
}); });
Future<void> crateBridgeApiRustApiSendTyping({ Future<void> crateBridgeApiRustApiSendTyping({
@ -2329,7 +2328,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
); );
@override @override
Future<PreparedOutgoingMessage?> crateBridgeApiRustApiPrepareQueuedMessage({ Future<Uint8List?> crateBridgeApiRustApiPrepareQueuedMessage({
required String receiptId, required String receiptId,
}) { }) {
return handler.executeNormal( return handler.executeNormal(
@ -2345,8 +2344,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
); );
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: decodeSuccessData: sse_decode_opt_list_prim_u_8_strict,
sse_decode_opt_box_autoadd_prepared_outgoing_message,
decodeErrorData: sse_decode_AnyhowException, decodeErrorData: sse_decode_AnyhowException,
), ),
constMeta: kCrateBridgeApiRustApiPrepareQueuedMessageConstMeta, constMeta: kCrateBridgeApiRustApiPrepareQueuedMessageConstMeta,
@ -2812,7 +2810,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
); );
@override @override
Future<PreparedOutgoingMessage?> crateBridgeApiRustApiSendEncryptedContent({ Future<Uint8List?> crateBridgeApiRustApiSendEncryptedContent({
required PlatformInt64 contactId, required PlatformInt64 contactId,
required List<int> content, required List<int> content,
String? messageId, String? messageId,
@ -2838,8 +2836,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
); );
}, },
codec: SseCodec( codec: SseCodec(
decodeSuccessData: decodeSuccessData: sse_decode_opt_list_prim_u_8_strict,
sse_decode_opt_box_autoadd_prepared_outgoing_message,
decodeErrorData: sse_decode_AnyhowException, decodeErrorData: sse_decode_AnyhowException,
), ),
constMeta: kCrateBridgeApiRustApiSendEncryptedContentConstMeta, constMeta: kCrateBridgeApiRustApiSendEncryptedContentConstMeta,
@ -2951,7 +2948,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
Future<void> crateBridgeApiRustApiSendTextMessage({ Future<void> crateBridgeApiRustApiSendTextMessage({
required PlatformInt64 userId, required PlatformInt64 userId,
required List<int> body, required List<int> body,
Uint8List? pushData,
}) { }) {
return handler.executeNormal( return handler.executeNormal(
NormalTask( NormalTask(
@ -2959,7 +2955,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
final serializer = SseSerializer(generalizedFrbRustBinding); final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_i_64(userId, serializer); sse_encode_i_64(userId, serializer);
sse_encode_list_prim_u_8_loose(body, serializer); sse_encode_list_prim_u_8_loose(body, serializer);
sse_encode_opt_list_prim_u_8_strict(pushData, serializer);
pdeCallFfi( pdeCallFfi(
generalizedFrbRustBinding, generalizedFrbRustBinding,
serializer, serializer,
@ -2972,7 +2967,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
decodeErrorData: sse_decode_AnyhowException, decodeErrorData: sse_decode_AnyhowException,
), ),
constMeta: kCrateBridgeApiRustApiSendTextMessageConstMeta, constMeta: kCrateBridgeApiRustApiSendTextMessageConstMeta,
argValues: [userId, body, pushData], argValues: [userId, body],
apiImpl: this, apiImpl: this,
), ),
); );
@ -2981,7 +2976,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
TaskConstMeta get kCrateBridgeApiRustApiSendTextMessageConstMeta => TaskConstMeta get kCrateBridgeApiRustApiSendTextMessageConstMeta =>
const TaskConstMeta( const TaskConstMeta(
debugName: "rust_api_send_text_message", debugName: "rust_api_send_text_message",
argNames: ["userId", "body", "pushData"], argNames: ["userId", "body"],
); );
@override @override
@ -5302,14 +5297,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return dco_decode_passwordless_recovery_config(raw); return dco_decode_passwordless_recovery_config(raw);
} }
@protected
PreparedOutgoingMessage dco_decode_box_autoadd_prepared_outgoing_message(
dynamic raw,
) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return dco_decode_prepared_outgoing_message(raw);
}
@protected @protected
TwonlySafeBackup dco_decode_box_autoadd_twonly_safe_backup(dynamic raw) { TwonlySafeBackup dco_decode_box_autoadd_twonly_safe_backup(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
@ -5618,16 +5605,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
: dco_decode_box_autoadd_passwordless_recovery_config(raw); : dco_decode_box_autoadd_passwordless_recovery_config(raw);
} }
@protected
PreparedOutgoingMessage? dco_decode_opt_box_autoadd_prepared_outgoing_message(
dynamic raw,
) {
// Codec=Dco (DartCObject based), see doc to use other codecs
return raw == null
? null
: dco_decode_box_autoadd_prepared_outgoing_message(raw);
}
@protected @protected
TwonlySafeBackup? dco_decode_opt_box_autoadd_twonly_safe_backup(dynamic raw) { TwonlySafeBackup? dco_decode_opt_box_autoadd_twonly_safe_backup(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
@ -5692,18 +5669,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
); );
} }
@protected
PreparedOutgoingMessage dco_decode_prepared_outgoing_message(dynamic raw) {
// Codec=Dco (DartCObject based), see doc to use other codecs
final arr = raw as List<dynamic>;
if (arr.length != 2)
throw Exception('unexpected arr length: expect 2 but see ${arr.length}');
return PreparedOutgoingMessage(
message: dco_decode_list_prim_u_8_strict(arr[0]),
pushData: dco_decode_opt_list_prim_u_8_strict(arr[1]),
);
}
@protected @protected
(PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict( (PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict(
dynamic raw, dynamic raw,
@ -6197,14 +6162,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
return (sse_decode_passwordless_recovery_config(deserializer)); return (sse_decode_passwordless_recovery_config(deserializer));
} }
@protected
PreparedOutgoingMessage sse_decode_box_autoadd_prepared_outgoing_message(
SseDeserializer deserializer,
) {
// Codec=Sse (Serialization based), see doc to use other codecs
return (sse_decode_prepared_outgoing_message(deserializer));
}
@protected @protected
TwonlySafeBackup sse_decode_box_autoadd_twonly_safe_backup( TwonlySafeBackup sse_decode_box_autoadd_twonly_safe_backup(
SseDeserializer deserializer, SseDeserializer deserializer,
@ -6632,19 +6589,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
} }
} }
@protected
PreparedOutgoingMessage? sse_decode_opt_box_autoadd_prepared_outgoing_message(
SseDeserializer deserializer,
) {
// Codec=Sse (Serialization based), see doc to use other codecs
if (sse_decode_bool(deserializer)) {
return (sse_decode_box_autoadd_prepared_outgoing_message(deserializer));
} else {
return null;
}
}
@protected @protected
TwonlySafeBackup? sse_decode_opt_box_autoadd_twonly_safe_backup( TwonlySafeBackup? sse_decode_opt_box_autoadd_twonly_safe_backup(
SseDeserializer deserializer, SseDeserializer deserializer,
@ -6754,19 +6698,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
); );
} }
@protected
PreparedOutgoingMessage sse_decode_prepared_outgoing_message(
SseDeserializer deserializer,
) {
// Codec=Sse (Serialization based), see doc to use other codecs
var var_message = sse_decode_list_prim_u_8_strict(deserializer);
var var_pushData = sse_decode_opt_list_prim_u_8_strict(deserializer);
return PreparedOutgoingMessage(
message: var_message,
pushData: var_pushData,
);
}
@protected @protected
(PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict( (PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict(
SseDeserializer deserializer, SseDeserializer deserializer,
@ -7442,15 +7373,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_passwordless_recovery_config(self, serializer); sse_encode_passwordless_recovery_config(self, serializer);
} }
@protected
void sse_encode_box_autoadd_prepared_outgoing_message(
PreparedOutgoingMessage self,
SseSerializer serializer,
) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_prepared_outgoing_message(self, serializer);
}
@protected @protected
void sse_encode_box_autoadd_twonly_safe_backup( void sse_encode_box_autoadd_twonly_safe_backup(
TwonlySafeBackup self, TwonlySafeBackup self,
@ -7834,19 +7756,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
} }
} }
@protected
void sse_encode_opt_box_autoadd_prepared_outgoing_message(
PreparedOutgoingMessage? self,
SseSerializer serializer,
) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_bool(self != null, serializer);
if (self != null) {
sse_encode_box_autoadd_prepared_outgoing_message(self, serializer);
}
}
@protected @protected
void sse_encode_opt_box_autoadd_twonly_safe_backup( void sse_encode_opt_box_autoadd_twonly_safe_backup(
TwonlySafeBackup? self, TwonlySafeBackup? self,
@ -7940,16 +7849,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
sse_encode_list_prim_u_8_strict(self.kyberPreKeySignature, serializer); sse_encode_list_prim_u_8_strict(self.kyberPreKeySignature, serializer);
} }
@protected
void sse_encode_prepared_outgoing_message(
PreparedOutgoingMessage self,
SseSerializer serializer,
) {
// Codec=Sse (Serialization based), see doc to use other codecs
sse_encode_list_prim_u_8_strict(self.message, serializer);
sse_encode_opt_list_prim_u_8_strict(self.pushData, serializer);
}
@protected @protected
void sse_encode_record_i_64_list_prim_u_8_strict( void sse_encode_record_i_64_list_prim_u_8_strict(
(PlatformInt64, Uint8List) self, (PlatformInt64, Uint8List) self,

View file

@ -150,11 +150,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
PasswordlessRecoveryConfig PasswordlessRecoveryConfig
dco_decode_box_autoadd_passwordless_recovery_config(dynamic raw); dco_decode_box_autoadd_passwordless_recovery_config(dynamic raw);
@protected
PreparedOutgoingMessage dco_decode_box_autoadd_prepared_outgoing_message(
dynamic raw,
);
@protected @protected
TwonlySafeBackup dco_decode_box_autoadd_twonly_safe_backup(dynamic raw); TwonlySafeBackup dco_decode_box_autoadd_twonly_safe_backup(dynamic raw);
@ -283,11 +278,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
PasswordlessRecoveryConfig? PasswordlessRecoveryConfig?
dco_decode_opt_box_autoadd_passwordless_recovery_config(dynamic raw); dco_decode_opt_box_autoadd_passwordless_recovery_config(dynamic raw);
@protected
PreparedOutgoingMessage? dco_decode_opt_box_autoadd_prepared_outgoing_message(
dynamic raw,
);
@protected @protected
TwonlySafeBackup? dco_decode_opt_box_autoadd_twonly_safe_backup(dynamic raw); TwonlySafeBackup? dco_decode_opt_box_autoadd_twonly_safe_backup(dynamic raw);
@ -311,9 +301,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
PqcPreKeyInput dco_decode_pqc_pre_key_input(dynamic raw); PqcPreKeyInput dco_decode_pqc_pre_key_input(dynamic raw);
@protected
PreparedOutgoingMessage dco_decode_prepared_outgoing_message(dynamic raw);
@protected @protected
(PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict( (PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict(
dynamic raw, dynamic raw,
@ -488,11 +475,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseDeserializer deserializer, SseDeserializer deserializer,
); );
@protected
PreparedOutgoingMessage sse_decode_box_autoadd_prepared_outgoing_message(
SseDeserializer deserializer,
);
@protected @protected
TwonlySafeBackup sse_decode_box_autoadd_twonly_safe_backup( TwonlySafeBackup sse_decode_box_autoadd_twonly_safe_backup(
SseDeserializer deserializer, SseDeserializer deserializer,
@ -641,11 +623,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseDeserializer deserializer, SseDeserializer deserializer,
); );
@protected
PreparedOutgoingMessage? sse_decode_opt_box_autoadd_prepared_outgoing_message(
SseDeserializer deserializer,
);
@protected @protected
TwonlySafeBackup? sse_decode_opt_box_autoadd_twonly_safe_backup( TwonlySafeBackup? sse_decode_opt_box_autoadd_twonly_safe_backup(
SseDeserializer deserializer, SseDeserializer deserializer,
@ -673,11 +650,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
PqcPreKeyInput sse_decode_pqc_pre_key_input(SseDeserializer deserializer); PqcPreKeyInput sse_decode_pqc_pre_key_input(SseDeserializer deserializer);
@protected
PreparedOutgoingMessage sse_decode_prepared_outgoing_message(
SseDeserializer deserializer,
);
@protected @protected
(PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict( (PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict(
SseDeserializer deserializer, SseDeserializer deserializer,
@ -938,12 +910,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer, SseSerializer serializer,
); );
@protected
void sse_encode_box_autoadd_prepared_outgoing_message(
PreparedOutgoingMessage self,
SseSerializer serializer,
);
@protected @protected
void sse_encode_box_autoadd_twonly_safe_backup( void sse_encode_box_autoadd_twonly_safe_backup(
TwonlySafeBackup self, TwonlySafeBackup self,
@ -1124,12 +1090,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer, SseSerializer serializer,
); );
@protected
void sse_encode_opt_box_autoadd_prepared_outgoing_message(
PreparedOutgoingMessage? self,
SseSerializer serializer,
);
@protected @protected
void sse_encode_opt_box_autoadd_twonly_safe_backup( void sse_encode_opt_box_autoadd_twonly_safe_backup(
TwonlySafeBackup? self, TwonlySafeBackup? self,
@ -1166,12 +1126,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer, SseSerializer serializer,
); );
@protected
void sse_encode_prepared_outgoing_message(
PreparedOutgoingMessage self,
SseSerializer serializer,
);
@protected @protected
void sse_encode_record_i_64_list_prim_u_8_strict( void sse_encode_record_i_64_list_prim_u_8_strict(
(PlatformInt64, Uint8List) self, (PlatformInt64, Uint8List) self,

View file

@ -152,11 +152,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
PasswordlessRecoveryConfig PasswordlessRecoveryConfig
dco_decode_box_autoadd_passwordless_recovery_config(dynamic raw); dco_decode_box_autoadd_passwordless_recovery_config(dynamic raw);
@protected
PreparedOutgoingMessage dco_decode_box_autoadd_prepared_outgoing_message(
dynamic raw,
);
@protected @protected
TwonlySafeBackup dco_decode_box_autoadd_twonly_safe_backup(dynamic raw); TwonlySafeBackup dco_decode_box_autoadd_twonly_safe_backup(dynamic raw);
@ -285,11 +280,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
PasswordlessRecoveryConfig? PasswordlessRecoveryConfig?
dco_decode_opt_box_autoadd_passwordless_recovery_config(dynamic raw); dco_decode_opt_box_autoadd_passwordless_recovery_config(dynamic raw);
@protected
PreparedOutgoingMessage? dco_decode_opt_box_autoadd_prepared_outgoing_message(
dynamic raw,
);
@protected @protected
TwonlySafeBackup? dco_decode_opt_box_autoadd_twonly_safe_backup(dynamic raw); TwonlySafeBackup? dco_decode_opt_box_autoadd_twonly_safe_backup(dynamic raw);
@ -313,9 +303,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
PqcPreKeyInput dco_decode_pqc_pre_key_input(dynamic raw); PqcPreKeyInput dco_decode_pqc_pre_key_input(dynamic raw);
@protected
PreparedOutgoingMessage dco_decode_prepared_outgoing_message(dynamic raw);
@protected @protected
(PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict( (PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict(
dynamic raw, dynamic raw,
@ -490,11 +477,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseDeserializer deserializer, SseDeserializer deserializer,
); );
@protected
PreparedOutgoingMessage sse_decode_box_autoadd_prepared_outgoing_message(
SseDeserializer deserializer,
);
@protected @protected
TwonlySafeBackup sse_decode_box_autoadd_twonly_safe_backup( TwonlySafeBackup sse_decode_box_autoadd_twonly_safe_backup(
SseDeserializer deserializer, SseDeserializer deserializer,
@ -643,11 +625,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseDeserializer deserializer, SseDeserializer deserializer,
); );
@protected
PreparedOutgoingMessage? sse_decode_opt_box_autoadd_prepared_outgoing_message(
SseDeserializer deserializer,
);
@protected @protected
TwonlySafeBackup? sse_decode_opt_box_autoadd_twonly_safe_backup( TwonlySafeBackup? sse_decode_opt_box_autoadd_twonly_safe_backup(
SseDeserializer deserializer, SseDeserializer deserializer,
@ -675,11 +652,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected @protected
PqcPreKeyInput sse_decode_pqc_pre_key_input(SseDeserializer deserializer); PqcPreKeyInput sse_decode_pqc_pre_key_input(SseDeserializer deserializer);
@protected
PreparedOutgoingMessage sse_decode_prepared_outgoing_message(
SseDeserializer deserializer,
);
@protected @protected
(PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict( (PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict(
SseDeserializer deserializer, SseDeserializer deserializer,
@ -940,12 +912,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer, SseSerializer serializer,
); );
@protected
void sse_encode_box_autoadd_prepared_outgoing_message(
PreparedOutgoingMessage self,
SseSerializer serializer,
);
@protected @protected
void sse_encode_box_autoadd_twonly_safe_backup( void sse_encode_box_autoadd_twonly_safe_backup(
TwonlySafeBackup self, TwonlySafeBackup self,
@ -1126,12 +1092,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer, SseSerializer serializer,
); );
@protected
void sse_encode_opt_box_autoadd_prepared_outgoing_message(
PreparedOutgoingMessage? self,
SseSerializer serializer,
);
@protected @protected
void sse_encode_opt_box_autoadd_twonly_safe_backup( void sse_encode_opt_box_autoadd_twonly_safe_backup(
TwonlySafeBackup? self, TwonlySafeBackup? self,
@ -1168,12 +1128,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer, SseSerializer serializer,
); );
@protected
void sse_encode_prepared_outgoing_message(
PreparedOutgoingMessage self,
SseSerializer serializer,
);
@protected @protected
void sse_encode_record_i_64_list_prim_u_8_strict( void sse_encode_record_i_64_list_prim_u_8_strict(
(PlatformInt64, Uint8List) self, (PlatformInt64, Uint8List) self,

View file

@ -691,11 +691,13 @@ Future<void> _createUploadRequest(MediaFileService media) async {
); );
} }
final cipherText = await sendCipherText( final cipherText = await RustApi.sendEncryptedContent(
groupMember.contactId, contactId: groupMember.contactId,
notEncryptedContent, content: notEncryptedContent.writeToBuffer(),
messageId: message.messageId, messageId: message.messageId,
onlySendIfNoReceiptsAreOpen: false,
onlyReturnEncryptedData: true, onlyReturnEncryptedData: true,
blocking: true,
); );
if (cipherText == null) { if (cipherText == null) {
@ -706,13 +708,9 @@ Future<void> _createUploadRequest(MediaFileService media) async {
} }
final messageOnSuccess = TextMessage() final messageOnSuccess = TextMessage()
..body = cipherText.$1 ..body = cipherText
..userId = Int64(groupMember.contactId); ..userId = Int64(groupMember.contactId);
if (cipherText.$2 != null) {
messageOnSuccess.pushData = cipherText.$2!;
}
messagesOnSuccess.add(messageOnSuccess); messagesOnSuccess.add(messageOnSuccess);
downloadTokens.add(downloadToken); downloadTokens.add(downloadToken);
} }

View file

@ -1,13 +1,9 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'
as frb;
import 'package:twonly/core/bridge/api.dart' as rust_api; import 'package:twonly/core/bridge/api.dart' as rust_api;
import 'package:twonly/src/database/twonly.db.dart' show Receipt; import 'package:twonly/src/database/twonly.db.dart' show Receipt;
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
as pb;
Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({ Future<Uint8List?> tryToSendCompleteMessage({
String? receiptId, String? receiptId,
Receipt? receipt, Receipt? receipt,
bool onlyReturnEncryptedData = false, bool onlyReturnEncryptedData = false,
@ -19,86 +15,5 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
await rust_api.RustApi.sendQueuedMessage(receiptId: id); await rust_api.RustApi.sendQueuedMessage(receiptId: id);
return null; return null;
} }
final prepared = await rust_api.RustApi.prepareQueuedMessage(receiptId: id); return rust_api.RustApi.prepareQueuedMessage(receiptId: id);
return prepared == null ? null : (prepared.message, prepared.pushData);
} }
Future<void> insertAndSendTextMessage(
String groupId,
String textMessage,
String? quotesMessageId,
) async {
await rust_api.RustApi.insertAndSendText(
groupId: groupId,
text: textMessage,
quoteMessageId: quotesMessageId,
);
}
Future<void> insertAndSendContactShareMessage(
String groupId,
List<int> contactsToShare,
) async {
await rust_api.RustApi.insertAndSendContactShare(
groupId: groupId,
contactIds: frb.Int64List.fromList(contactsToShare),
);
}
Future<void> insertAndSendAskAboutUserMessage(
int contactId,
int askAboutUserId,
) async {
await rust_api.RustApi.insertAndSendAskAboutUser(
contactId: contactId,
askAboutUserId: askAboutUserId,
);
}
Future<void> sendCipherTextToGroup(
String groupId,
pb.EncryptedContent encryptedContent, {
String? messageId,
bool onlySendIfNoReceiptsAreOpen = false,
}) async {
await rust_api.RustApi.sendEncryptedContentToGroup(
groupId: groupId,
content: encryptedContent.writeToBuffer(),
messageId: messageId,
onlySendIfNoReceiptsAreOpen: onlySendIfNoReceiptsAreOpen,
);
}
Future<(Uint8List, Uint8List?)?> sendCipherText(
int contactId,
pb.EncryptedContent encryptedContent, {
bool onlyReturnEncryptedData = false,
bool blocking = true,
String? messageId,
bool onlySendIfNoReceiptsAreOpen = false,
}) async {
final prepared = await rust_api.RustApi.sendEncryptedContent(
contactId: contactId,
content: encryptedContent.writeToBuffer(),
messageId: messageId,
onlySendIfNoReceiptsAreOpen: onlySendIfNoReceiptsAreOpen,
onlyReturnEncryptedData: onlyReturnEncryptedData,
blocking: blocking,
);
if (!onlyReturnEncryptedData || prepared == null) return null;
return (prepared.message, prepared.pushData);
}
Future<void> sendTypingIndication(String groupId, bool isTyping) =>
rust_api.RustApi.sendTyping(groupId: groupId, isTyping: isTyping);
Future<void> notifyContactAboutOpeningMessage(
int contactId,
List<String> messageOtherIds,
) => rust_api.RustApi.notifyMessagesOpened(
contactId: contactId,
messageIds: messageOtherIds,
);
Future<void> sendContactMyProfileData(int contactId) =>
rust_api.RustApi.sendContactProfile(contactId: contactId);

View file

@ -10,7 +10,6 @@ import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart
as server; as server;
import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart' import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart'
hide Message; hide Message;
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/services/signal/session.signal.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
@ -62,14 +61,17 @@ Future<void> handleMediaError(MediaFile media) async {
if (messages.length != 1) return; if (messages.length != 1) return;
final message = messages.first; final message = messages.first;
if (message.senderId == null) return; if (message.senderId == null) return;
await sendCipherText( await RustApi.sendEncryptedContent(
message.senderId!, contactId: message.senderId!,
EncryptedContent( content: EncryptedContent(
mediaUpdate: EncryptedContent_MediaUpdate( mediaUpdate: EncryptedContent_MediaUpdate(
type: EncryptedContent_MediaUpdate_Type.DECRYPTION_ERROR, type: EncryptedContent_MediaUpdate_Type.DECRYPTION_ERROR,
targetMessageId: message.messageId, targetMessageId: message.messageId,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
onlyReturnEncryptedData: false,
blocking: true,
); );
} }
@ -81,13 +83,16 @@ Future<bool> importSignalContactAndCreateRequest(
} }
// 2. Then send user request // 2. Then send user request
await sendCipherText( await RustApi.sendEncryptedContent(
userdata.userId.toInt(), contactId: userdata.userId.toInt(),
EncryptedContent( content: EncryptedContent(
contactRequest: EncryptedContent_ContactRequest( contactRequest: EncryptedContent_ContactRequest(
type: EncryptedContent_ContactRequest_Type.REQUEST, type: EncryptedContent_ContactRequest_Type.REQUEST,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
onlyReturnEncryptedData: false,
blocking: true,
); );
return true; return true;

View file

@ -5,7 +5,6 @@ import 'package:fixnum/fixnum.dart';
import 'package:twonly/locator.dart'; import 'package:twonly/locator.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/services/user.service.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
@ -39,9 +38,9 @@ Future<void> syncFlameCounters({String? forceForGroup}) async {
continue; continue;
} }
await sendCipherTextToGroup( await RustApi.sendEncryptedContentToGroup(
group.groupId, groupId: group.groupId,
EncryptedContent( content: EncryptedContent(
flameSync: EncryptedContent_FlameSync( flameSync: EncryptedContent_FlameSync(
flameCounter: Int64(flameResult.counter), flameCounter: Int64(flameResult.counter),
lastFlameCounterChange: Int64( lastFlameCounterChange: Int64(
@ -50,7 +49,8 @@ Future<void> syncFlameCounters({String? forceForGroup}) async {
bestFriend: group.groupId == bestFriend.groupId, bestFriend: group.groupId == bestFriend.groupId,
forceUpdate: group.groupId == forceForGroup, forceUpdate: group.groupId == forceForGroup,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
); );
await twonlyDB.groupsDao.updateGroup( await twonlyDB.groupsDao.updateGroup(

View file

@ -10,7 +10,6 @@ import 'package:twonly/src/database/tables/contacts.table.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart' import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
as pb; as pb;
import 'package:twonly/src/providers/routing.provider.dart'; import 'package:twonly/src/providers/routing.provider.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/signal/identity.signal.dart'; import 'package:twonly/src/services/signal/identity.signal.dart';
import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/services/signal/session.signal.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
@ -56,13 +55,16 @@ class KeyVerificationService {
false, false,
); );
await sendCipherText( await RustApi.sendEncryptedContent(
contactId, contactId: contactId,
pb.EncryptedContent( content: pb.EncryptedContent(
keyVerificationProof: pb.EncryptedContent_KeyVerificationProof( keyVerificationProof: pb.EncryptedContent_KeyVerificationProof(
calculatedMac: calculatedMac, calculatedMac: calculatedMac,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
onlyReturnEncryptedData: false,
blocking: true,
); );
} }

View file

@ -22,7 +22,6 @@ import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
as pb; as pb;
import 'package:twonly/src/model/protobuf/client/generated/passwordless_recovery.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/passwordless_recovery.pb.dart';
import 'package:twonly/src/providers/routing.provider.dart'; import 'package:twonly/src/providers/routing.provider.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/services/user.service.dart';
import 'package:twonly/src/utils/keyvalue.dart'; import 'package:twonly/src/utils/keyvalue.dart';
@ -152,13 +151,16 @@ class PasswordlessRecoveryService {
for (final contact in oldTrustedFriends) { for (final contact in oldTrustedFriends) {
try { try {
await sendCipherText( await RustApi.sendEncryptedContent(
contact.userId, contactId: contact.userId,
pb.EncryptedContent( content: pb.EncryptedContent(
passwordlessRecovery: pb.EncryptedContent_PasswordLessRecovery( passwordlessRecovery: pb.EncryptedContent_PasswordLessRecovery(
delete: true, delete: true,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
onlyReturnEncryptedData: false,
blocking: true,
); );
} catch (e) { } catch (e) {
Log.error( Log.error(
@ -482,13 +484,16 @@ class PasswordlessRecoveryService {
// It's PIN, we can't migrate it because we don't have the PIN. We delete it so the user has to do it again. // It's PIN, we can't migrate it because we don't have the PIN. We delete it so the user has to do it again.
for (final contact in oldTrustedFriends) { for (final contact in oldTrustedFriends) {
try { try {
await sendCipherText( await RustApi.sendEncryptedContent(
contact.userId, contactId: contact.userId,
pb.EncryptedContent( content: pb.EncryptedContent(
passwordlessRecovery: pb.EncryptedContent_PasswordLessRecovery( passwordlessRecovery: pb.EncryptedContent_PasswordLessRecovery(
delete: true, delete: true,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
onlyReturnEncryptedData: false,
blocking: true,
); );
} catch (e) { } catch (e) {
Log.error( Log.error(

View file

@ -3,10 +3,9 @@ import 'dart:typed_data';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
// ignore: implementation_imports // ignore: implementation_imports
import 'package:libsignal_protocol_dart/src/invalid_message_exception.dart'; import 'package:libsignal_protocol_dart/src/invalid_message_exception.dart';
import 'package:twonly/core/bridge/wrapper/signal.dart'; import 'package:twonly/locator.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart' import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
as pb; as pb;
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/signal/protocol_state.signal.dart'; import 'package:twonly/src/services/signal/protocol_state.signal.dart';
import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/services/signal/session.signal.dart';
import 'package:twonly/src/services/signal/utils.signal.dart'; import 'package:twonly/src/services/signal/utils.signal.dart';
@ -23,31 +22,6 @@ Future<SignalEncryptResult?> signalEncryptMessage(
Uint8List plaintextContent, Uint8List plaintextContent,
) async { ) async {
return lockingSignalProtocol.protect<SignalEncryptResult?>(() async { return lockingSignalProtocol.protect<SignalEncryptResult?>(() async {
return _signalEncryptMessageV1(target, plaintextContent);
});
}
Future<SignalEncryptResult?> signalEncryptMessageV2(
int target,
Uint8List plaintextContent,
) async {
try {
final res = await RustSignal.encrypt(
name: target.toString(),
deviceId: 1,
plaintext: plaintextContent,
);
return SignalEncryptResult(res, pb.Message_Type.CIPHERTEXT_V2);
} catch (e) {
Log.error('Could not encrypt message (V2) for target $target: $e');
return null;
}
}
Future<SignalEncryptResult?> _signalEncryptMessageV1(
int target,
Uint8List plaintextContent,
) async {
try { try {
final signalStore = (await getSignalStore())!; final signalStore = (await getSignalStore())!;
final address = getSignalAddress(target); final address = getSignalAddress(target);
@ -67,62 +41,10 @@ Future<SignalEncryptResult?> _signalEncryptMessageV1(
return SignalEncryptResult(cipherText.serialize(), type); return SignalEncryptResult(cipherText.serialize(), type);
} catch (e) { } catch (e) {
Log.error('Could not encrypt message (V1) for target $target: $e'); Log.error('Could not encrypt message for target $target: $e');
return null; return null;
} }
}
Future<(pb.EncryptedContent?, pb.PlaintextContent_DecryptionErrorMessage_Type?)>
signalDecryptMessageV2(
int fromUserId,
Uint8List encryptedContentRaw, {
Set<int>? brokenSessionsInCurrentBatch,
}) async {
Log.info('Acquiring lockingSignalProtocol for $fromUserId (V2)');
final (
decryptedContent,
errorType,
needsResync,
) = await lockingSignalProtocol.protect(() async {
Log.info('Lock acquired for $fromUserId (V2)');
try {
final plaintext = await RustSignal.decrypt(
name: fromUserId.toString(),
deviceId: 1,
ciphertext: encryptedContentRaw,
);
recordResyncAttempt(fromUserId, success: true);
return (pb.EncryptedContent.fromBuffer(plaintext), null, false);
} catch (e) {
Log.error('Could not decrypt message (V2) from $fromUserId: $e');
return (
null,
pb.PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN,
true, // Needs resync on failure
);
}
}); });
Log.info('Released lockingSignalProtocol for $fromUserId (V2)');
if (needsResync) {
brokenSessionsInCurrentBatch?.add(fromUserId);
if (shouldAttemptResync(fromUserId)) {
if (await handleSessionResync(fromUserId)) {
recordResyncAttempt(fromUserId, success: false);
await sendCipherText(
fromUserId,
pb.EncryptedContent(
errorMessages: pb.EncryptedContent_ErrorMessages(
type: pb.EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC,
),
),
);
}
}
}
return (decryptedContent, errorType);
} }
Future<(pb.EncryptedContent?, pb.PlaintextContent_DecryptionErrorMessage_Type?)> Future<(pb.EncryptedContent?, pb.PlaintextContent_DecryptionErrorMessage_Type?)>
@ -218,13 +140,16 @@ signalDecryptMessageV1(
// This message contains a new PreKeyBundle establishing a new signal // This message contains a new PreKeyBundle establishing a new signal
// session // session
await sendCipherText( await RustApi.sendEncryptedContent(
fromUserId, contactId: fromUserId,
pb.EncryptedContent( content: pb.EncryptedContent(
errorMessages: pb.EncryptedContent_ErrorMessages( errorMessages: pb.EncryptedContent_ErrorMessages(
type: pb.EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC, type: pb.EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
onlyReturnEncryptedData: false,
blocking: true,
); );
} }
} }

View file

@ -8,7 +8,6 @@ import 'package:twonly/locator.dart';
import 'package:twonly/src/database/tables/groups.table.dart'; import 'package:twonly/src/database/tables/groups.table.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/group.service.dart'; import 'package:twonly/src/services/group.service.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/elements/better_list_title.element.dart'; import 'package:twonly/src/visual/elements/better_list_title.element.dart';
@ -96,14 +95,15 @@ class _SelectChatDeletionTimeListTitleState
), ),
), ),
); );
await sendCipherTextToGroup( await RustApi.sendEncryptedContentToGroup(
group!.groupId, groupId: group!.groupId,
EncryptedContent( content: EncryptedContent(
groupUpdate: EncryptedContent_GroupUpdate( groupUpdate: EncryptedContent_GroupUpdate(
groupActionType: GroupActionType.changeDisplayMaxTime.name, groupActionType: GroupActionType.changeDisplayMaxTime.name,
newDeleteMessagesAfterMilliseconds: Int64(selected), newDeleteMessagesAfterMilliseconds: Int64(selected),
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
); );
} else { } else {
if (!await updateChatDeletionTime(group!, selected)) { if (!await updateChatDeletionTime(group!, selected)) {

View file

@ -14,7 +14,6 @@ import 'package:twonly/src/database/tables/messages.table.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/memory_item.model.dart'; import 'package:twonly/src/model/memory_item.model.dart';
import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/services/notifications/background.notifications.dart'; import 'package:twonly/src/services/notifications/background.notifications.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
@ -179,12 +178,12 @@ class _ChatMessagesViewState extends State<ChatMessagesView>
} }
if (userService.currentUser.typingIndicators) { if (userService.currentUser.typingIndicators) {
unawaited(sendTypingIndication(widget.groupId, false)); unawaited(RustApi.sendTyping(groupId: widget.groupId, isTyping: false));
_nextTypingIndicator = Timer.periodic(const Duration(seconds: 2), ( _nextTypingIndicator = Timer.periodic(const Duration(seconds: 2), (
_, _,
) async { ) async {
if (_isViewActive()) { if (_isViewActive()) {
await sendTypingIndication(widget.groupId, false); await RustApi.sendTyping(groupId: widget.groupId, isTyping: false);
} }
}); });
} }
@ -437,9 +436,9 @@ class _ChatMessagesViewState extends State<ChatMessagesView>
openedMessages[contactId]!, openedMessages[contactId]!,
); );
unawaited( unawaited(
notifyContactAboutOpeningMessage( RustApi.notifyMessagesOpened(
contactId, contactId: contactId,
openedMessages[contactId]!, messageIds: openedMessages[contactId]!,
), ),
); );
} }

View file

@ -6,7 +6,6 @@ import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart' import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
as pb; as pb;
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/components/animate_icon.comp.dart'; import 'package:twonly/src/visual/components/animate_icon.comp.dart';
import 'package:twonly/src/visual/components/avatar_icon.comp.dart'; import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
@ -56,15 +55,16 @@ class _AllReactionsViewState extends State<AllReactionsView> {
emoji, emoji,
true, true,
); );
await sendCipherTextToGroup( await RustApi.sendEncryptedContentToGroup(
widget.message.groupId, groupId: widget.message.groupId,
pb.EncryptedContent( content: pb.EncryptedContent(
reaction: pb.EncryptedContent_Reaction( reaction: pb.EncryptedContent_Reaction(
targetMessageId: widget.message.messageId, targetMessageId: widget.message.messageId,
emoji: emoji, emoji: emoji,
remove: true, remove: true,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
); );
} }

View file

@ -1,9 +1,10 @@
import 'package:drift/drift.dart' show Value; import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'
show Int64List;
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/locator.dart'; import 'package:twonly/locator.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/views/shared/select_contacts.view.dart'; import 'package:twonly/src/visual/views/shared/select_contacts.view.dart';
@ -40,9 +41,9 @@ class _ShareAdditionalViewState extends State<ShareAdditionalView> {
) )
as List<int>?; as List<int>?;
if (selectedContacts != null && selectedContacts.isNotEmpty) { if (selectedContacts != null && selectedContacts.isNotEmpty) {
await insertAndSendContactShareMessage( await RustApi.insertAndSendContactShare(
widget.group.groupId, groupId: widget.group.groupId,
selectedContacts, contactIds: Int64List.fromList(selectedContacts),
); );
if (widget.group.isDirectChat) { if (widget.group.isDirectChat) {
final members = await twonlyDB.groupsDao.getGroupContact( final members = await twonlyDB.groupsDao.getGroupContact(

View file

@ -1,9 +1,9 @@
import 'package:audio_waveforms/audio_waveforms.dart'; import 'package:audio_waveforms/audio_waveforms.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/visual/elements/better_text.element.dart'; import 'package:twonly/src/visual/elements/better_text.element.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/common.dart'; import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/common.dart';
@ -217,9 +217,9 @@ class _InChatAudioPlayerState extends State<InChatAudioPlayer> {
_playerController.startPlayer(); _playerController.startPlayer();
if (widget.message.senderId != null && if (widget.message.senderId != null &&
widget.message.openedAt == null) { widget.message.openedAt == null) {
notifyContactAboutOpeningMessage( RustApi.notifyMessagesOpened(
widget.message.senderId!, contactId: widget.message.senderId!,
[widget.message.messageId], messageIds: [widget.message.messageId],
); );
} }
} }

View file

@ -12,7 +12,6 @@ import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
hide Message; hide Message;
import 'package:twonly/src/services/api/mediafiles/download.api.dart' import 'package:twonly/src/services/api/mediafiles/download.api.dart'
as received; as received;
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/elements/better_text.element.dart'; import 'package:twonly/src/visual/elements/better_text.element.dart';
@ -78,14 +77,17 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
} }
if (widget.mediaService.canBeOpenedAgain && if (widget.mediaService.canBeOpenedAgain &&
widget.message.senderId != null) { widget.message.senderId != null) {
await sendCipherText( await RustApi.sendEncryptedContent(
widget.message.senderId!, contactId: widget.message.senderId!,
EncryptedContent( content: EncryptedContent(
mediaUpdate: EncryptedContent_MediaUpdate( mediaUpdate: EncryptedContent_MediaUpdate(
type: EncryptedContent_MediaUpdate_Type.REOPENED, type: EncryptedContent_MediaUpdate_Type.REOPENED,
targetMessageId: widget.message.messageId, targetMessageId: widget.message.messageId,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
onlyReturnEncryptedData: false,
blocking: true,
); );
await twonlyDB.messagesDao.updateMessageId( await twonlyDB.messagesDao.updateMessageId(
widget.message.messageId, widget.message.messageId,

View file

@ -12,7 +12,6 @@ import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/memory_item.model.dart'; import 'package:twonly/src/model/memory_item.model.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart' import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart'
as pb; as pb;
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/components/emoji_picker.bottom.dart'; import 'package:twonly/src/visual/components/emoji_picker.bottom.dart';
@ -53,14 +52,17 @@ class MessageContextMenu extends StatelessWidget {
if (message.senderId != null) { if (message.senderId != null) {
// notify the sender // notify the sender
await sendCipherText( await RustApi.sendEncryptedContent(
message.senderId!, contactId: message.senderId!,
pb.EncryptedContent( content: pb.EncryptedContent(
mediaUpdate: pb.EncryptedContent_MediaUpdate( mediaUpdate: pb.EncryptedContent_MediaUpdate(
type: pb.EncryptedContent_MediaUpdate_Type.REOPENED, type: pb.EncryptedContent_MediaUpdate_Type.REOPENED,
targetMessageId: message.messageId, targetMessageId: message.messageId,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
onlyReturnEncryptedData: false,
blocking: true,
); );
await twonlyDB.messagesDao.updateMessageId( await twonlyDB.messagesDao.updateMessageId(
message.messageId, message.messageId,
@ -125,15 +127,16 @@ class MessageContextMenu extends StatelessWidget {
false, false,
); );
await sendCipherTextToGroup( await RustApi.sendEncryptedContentToGroup(
message.groupId, groupId: message.groupId,
pb.EncryptedContent( content: pb.EncryptedContent(
reaction: pb.EncryptedContent_Reaction( reaction: pb.EncryptedContent_Reaction(
targetMessageId: message.messageId, targetMessageId: message.messageId,
emoji: layer.text, emoji: layer.text,
remove: false, remove: false,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
); );
}, },
icon: FontAwesomeIcons.faceLaugh, icon: FontAwesomeIcons.faceLaugh,
@ -186,14 +189,15 @@ class MessageContextMenu extends StatelessWidget {
message.messageId, message.messageId,
clock.now(), clock.now(),
); );
await sendCipherTextToGroup( await RustApi.sendEncryptedContentToGroup(
message.groupId, groupId: message.groupId,
pb.EncryptedContent( content: pb.EncryptedContent(
messageUpdate: pb.EncryptedContent_MessageUpdate( messageUpdate: pb.EncryptedContent_MessageUpdate(
type: pb.EncryptedContent_MessageUpdate_Type.DELETE, type: pb.EncryptedContent_MessageUpdate_Type.DELETE,
senderMessageId: message.messageId, senderMessageId: message.messageId,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
); );
} else if (action == 'delete_for_me') { } else if (action == 'delete_for_me') {
await twonlyDB.messagesDao.deleteMessagesById( await twonlyDB.messagesDao.deleteMessagesById(
@ -403,9 +407,9 @@ Future<void> editTextMessage(BuildContext context, Message message) async {
newText!, newText!,
timestamp, timestamp,
); );
await sendCipherTextToGroup( await RustApi.sendEncryptedContentToGroup(
message.groupId, groupId: message.groupId,
pb.EncryptedContent( content: pb.EncryptedContent(
messageUpdate: messageUpdate:
pb.EncryptedContent_MessageUpdate( pb.EncryptedContent_MessageUpdate(
type: pb type: pb
@ -417,7 +421,8 @@ Future<void> editTextMessage(BuildContext context, Message message) async {
timestamp.millisecondsSinceEpoch, timestamp.millisecondsSinceEpoch,
), ),
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
); );
} }
if (!context.mounted) return; if (!context.mounted) return;

View file

@ -14,7 +14,6 @@ import 'package:twonly/locator.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/views/camera/camera_send_to.view.dart'; import 'package:twonly/src/visual/views/camera/camera_send_to.view.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/bottom_sheets/share_additional.bottom_sheet.dart'; import 'package:twonly/src/visual/views/chats/chat_messages_components/bottom_sheets/share_additional.bottom_sheet.dart';
@ -64,10 +63,10 @@ class _MessageInputState extends State<MessageInput> {
Future<void> _sendMessage() async { Future<void> _sendMessage() async {
if (_textFieldController.text == '') return; if (_textFieldController.text == '') return;
await insertAndSendTextMessage( await RustApi.insertAndSendText(
widget.group.groupId, groupId: widget.group.groupId,
_textFieldController.text, text: _textFieldController.text,
widget.quotesMessage?.messageId, quoteMessageId: widget.quotesMessage?.messageId,
); );
_textFieldController.clear(); _textFieldController.clear();
@ -93,7 +92,7 @@ class _MessageInputState extends State<MessageInput> {
_lastTextChangeTime != null && _lastTextChangeTime != null &&
DateTime.now().difference(_lastTextChangeTime!) <= DateTime.now().difference(_lastTextChangeTime!) <=
const Duration(seconds: 6)) { const Duration(seconds: 6)) {
await sendTypingIndication(widget.group.groupId, true); await RustApi.sendTyping(groupId: widget.group.groupId, isTyping: true);
} }
}); });
} }

View file

@ -17,7 +17,6 @@ import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart' import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
as pb; as pb;
import 'package:twonly/src/services/api/mediafiles/download.api.dart'; import 'package:twonly/src/services/api/mediafiles/download.api.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/api/utils.api.dart'; import 'package:twonly/src/services/api/utils.api.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/services/notifications/background.notifications.dart'; import 'package:twonly/src/services/notifications/background.notifications.dart';
@ -417,9 +416,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
markAsOpenMessageIDs = messageIds; markAsOpenMessageIDs = messageIds;
} }
await notifyContactAboutOpeningMessage( await RustApi.notifyMessagesOpened(
currentMessage!.senderId!, contactId: currentMessage!.senderId!,
markAsOpenMessageIDs, messageIds: markAsOpenMessageIDs,
); );
} }
@ -531,14 +530,15 @@ class _MediaViewerViewState extends State<MediaViewerView> {
mediaStored: Value(true), mediaStored: Value(true),
), ),
); );
await sendCipherTextToGroup( await RustApi.sendEncryptedContentToGroup(
widget.group.groupId, groupId: widget.group.groupId,
pb.EncryptedContent( content: pb.EncryptedContent(
mediaUpdate: pb.EncryptedContent_MediaUpdate( mediaUpdate: pb.EncryptedContent_MediaUpdate(
type: pb.EncryptedContent_MediaUpdate_Type.STORED, type: pb.EncryptedContent_MediaUpdate_Type.STORED,
targetMessageId: msg.messageId, targetMessageId: msg.messageId,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
); );
setState(() { setState(() {
imageSaved = true; imageSaved = true;
@ -620,10 +620,10 @@ class _MediaViewerViewState extends State<MediaViewerView> {
void _sendTextMessage() { void _sendTextMessage() {
if (textMessageController.text.isNotEmpty) { if (textMessageController.text.isNotEmpty) {
unawaited( unawaited(
insertAndSendTextMessage( RustApi.insertAndSendText(
widget.group.groupId, groupId: widget.group.groupId,
textMessageController.text, text: textMessageController.text,
currentMessage!.messageId, quoteMessageId: currentMessage!.messageId,
), ),
); );
textMessageController.clear(); textMessageController.clear();

View file

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/locator.dart'; import 'package:twonly/locator.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/visual/components/animate_icon.comp.dart'; import 'package:twonly/src/visual/components/animate_icon.comp.dart';
import 'package:twonly/src/visual/views/chats/media_viewer_components/reaction_buttons.comp.dart'; import 'package:twonly/src/visual/views/chats/media_viewer_components/reaction_buttons.comp.dart';
@ -29,15 +28,16 @@ Future<void> sendReaction(
emoji, emoji,
false, false,
); );
await sendCipherTextToGroup( await RustApi.sendEncryptedContentToGroup(
groupId, groupId: groupId,
EncryptedContent( content: EncryptedContent(
reaction: EncryptedContent_Reaction( reaction: EncryptedContent_Reaction(
targetMessageId: messageId, targetMessageId: messageId,
emoji: emoji, emoji: emoji,
remove: false, remove: false,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
); );
} }

View file

@ -5,7 +5,6 @@ import 'package:twonly/locator.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/daos/user_discovery.dao.dart'; import 'package:twonly/src/database/daos/user_discovery.dao.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/api/utils.api.dart'; import 'package:twonly/src/services/api/utils.api.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
@ -150,7 +149,10 @@ class FriendSuggestionsComp extends StatelessWidget {
if (result == true && selectedFriends.isNotEmpty) { if (result == true && selectedFriends.isNotEmpty) {
for (final contactId in selectedFriends) { for (final contactId in selectedFriends) {
await insertAndSendAskAboutUserMessage(contactId, user.announcedUserId); await RustApi.insertAndSendAskAboutUser(
contactId: contactId,
askAboutUserId: user.announcedUserId,
);
} }
await twonlyDB.userDiscoveryDao.updateAnnouncedUser( await twonlyDB.userDiscoveryDao.updateAnnouncedUser(

View file

@ -5,7 +5,6 @@ import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/daos/user_discovery.dao.dart'; import 'package:twonly/src/database/daos/user_discovery.dao.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/components/alert.dialog.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart';
import 'package:twonly/src/visual/components/avatar_icon.comp.dart'; import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
@ -111,15 +110,18 @@ class OpenRequestsListComp extends StatelessWidget {
groupName: Value(getContactDisplayName(contact)), groupName: Value(getContactDisplayName(contact)),
), ),
); );
await sendCipherText( await RustApi.sendEncryptedContent(
contact.userId, contactId: contact.userId,
EncryptedContent( content: EncryptedContent(
contactRequest: EncryptedContent_ContactRequest( contactRequest: EncryptedContent_ContactRequest(
type: EncryptedContent_ContactRequest_Type.ACCEPT, type: EncryptedContent_ContactRequest_Type.ACCEPT,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
onlyReturnEncryptedData: false,
blocking: true,
); );
await sendContactMyProfileData(contact.userId); await RustApi.sendContactProfile(contactId: contact.userId);
}, },
), ),
), ),
@ -131,13 +133,16 @@ class OpenRequestsListComp extends StatelessWidget {
constraints: const BoxConstraints(), constraints: const BoxConstraints(),
icon: const Icon(Icons.close, size: 18), icon: const Icon(Icons.close, size: 18),
onPressed: () async { onPressed: () async {
await sendCipherText( await RustApi.sendEncryptedContent(
contact.userId, contactId: contact.userId,
EncryptedContent( content: EncryptedContent(
contactRequest: EncryptedContent_ContactRequest( contactRequest: EncryptedContent_ContactRequest(
type: EncryptedContent_ContactRequest_Type.REJECT, type: EncryptedContent_ContactRequest_Type.REJECT,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
onlyReturnEncryptedData: false,
blocking: true,
); );
await twonlyDB.contactsDao.updateContact( await twonlyDB.contactsDao.updateContact(
contact.userId, contact.userId,

View file

@ -12,7 +12,6 @@ import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart' import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
as pb; as pb;
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/flame.service.dart'; import 'package:twonly/src/services/flame.service.dart';
import 'package:twonly/src/services/subscription.service.dart'; import 'package:twonly/src/services/subscription.service.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
@ -100,10 +99,11 @@ class _RestoreFlameCompState extends State<RestoreFlameComp> {
); );
await syncFlameCounters(forceForGroup: _groupId); await syncFlameCounters(forceForGroup: _groupId);
await sendCipherTextToGroup( await RustApi.sendEncryptedContentToGroup(
_groupId, groupId: _groupId,
encryptedContent, content: encryptedContent.writeToBuffer(),
messageId: message.messageId, messageId: message.messageId,
onlySendIfNoReceiptsAreOpen: false,
); );
} }

View file

@ -8,7 +8,6 @@ import 'package:twonly/src/database/daos/contacts.dao.dart';
import 'package:twonly/src/database/tables/groups.table.dart'; import 'package:twonly/src/database/tables/groups.table.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/group.service.dart'; import 'package:twonly/src/services/group.service.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/components/alert.dialog.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart';
@ -99,13 +98,16 @@ class GroupMemberContextMenu extends StatelessWidget {
deletedByUser: Value(false), deletedByUser: Value(false),
), ),
); );
await sendCipherText( await RustApi.sendEncryptedContent(
member.contactId, contactId: member.contactId,
EncryptedContent( content: EncryptedContent(
contactRequest: EncryptedContent_ContactRequest( contactRequest: EncryptedContent_ContactRequest(
type: EncryptedContent_ContactRequest_Type.REQUEST, type: EncryptedContent_ContactRequest_Type.REQUEST,
), ),
), ).writeToBuffer(),
onlySendIfNoReceiptsAreOpen: false,
onlyReturnEncryptedData: false,
blocking: true,
); );
if (context.mounted) { if (context.mounted) {
showSnackbar( showSnackbar(

View file

@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:twonly/locator.dart'; import 'package:twonly/locator.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/signal/utils.signal.dart'; import 'package:twonly/src/services/signal/utils.signal.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
@ -64,10 +63,9 @@ class _AutomatedTestingViewState extends State<AutomatedTestingView> {
final serializedSession = originalSession.serialize(); final serializedSession = originalSession.serialize();
for (var i = 0; i < 10; i++) { for (var i = 0; i < 10; i++) {
await insertAndSendTextMessage( await RustApi.insertAndSendText(
group.groupId, groupId: group.groupId,
'DesyncTest_1', text: 'DesyncTest_1',
null,
); );
} }
@ -79,10 +77,9 @@ class _AutomatedTestingViewState extends State<AutomatedTestingView> {
corruptedSession, corruptedSession,
); );
await insertAndSendTextMessage( await RustApi.insertAndSendText(
group.groupId, groupId: group.groupId,
'DesyncTest_2', text: 'DesyncTest_2',
null,
); );
// The other client should res // The other client should res
@ -110,10 +107,9 @@ class _AutomatedTestingViewState extends State<AutomatedTestingView> {
lotsOfMessagesStatus = lotsOfMessagesStatus =
'At message $i to ${contact.username}.'; 'At message $i to ${contact.username}.';
}); });
await insertAndSendTextMessage( await RustApi.insertAndSendText(
group!.groupId, groupId: group!.groupId,
'Message $i.', text: 'Message $i.',
null,
); );
} }
} }

View file

@ -3,9 +3,21 @@
* *
*/ */
pub mod client2client; mod additional_data;
pub(crate) mod contact;
mod errors;
mod groups;
mod media;
pub mod messages;
mod reaction;
pub mod recovery;
mod text_message;
mod typing_indicator;
mod user_discovery;
mod verification;
use crate::api::messages::incoming::client2client::messages::{ use crate::api::messages::content_type_kind;
use crate::api::messages::incoming::messages::{
decrypt_legacy_signal_with_error, ensure_contact_exists, handle_plaintext_content, decrypt_legacy_signal_with_error, ensure_contact_exists, handle_plaintext_content,
handle_sender_delivery_receipt, process_encrypted_or_queue_error, queue_decryption_error, handle_sender_delivery_receipt, process_encrypted_or_queue_error, queue_decryption_error,
queue_sender_delivery_receipt, retransmit_queued_receipts, spawn_receipt_delivery, queue_sender_delivery_receipt, retransmit_queued_receipts, spawn_receipt_delivery,
@ -15,13 +27,14 @@ use crate::api::proto::server_to_client::NewMessage;
use crate::api::proto::{client_to_server, server_to_client}; use crate::api::proto::{client_to_server, server_to_client};
use crate::bridge::callbacks::get_callbacks; use crate::bridge::callbacks::get_callbacks;
use crate::context::Context; use crate::context::Context;
use crate::database::app::tables::Receipt; use crate::database::app::tables::{Contact, Group, Receipt};
use crate::error::{Result, TwonlyError}; use crate::error::{Result, TwonlyError};
use crate::sealed_sender::SealedSender; use crate::sealed_sender::SealedSender;
use client_to_server::response::{ok, Response}; use client_to_server::response::{ok, Response};
use prost::Message as _; use prost::Message as _;
use proto::message::Type; use proto::message::Type;
use server_to_client::v0::Kind; use server_to_client::v0::Kind;
use sqlx::{Sqlite, Transaction};
use std::sync::Arc; use std::sync::Arc;
pub(crate) async fn handle_server_message( pub(crate) async fn handle_server_message(
@ -162,25 +175,29 @@ pub(crate) async fn handle_decoded_server_message(
let database = ctx.app_db.read().await.clone(); let database = ctx.app_db.read().await.clone();
let mut tr = database.pool.begin().await?; let mut t = database.pool.begin().await?;
let claimed = Receipt::claim_received(&mut tr, &message.receipt_id).await?; let claimed = Receipt::claim_received(&mut t, &message.receipt_id).await?;
if !claimed { if !claimed {
// Delivery receipts are terminal messages and must never themselves be // Delivery receipts are terminal messages and must never themselves be
// acknowledged. For regular messages Dart retries the delivery receipt // acknowledged. For regular messages Dart retries the delivery receipt
// after ten days, atomically claiming the retry by moving created_at. // after ten days, atomically claiming the retry by moving created_at.
let should_resend = message_type != Type::SenderDeliveryReceipt let should_resend = message_type != Type::SenderDeliveryReceipt
&& Receipt::claim_received_retry(&mut tr, &message.receipt_id).await?; && Receipt::claim_received_retry(&mut t, &message.receipt_id).await?;
if should_resend { if should_resend {
tracing::info!("Queueing sender delivery receipt for retry"); tracing::info!("Queueing sender delivery receipt for retry");
queue_sender_delivery_receipt(&mut tr, from_user_id, &message.receipt_id).await?; queue_sender_delivery_receipt(&mut t, from_user_id, &message.receipt_id).await?;
} }
tr.commit().await?;
t.commit().await?;
if should_resend { if should_resend {
tracing::info!("Spawning receipt delivery for retry"); tracing::info!("Spawning receipt delivery for retry");
spawn_receipt_delivery(ctx, message.receipt_id); spawn_receipt_delivery(ctx, message.receipt_id);
} }
tracing::info!("Returning early because receipt was already claimed"); tracing::info!("Returning early because receipt was already claimed");
return Ok(()); return Ok(());
} }
@ -190,7 +207,7 @@ pub(crate) async fn handle_decoded_server_message(
match message_type { match message_type {
Type::SenderDeliveryReceipt => { Type::SenderDeliveryReceipt => {
tracing::info!("Received sender delivery receipt"); tracing::info!("Received sender delivery receipt");
handle_sender_delivery_receipt(&mut tr, from_user_id, &message.receipt_id).await?; handle_sender_delivery_receipt(&mut t, from_user_id, &message.receipt_id).await?;
} }
Type::Ciphertext | Type::PrekeyBundle => { Type::Ciphertext | Type::PrekeyBundle => {
let ciphertext = message.encrypted_content.ok_or_else(|| { let ciphertext = message.encrypted_content.ok_or_else(|| {
@ -203,7 +220,7 @@ pub(crate) async fn handle_decoded_server_message(
tracing::info!("Decrypted successfully, processing..."); tracing::info!("Decrypted successfully, processing...");
sends_error_response = process_encrypted_or_queue_error( sends_error_response = process_encrypted_or_queue_error(
ctx, ctx,
&mut tr, &mut t,
from_user_id, from_user_id,
&message.receipt_id, &message.receipt_id,
content, content,
@ -221,7 +238,7 @@ pub(crate) async fn handle_decoded_server_message(
(callbacks.api.resync_signal_session)(from_user_id).await; (callbacks.api.resync_signal_session)(from_user_id).await;
} }
} }
queue_decryption_error(&mut tr, from_user_id, &message.receipt_id, error_type) queue_decryption_error(&mut t, from_user_id, &message.receipt_id, error_type)
.await?; .await?;
sends_error_response = true; sends_error_response = true;
} }
@ -249,7 +266,7 @@ pub(crate) async fn handle_decoded_server_message(
})?; })?;
sends_error_response = process_encrypted_or_queue_error( sends_error_response = process_encrypted_or_queue_error(
ctx, ctx,
&mut tr, &mut t,
from_user_id, from_user_id,
&message.receipt_id, &message.receipt_id,
content, content,
@ -262,7 +279,7 @@ pub(crate) async fn handle_decoded_server_message(
receipt_id = message.receipt_id, receipt_id = message.receipt_id,
"V2 decryption failed: {error}" "V2 decryption failed: {error}"
); );
queue_decryption_error(&mut tr, from_user_id, &message.receipt_id, 0).await?; queue_decryption_error(&mut t, from_user_id, &message.receipt_id, 0).await?;
sends_error_response = true; sends_error_response = true;
} }
} }
@ -272,17 +289,17 @@ pub(crate) async fn handle_decoded_server_message(
let plaintext = message.plaintext_content.ok_or_else(|| { let plaintext = message.plaintext_content.ok_or_else(|| {
TwonlyError::Generic("plaintext client message has no content".into()) TwonlyError::Generic("plaintext client message has no content".into())
})?; })?;
handle_plaintext_content(ctx, &mut tr, from_user_id, &message.receipt_id, plaintext) handle_plaintext_content(ctx, &mut t, from_user_id, &message.receipt_id, plaintext)
.await?; .await?;
} }
Type::TestNotification => {} Type::TestNotification => {}
} }
if is_encrypted_message & !sends_error_response { if is_encrypted_message & !sends_error_response {
queue_sender_delivery_receipt(&mut tr, from_user_id, &message.receipt_id).await?; queue_sender_delivery_receipt(&mut t, from_user_id, &message.receipt_id).await?;
} }
tr.commit().await?; t.commit().await?;
database.notify_committed([ database.notify_committed([
"received_receipts", "received_receipts",
@ -303,3 +320,187 @@ pub(crate) async fn handle_decoded_server_message(
Ok(()) Ok(())
} }
/// Dispatches already-decrypted client-to-client content to its concrete
/// feature module. Transport decoding and Signal decryption do not belong in
/// this dispatcher.
pub(crate) async fn handle_encrypted(
ctx: &Arc<Context>,
t: &mut Transaction<'_, Sqlite>,
from_user_id: i64,
receipt_id: &str,
content: proto::EncryptedContent,
) -> Result<()> {
Receipt::mark_all_for_retry(t, from_user_id).await?;
if let Some(version) = content.sender_user_discovery_version.clone() {
user_discovery::check_sender_version(ctx, t, from_user_id, version).await?;
}
contact::check_for_profile_update(t, from_user_id, &content).await?;
if content.ask_for_friend_promotions == Some(true) {
Contact::update_ask_for_friend_promotions(t, from_user_id).await?;
}
let type_kind = content_type_kind(&content);
tracing::Span::current().record("kind", type_kind);
tracing::info!("Handling incoming message: {type_kind}");
if let Some(request) = content.contact_request {
return contact::handle_contact_request(ctx, t, from_user_id, request).await;
}
if let Some(update) = content.contact_update {
return contact::handle_contact_update(
ctx,
t,
from_user_id,
content.sender_profile_counter,
update,
)
.await;
}
if let Some(update) = content.message_update {
return text_message::handle_message_update(ctx, t, from_user_id, update).await;
}
if let Some(update) = content.media_update {
return media::handle_media_update(t, from_user_id, update).await;
}
if let Some(error) = content.error_messages {
return errors::handle_error_message(
ctx,
t,
from_user_id,
content.group_id.as_deref(),
error,
)
.await;
}
if let Some(update) = content.user_discovery_update {
return user_discovery::handle_user_discovery_update(ctx, t, from_user_id, update).await;
}
if let Some(request) = content.user_discovery_request {
return user_discovery::handle_user_discovery_request(ctx, t, from_user_id, request).await;
}
if let Some(proof) = content.key_verification_proof {
return verification::handle_key_verification_proof(t, from_user_id, proof).await;
}
if let Some(recovery) = content.passwordless_recovery {
return recovery::handle_passwordless_recovery(t, from_user_id, recovery).await;
}
if let Some(heartbeat) = content.passwordless_recovery_heartbeat {
return recovery::handle_passwordless_recovery_heartbeat(t, from_user_id, heartbeat).await;
}
let group_id = content
.group_id
.ok_or_else(|| TwonlyError::Generic("group-scoped message has no group ID".into()))?;
if let Some(create) = content.group_create {
return groups::handle_group_create(ctx, t, from_user_id, &group_id, create).await;
}
if let Some(join) = content.group_join {
return groups::handle_group_join(t, from_user_id, &group_id, join).await;
}
let is_member = sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM group_members WHERE group_id = ? AND contact_id = ?)",
group_id,
from_user_id,
)
.fetch_one(&mut **t)
.await?
!= 0;
if !is_member {
let local_user_id = ctx.user_id().await?;
if Group::direct_chat_id(local_user_id, from_user_id) == group_id {
let contact = Contact::get_contact_by_id(t, from_user_id).await?;
if let Some(contact) =
contact.filter(|value| value.accepted != 0 && value.deleted_by_user == 0)
{
Group::create_direct_chat(ctx, t, contact).await?;
} else {
sqlx::query!(
"UPDATE contacts SET requested = 1, deleted_by_user = 0 WHERE user_id = ?",
from_user_id,
)
.execute(&mut **t)
.await?;
messages::queue_encrypted_content(
t,
from_user_id,
proto::EncryptedContent {
error_messages: Some(proto::encrypted_content::ErrorMessages {
r#type: proto::encrypted_content::error_messages::Type::ErrorProcessingMessageCreatedAccountRequestInstead as i32,
related_receipt_id: receipt_id.to_owned(),
}),
..Default::default()
},
false,
)
.await?;
return Ok(());
}
}
}
groups::ensure_group_member(t, from_user_id, &group_id).await?;
if content.resend_group_public_key.is_some() {
return groups::handle_resend_group_public_key(t, from_user_id, &group_id).await;
}
if let Some(update) = content.group_update {
return groups::handle_group_update(ctx, t, from_user_id, &group_id, update).await;
}
if let Some(flame) = content.flame_sync {
return groups::handle_flame_sync(t, &group_id, flame).await;
}
if let Some(message) = content.text_message {
return text_message::handle_text_message(t, from_user_id, &group_id, message).await;
}
if let Some(message) = content.additional_data_message {
return additional_data::handle_additional_data_message(
ctx,
t,
from_user_id,
&group_id,
message,
)
.await;
}
if let Some(media) = content.media {
return media::handle_media(t, from_user_id, &group_id, media).await;
}
if let Some(reaction) = content.reaction {
return reaction::handle_reaction(t, from_user_id, &group_id, reaction).await;
}
if let Some(indicator) = content.typing_indicator {
return typing_indicator::handle_typing_indicator(t, from_user_id, &group_id, indicator)
.await;
}
Err(TwonlyError::Generic(format!(
"client2client content in receipt {receipt_id} is not implemented in Rust"
)))
}

View file

@ -1,209 +0,0 @@
/*
* Copyright (c) 2026, Tobias Müller git@tsmr.eu
*
*/
use crate::api::messages::content_type_kind;
use crate::api::proto::client as proto;
use crate::context::Context;
use crate::database::app::tables::{Contact, Group, Receipt};
use crate::error::{Result, TwonlyError};
use sqlx::{Sqlite, Transaction};
use std::sync::Arc;
use typing_indicator::handle_typing_indicator;
mod additional_data;
pub(crate) mod contact;
mod errors;
mod groups;
mod media;
pub mod messages;
mod reaction;
pub mod recovery;
mod text_message;
mod typing_indicator;
mod user_discovery;
mod verification;
/// Dispatches already-decrypted client-to-client content to its concrete
/// feature module. Transport decoding and Signal decryption do not belong in
/// this dispatcher.
pub(crate) async fn handle_encrypted(
ctx: &Arc<Context>,
t: &mut Transaction<'_, Sqlite>,
from_user_id: i64,
receipt_id: &str,
content: proto::EncryptedContent,
) -> Result<()> {
Receipt::mark_all_for_retry(t, from_user_id).await?;
if let Some(version) = content.sender_user_discovery_version.clone() {
user_discovery::check_sender_version(ctx, t, from_user_id, version).await?;
}
contact::check_for_profile_update(t, from_user_id, &content).await?;
if content.ask_for_friend_promotions == Some(true) {
Contact::update_ask_for_friend_promotions(t, from_user_id).await?;
}
let type_kind = content_type_kind(&content);
tracing::Span::current().record("kind", type_kind);
tracing::info!("Handling incoming message: {type_kind}");
if let Some(request) = content.contact_request {
return contact::handle_contact_request(ctx, t, from_user_id, request).await;
}
if let Some(update) = content.contact_update {
return contact::handle_contact_update(
ctx,
t,
from_user_id,
content.sender_profile_counter,
update,
)
.await;
}
if let Some(update) = content.message_update {
return text_message::handle_message_update(ctx, t, from_user_id, update).await;
}
if let Some(update) = content.media_update {
return media::handle_media_update(t, from_user_id, update).await;
}
if let Some(error) = content.error_messages {
return errors::handle_error_message(
ctx,
t,
from_user_id,
content.group_id.as_deref(),
error,
)
.await;
}
if let Some(update) = content.user_discovery_update {
return user_discovery::handle_user_discovery_update(ctx, t, from_user_id, update).await;
}
if let Some(request) = content.user_discovery_request {
return user_discovery::handle_user_discovery_request(ctx, t, from_user_id, request).await;
}
if let Some(proof) = content.key_verification_proof {
return verification::handle_key_verification_proof(t, from_user_id, proof).await;
}
if let Some(recovery) = content.passwordless_recovery {
return recovery::handle_passwordless_recovery(t, from_user_id, recovery).await;
}
if let Some(heartbeat) = content.passwordless_recovery_heartbeat {
return recovery::handle_passwordless_recovery_heartbeat(t, from_user_id, heartbeat).await;
}
let group_id = content
.group_id
.ok_or_else(|| TwonlyError::Generic("group-scoped message has no group ID".into()))?;
if let Some(create) = content.group_create {
return groups::handle_group_create(ctx, t, from_user_id, &group_id, create).await;
}
if let Some(join) = content.group_join {
return groups::handle_group_join(t, from_user_id, &group_id, join).await;
}
let is_member = sqlx::query_scalar!(
"SELECT EXISTS(SELECT 1 FROM group_members WHERE group_id = ? AND contact_id = ?)",
group_id,
from_user_id,
)
.fetch_one(&mut **t)
.await?
!= 0;
if !is_member {
let local_user_id = ctx.user_id().await?;
if Group::direct_chat_id(local_user_id, from_user_id) == group_id {
let contact = Contact::get_contact_by_id(t, from_user_id).await?;
if let Some(contact) =
contact.filter(|value| value.accepted != 0 && value.deleted_by_user == 0)
{
Group::create_direct_chat(ctx, t, contact).await?;
} else {
sqlx::query!(
"UPDATE contacts SET requested = 1, deleted_by_user = 0 WHERE user_id = ?",
from_user_id,
)
.execute(&mut **t)
.await?;
messages::queue_encrypted_content(
t,
from_user_id,
proto::EncryptedContent {
error_messages: Some(proto::encrypted_content::ErrorMessages {
r#type: proto::encrypted_content::error_messages::Type::ErrorProcessingMessageCreatedAccountRequestInstead as i32,
related_receipt_id: receipt_id.to_owned(),
}),
..Default::default()
},
false,
)
.await?;
return Ok(());
}
}
}
groups::ensure_group_member(t, from_user_id, &group_id).await?;
if content.resend_group_public_key.is_some() {
return groups::handle_resend_group_public_key(t, from_user_id, &group_id).await;
}
if let Some(update) = content.group_update {
return groups::handle_group_update(ctx, t, from_user_id, &group_id, update).await;
}
if let Some(flame) = content.flame_sync {
return groups::handle_flame_sync(t, &group_id, flame).await;
}
if let Some(message) = content.text_message {
return text_message::handle_text_message(t, from_user_id, &group_id, message).await;
}
if let Some(message) = content.additional_data_message {
return additional_data::handle_additional_data_message(
ctx,
t,
from_user_id,
&group_id,
message,
)
.await;
}
if let Some(media) = content.media {
return media::handle_media(t, from_user_id, &group_id, media).await;
}
if let Some(reaction) = content.reaction {
return reaction::handle_reaction(t, from_user_id, &group_id, reaction).await;
}
if let Some(indicator) = content.typing_indicator {
return handle_typing_indicator(t, from_user_id, &group_id, indicator).await;
}
Err(TwonlyError::Generic(format!(
"client2client content in receipt {receipt_id} is not implemented in Rust"
)))
}

View file

@ -5,7 +5,7 @@
use std::sync::Arc; use std::sync::Arc;
use crate::api::messages::incoming::client2client::messages::queue_encrypted_content; use crate::api::messages::incoming::messages::queue_encrypted_content;
use crate::api::proto::client::encrypted_content; use crate::api::proto::client::encrypted_content;
use crate::context::Context; use crate::context::Context;
use crate::database::app::tables::{Contact, Group}; use crate::database::app::tables::{Contact, Group};

View file

@ -352,8 +352,6 @@ pub(crate) async fn send_queued_receipt(ctx: &Arc<Context>, receipt_id: &str) ->
let message_type = proto::message::Type::try_from(message.r#type)?; let message_type = proto::message::Type::try_from(message.r#type)?;
let push_data: Option<Vec<u8>> = None;
match message_type { match message_type {
proto::message::Type::Ciphertext | proto::message::Type::PrekeyBundle => { proto::message::Type::Ciphertext | proto::message::Type::PrekeyBundle => {
let plaintext = message.encrypted_content.take().ok_or_else(|| { let plaintext = message.encrypted_content.take().ok_or_else(|| {
@ -375,8 +373,7 @@ pub(crate) async fn send_queued_receipt(ctx: &Arc<Context>, receipt_id: &str) ->
_ => {} _ => {}
} }
match Server::send_text_message(ctx, row.contact_id, message.encode_to_vec(), push_data).await? match Server::send_text_message(ctx, row.contact_id, message.encode_to_vec()).await? {
{
ServerResult::Ok(()) => {} ServerResult::Ok(()) => {}
ServerResult::ErrorCode(code) => { ServerResult::ErrorCode(code) => {
return Err(TwonlyError::Generic(format!( return Err(TwonlyError::Generic(format!(
@ -426,7 +423,7 @@ pub(crate) async fn send_queued_receipt(ctx: &Arc<Context>, receipt_id: &str) ->
pub(crate) async fn prepare_queued_receipt( pub(crate) async fn prepare_queued_receipt(
ctx: &Arc<Context>, ctx: &Arc<Context>,
receipt_id: &str, receipt_id: &str,
) -> Result<Option<(Vec<u8>, Option<Vec<u8>>)>> { ) -> Result<Option<Vec<u8>>> {
let database = ctx.app_db.read().await.clone(); let database = ctx.app_db.read().await.clone();
let row = sqlx::query!( let row = sqlx::query!(
r#"SELECT contact_id, message, message_id, retry_count r#"SELECT contact_id, message, message_id, retry_count
@ -439,7 +436,6 @@ pub(crate) async fn prepare_queued_receipt(
let mut message = proto::Message::decode(row.message.as_slice()) let mut message = proto::Message::decode(row.message.as_slice())
.map_err(|error| TwonlyError::Generic(format!("invalid queued message: {error}")))?; .map_err(|error| TwonlyError::Generic(format!("invalid queued message: {error}")))?;
message.receipt_id = receipt_id.to_owned(); message.receipt_id = receipt_id.to_owned();
let push_data: Option<Vec<u8>> = None;
match proto::message::Type::try_from(message.r#type) match proto::message::Type::try_from(message.r#type)
.map_err(|_| TwonlyError::Generic("queued message has invalid type".into()))? .map_err(|_| TwonlyError::Generic("queued message has invalid type".into()))?
@ -467,7 +463,7 @@ pub(crate) async fn prepare_queued_receipt(
} }
_ => {} _ => {}
} }
Ok(Some((message.encode_to_vec(), push_data))) Ok(Some(message.encode_to_vec()))
} }
pub async fn retransmit_queued_receipts(ctx: &Arc<Context>) -> Result<()> { pub async fn retransmit_queued_receipts(ctx: &Arc<Context>) -> Result<()> {
@ -579,6 +575,7 @@ pub(crate) async fn handle_sender_delivery_receipt(
.fetch_optional(&mut **transaction) .fetch_optional(&mut **transaction)
.await? .await?
.flatten(); .flatten();
if let Some(message_id) = message_id { if let Some(message_id) = message_id {
sqlx::query!( sqlx::query!(
r#" r#"
@ -605,6 +602,7 @@ pub(crate) async fn handle_sender_delivery_receipt(
) )
.execute(&mut **transaction) .execute(&mut **transaction)
.await?; .await?;
Ok(()) Ok(())
} }

View file

@ -4,7 +4,6 @@
*/ */
pub mod incoming; pub mod incoming;
#[doc(hidden)]
pub mod outgoing; pub mod outgoing;
pub(crate) fn content_type_kind( pub(crate) fn content_type_kind(

View file

@ -4,7 +4,7 @@
*/ */
use crate::api::messages::content_type_kind; use crate::api::messages::content_type_kind;
use crate::api::messages::incoming::client2client::messages; use crate::api::messages::incoming::messages;
use crate::api::proto::client as proto; use crate::api::proto::client as proto;
use crate::context::Context; use crate::context::Context;
use crate::error::Result; use crate::error::Result;
@ -48,13 +48,8 @@ pub(crate) async fn decorate_content(
.fetch_one(&database.pool) .fetch_one(&database.pool)
.await?; .await?;
if allowed != 0 { if allowed != 0 {
content.sender_user_discovery_version = Some( content.sender_user_discovery_version =
ctx.user_discovery Some(ctx.user_discovery.get().await.get_current_version().await?);
.get()
.await
.get_current_version()
.await?,
);
} }
} }
Ok(()) Ok(())
@ -69,7 +64,7 @@ pub async fn send_c2c_message_to_contact(
#[builder(default)] only_send_if_no_receipts_are_open: bool, #[builder(default)] only_send_if_no_receipts_are_open: bool,
#[builder(default)] only_return_encrypted_data: bool, #[builder(default)] only_return_encrypted_data: bool,
#[builder(default)] blocking: bool, #[builder(default)] blocking: bool,
) -> Result<Option<(Vec<u8>, Option<Vec<u8>>)>> { ) -> Result<Option<Vec<u8>>> {
let mut content = proto::EncryptedContent::decode(encrypted_content.as_slice())?; let mut content = proto::EncryptedContent::decode(encrypted_content.as_slice())?;
decorate_content(ctx, contact_id, &mut content, message_id.is_some()).await?; decorate_content(ctx, contact_id, &mut content, message_id.is_some()).await?;

View file

@ -3,7 +3,7 @@
* *
*/ */
use crate::api::messages::incoming::client2client::{messages, recovery}; use crate::api::messages::incoming::{messages, recovery};
use crate::api::proto::server_to_client; use crate::api::proto::server_to_client;
use crate::api::runtime::ApiRuntime; use crate::api::runtime::ApiRuntime;
use crate::api::Server; use crate::api::Server;

View file

@ -136,7 +136,6 @@ impl Server {
ctx: &Arc<Context>, ctx: &Arc<Context>,
user_id: i64, user_id: i64,
body: Vec<u8>, body: Vec<u8>,
push_data: Option<Vec<u8>>,
) -> Result<ServerResult<()>> { ) -> Result<ServerResult<()>> {
server_ok!( server_ok!(
Self::application_for_contact( Self::application_for_contact(
@ -145,7 +144,7 @@ impl Server {
client_to_server::application_data::TextMessage { client_to_server::application_data::TextMessage {
user_id, user_id,
body, body,
push_data, push_data: None,
}, },
), ),
user_id, user_id,

View file

@ -3,7 +3,7 @@
* *
*/ */
use crate::api::messages::incoming::client2client::messages; use crate::api::messages::incoming::messages;
use crate::api::proto::server_to_client::response::ok::Ok as ResponseOk; use crate::api::proto::server_to_client::response::ok::Ok as ResponseOk;
use crate::api::runtime::helpers::decode_ok_value; use crate::api::runtime::helpers::decode_ok_value;
use crate::api::ApiRuntime; use crate::api::ApiRuntime;
@ -104,11 +104,6 @@ pub enum ApiEventKind {
/// Flutter-facing facade for the Rust-owned API runtime. /// Flutter-facing facade for the Rust-owned API runtime.
pub struct RustApi {} pub struct RustApi {}
pub struct PreparedOutgoingMessage {
pub message: Vec<u8>,
pub push_data: Option<Vec<u8>>,
}
impl RustApi { impl RustApi {
pub async fn request_contact_by_username(username: String) -> Result<()> { pub async fn request_contact_by_username(username: String) -> Result<()> {
let ctx = Context::get_static()?; let ctx = Context::get_static()?;
@ -334,7 +329,7 @@ impl RustApi {
} }
pub async fn perform_passwordless_recovery_heartbeat() -> Result<()> { pub async fn perform_passwordless_recovery_heartbeat() -> Result<()> {
let ctx = Context::get_static()?; let ctx = Context::get_static()?;
crate::api::messages::incoming::client2client::recovery::perform_heartbeat(ctx).await crate::api::messages::incoming::recovery::perform_heartbeat(ctx).await
} }
pub async fn get_server_key_for_passwordless_recovery( pub async fn get_server_key_for_passwordless_recovery(
user_id: i64, user_id: i64,
@ -459,13 +454,9 @@ impl RustApi {
.await .await
.and_then(empty_api_response) .and_then(empty_api_response)
} }
pub async fn send_text_message( pub async fn send_text_message(user_id: i64, body: Vec<u8>) -> Result<()> {
user_id: i64,
body: Vec<u8>,
push_data: Option<Vec<u8>>,
) -> Result<()> {
let ctx = Context::get_static()?; let ctx = Context::get_static()?;
Server::send_text_message(ctx, user_id, body, push_data) Server::send_text_message(ctx, user_id, body)
.await .await
.and_then(api_result) .and_then(api_result)
} }
@ -477,9 +468,8 @@ impl RustApi {
only_send_if_no_receipts_are_open: bool, only_send_if_no_receipts_are_open: bool,
only_return_encrypted_data: bool, only_return_encrypted_data: bool,
blocking: bool, blocking: bool,
) -> Result<Option<PreparedOutgoingMessage>> { ) -> Result<Option<Vec<u8>>> {
let ctx = Context::get_static()?; let ctx = Context::get_static()?;
Ok(
crate::api::messages::outgoing::send_c2c_message_to_contact() crate::api::messages::outgoing::send_c2c_message_to_contact()
.ctx(ctx) .ctx(ctx)
.contact_id(contact_id) .contact_id(contact_id)
@ -489,9 +479,8 @@ impl RustApi {
.only_return_encrypted_data(only_return_encrypted_data) .only_return_encrypted_data(only_return_encrypted_data)
.blocking(blocking) .blocking(blocking)
.call() .call()
.await? .await
.map(|(message, push_data)| PreparedOutgoingMessage { message, push_data }), .map_err(Into::into)
)
} }
pub async fn send_encrypted_content_to_group( pub async fn send_encrypted_content_to_group(
@ -574,14 +563,10 @@ impl RustApi {
.await .await
} }
pub async fn prepare_queued_message( pub async fn prepare_queued_message(receipt_id: String) -> Result<Option<Vec<u8>>> {
receipt_id: String,
) -> Result<Option<PreparedOutgoingMessage>> {
Ok(
messages::prepare_queued_receipt(Context::get_static()?, &receipt_id) messages::prepare_queued_receipt(Context::get_static()?, &receipt_id)
.await? .await
.map(|(message, push_data)| PreparedOutgoingMessage { message, push_data }), .map_err(Into::into)
)
} }
pub async fn notify_messages_opened(contact_id: i64, message_ids: Vec<String>) -> Result<()> { pub async fn notify_messages_opened(contact_id: i64, message_ids: Vec<String>) -> Result<()> {

View file

@ -2536,16 +2536,12 @@ fn wire__crate__bridge__api__rust_api_send_text_message_impl(
flutter_rust_bridge::for_generated::SseDeserializer::new(message); flutter_rust_bridge::for_generated::SseDeserializer::new(message);
let api_user_id = <i64>::sse_decode(&mut deserializer); let api_user_id = <i64>::sse_decode(&mut deserializer);
let api_body = <Vec<u8>>::sse_decode(&mut deserializer); let api_body = <Vec<u8>>::sse_decode(&mut deserializer);
let api_push_data = <Option<Vec<u8>>>::sse_decode(&mut deserializer);
deserializer.end(); deserializer.end();
move |context| async move { move |context| async move {
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>( transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
(move || async move { (move || async move {
let output_ok = crate::bridge::api::RustApi::send_text_message( let output_ok =
api_user_id, crate::bridge::api::RustApi::send_text_message(api_user_id, api_body)
api_body,
api_push_data,
)
.await?; .await?;
Ok(output_ok) Ok(output_ok)
})() })()
@ -4952,19 +4948,6 @@ impl SseDecode for Option<crate::user_config::PasswordlessRecoveryConfig> {
} }
} }
impl SseDecode for Option<crate::bridge::api::PreparedOutgoingMessage> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
if (<bool>::sse_decode(deserializer)) {
return Some(<crate::bridge::api::PreparedOutgoingMessage>::sse_decode(
deserializer,
));
} else {
return None;
}
}
}
impl SseDecode for Option<crate::user_config::TwonlySafeBackup> { impl SseDecode for Option<crate::user_config::TwonlySafeBackup> {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@ -5064,18 +5047,6 @@ impl SseDecode for crate::api::server::prekeys::PqcPreKeyInput {
} }
} }
impl SseDecode for crate::bridge::api::PreparedOutgoingMessage {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
let mut var_message = <Vec<u8>>::sse_decode(deserializer);
let mut var_pushData = <Option<Vec<u8>>>::sse_decode(deserializer);
return crate::bridge::api::PreparedOutgoingMessage {
message: var_message,
push_data: var_pushData,
};
}
}
impl SseDecode for (i64, Vec<u8>) { impl SseDecode for (i64, Vec<u8>) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
@ -5927,27 +5898,6 @@ impl flutter_rust_bridge::IntoIntoDart<crate::api::server::prekeys::PqcPreKeyInp
} }
} }
// Codec=Dco (DartCObject based), see doc to use other codecs // Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for crate::bridge::api::PreparedOutgoingMessage {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
[
self.message.into_into_dart().into_dart(),
self.push_data.into_into_dart().into_dart(),
]
.into_dart()
}
}
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
for crate::bridge::api::PreparedOutgoingMessage
{
}
impl flutter_rust_bridge::IntoIntoDart<crate::bridge::api::PreparedOutgoingMessage>
for crate::bridge::api::PreparedOutgoingMessage
{
fn into_into_dart(self) -> crate::bridge::api::PreparedOutgoingMessage {
self
}
}
// Codec=Dco (DartCObject based), see doc to use other codecs
impl flutter_rust_bridge::IntoDart for crate::bridge::api::RustApi { impl flutter_rust_bridge::IntoDart for crate::bridge::api::RustApi {
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
Vec::<u8>::new().into_dart() Vec::<u8>::new().into_dart()
@ -6779,16 +6729,6 @@ impl SseEncode for Option<crate::user_config::PasswordlessRecoveryConfig> {
} }
} }
impl SseEncode for Option<crate::bridge::api::PreparedOutgoingMessage> {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<bool>::sse_encode(self.is_some(), serializer);
if let Some(value) = self {
<crate::bridge::api::PreparedOutgoingMessage>::sse_encode(value, serializer);
}
}
}
impl SseEncode for Option<crate::user_config::TwonlySafeBackup> { impl SseEncode for Option<crate::user_config::TwonlySafeBackup> {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
@ -6866,14 +6806,6 @@ impl SseEncode for crate::api::server::prekeys::PqcPreKeyInput {
} }
} }
impl SseEncode for crate::bridge::api::PreparedOutgoingMessage {
// Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
<Vec<u8>>::sse_encode(self.message, serializer);
<Option<Vec<u8>>>::sse_encode(self.push_data, serializer);
}
}
impl SseEncode for (i64, Vec<u8>) { impl SseEncode for (i64, Vec<u8>) {
// Codec=Sse (Serialization based), see doc to use other codecs // Codec=Sse (Serialization based), see doc to use other codecs
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {

View file

@ -7,7 +7,7 @@ pub(crate) mod crypto;
pub(crate) mod model; pub(crate) mod model;
use crate::api::groups::GroupApi; use crate::api::groups::GroupApi;
use crate::api::messages::incoming::client2client::messages::queue_encrypted_content; use crate::api::messages::incoming::messages::queue_encrypted_content;
use crate::api::messages::outgoing::send_c2c_message_to_contact; use crate::api::messages::outgoing::send_c2c_message_to_contact;
use crate::api::proto::client::encrypted_content::GroupJoin; use crate::api::proto::client::encrypted_content::GroupJoin;
use crate::api::proto::client::{ use crate::api::proto::client::{

View file

@ -3,7 +3,7 @@
* *
*/ */
use crate::api::messages::incoming::client2client::messages::{ use crate::api::messages::incoming::messages::{
self, queue_encrypted_content, send_queued_receipt, self, queue_encrypted_content, send_queued_receipt,
}; };
use crate::api::messages::outgoing::{decorate_content, send_c2c_message_to_contact}; use crate::api::messages::outgoing::{decorate_content, send_c2c_message_to_contact};

View file

@ -84,7 +84,7 @@ async fn test_group_membership_error_healing() -> anyhow::Result<()> {
// Wait a brief moment for group join to be acknowledged, then ensure queued receipts are retransmitted // Wait a brief moment for group join to be acknowledged, then ensure queued receipts are retransmitted
tokio::time::sleep(std::time::Duration::from_millis(500)).await; tokio::time::sleep(std::time::Duration::from_millis(500)).await;
let _ = rust_lib_twonly::api::messages::incoming::client2client::messages::retransmit_queued_receipts(&tester_a.context).await; let _ = rust_lib_twonly::api::messages::incoming::messages::retransmit_queued_receipts(&tester_a.context).await;
tester_b tester_b
.wait_for_text_message(&msg_id, tester_a.user_id, "Message triggering heal") .wait_for_text_message(&msg_id, tester_a.user_id, "Message triggering heal")

View file

@ -1,6 +1,6 @@
use super::Tester; use super::Tester;
use prost::Message as _; use prost::Message as _;
use rust_lib_twonly::api::messages::incoming::client2client::recovery::perform_heartbeat; use rust_lib_twonly::api::messages::incoming::recovery::perform_heartbeat;
use rust_lib_twonly::api::messages::outgoing::send_c2c_message_to_contact; use rust_lib_twonly::api::messages::outgoing::send_c2c_message_to_contact;
use rust_lib_twonly::api::proto::client::{self as proto, encrypted_content}; use rust_lib_twonly::api::proto::client::{self as proto, encrypted_content};
use rust_lib_twonly::bridge::api::ApiConnectionState; use rust_lib_twonly::bridge::api::ApiConnectionState;