mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-09-01 11:44:07 +00:00
moving files
This commit is contained in:
parent
4557919065
commit
da9752d317
51 changed files with 459 additions and 915 deletions
|
|
@ -7,7 +7,7 @@ import 'frb_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 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}) =>
|
||||
RustLib.instance.api.crateBridgeInitializeTwonlyFlutter(config: config);
|
||||
|
|
|
|||
|
|
@ -56,27 +56,6 @@ enum ApiEventKind {
|
|||
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.
|
||||
class RustApi {
|
||||
const RustApi();
|
||||
|
|
@ -255,11 +234,10 @@ class RustApi {
|
|||
.api
|
||||
.crateBridgeApiRustApiPerformPasswordlessRecoveryHeartbeat();
|
||||
|
||||
static Future<PreparedOutgoingMessage?> prepareQueuedMessage({
|
||||
required String receiptId,
|
||||
}) => RustLib.instance.api.crateBridgeApiRustApiPrepareQueuedMessage(
|
||||
receiptId: receiptId,
|
||||
);
|
||||
static Future<Uint8List?> prepareQueuedMessage({required String receiptId}) =>
|
||||
RustLib.instance.api.crateBridgeApiRustApiPrepareQueuedMessage(
|
||||
receiptId: receiptId,
|
||||
);
|
||||
|
||||
static Future<PlatformInt64> register({
|
||||
required String username,
|
||||
|
|
@ -348,7 +326,7 @@ class RustApi {
|
|||
contactId: contactId,
|
||||
);
|
||||
|
||||
static Future<PreparedOutgoingMessage?> sendEncryptedContent({
|
||||
static Future<Uint8List?> sendEncryptedContent({
|
||||
required PlatformInt64 contactId,
|
||||
required List<int> content,
|
||||
String? messageId,
|
||||
|
|
@ -384,11 +362,9 @@ class RustApi {
|
|||
static Future<void> sendTextMessage({
|
||||
required PlatformInt64 userId,
|
||||
required List<int> body,
|
||||
Uint8List? pushData,
|
||||
}) => RustLib.instance.api.crateBridgeApiRustApiSendTextMessage(
|
||||
userId: userId,
|
||||
body: body,
|
||||
pushData: pushData,
|
||||
);
|
||||
|
||||
static Future<void> sendTyping({
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ abstract class RustLibApi extends BaseApi {
|
|||
|
||||
Future<void> crateBridgeApiRustApiPerformPasswordlessRecoveryHeartbeat();
|
||||
|
||||
Future<PreparedOutgoingMessage?> crateBridgeApiRustApiPrepareQueuedMessage({
|
||||
Future<Uint8List?> crateBridgeApiRustApiPrepareQueuedMessage({
|
||||
required String receiptId,
|
||||
});
|
||||
|
||||
|
|
@ -352,7 +352,7 @@ abstract class RustLibApi extends BaseApi {
|
|||
required PlatformInt64 contactId,
|
||||
});
|
||||
|
||||
Future<PreparedOutgoingMessage?> crateBridgeApiRustApiSendEncryptedContent({
|
||||
Future<Uint8List?> crateBridgeApiRustApiSendEncryptedContent({
|
||||
required PlatformInt64 contactId,
|
||||
required List<int> content,
|
||||
String? messageId,
|
||||
|
|
@ -375,7 +375,6 @@ abstract class RustLibApi extends BaseApi {
|
|||
Future<void> crateBridgeApiRustApiSendTextMessage({
|
||||
required PlatformInt64 userId,
|
||||
required List<int> body,
|
||||
Uint8List? pushData,
|
||||
});
|
||||
|
||||
Future<void> crateBridgeApiRustApiSendTyping({
|
||||
|
|
@ -2329,7 +2328,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
);
|
||||
|
||||
@override
|
||||
Future<PreparedOutgoingMessage?> crateBridgeApiRustApiPrepareQueuedMessage({
|
||||
Future<Uint8List?> crateBridgeApiRustApiPrepareQueuedMessage({
|
||||
required String receiptId,
|
||||
}) {
|
||||
return handler.executeNormal(
|
||||
|
|
@ -2345,8 +2344,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData:
|
||||
sse_decode_opt_box_autoadd_prepared_outgoing_message,
|
||||
decodeSuccessData: sse_decode_opt_list_prim_u_8_strict,
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta: kCrateBridgeApiRustApiPrepareQueuedMessageConstMeta,
|
||||
|
|
@ -2812,7 +2810,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
);
|
||||
|
||||
@override
|
||||
Future<PreparedOutgoingMessage?> crateBridgeApiRustApiSendEncryptedContent({
|
||||
Future<Uint8List?> crateBridgeApiRustApiSendEncryptedContent({
|
||||
required PlatformInt64 contactId,
|
||||
required List<int> content,
|
||||
String? messageId,
|
||||
|
|
@ -2838,8 +2836,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData:
|
||||
sse_decode_opt_box_autoadd_prepared_outgoing_message,
|
||||
decodeSuccessData: sse_decode_opt_list_prim_u_8_strict,
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta: kCrateBridgeApiRustApiSendEncryptedContentConstMeta,
|
||||
|
|
@ -2951,7 +2948,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
Future<void> crateBridgeApiRustApiSendTextMessage({
|
||||
required PlatformInt64 userId,
|
||||
required List<int> body,
|
||||
Uint8List? pushData,
|
||||
}) {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
|
|
@ -2959,7 +2955,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
sse_encode_i_64(userId, serializer);
|
||||
sse_encode_list_prim_u_8_loose(body, serializer);
|
||||
sse_encode_opt_list_prim_u_8_strict(pushData, serializer);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
|
|
@ -2972,7 +2967,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta: kCrateBridgeApiRustApiSendTextMessageConstMeta,
|
||||
argValues: [userId, body, pushData],
|
||||
argValues: [userId, body],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
|
|
@ -2981,7 +2976,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
TaskConstMeta get kCrateBridgeApiRustApiSendTextMessageConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "rust_api_send_text_message",
|
||||
argNames: ["userId", "body", "pushData"],
|
||||
argNames: ["userId", "body"],
|
||||
);
|
||||
|
||||
@override
|
||||
|
|
@ -5302,14 +5297,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
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
|
||||
TwonlySafeBackup dco_decode_box_autoadd_twonly_safe_backup(dynamic raw) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
@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
|
||||
TwonlySafeBackup? dco_decode_opt_box_autoadd_twonly_safe_backup(dynamic raw) {
|
||||
// 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
|
||||
(PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict(
|
||||
dynamic raw,
|
||||
|
|
@ -6197,14 +6162,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
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
|
||||
TwonlySafeBackup sse_decode_box_autoadd_twonly_safe_backup(
|
||||
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
|
||||
TwonlySafeBackup? sse_decode_opt_box_autoadd_twonly_safe_backup(
|
||||
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
|
||||
(PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -7442,15 +7373,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
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
|
||||
void sse_encode_box_autoadd_twonly_safe_backup(
|
||||
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
|
||||
void sse_encode_opt_box_autoadd_twonly_safe_backup(
|
||||
TwonlySafeBackup? self,
|
||||
|
|
@ -7940,16 +7849,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
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
|
||||
void sse_encode_record_i_64_list_prim_u_8_strict(
|
||||
(PlatformInt64, Uint8List) self,
|
||||
|
|
|
|||
|
|
@ -150,11 +150,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
PasswordlessRecoveryConfig
|
||||
dco_decode_box_autoadd_passwordless_recovery_config(dynamic raw);
|
||||
|
||||
@protected
|
||||
PreparedOutgoingMessage dco_decode_box_autoadd_prepared_outgoing_message(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
TwonlySafeBackup dco_decode_box_autoadd_twonly_safe_backup(dynamic raw);
|
||||
|
||||
|
|
@ -283,11 +278,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
PasswordlessRecoveryConfig?
|
||||
dco_decode_opt_box_autoadd_passwordless_recovery_config(dynamic raw);
|
||||
|
||||
@protected
|
||||
PreparedOutgoingMessage? dco_decode_opt_box_autoadd_prepared_outgoing_message(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
TwonlySafeBackup? dco_decode_opt_box_autoadd_twonly_safe_backup(dynamic raw);
|
||||
|
||||
|
|
@ -311,9 +301,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PqcPreKeyInput dco_decode_pqc_pre_key_input(dynamic raw);
|
||||
|
||||
@protected
|
||||
PreparedOutgoingMessage dco_decode_prepared_outgoing_message(dynamic raw);
|
||||
|
||||
@protected
|
||||
(PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict(
|
||||
dynamic raw,
|
||||
|
|
@ -488,11 +475,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedOutgoingMessage sse_decode_box_autoadd_prepared_outgoing_message(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
TwonlySafeBackup sse_decode_box_autoadd_twonly_safe_backup(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -641,11 +623,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedOutgoingMessage? sse_decode_opt_box_autoadd_prepared_outgoing_message(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
TwonlySafeBackup? sse_decode_opt_box_autoadd_twonly_safe_backup(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -673,11 +650,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PqcPreKeyInput sse_decode_pqc_pre_key_input(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PreparedOutgoingMessage sse_decode_prepared_outgoing_message(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
(PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -938,12 +910,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_prepared_outgoing_message(
|
||||
PreparedOutgoingMessage self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_twonly_safe_backup(
|
||||
TwonlySafeBackup self,
|
||||
|
|
@ -1124,12 +1090,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_prepared_outgoing_message(
|
||||
PreparedOutgoingMessage? self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_twonly_safe_backup(
|
||||
TwonlySafeBackup? self,
|
||||
|
|
@ -1166,12 +1126,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_prepared_outgoing_message(
|
||||
PreparedOutgoingMessage self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_record_i_64_list_prim_u_8_strict(
|
||||
(PlatformInt64, Uint8List) self,
|
||||
|
|
|
|||
|
|
@ -152,11 +152,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
PasswordlessRecoveryConfig
|
||||
dco_decode_box_autoadd_passwordless_recovery_config(dynamic raw);
|
||||
|
||||
@protected
|
||||
PreparedOutgoingMessage dco_decode_box_autoadd_prepared_outgoing_message(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
TwonlySafeBackup dco_decode_box_autoadd_twonly_safe_backup(dynamic raw);
|
||||
|
||||
|
|
@ -285,11 +280,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
PasswordlessRecoveryConfig?
|
||||
dco_decode_opt_box_autoadd_passwordless_recovery_config(dynamic raw);
|
||||
|
||||
@protected
|
||||
PreparedOutgoingMessage? dco_decode_opt_box_autoadd_prepared_outgoing_message(
|
||||
dynamic raw,
|
||||
);
|
||||
|
||||
@protected
|
||||
TwonlySafeBackup? dco_decode_opt_box_autoadd_twonly_safe_backup(dynamic raw);
|
||||
|
||||
|
|
@ -313,9 +303,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PqcPreKeyInput dco_decode_pqc_pre_key_input(dynamic raw);
|
||||
|
||||
@protected
|
||||
PreparedOutgoingMessage dco_decode_prepared_outgoing_message(dynamic raw);
|
||||
|
||||
@protected
|
||||
(PlatformInt64, Uint8List) dco_decode_record_i_64_list_prim_u_8_strict(
|
||||
dynamic raw,
|
||||
|
|
@ -490,11 +477,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedOutgoingMessage sse_decode_box_autoadd_prepared_outgoing_message(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
TwonlySafeBackup sse_decode_box_autoadd_twonly_safe_backup(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -643,11 +625,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
PreparedOutgoingMessage? sse_decode_opt_box_autoadd_prepared_outgoing_message(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
TwonlySafeBackup? sse_decode_opt_box_autoadd_twonly_safe_backup(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -675,11 +652,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
@protected
|
||||
PqcPreKeyInput sse_decode_pqc_pre_key_input(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PreparedOutgoingMessage sse_decode_prepared_outgoing_message(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
(PlatformInt64, Uint8List) sse_decode_record_i_64_list_prim_u_8_strict(
|
||||
SseDeserializer deserializer,
|
||||
|
|
@ -940,12 +912,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_prepared_outgoing_message(
|
||||
PreparedOutgoingMessage self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_box_autoadd_twonly_safe_backup(
|
||||
TwonlySafeBackup self,
|
||||
|
|
@ -1126,12 +1092,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_prepared_outgoing_message(
|
||||
PreparedOutgoingMessage? self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_opt_box_autoadd_twonly_safe_backup(
|
||||
TwonlySafeBackup? self,
|
||||
|
|
@ -1168,12 +1128,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
|||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_prepared_outgoing_message(
|
||||
PreparedOutgoingMessage self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_record_i_64_list_prim_u_8_strict(
|
||||
(PlatformInt64, Uint8List) self,
|
||||
|
|
|
|||
|
|
@ -691,11 +691,13 @@ Future<void> _createUploadRequest(MediaFileService media) async {
|
|||
);
|
||||
}
|
||||
|
||||
final cipherText = await sendCipherText(
|
||||
groupMember.contactId,
|
||||
notEncryptedContent,
|
||||
final cipherText = await RustApi.sendEncryptedContent(
|
||||
contactId: groupMember.contactId,
|
||||
content: notEncryptedContent.writeToBuffer(),
|
||||
messageId: message.messageId,
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
onlyReturnEncryptedData: true,
|
||||
blocking: true,
|
||||
);
|
||||
|
||||
if (cipherText == null) {
|
||||
|
|
@ -706,13 +708,9 @@ Future<void> _createUploadRequest(MediaFileService media) async {
|
|||
}
|
||||
|
||||
final messageOnSuccess = TextMessage()
|
||||
..body = cipherText.$1
|
||||
..body = cipherText
|
||||
..userId = Int64(groupMember.contactId);
|
||||
|
||||
if (cipherText.$2 != null) {
|
||||
messageOnSuccess.pushData = cipherText.$2!;
|
||||
}
|
||||
|
||||
messagesOnSuccess.add(messageOnSuccess);
|
||||
downloadTokens.add(downloadToken);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
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/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,
|
||||
Receipt? receipt,
|
||||
bool onlyReturnEncryptedData = false,
|
||||
|
|
@ -19,86 +15,5 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
|
|||
await rust_api.RustApi.sendQueuedMessage(receiptId: id);
|
||||
return null;
|
||||
}
|
||||
final prepared = await rust_api.RustApi.prepareQueuedMessage(receiptId: id);
|
||||
return prepared == null ? null : (prepared.message, prepared.pushData);
|
||||
return rust_api.RustApi.prepareQueuedMessage(receiptId: id);
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart
|
|||
as server;
|
||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart'
|
||||
hide Message;
|
||||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
|
@ -62,14 +61,17 @@ Future<void> handleMediaError(MediaFile media) async {
|
|||
if (messages.length != 1) return;
|
||||
final message = messages.first;
|
||||
if (message.senderId == null) return;
|
||||
await sendCipherText(
|
||||
message.senderId!,
|
||||
EncryptedContent(
|
||||
await RustApi.sendEncryptedContent(
|
||||
contactId: message.senderId!,
|
||||
content: EncryptedContent(
|
||||
mediaUpdate: EncryptedContent_MediaUpdate(
|
||||
type: EncryptedContent_MediaUpdate_Type.DECRYPTION_ERROR,
|
||||
targetMessageId: message.messageId,
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
onlyReturnEncryptedData: false,
|
||||
blocking: true,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -81,13 +83,16 @@ Future<bool> importSignalContactAndCreateRequest(
|
|||
}
|
||||
|
||||
// 2. Then send user request
|
||||
await sendCipherText(
|
||||
userdata.userId.toInt(),
|
||||
EncryptedContent(
|
||||
await RustApi.sendEncryptedContent(
|
||||
contactId: userdata.userId.toInt(),
|
||||
content: EncryptedContent(
|
||||
contactRequest: EncryptedContent_ContactRequest(
|
||||
type: EncryptedContent_ContactRequest_Type.REQUEST,
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
onlyReturnEncryptedData: false,
|
||||
blocking: true,
|
||||
);
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import 'package:fixnum/fixnum.dart';
|
|||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
|
|
@ -39,9 +38,9 @@ Future<void> syncFlameCounters({String? forceForGroup}) async {
|
|||
continue;
|
||||
}
|
||||
|
||||
await sendCipherTextToGroup(
|
||||
group.groupId,
|
||||
EncryptedContent(
|
||||
await RustApi.sendEncryptedContentToGroup(
|
||||
groupId: group.groupId,
|
||||
content: EncryptedContent(
|
||||
flameSync: EncryptedContent_FlameSync(
|
||||
flameCounter: Int64(flameResult.counter),
|
||||
lastFlameCounterChange: Int64(
|
||||
|
|
@ -50,7 +49,8 @@ Future<void> syncFlameCounters({String? forceForGroup}) async {
|
|||
bestFriend: group.groupId == bestFriend.groupId,
|
||||
forceUpdate: group.groupId == forceForGroup,
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
);
|
||||
|
||||
await twonlyDB.groupsDao.updateGroup(
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import 'package:twonly/src/database/tables/contacts.table.dart';
|
|||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
||||
as pb;
|
||||
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/session.signal.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
|
@ -56,13 +55,16 @@ class KeyVerificationService {
|
|||
false,
|
||||
);
|
||||
|
||||
await sendCipherText(
|
||||
contactId,
|
||||
pb.EncryptedContent(
|
||||
await RustApi.sendEncryptedContent(
|
||||
contactId: contactId,
|
||||
content: pb.EncryptedContent(
|
||||
keyVerificationProof: pb.EncryptedContent_KeyVerificationProof(
|
||||
calculatedMac: calculatedMac,
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
onlyReturnEncryptedData: false,
|
||||
blocking: true,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
|||
as pb;
|
||||
import 'package:twonly/src/model/protobuf/client/generated/passwordless_recovery.pb.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/utils/keyvalue.dart';
|
||||
|
|
@ -152,13 +151,16 @@ class PasswordlessRecoveryService {
|
|||
|
||||
for (final contact in oldTrustedFriends) {
|
||||
try {
|
||||
await sendCipherText(
|
||||
contact.userId,
|
||||
pb.EncryptedContent(
|
||||
await RustApi.sendEncryptedContent(
|
||||
contactId: contact.userId,
|
||||
content: pb.EncryptedContent(
|
||||
passwordlessRecovery: pb.EncryptedContent_PasswordLessRecovery(
|
||||
delete: true,
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
onlyReturnEncryptedData: false,
|
||||
blocking: true,
|
||||
);
|
||||
} catch (e) {
|
||||
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.
|
||||
for (final contact in oldTrustedFriends) {
|
||||
try {
|
||||
await sendCipherText(
|
||||
contact.userId,
|
||||
pb.EncryptedContent(
|
||||
await RustApi.sendEncryptedContent(
|
||||
contactId: contact.userId,
|
||||
content: pb.EncryptedContent(
|
||||
passwordlessRecovery: pb.EncryptedContent_PasswordLessRecovery(
|
||||
delete: true,
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
onlyReturnEncryptedData: false,
|
||||
blocking: true,
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error(
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@ import 'dart:typed_data';
|
|||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
// ignore: implementation_imports
|
||||
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'
|
||||
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/session.signal.dart';
|
||||
import 'package:twonly/src/services/signal/utils.signal.dart';
|
||||
|
|
@ -23,106 +22,29 @@ Future<SignalEncryptResult?> signalEncryptMessage(
|
|||
Uint8List plaintextContent,
|
||||
) 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 {
|
||||
final signalStore = (await getSignalStore())!;
|
||||
final address = getSignalAddress(target);
|
||||
final session = SessionCipher.fromStore(signalStore, address);
|
||||
final cipherText = await session.encrypt(plaintextContent);
|
||||
|
||||
pb.Message_Type type;
|
||||
switch (cipherText.getType()) {
|
||||
case CiphertextMessage.prekeyType:
|
||||
type = pb.Message_Type.PREKEY_BUNDLE;
|
||||
case CiphertextMessage.whisperType:
|
||||
type = pb.Message_Type.CIPHERTEXT;
|
||||
default:
|
||||
Log.error('Invalid ciphertext type: ${cipherText.getType()}.');
|
||||
return null;
|
||||
}
|
||||
|
||||
return SignalEncryptResult(cipherText.serialize(), type);
|
||||
} catch (e) {
|
||||
Log.error('Could not encrypt message (V1) for target $target: $e');
|
||||
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);
|
||||
final signalStore = (await getSignalStore())!;
|
||||
final address = getSignalAddress(target);
|
||||
final session = SessionCipher.fromStore(signalStore, address);
|
||||
final cipherText = await session.encrypt(plaintextContent);
|
||||
|
||||
pb.Message_Type type;
|
||||
switch (cipherText.getType()) {
|
||||
case CiphertextMessage.prekeyType:
|
||||
type = pb.Message_Type.PREKEY_BUNDLE;
|
||||
case CiphertextMessage.whisperType:
|
||||
type = pb.Message_Type.CIPHERTEXT;
|
||||
default:
|
||||
Log.error('Invalid ciphertext type: ${cipherText.getType()}.');
|
||||
return null;
|
||||
}
|
||||
|
||||
return SignalEncryptResult(cipherText.serialize(), type);
|
||||
} 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.error('Could not encrypt message for target $target: $e');
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
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?)>
|
||||
|
|
@ -218,13 +140,16 @@ signalDecryptMessageV1(
|
|||
|
||||
// This message contains a new PreKeyBundle establishing a new signal
|
||||
// session
|
||||
await sendCipherText(
|
||||
fromUserId,
|
||||
pb.EncryptedContent(
|
||||
await RustApi.sendEncryptedContent(
|
||||
contactId: fromUserId,
|
||||
content: pb.EncryptedContent(
|
||||
errorMessages: pb.EncryptedContent_ErrorMessages(
|
||||
type: pb.EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC,
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
onlyReturnEncryptedData: false,
|
||||
blocking: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import 'package:twonly/locator.dart';
|
|||
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
import 'package:twonly/src/services/group.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/better_list_title.element.dart';
|
||||
|
|
@ -96,14 +95,15 @@ class _SelectChatDeletionTimeListTitleState
|
|||
),
|
||||
),
|
||||
);
|
||||
await sendCipherTextToGroup(
|
||||
group!.groupId,
|
||||
EncryptedContent(
|
||||
await RustApi.sendEncryptedContentToGroup(
|
||||
groupId: group!.groupId,
|
||||
content: EncryptedContent(
|
||||
groupUpdate: EncryptedContent_GroupUpdate(
|
||||
groupActionType: GroupActionType.changeDisplayMaxTime.name,
|
||||
newDeleteMessagesAfterMilliseconds: Int64(selected),
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
);
|
||||
} else {
|
||||
if (!await updateChatDeletionTime(group!, selected)) {
|
||||
|
|
|
|||
|
|
@ -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/model/memory_item.model.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/notifications/background.notifications.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
|
@ -179,12 +178,12 @@ class _ChatMessagesViewState extends State<ChatMessagesView>
|
|||
}
|
||||
|
||||
if (userService.currentUser.typingIndicators) {
|
||||
unawaited(sendTypingIndication(widget.groupId, false));
|
||||
unawaited(RustApi.sendTyping(groupId: widget.groupId, isTyping: false));
|
||||
_nextTypingIndicator = Timer.periodic(const Duration(seconds: 2), (
|
||||
_,
|
||||
) async {
|
||||
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]!,
|
||||
);
|
||||
unawaited(
|
||||
notifyContactAboutOpeningMessage(
|
||||
contactId,
|
||||
openedMessages[contactId]!,
|
||||
RustApi.notifyMessagesOpened(
|
||||
contactId: contactId,
|
||||
messageIds: openedMessages[contactId]!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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/model/protobuf/client/generated/messages.pb.dart'
|
||||
as pb;
|
||||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/animate_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
|
|
@ -56,15 +55,16 @@ class _AllReactionsViewState extends State<AllReactionsView> {
|
|||
emoji,
|
||||
true,
|
||||
);
|
||||
await sendCipherTextToGroup(
|
||||
widget.message.groupId,
|
||||
pb.EncryptedContent(
|
||||
await RustApi.sendEncryptedContentToGroup(
|
||||
groupId: widget.message.groupId,
|
||||
content: pb.EncryptedContent(
|
||||
reaction: pb.EncryptedContent_Reaction(
|
||||
targetMessageId: widget.message.messageId,
|
||||
emoji: emoji,
|
||||
remove: true,
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import 'package:drift/drift.dart' show Value;
|
||||
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:twonly/locator.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/visual/views/shared/select_contacts.view.dart';
|
||||
|
||||
|
|
@ -40,9 +41,9 @@ class _ShareAdditionalViewState extends State<ShareAdditionalView> {
|
|||
)
|
||||
as List<int>?;
|
||||
if (selectedContacts != null && selectedContacts.isNotEmpty) {
|
||||
await insertAndSendContactShareMessage(
|
||||
widget.group.groupId,
|
||||
selectedContacts,
|
||||
await RustApi.insertAndSendContactShare(
|
||||
groupId: widget.group.groupId,
|
||||
contactIds: Int64List.fromList(selectedContacts),
|
||||
);
|
||||
if (widget.group.isDirectChat) {
|
||||
final members = await twonlyDB.groupsDao.getGroupContact(
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import 'package:audio_waveforms/audio_waveforms.dart';
|
||||
import 'package:flutter/material.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/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/visual/elements/better_text.element.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();
|
||||
if (widget.message.senderId != null &&
|
||||
widget.message.openedAt == null) {
|
||||
notifyContactAboutOpeningMessage(
|
||||
widget.message.senderId!,
|
||||
[widget.message.messageId],
|
||||
RustApi.notifyMessagesOpened(
|
||||
contactId: widget.message.senderId!,
|
||||
messageIds: [widget.message.messageId],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
|||
hide Message;
|
||||
import 'package:twonly/src/services/api/mediafiles/download.api.dart'
|
||||
as received;
|
||||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/better_text.element.dart';
|
||||
|
|
@ -78,14 +77,17 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
|||
}
|
||||
if (widget.mediaService.canBeOpenedAgain &&
|
||||
widget.message.senderId != null) {
|
||||
await sendCipherText(
|
||||
widget.message.senderId!,
|
||||
EncryptedContent(
|
||||
await RustApi.sendEncryptedContent(
|
||||
contactId: widget.message.senderId!,
|
||||
content: EncryptedContent(
|
||||
mediaUpdate: EncryptedContent_MediaUpdate(
|
||||
type: EncryptedContent_MediaUpdate_Type.REOPENED,
|
||||
targetMessageId: widget.message.messageId,
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
onlyReturnEncryptedData: false,
|
||||
blocking: true,
|
||||
);
|
||||
await twonlyDB.messagesDao.updateMessageId(
|
||||
widget.message.messageId,
|
||||
|
|
|
|||
|
|
@ -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/protobuf/client/generated/messages.pbserver.dart'
|
||||
as pb;
|
||||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/emoji_picker.bottom.dart';
|
||||
|
|
@ -53,14 +52,17 @@ class MessageContextMenu extends StatelessWidget {
|
|||
|
||||
if (message.senderId != null) {
|
||||
// notify the sender
|
||||
await sendCipherText(
|
||||
message.senderId!,
|
||||
pb.EncryptedContent(
|
||||
await RustApi.sendEncryptedContent(
|
||||
contactId: message.senderId!,
|
||||
content: pb.EncryptedContent(
|
||||
mediaUpdate: pb.EncryptedContent_MediaUpdate(
|
||||
type: pb.EncryptedContent_MediaUpdate_Type.REOPENED,
|
||||
targetMessageId: message.messageId,
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
onlyReturnEncryptedData: false,
|
||||
blocking: true,
|
||||
);
|
||||
await twonlyDB.messagesDao.updateMessageId(
|
||||
message.messageId,
|
||||
|
|
@ -125,15 +127,16 @@ class MessageContextMenu extends StatelessWidget {
|
|||
false,
|
||||
);
|
||||
|
||||
await sendCipherTextToGroup(
|
||||
message.groupId,
|
||||
pb.EncryptedContent(
|
||||
await RustApi.sendEncryptedContentToGroup(
|
||||
groupId: message.groupId,
|
||||
content: pb.EncryptedContent(
|
||||
reaction: pb.EncryptedContent_Reaction(
|
||||
targetMessageId: message.messageId,
|
||||
emoji: layer.text,
|
||||
remove: false,
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
);
|
||||
},
|
||||
icon: FontAwesomeIcons.faceLaugh,
|
||||
|
|
@ -186,14 +189,15 @@ class MessageContextMenu extends StatelessWidget {
|
|||
message.messageId,
|
||||
clock.now(),
|
||||
);
|
||||
await sendCipherTextToGroup(
|
||||
message.groupId,
|
||||
pb.EncryptedContent(
|
||||
await RustApi.sendEncryptedContentToGroup(
|
||||
groupId: message.groupId,
|
||||
content: pb.EncryptedContent(
|
||||
messageUpdate: pb.EncryptedContent_MessageUpdate(
|
||||
type: pb.EncryptedContent_MessageUpdate_Type.DELETE,
|
||||
senderMessageId: message.messageId,
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
);
|
||||
} else if (action == 'delete_for_me') {
|
||||
await twonlyDB.messagesDao.deleteMessagesById(
|
||||
|
|
@ -403,9 +407,9 @@ Future<void> editTextMessage(BuildContext context, Message message) async {
|
|||
newText!,
|
||||
timestamp,
|
||||
);
|
||||
await sendCipherTextToGroup(
|
||||
message.groupId,
|
||||
pb.EncryptedContent(
|
||||
await RustApi.sendEncryptedContentToGroup(
|
||||
groupId: message.groupId,
|
||||
content: pb.EncryptedContent(
|
||||
messageUpdate:
|
||||
pb.EncryptedContent_MessageUpdate(
|
||||
type: pb
|
||||
|
|
@ -417,7 +421,8 @@ Future<void> editTextMessage(BuildContext context, Message message) async {
|
|||
timestamp.millisecondsSinceEpoch,
|
||||
),
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
);
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import 'package:twonly/locator.dart';
|
|||
import 'package:twonly/src/database/tables/mediafiles.table.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/messages.api.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/chats/chat_messages_components/bottom_sheets/share_additional.bottom_sheet.dart';
|
||||
|
|
@ -64,10 +63,10 @@ class _MessageInputState extends State<MessageInput> {
|
|||
Future<void> _sendMessage() async {
|
||||
if (_textFieldController.text == '') return;
|
||||
|
||||
await insertAndSendTextMessage(
|
||||
widget.group.groupId,
|
||||
_textFieldController.text,
|
||||
widget.quotesMessage?.messageId,
|
||||
await RustApi.insertAndSendText(
|
||||
groupId: widget.group.groupId,
|
||||
text: _textFieldController.text,
|
||||
quoteMessageId: widget.quotesMessage?.messageId,
|
||||
);
|
||||
|
||||
_textFieldController.clear();
|
||||
|
|
@ -93,7 +92,7 @@ class _MessageInputState extends State<MessageInput> {
|
|||
_lastTextChangeTime != null &&
|
||||
DateTime.now().difference(_lastTextChangeTime!) <=
|
||||
const Duration(seconds: 6)) {
|
||||
await sendTypingIndication(widget.group.groupId, true);
|
||||
await RustApi.sendTyping(groupId: widget.group.groupId, isTyping: true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import 'package:twonly/src/database/twonly.db.dart';
|
|||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
||||
as pb;
|
||||
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/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/services/notifications/background.notifications.dart';
|
||||
|
|
@ -417,9 +416,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
markAsOpenMessageIDs = messageIds;
|
||||
}
|
||||
|
||||
await notifyContactAboutOpeningMessage(
|
||||
currentMessage!.senderId!,
|
||||
markAsOpenMessageIDs,
|
||||
await RustApi.notifyMessagesOpened(
|
||||
contactId: currentMessage!.senderId!,
|
||||
messageIds: markAsOpenMessageIDs,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -531,14 +530,15 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
mediaStored: Value(true),
|
||||
),
|
||||
);
|
||||
await sendCipherTextToGroup(
|
||||
widget.group.groupId,
|
||||
pb.EncryptedContent(
|
||||
await RustApi.sendEncryptedContentToGroup(
|
||||
groupId: widget.group.groupId,
|
||||
content: pb.EncryptedContent(
|
||||
mediaUpdate: pb.EncryptedContent_MediaUpdate(
|
||||
type: pb.EncryptedContent_MediaUpdate_Type.STORED,
|
||||
targetMessageId: msg.messageId,
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
);
|
||||
setState(() {
|
||||
imageSaved = true;
|
||||
|
|
@ -620,10 +620,10 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
void _sendTextMessage() {
|
||||
if (textMessageController.text.isNotEmpty) {
|
||||
unawaited(
|
||||
insertAndSendTextMessage(
|
||||
widget.group.groupId,
|
||||
textMessageController.text,
|
||||
currentMessage!.messageId,
|
||||
RustApi.insertAndSendText(
|
||||
groupId: widget.group.groupId,
|
||||
text: textMessageController.text,
|
||||
quoteMessageId: currentMessage!.messageId,
|
||||
),
|
||||
);
|
||||
textMessageController.clear();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.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/views/chats/media_viewer_components/reaction_buttons.comp.dart';
|
||||
|
||||
|
|
@ -29,15 +28,16 @@ Future<void> sendReaction(
|
|||
emoji,
|
||||
false,
|
||||
);
|
||||
await sendCipherTextToGroup(
|
||||
groupId,
|
||||
EncryptedContent(
|
||||
await RustApi.sendEncryptedContentToGroup(
|
||||
groupId: groupId,
|
||||
content: EncryptedContent(
|
||||
reaction: EncryptedContent_Reaction(
|
||||
targetMessageId: messageId,
|
||||
emoji: emoji,
|
||||
remove: false,
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import 'package:twonly/locator.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/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/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
|
@ -150,7 +149,10 @@ class FriendSuggestionsComp extends StatelessWidget {
|
|||
|
||||
if (result == true && selectedFriends.isNotEmpty) {
|
||||
for (final contactId in selectedFriends) {
|
||||
await insertAndSendAskAboutUserMessage(contactId, user.announcedUserId);
|
||||
await RustApi.insertAndSendAskAboutUser(
|
||||
contactId: contactId,
|
||||
askAboutUserId: user.announcedUserId,
|
||||
);
|
||||
}
|
||||
|
||||
await twonlyDB.userDiscoveryDao.updateAnnouncedUser(
|
||||
|
|
|
|||
|
|
@ -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/twonly.db.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/visual/components/alert.dialog.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
|
|
@ -111,15 +110,18 @@ class OpenRequestsListComp extends StatelessWidget {
|
|||
groupName: Value(getContactDisplayName(contact)),
|
||||
),
|
||||
);
|
||||
await sendCipherText(
|
||||
contact.userId,
|
||||
EncryptedContent(
|
||||
await RustApi.sendEncryptedContent(
|
||||
contactId: contact.userId,
|
||||
content: EncryptedContent(
|
||||
contactRequest: EncryptedContent_ContactRequest(
|
||||
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(),
|
||||
icon: const Icon(Icons.close, size: 18),
|
||||
onPressed: () async {
|
||||
await sendCipherText(
|
||||
contact.userId,
|
||||
EncryptedContent(
|
||||
await RustApi.sendEncryptedContent(
|
||||
contactId: contact.userId,
|
||||
content: EncryptedContent(
|
||||
contactRequest: EncryptedContent_ContactRequest(
|
||||
type: EncryptedContent_ContactRequest_Type.REJECT,
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
onlyReturnEncryptedData: false,
|
||||
blocking: true,
|
||||
);
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
contact.userId,
|
||||
|
|
|
|||
|
|
@ -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/messages.pb.dart'
|
||||
as pb;
|
||||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
import 'package:twonly/src/services/flame.service.dart';
|
||||
import 'package:twonly/src/services/subscription.service.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
|
@ -100,10 +99,11 @@ class _RestoreFlameCompState extends State<RestoreFlameComp> {
|
|||
);
|
||||
|
||||
await syncFlameCounters(forceForGroup: _groupId);
|
||||
await sendCipherTextToGroup(
|
||||
_groupId,
|
||||
encryptedContent,
|
||||
await RustApi.sendEncryptedContentToGroup(
|
||||
groupId: _groupId,
|
||||
content: encryptedContent.writeToBuffer(),
|
||||
messageId: message.messageId,
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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/twonly.db.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/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/alert.dialog.dart';
|
||||
|
|
@ -99,13 +98,16 @@ class GroupMemberContextMenu extends StatelessWidget {
|
|||
deletedByUser: Value(false),
|
||||
),
|
||||
);
|
||||
await sendCipherText(
|
||||
member.contactId,
|
||||
EncryptedContent(
|
||||
await RustApi.sendEncryptedContent(
|
||||
contactId: member.contactId,
|
||||
content: EncryptedContent(
|
||||
contactRequest: EncryptedContent_ContactRequest(
|
||||
type: EncryptedContent_ContactRequest_Type.REQUEST,
|
||||
),
|
||||
),
|
||||
).writeToBuffer(),
|
||||
onlySendIfNoReceiptsAreOpen: false,
|
||||
onlyReturnEncryptedData: false,
|
||||
blocking: true,
|
||||
);
|
||||
if (context.mounted) {
|
||||
showSnackbar(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.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/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
|
@ -64,10 +63,9 @@ class _AutomatedTestingViewState extends State<AutomatedTestingView> {
|
|||
final serializedSession = originalSession.serialize();
|
||||
|
||||
for (var i = 0; i < 10; i++) {
|
||||
await insertAndSendTextMessage(
|
||||
group.groupId,
|
||||
'DesyncTest_1',
|
||||
null,
|
||||
await RustApi.insertAndSendText(
|
||||
groupId: group.groupId,
|
||||
text: 'DesyncTest_1',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -79,10 +77,9 @@ class _AutomatedTestingViewState extends State<AutomatedTestingView> {
|
|||
corruptedSession,
|
||||
);
|
||||
|
||||
await insertAndSendTextMessage(
|
||||
group.groupId,
|
||||
'DesyncTest_2',
|
||||
null,
|
||||
await RustApi.insertAndSendText(
|
||||
groupId: group.groupId,
|
||||
text: 'DesyncTest_2',
|
||||
);
|
||||
|
||||
// The other client should res
|
||||
|
|
@ -110,10 +107,9 @@ class _AutomatedTestingViewState extends State<AutomatedTestingView> {
|
|||
lotsOfMessagesStatus =
|
||||
'At message $i to ${contact.username}.';
|
||||
});
|
||||
await insertAndSendTextMessage(
|
||||
group!.groupId,
|
||||
'Message $i.',
|
||||
null,
|
||||
await RustApi.insertAndSendText(
|
||||
groupId: group!.groupId,
|
||||
text: 'Message $i.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
handle_sender_delivery_receipt, process_encrypted_or_queue_error, queue_decryption_error,
|
||||
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::bridge::callbacks::get_callbacks;
|
||||
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::sealed_sender::SealedSender;
|
||||
use client_to_server::response::{ok, Response};
|
||||
use prost::Message as _;
|
||||
use proto::message::Type;
|
||||
use server_to_client::v0::Kind;
|
||||
use sqlx::{Sqlite, Transaction};
|
||||
use std::sync::Arc;
|
||||
|
||||
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 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 {
|
||||
// Delivery receipts are terminal messages and must never themselves be
|
||||
// acknowledged. For regular messages Dart retries the delivery receipt
|
||||
// after ten days, atomically claiming the retry by moving created_at.
|
||||
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 {
|
||||
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 {
|
||||
tracing::info!("Spawning receipt delivery for retry");
|
||||
spawn_receipt_delivery(ctx, message.receipt_id);
|
||||
}
|
||||
|
||||
tracing::info!("Returning early because receipt was already claimed");
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -190,7 +207,7 @@ pub(crate) async fn handle_decoded_server_message(
|
|||
match message_type {
|
||||
Type::SenderDeliveryReceipt => {
|
||||
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 => {
|
||||
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...");
|
||||
sends_error_response = process_encrypted_or_queue_error(
|
||||
ctx,
|
||||
&mut tr,
|
||||
&mut t,
|
||||
from_user_id,
|
||||
&message.receipt_id,
|
||||
content,
|
||||
|
|
@ -221,7 +238,7 @@ pub(crate) async fn handle_decoded_server_message(
|
|||
(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?;
|
||||
sends_error_response = true;
|
||||
}
|
||||
|
|
@ -249,7 +266,7 @@ pub(crate) async fn handle_decoded_server_message(
|
|||
})?;
|
||||
sends_error_response = process_encrypted_or_queue_error(
|
||||
ctx,
|
||||
&mut tr,
|
||||
&mut t,
|
||||
from_user_id,
|
||||
&message.receipt_id,
|
||||
content,
|
||||
|
|
@ -262,7 +279,7 @@ pub(crate) async fn handle_decoded_server_message(
|
|||
receipt_id = message.receipt_id,
|
||||
"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;
|
||||
}
|
||||
}
|
||||
|
|
@ -272,17 +289,17 @@ pub(crate) async fn handle_decoded_server_message(
|
|||
let plaintext = message.plaintext_content.ok_or_else(|| {
|
||||
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?;
|
||||
}
|
||||
Type::TestNotification => {}
|
||||
}
|
||||
|
||||
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([
|
||||
"received_receipts",
|
||||
|
|
@ -303,3 +320,187 @@ pub(crate) async fn handle_decoded_server_message(
|
|||
|
||||
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"
|
||||
)))
|
||||
}
|
||||
|
|
@ -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"
|
||||
)))
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
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::context::Context;
|
||||
use crate::database::app::tables::{Contact, Group};
|
||||
|
|
@ -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 push_data: Option<Vec<u8>> = None;
|
||||
|
||||
match message_type {
|
||||
proto::message::Type::Ciphertext | proto::message::Type::PrekeyBundle => {
|
||||
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::ErrorCode(code) => {
|
||||
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(
|
||||
ctx: &Arc<Context>,
|
||||
receipt_id: &str,
|
||||
) -> Result<Option<(Vec<u8>, Option<Vec<u8>>)>> {
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
let database = ctx.app_db.read().await.clone();
|
||||
let row = sqlx::query!(
|
||||
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())
|
||||
.map_err(|error| TwonlyError::Generic(format!("invalid queued message: {error}")))?;
|
||||
message.receipt_id = receipt_id.to_owned();
|
||||
let push_data: Option<Vec<u8>> = None;
|
||||
|
||||
match proto::message::Type::try_from(message.r#type)
|
||||
.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<()> {
|
||||
|
|
@ -579,6 +575,7 @@ pub(crate) async fn handle_sender_delivery_receipt(
|
|||
.fetch_optional(&mut **transaction)
|
||||
.await?
|
||||
.flatten();
|
||||
|
||||
if let Some(message_id) = message_id {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
|
|
@ -605,6 +602,7 @@ pub(crate) async fn handle_sender_delivery_receipt(
|
|||
)
|
||||
.execute(&mut **transaction)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -4,7 +4,6 @@
|
|||
*/
|
||||
|
||||
pub mod incoming;
|
||||
#[doc(hidden)]
|
||||
pub mod outgoing;
|
||||
|
||||
pub(crate) fn content_type_kind(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
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::context::Context;
|
||||
use crate::error::Result;
|
||||
|
|
@ -48,13 +48,8 @@ pub(crate) async fn decorate_content(
|
|||
.fetch_one(&database.pool)
|
||||
.await?;
|
||||
if allowed != 0 {
|
||||
content.sender_user_discovery_version = Some(
|
||||
ctx.user_discovery
|
||||
.get()
|
||||
.await
|
||||
.get_current_version()
|
||||
.await?,
|
||||
);
|
||||
content.sender_user_discovery_version =
|
||||
Some(ctx.user_discovery.get().await.get_current_version().await?);
|
||||
}
|
||||
}
|
||||
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_return_encrypted_data: 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())?;
|
||||
|
||||
decorate_content(ctx, contact_id, &mut content, message_id.is_some()).await?;
|
||||
|
|
|
|||
|
|
@ -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::runtime::ApiRuntime;
|
||||
use crate::api::Server;
|
||||
|
|
|
|||
|
|
@ -136,7 +136,6 @@ impl Server {
|
|||
ctx: &Arc<Context>,
|
||||
user_id: i64,
|
||||
body: Vec<u8>,
|
||||
push_data: Option<Vec<u8>>,
|
||||
) -> Result<ServerResult<()>> {
|
||||
server_ok!(
|
||||
Self::application_for_contact(
|
||||
|
|
@ -145,7 +144,7 @@ impl Server {
|
|||
client_to_server::application_data::TextMessage {
|
||||
user_id,
|
||||
body,
|
||||
push_data,
|
||||
push_data: None,
|
||||
},
|
||||
),
|
||||
user_id,
|
||||
|
|
|
|||
|
|
@ -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::runtime::helpers::decode_ok_value;
|
||||
use crate::api::ApiRuntime;
|
||||
|
|
@ -104,11 +104,6 @@ pub enum ApiEventKind {
|
|||
/// Flutter-facing facade for the Rust-owned API runtime.
|
||||
pub struct RustApi {}
|
||||
|
||||
pub struct PreparedOutgoingMessage {
|
||||
pub message: Vec<u8>,
|
||||
pub push_data: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl RustApi {
|
||||
pub async fn request_contact_by_username(username: String) -> Result<()> {
|
||||
let ctx = Context::get_static()?;
|
||||
|
|
@ -334,7 +329,7 @@ impl RustApi {
|
|||
}
|
||||
pub async fn perform_passwordless_recovery_heartbeat() -> Result<()> {
|
||||
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(
|
||||
user_id: i64,
|
||||
|
|
@ -459,13 +454,9 @@ impl RustApi {
|
|||
.await
|
||||
.and_then(empty_api_response)
|
||||
}
|
||||
pub async fn send_text_message(
|
||||
user_id: i64,
|
||||
body: Vec<u8>,
|
||||
push_data: Option<Vec<u8>>,
|
||||
) -> Result<()> {
|
||||
pub async fn send_text_message(user_id: i64, body: Vec<u8>) -> Result<()> {
|
||||
let ctx = Context::get_static()?;
|
||||
Server::send_text_message(ctx, user_id, body, push_data)
|
||||
Server::send_text_message(ctx, user_id, body)
|
||||
.await
|
||||
.and_then(api_result)
|
||||
}
|
||||
|
|
@ -477,21 +468,19 @@ impl RustApi {
|
|||
only_send_if_no_receipts_are_open: bool,
|
||||
only_return_encrypted_data: bool,
|
||||
blocking: bool,
|
||||
) -> Result<Option<PreparedOutgoingMessage>> {
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
let ctx = Context::get_static()?;
|
||||
Ok(
|
||||
crate::api::messages::outgoing::send_c2c_message_to_contact()
|
||||
.ctx(ctx)
|
||||
.contact_id(contact_id)
|
||||
.encrypted_content(content)
|
||||
.maybe_message_id(message_id)
|
||||
.only_send_if_no_receipts_are_open(only_send_if_no_receipts_are_open)
|
||||
.only_return_encrypted_data(only_return_encrypted_data)
|
||||
.blocking(blocking)
|
||||
.call()
|
||||
.await?
|
||||
.map(|(message, push_data)| PreparedOutgoingMessage { message, push_data }),
|
||||
)
|
||||
crate::api::messages::outgoing::send_c2c_message_to_contact()
|
||||
.ctx(ctx)
|
||||
.contact_id(contact_id)
|
||||
.encrypted_content(content)
|
||||
.maybe_message_id(message_id)
|
||||
.only_send_if_no_receipts_are_open(only_send_if_no_receipts_are_open)
|
||||
.only_return_encrypted_data(only_return_encrypted_data)
|
||||
.blocking(blocking)
|
||||
.call()
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub async fn send_encrypted_content_to_group(
|
||||
|
|
@ -574,14 +563,10 @@ impl RustApi {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn prepare_queued_message(
|
||||
receipt_id: String,
|
||||
) -> Result<Option<PreparedOutgoingMessage>> {
|
||||
Ok(
|
||||
messages::prepare_queued_receipt(Context::get_static()?, &receipt_id)
|
||||
.await?
|
||||
.map(|(message, push_data)| PreparedOutgoingMessage { message, push_data }),
|
||||
)
|
||||
pub async fn prepare_queued_message(receipt_id: String) -> Result<Option<Vec<u8>>> {
|
||||
messages::prepare_queued_receipt(Context::get_static()?, &receipt_id)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub async fn notify_messages_opened(contact_id: i64, message_ids: Vec<String>) -> Result<()> {
|
||||
|
|
|
|||
|
|
@ -2536,17 +2536,13 @@ fn wire__crate__bridge__api__rust_api_send_text_message_impl(
|
|||
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||
let api_user_id = <i64>::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();
|
||||
move |context| async move {
|
||||
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
|
||||
(move || async move {
|
||||
let output_ok = crate::bridge::api::RustApi::send_text_message(
|
||||
api_user_id,
|
||||
api_body,
|
||||
api_push_data,
|
||||
)
|
||||
.await?;
|
||||
let output_ok =
|
||||
crate::bridge::api::RustApi::send_text_message(api_user_id, api_body)
|
||||
.await?;
|
||||
Ok(output_ok)
|
||||
})()
|
||||
.await,
|
||||
|
|
@ -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> {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
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>) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
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
|
||||
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 {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
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> {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
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>) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ pub(crate) mod crypto;
|
|||
pub(crate) mod model;
|
||||
|
||||
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::proto::client::encrypted_content::GroupJoin;
|
||||
use crate::api::proto::client::{
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
use crate::api::messages::incoming::client2client::messages::{
|
||||
use crate::api::messages::incoming::messages::{
|
||||
self, queue_encrypted_content, send_queued_receipt,
|
||||
};
|
||||
use crate::api::messages::outgoing::{decorate_content, send_c2c_message_to_contact};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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
|
||||
.wait_for_text_message(&msg_id, tester_a.user_id, "Message triggering heal")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use super::Tester;
|
||||
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::proto::client::{self as proto, encrypted_content};
|
||||
use rust_lib_twonly::bridge::api::ApiConnectionState;
|
||||
|
|
|
|||
Loading…
Reference in a new issue