mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-09-01 10:44:07 +00:00
move api fully to rust
This commit is contained in:
parent
b09d79f5d6
commit
63e3eb0f3d
32 changed files with 381 additions and 694 deletions
|
|
@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:flutter_sharing_intent/model/sharing_file.dart' show SharedFile;
|
import 'package:flutter_sharing_intent/model/sharing_file.dart' show SharedFile;
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:twonly/core/bridge/api.dart' as rust_api;
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/keyvalue.keys.dart';
|
import 'package:twonly/src/constants/keyvalue.keys.dart';
|
||||||
|
|
@ -59,11 +60,16 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
if (_wasPaused) {
|
if (_wasPaused) {
|
||||||
AppState.isAppInBackground = false;
|
AppState.isAppInBackground = false;
|
||||||
twonlyDB.markUpdated();
|
twonlyDB.markUpdated();
|
||||||
unawaited(apiService.connect());
|
unawaited(
|
||||||
|
rust_api.RustApi.setBackground(inBackground: false),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if (state == AppLifecycleState.paused) {
|
} else if (state == AppLifecycleState.paused) {
|
||||||
_wasPaused = true;
|
_wasPaused = true;
|
||||||
AppState.isAppInBackground = true;
|
AppState.isAppInBackground = true;
|
||||||
|
unawaited(
|
||||||
|
rust_api.RustApi.setBackground(inBackground: true),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||||
|
|
||||||
// These functions are ignored because they are not marked as `pub`: `get_callbacks`
|
// These functions are ignored because they are not marked as `pub`: `get_callbacks`
|
||||||
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `Api`, `FlutterCallbacks`, `LegacySignal`, `Logging`
|
// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `Api`, `FlutterCallbacks`, `LegacySignal`, `Logging`
|
||||||
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `fmt`, `fmt`, `fmt`
|
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `fmt`, `fmt`
|
||||||
|
|
||||||
Future<void> initFlutterCallbacks({
|
Future<void> initFlutterCallbacks({
|
||||||
required int callbackId,
|
required int callbackId,
|
||||||
|
|
@ -25,8 +25,6 @@ Future<void> initFlutterCallbacks({
|
||||||
Uint8List,
|
Uint8List,
|
||||||
)
|
)
|
||||||
legacySignalEncrypt,
|
legacySignalEncrypt,
|
||||||
required FutureOr<List<LegacySignalPreKey>> Function()
|
|
||||||
legacySignalGeneratePrekeys,
|
|
||||||
required FutureOr<void> Function(PlatformInt64) apiResyncSignalSession,
|
required FutureOr<void> Function(PlatformInt64) apiResyncSignalSession,
|
||||||
required FutureOr<void> Function(String, String, PlatformInt64, String)
|
required FutureOr<void> Function(String, String, PlatformInt64, String)
|
||||||
apiMediaAction,
|
apiMediaAction,
|
||||||
|
|
@ -40,7 +38,6 @@ Future<void> initFlutterCallbacks({
|
||||||
loggingGetStreamSink: loggingGetStreamSink,
|
loggingGetStreamSink: loggingGetStreamSink,
|
||||||
legacySignalDecrypt: legacySignalDecrypt,
|
legacySignalDecrypt: legacySignalDecrypt,
|
||||||
legacySignalEncrypt: legacySignalEncrypt,
|
legacySignalEncrypt: legacySignalEncrypt,
|
||||||
legacySignalGeneratePrekeys: legacySignalGeneratePrekeys,
|
|
||||||
apiResyncSignalSession: apiResyncSignalSession,
|
apiResyncSignalSession: apiResyncSignalSession,
|
||||||
apiMediaAction: apiMediaAction,
|
apiMediaAction: apiMediaAction,
|
||||||
apiVerificationProof: apiVerificationProof,
|
apiVerificationProof: apiVerificationProof,
|
||||||
|
|
@ -95,24 +92,3 @@ class LegacySignalEncryptResult {
|
||||||
ciphertext == other.ciphertext &&
|
ciphertext == other.ciphertext &&
|
||||||
messageType == other.messageType;
|
messageType == other.messageType;
|
||||||
}
|
}
|
||||||
|
|
||||||
class LegacySignalPreKey {
|
|
||||||
final PlatformInt64 id;
|
|
||||||
final Uint8List publicKey;
|
|
||||||
|
|
||||||
const LegacySignalPreKey({
|
|
||||||
required this.id,
|
|
||||||
required this.publicKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => id.hashCode ^ publicKey.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is LegacySignalPreKey &&
|
|
||||||
runtimeType == other.runtimeType &&
|
|
||||||
id == other.id &&
|
|
||||||
publicKey == other.publicKey;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -149,8 +149,6 @@ abstract class RustLibApi extends BaseApi {
|
||||||
Uint8List,
|
Uint8List,
|
||||||
)
|
)
|
||||||
legacySignalEncrypt,
|
legacySignalEncrypt,
|
||||||
required FutureOr<List<LegacySignalPreKey>> Function()
|
|
||||||
legacySignalGeneratePrekeys,
|
|
||||||
required FutureOr<void> Function(PlatformInt64) apiResyncSignalSession,
|
required FutureOr<void> Function(PlatformInt64) apiResyncSignalSession,
|
||||||
required FutureOr<void> Function(String, String, PlatformInt64, String)
|
required FutureOr<void> Function(String, String, PlatformInt64, String)
|
||||||
apiMediaAction,
|
apiMediaAction,
|
||||||
|
|
@ -933,8 +931,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
Uint8List,
|
Uint8List,
|
||||||
)
|
)
|
||||||
legacySignalEncrypt,
|
legacySignalEncrypt,
|
||||||
required FutureOr<List<LegacySignalPreKey>> Function()
|
|
||||||
legacySignalGeneratePrekeys,
|
|
||||||
required FutureOr<void> Function(PlatformInt64) apiResyncSignalSession,
|
required FutureOr<void> Function(PlatformInt64) apiResyncSignalSession,
|
||||||
required FutureOr<void> Function(String, String, PlatformInt64, String)
|
required FutureOr<void> Function(String, String, PlatformInt64, String)
|
||||||
apiMediaAction,
|
apiMediaAction,
|
||||||
|
|
@ -961,10 +957,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
legacySignalEncrypt,
|
legacySignalEncrypt,
|
||||||
serializer,
|
serializer,
|
||||||
);
|
);
|
||||||
sse_encode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException(
|
|
||||||
legacySignalGeneratePrekeys,
|
|
||||||
serializer,
|
|
||||||
);
|
|
||||||
sse_encode_DartFn_Inputs_i_64_Output_unit_AnyhowException(
|
sse_encode_DartFn_Inputs_i_64_Output_unit_AnyhowException(
|
||||||
apiResyncSignalSession,
|
apiResyncSignalSession,
|
||||||
serializer,
|
serializer,
|
||||||
|
|
@ -1006,7 +998,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
loggingGetStreamSink,
|
loggingGetStreamSink,
|
||||||
legacySignalDecrypt,
|
legacySignalDecrypt,
|
||||||
legacySignalEncrypt,
|
legacySignalEncrypt,
|
||||||
legacySignalGeneratePrekeys,
|
|
||||||
apiResyncSignalSession,
|
apiResyncSignalSession,
|
||||||
apiMediaAction,
|
apiMediaAction,
|
||||||
apiVerificationProof,
|
apiVerificationProof,
|
||||||
|
|
@ -1027,7 +1018,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
"loggingGetStreamSink",
|
"loggingGetStreamSink",
|
||||||
"legacySignalDecrypt",
|
"legacySignalDecrypt",
|
||||||
"legacySignalEncrypt",
|
"legacySignalEncrypt",
|
||||||
"legacySignalGeneratePrekeys",
|
|
||||||
"apiResyncSignalSession",
|
"apiResyncSignalSession",
|
||||||
"apiMediaAction",
|
"apiMediaAction",
|
||||||
"apiVerificationProof",
|
"apiVerificationProof",
|
||||||
|
|
@ -4893,43 +4883,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> Function(
|
|
||||||
int,
|
|
||||||
)
|
|
||||||
encode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException(
|
|
||||||
FutureOr<List<LegacySignalPreKey>> Function() raw,
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
callId,
|
|
||||||
) async {
|
|
||||||
Box<List<LegacySignalPreKey>>? rawOutput;
|
|
||||||
Box<AnyhowException>? rawError;
|
|
||||||
try {
|
|
||||||
rawOutput = Box(await raw());
|
|
||||||
} catch (e, s) {
|
|
||||||
rawError = Box(AnyhowException("$e\n\n$s"));
|
|
||||||
}
|
|
||||||
|
|
||||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
|
||||||
assert((rawOutput != null) ^ (rawError != null));
|
|
||||||
if (rawOutput != null) {
|
|
||||||
serializer.buffer.putUint8(0);
|
|
||||||
sse_encode_list_legacy_signal_pre_key(rawOutput.value, serializer);
|
|
||||||
} else {
|
|
||||||
serializer.buffer.putUint8(1);
|
|
||||||
sse_encode_AnyhowException(rawError!.value, serializer);
|
|
||||||
}
|
|
||||||
final output = serializer.intoRaw();
|
|
||||||
|
|
||||||
generalizedFrbRustBinding.dartFnDeliverOutput(
|
|
||||||
callId: callId,
|
|
||||||
ptr: output.ptr,
|
|
||||||
rustVecLen: output.rustVecLen,
|
|
||||||
dataLen: output.dataLen,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> Function(int, dynamic)
|
Future<void> Function(int, dynamic)
|
||||||
encode_DartFn_Inputs_i_64_Output_unit_AnyhowException(
|
encode_DartFn_Inputs_i_64_Output_unit_AnyhowException(
|
||||||
FutureOr<void> Function(PlatformInt64) raw,
|
FutureOr<void> Function(PlatformInt64) raw,
|
||||||
|
|
@ -5152,15 +5105,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
throw UnimplementedError('');
|
throw UnimplementedError('');
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<List<LegacySignalPreKey>> Function()
|
|
||||||
dco_decode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
) {
|
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
|
||||||
throw UnimplementedError('');
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
FutureOr<void> Function(PlatformInt64)
|
FutureOr<void> Function(PlatformInt64)
|
||||||
dco_decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(dynamic raw) {
|
dco_decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(dynamic raw) {
|
||||||
|
|
@ -5511,18 +5455,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalPreKey dco_decode_legacy_signal_pre_key(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 LegacySignalPreKey(
|
|
||||||
id: dco_decode_i_64(arr[0]),
|
|
||||||
publicKey: dco_decode_list_prim_u_8_strict(arr[1]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
LegacyTableMigrationCount dco_decode_legacy_table_migration_count(
|
LegacyTableMigrationCount dco_decode_legacy_table_migration_count(
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
|
|
@ -5549,14 +5481,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
return (raw as List<dynamic>).map(dco_decode_frb_pqc_pre_key).toList();
|
return (raw as List<dynamic>).map(dco_decode_frb_pqc_pre_key).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
|
||||||
List<LegacySignalPreKey> dco_decode_list_legacy_signal_pre_key(dynamic raw) {
|
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
|
||||||
return (raw as List<dynamic>)
|
|
||||||
.map(dco_decode_legacy_signal_pre_key)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
List<LegacyTableMigrationCount> dco_decode_list_legacy_table_migration_count(
|
List<LegacyTableMigrationCount> dco_decode_list_legacy_table_migration_count(
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
|
|
@ -6441,16 +6365,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalPreKey sse_decode_legacy_signal_pre_key(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
) {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
var var_id = sse_decode_i_64(deserializer);
|
|
||||||
var var_publicKey = sse_decode_list_prim_u_8_strict(deserializer);
|
|
||||||
return LegacySignalPreKey(id: var_id, publicKey: var_publicKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
LegacyTableMigrationCount sse_decode_legacy_table_migration_count(
|
LegacyTableMigrationCount sse_decode_legacy_table_migration_count(
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
|
|
@ -6487,20 +6401,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
return ans_;
|
return ans_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
|
||||||
List<LegacySignalPreKey> sse_decode_list_legacy_signal_pre_key(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
) {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
|
|
||||||
var len_ = sse_decode_i_32(deserializer);
|
|
||||||
var ans_ = <LegacySignalPreKey>[];
|
|
||||||
for (var idx_ = 0; idx_ < len_; ++idx_) {
|
|
||||||
ans_.add(sse_decode_legacy_signal_pre_key(deserializer));
|
|
||||||
}
|
|
||||||
return ans_;
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
List<LegacyTableMigrationCount> sse_decode_list_legacy_table_migration_count(
|
List<LegacyTableMigrationCount> sse_decode_list_legacy_table_migration_count(
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
|
|
@ -7269,21 +7169,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException(
|
|
||||||
FutureOr<List<LegacySignalPreKey>> Function() self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
) {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
sse_encode_DartOpaque(
|
|
||||||
encode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException(
|
|
||||||
self,
|
|
||||||
),
|
|
||||||
serializer,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_DartFn_Inputs_i_64_Output_unit_AnyhowException(
|
void sse_encode_DartFn_Inputs_i_64_Output_unit_AnyhowException(
|
||||||
FutureOr<void> Function(PlatformInt64) self,
|
FutureOr<void> Function(PlatformInt64) self,
|
||||||
|
|
@ -7697,16 +7582,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
sse_encode_i_32(self.messageType, serializer);
|
sse_encode_i_32(self.messageType, serializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_legacy_signal_pre_key(
|
|
||||||
LegacySignalPreKey self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
) {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
sse_encode_i_64(self.id, serializer);
|
|
||||||
sse_encode_list_prim_u_8_strict(self.publicKey, serializer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_legacy_table_migration_count(
|
void sse_encode_legacy_table_migration_count(
|
||||||
LegacyTableMigrationCount self,
|
LegacyTableMigrationCount self,
|
||||||
|
|
@ -7738,18 +7613,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_list_legacy_signal_pre_key(
|
|
||||||
List<LegacySignalPreKey> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
) {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
sse_encode_i_32(self.length, serializer);
|
|
||||||
for (final item in self) {
|
|
||||||
sse_encode_legacy_signal_pre_key(item, serializer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_list_legacy_table_migration_count(
|
void sse_encode_list_legacy_table_migration_count(
|
||||||
List<LegacyTableMigrationCount> self,
|
List<LegacyTableMigrationCount> self,
|
||||||
|
|
|
||||||
|
|
@ -56,12 +56,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<List<LegacySignalPreKey>> Function()
|
|
||||||
dco_decode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
FutureOr<void> Function(PlatformInt64)
|
FutureOr<void> Function(PlatformInt64)
|
||||||
dco_decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(dynamic raw);
|
dco_decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(dynamic raw);
|
||||||
|
|
@ -210,9 +204,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalPreKey dco_decode_legacy_signal_pre_key(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
LegacyTableMigrationCount dco_decode_legacy_table_migration_count(
|
LegacyTableMigrationCount dco_decode_legacy_table_migration_count(
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
|
|
@ -224,9 +215,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
List<FrbPqcPreKey> dco_decode_list_frb_pqc_pre_key(dynamic raw);
|
List<FrbPqcPreKey> dco_decode_list_frb_pqc_pre_key(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
List<LegacySignalPreKey> dco_decode_list_legacy_signal_pre_key(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
List<LegacyTableMigrationCount> dco_decode_list_legacy_table_migration_count(
|
List<LegacyTableMigrationCount> dco_decode_list_legacy_table_migration_count(
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
|
|
@ -562,11 +550,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalPreKey sse_decode_legacy_signal_pre_key(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
LegacyTableMigrationCount sse_decode_legacy_table_migration_count(
|
LegacyTableMigrationCount sse_decode_legacy_table_migration_count(
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
|
|
@ -580,11 +563,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
List<LegacySignalPreKey> sse_decode_list_legacy_signal_pre_key(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
List<LegacyTableMigrationCount> sse_decode_list_legacy_table_migration_count(
|
List<LegacyTableMigrationCount> sse_decode_list_legacy_table_migration_count(
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
|
|
@ -820,13 +798,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException(
|
|
||||||
FutureOr<List<LegacySignalPreKey>> Function() self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_DartFn_Inputs_i_64_Output_unit_AnyhowException(
|
void sse_encode_DartFn_Inputs_i_64_Output_unit_AnyhowException(
|
||||||
FutureOr<void> Function(PlatformInt64) self,
|
FutureOr<void> Function(PlatformInt64) self,
|
||||||
|
|
@ -1042,12 +1013,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_legacy_signal_pre_key(
|
|
||||||
LegacySignalPreKey self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_legacy_table_migration_count(
|
void sse_encode_legacy_table_migration_count(
|
||||||
LegacyTableMigrationCount self,
|
LegacyTableMigrationCount self,
|
||||||
|
|
@ -1063,12 +1028,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_list_legacy_signal_pre_key(
|
|
||||||
List<LegacySignalPreKey> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_list_legacy_table_migration_count(
|
void sse_encode_list_legacy_table_migration_count(
|
||||||
List<LegacyTableMigrationCount> self,
|
List<LegacyTableMigrationCount> self,
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
FutureOr<List<LegacySignalPreKey>> Function()
|
|
||||||
dco_decode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException(
|
|
||||||
dynamic raw,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
FutureOr<void> Function(PlatformInt64)
|
FutureOr<void> Function(PlatformInt64)
|
||||||
dco_decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(dynamic raw);
|
dco_decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(dynamic raw);
|
||||||
|
|
@ -212,9 +206,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalPreKey dco_decode_legacy_signal_pre_key(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
LegacyTableMigrationCount dco_decode_legacy_table_migration_count(
|
LegacyTableMigrationCount dco_decode_legacy_table_migration_count(
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
|
|
@ -226,9 +217,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
@protected
|
@protected
|
||||||
List<FrbPqcPreKey> dco_decode_list_frb_pqc_pre_key(dynamic raw);
|
List<FrbPqcPreKey> dco_decode_list_frb_pqc_pre_key(dynamic raw);
|
||||||
|
|
||||||
@protected
|
|
||||||
List<LegacySignalPreKey> dco_decode_list_legacy_signal_pre_key(dynamic raw);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
List<LegacyTableMigrationCount> dco_decode_list_legacy_table_migration_count(
|
List<LegacyTableMigrationCount> dco_decode_list_legacy_table_migration_count(
|
||||||
dynamic raw,
|
dynamic raw,
|
||||||
|
|
@ -564,11 +552,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
LegacySignalPreKey sse_decode_legacy_signal_pre_key(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
LegacyTableMigrationCount sse_decode_legacy_table_migration_count(
|
LegacyTableMigrationCount sse_decode_legacy_table_migration_count(
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
|
|
@ -582,11 +565,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
List<LegacySignalPreKey> sse_decode_list_legacy_signal_pre_key(
|
|
||||||
SseDeserializer deserializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
List<LegacyTableMigrationCount> sse_decode_list_legacy_table_migration_count(
|
List<LegacyTableMigrationCount> sse_decode_list_legacy_table_migration_count(
|
||||||
SseDeserializer deserializer,
|
SseDeserializer deserializer,
|
||||||
|
|
@ -822,13 +800,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void
|
|
||||||
sse_encode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException(
|
|
||||||
FutureOr<List<LegacySignalPreKey>> Function() self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_DartFn_Inputs_i_64_Output_unit_AnyhowException(
|
void sse_encode_DartFn_Inputs_i_64_Output_unit_AnyhowException(
|
||||||
FutureOr<void> Function(PlatformInt64) self,
|
FutureOr<void> Function(PlatformInt64) self,
|
||||||
|
|
@ -1044,12 +1015,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_legacy_signal_pre_key(
|
|
||||||
LegacySignalPreKey self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_legacy_table_migration_count(
|
void sse_encode_legacy_table_migration_count(
|
||||||
LegacyTableMigrationCount self,
|
LegacyTableMigrationCount self,
|
||||||
|
|
@ -1065,12 +1030,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||||
SseSerializer serializer,
|
SseSerializer serializer,
|
||||||
);
|
);
|
||||||
|
|
||||||
@protected
|
|
||||||
void sse_encode_list_legacy_signal_pre_key(
|
|
||||||
List<LegacySignalPreKey> self,
|
|
||||||
SseSerializer serializer,
|
|
||||||
);
|
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
void sse_encode_list_legacy_table_migration_count(
|
void sse_encode_list_legacy_table_migration_count(
|
||||||
List<LegacyTableMigrationCount> self,
|
List<LegacyTableMigrationCount> self,
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,6 @@ void main() async {
|
||||||
binding.addPostFrameCallback((_) async {
|
binding.addPostFrameCallback((_) async {
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
unawaited(postStartupTasks());
|
unawaited(postStartupTasks());
|
||||||
unawaited(apiService.connect());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ Future<void> initFlutterCallbacksForRust() async {
|
||||||
loggingGetStreamSink: LoggingCallbacks.getStreamSink,
|
loggingGetStreamSink: LoggingCallbacks.getStreamSink,
|
||||||
legacySignalDecrypt: LegacySignalCallbacks.decrypt,
|
legacySignalDecrypt: LegacySignalCallbacks.decrypt,
|
||||||
legacySignalEncrypt: LegacySignalCallbacks.encrypt,
|
legacySignalEncrypt: LegacySignalCallbacks.encrypt,
|
||||||
legacySignalGeneratePrekeys: LegacySignalCallbacks.generatePrekeys,
|
|
||||||
apiResyncSignalSession: handleSessionResync,
|
apiResyncSignalSession: handleSessionResync,
|
||||||
apiMediaAction: _apiMediaAction,
|
apiMediaAction: _apiMediaAction,
|
||||||
apiVerificationProof: KeyVerificationService.handleVerificationProof,
|
apiVerificationProof: KeyVerificationService.handleVerificationProof,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import 'package:twonly/core/bridge/callbacks.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
|
||||||
as pb;
|
as pb;
|
||||||
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
||||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
/// Flutter boundary for the legacy libsignal_protocol_dart implementation.
|
/// Flutter boundary for the legacy libsignal_protocol_dart implementation.
|
||||||
|
|
@ -62,25 +61,6 @@ abstract final class LegacySignalCallbacks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<LegacySignalPreKey>> generatePrekeys() async {
|
|
||||||
try {
|
|
||||||
final prekeys = await signalGetPreKeys();
|
|
||||||
return prekeys
|
|
||||||
.map(
|
|
||||||
(prekey) => LegacySignalPreKey(
|
|
||||||
id: prekey.id,
|
|
||||||
publicKey: Uint8List.fromList(
|
|
||||||
prekey.getKeyPair().publicKey.serialize(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(growable: false);
|
|
||||||
} catch (error) {
|
|
||||||
Log.error('Legacy Signal prekey callback failed: $error');
|
|
||||||
return const [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool _isLegacyMessageType(int messageType) =>
|
static bool _isLegacyMessageType(int messageType) =>
|
||||||
messageType == pb.Message_Type.CIPHERTEXT.value ||
|
messageType == pb.Message_Type.CIPHERTEXT.value ||
|
||||||
messageType == pb.Message_Type.PREKEY_BUNDLE.value;
|
messageType == pb.Message_Type.PREKEY_BUNDLE.value;
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,25 @@ import 'package:twonly/locator.dart';
|
||||||
class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
CustomChangeProvider() {
|
CustomChangeProvider() {
|
||||||
// The API is connected before the subscription has started so ensure that the connection state is correct
|
// The API is connected before the subscription has started so ensure that the connection state is correct
|
||||||
_isConnected = apiService.isConnected;
|
_isConnected = false;
|
||||||
_connSub = apiService.onConnectionStateUpdated.listen(
|
unawaited(_loadConnectionState());
|
||||||
updateConnectionState,
|
_connSub = apiService.events
|
||||||
);
|
.where((event) => event.kind == ApiEventKind.connectionStateChanged)
|
||||||
|
.listen(
|
||||||
|
(event) => updateConnectionState(
|
||||||
|
event.state == ApiConnectionState.authenticated,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
late bool _isConnected;
|
late bool _isConnected;
|
||||||
late StreamSubscription<bool> _connSub;
|
late StreamSubscription<ApiEvent> _connSub;
|
||||||
bool get isConnected => _isConnected;
|
bool get isConnected => _isConnected;
|
||||||
|
|
||||||
|
Future<void> _loadConnectionState() async {
|
||||||
|
final state = await RustApi.connectionState();
|
||||||
|
await updateConnectionState(state == ApiConnectionState.authenticated);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_connSub.cancel();
|
_connSub.cancel();
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,21 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
onError: _updateStreamOnError,
|
onError: _updateStreamOnError,
|
||||||
);
|
);
|
||||||
|
|
||||||
_planSub = apiService.onPlanUpdated.listen(updatePlan);
|
_apiSub = apiService.events.listen((event) async {
|
||||||
_connSub = apiService.onConnectionStateUpdated.listen((_) async {
|
if (event.kind == ApiEventKind.planUpdated) {
|
||||||
try {
|
updatePlan(planFromString(event.message ?? ''));
|
||||||
if (userService.isUserCreated) {
|
}
|
||||||
updatePlan(planFromString(userService.currentUser.subscriptionPlan));
|
|
||||||
|
if (event.kind == ApiEventKind.connectionStateChanged) {
|
||||||
|
try {
|
||||||
|
if (userService.isUserCreated) {
|
||||||
|
updatePlan(
|
||||||
|
planFromString(userService.currentUser.subscriptionPlan),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(e);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -63,8 +70,7 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
late StreamSubscription<List<PurchaseDetails>> _subscription;
|
late StreamSubscription<List<PurchaseDetails>> _subscription;
|
||||||
final InAppPurchase iapConnection = IAPConnection.instance;
|
final InAppPurchase iapConnection = IAPConnection.instance;
|
||||||
|
|
||||||
late StreamSubscription<SubscriptionPlan> _planSub;
|
late StreamSubscription<ApiEvent> _apiSub;
|
||||||
late StreamSubscription<bool> _connSub;
|
|
||||||
bool _userTriggeredBuyButton = false;
|
bool _userTriggeredBuyButton = false;
|
||||||
|
|
||||||
void updatePlan(SubscriptionPlan newPlan) {
|
void updatePlan(SubscriptionPlan newPlan) {
|
||||||
|
|
@ -228,7 +234,8 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
if (currentPlan != SubscriptionPlan.Family.name &&
|
if (currentPlan != SubscriptionPlan.Family.name &&
|
||||||
currentPlan != SubscriptionPlan.Pro.name) {
|
currentPlan != SubscriptionPlan.Pro.name) {
|
||||||
for (var i = 0; i < 100; i++) {
|
for (var i = 0; i < 100; i++) {
|
||||||
if (apiService.isAuthenticated) {
|
if (await RustApi.connectionState() ==
|
||||||
|
ApiConnectionState.authenticated) {
|
||||||
Log.info(
|
Log.info(
|
||||||
'current user does not have a sub: ${purchaseDetails.productID}',
|
'current user does not have a sub: ${purchaseDetails.productID}',
|
||||||
);
|
);
|
||||||
|
|
@ -251,8 +258,7 @@ class PurchasesProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_planSub.cancel();
|
_apiSub.cancel();
|
||||||
_connSub.cancel();
|
|
||||||
_subscription.cancel();
|
_subscription.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,125 +1,58 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:twonly/core/bridge/api.dart' as rust_api;
|
import 'package:twonly/core/bridge/api.dart' as rust_api;
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/download.api.dart';
|
|
||||||
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
||||||
import 'package:twonly/src/services/flame.service.dart';
|
|
||||||
import 'package:twonly/src/services/group.service.dart';
|
|
||||||
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
|
import 'package:twonly/src/services/memories/memories_cloud.service.dart';
|
||||||
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
|
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
|
||||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
|
||||||
import 'package:twonly/src/services/signal/protocol_state.signal.dart';
|
import 'package:twonly/src/services/signal/protocol_state.signal.dart';
|
||||||
import 'package:twonly/src/services/subscription.service.dart';
|
|
||||||
import 'package:twonly/src/services/user_discovery.service.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
/// The ApiProvider is responsible for communicating with the server.
|
/// The ApiProvider is responsible for communicating with the server.
|
||||||
/// It handles errors and does automatically tries to reconnect on
|
/// It handles errors and does automatically tries to reconnect on
|
||||||
/// errors or network changes.
|
/// errors or network changes.
|
||||||
class ApiService {
|
class ApiService {
|
||||||
ApiService();
|
ApiService() {
|
||||||
final String apiHost = kReleaseMode ? 'api.twonly.eu' : 'dev-api.twonly.eu';
|
events = rust_api.RustApi.events().asBroadcastStream();
|
||||||
// final String apiHost = kReleaseMode ? 'api.twonly.eu' : 'dev.twonly.eu';
|
_apiEventSubscription = events.listen(
|
||||||
final String apiSecure = kReleaseMode ? 's' : 's';
|
_handleApiEvent,
|
||||||
|
onError: (Object error, StackTrace stackTrace) {
|
||||||
String get apiEndpoint => 'http$apiSecure://$apiHost/api/';
|
Log.error(
|
||||||
|
'Rust API event stream failed',
|
||||||
final _planUpdateController = StreamController<SubscriptionPlan>.broadcast();
|
error: error,
|
||||||
Stream<SubscriptionPlan> get onPlanUpdated => _planUpdateController.stream;
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
final _connectionStateController = StreamController<bool>.broadcast();
|
},
|
||||||
Stream<bool> get onConnectionStateUpdated =>
|
);
|
||||||
_connectionStateController.stream;
|
}
|
||||||
|
late final Stream<ApiEvent> events;
|
||||||
final _appOutdatedController = StreamController<void>.broadcast();
|
|
||||||
Stream<void> get onAppOutdated => _appOutdatedController.stream;
|
|
||||||
|
|
||||||
final _newDeviceRegisteredController = StreamController<void>.broadcast();
|
|
||||||
Stream<void> get onNewDeviceRegistered =>
|
|
||||||
_newDeviceRegisteredController.stream;
|
|
||||||
|
|
||||||
bool appIsOutdated = false;
|
|
||||||
bool isAuthenticated = false;
|
|
||||||
bool isConnected = false;
|
|
||||||
|
|
||||||
// ignore: cancel_subscriptions
|
|
||||||
StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
|
StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
|
||||||
|
late final StreamSubscription<ApiEvent> _apiEventSubscription;
|
||||||
|
|
||||||
|
Future<void> _handleApiEvent(ApiEvent event) async {
|
||||||
|
if (event.kind == ApiEventKind.authenticated) {
|
||||||
|
await onAuthenticated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Function is called after the user is authenticated at the server
|
// Function is called after the user is authenticated at the server
|
||||||
Future<void> onAuthenticated() async {
|
Future<void> onAuthenticated() async {
|
||||||
await FcmNotificationService.initFCMAfterAuthenticated();
|
await FcmNotificationService.initFCMAfterAuthenticated();
|
||||||
_connectionStateController.add(true);
|
|
||||||
|
|
||||||
if (AppState.isInBackgroundTask) {
|
if (AppState.isInBackgroundTask) {
|
||||||
await RustApi.retransmitAllMessages();
|
|
||||||
await reuploadMediaFiles();
|
await reuploadMediaFiles();
|
||||||
await tryDownloadAllMediaFiles();
|
|
||||||
} else if (!AppState.isAppInBackground) {
|
} else if (!AppState.isAppInBackground) {
|
||||||
unawaited(RustApi.retransmitAllMessages());
|
|
||||||
unawaited(tryDownloadAllMediaFiles());
|
|
||||||
unawaited(reuploadMediaFiles());
|
unawaited(reuploadMediaFiles());
|
||||||
|
|
||||||
twonlyDB.markUpdated();
|
twonlyDB.markUpdated();
|
||||||
unawaited(syncFlameCounters());
|
|
||||||
unawaited(SignalIdentityService.onAuthenticated());
|
|
||||||
resetResyncedUsers();
|
resetResyncedUsers();
|
||||||
// resetUserDiscoveryRequestUpdates();
|
// resetUserDiscoveryRequestUpdates();
|
||||||
unawaited(fetchGroupStatesForUnjoinedGroups());
|
|
||||||
unawaited(fetchMissingGroupPublicKey());
|
|
||||||
unawaited(rust_api.RustApi.checkForDeletedUsernames());
|
|
||||||
unawaited(RustApi.performPasswordlessRecoveryHeartbeat());
|
|
||||||
|
|
||||||
unawaited(UserDiscoveryService.checkForNewAnnouncedUsers());
|
|
||||||
memoriesCloudService.init();
|
memoriesCloudService.init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> connect() async {
|
|
||||||
try {
|
|
||||||
await rust_api.RustApi.connect();
|
|
||||||
for (var attempt = 0; attempt < 100; attempt++) {
|
|
||||||
final state = await rust_api.RustApi.connectionState();
|
|
||||||
isConnected =
|
|
||||||
state == ApiConnectionState.connected ||
|
|
||||||
state == ApiConnectionState.authenticated;
|
|
||||||
isAuthenticated = state == ApiConnectionState.authenticated;
|
|
||||||
if (isAuthenticated ||
|
|
||||||
(!userService.isUserCreated &&
|
|
||||||
state == ApiConnectionState.connected)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (state == ApiConnectionState.permanentlyRejected ||
|
|
||||||
state == ApiConnectionState.suspended) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
await Future<void>.delayed(const Duration(milliseconds: 100));
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} catch (error) {
|
|
||||||
isConnected = false;
|
|
||||||
isAuthenticated = false;
|
|
||||||
Log.error('Rust API connection failed', error: error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> close(VoidCallback? callback) async {
|
|
||||||
await rust_api.RustApi.close();
|
|
||||||
isConnected = false;
|
|
||||||
isAuthenticated = false;
|
|
||||||
_connectionStateController.add(false);
|
|
||||||
callback?.call();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> authenticate() async {
|
|
||||||
await rust_api.RustApi.reloadConfiguration();
|
|
||||||
await connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> listenToNetworkChanges() async {
|
Future<void> listenToNetworkChanges() async {
|
||||||
if (_connectivitySubscription != null) {
|
if (_connectivitySubscription != null) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -127,10 +60,14 @@ class ApiService {
|
||||||
_connectivitySubscription = Connectivity().onConnectivityChanged.listen((
|
_connectivitySubscription = Connectivity().onConnectivityChanged.listen((
|
||||||
result,
|
result,
|
||||||
) async {
|
) async {
|
||||||
if (!result.contains(ConnectivityResult.none)) {
|
await rust_api.RustApi.setNetworkAvailable(
|
||||||
await connect();
|
available: !result.contains(ConnectivityResult.none),
|
||||||
}
|
);
|
||||||
// Received changes in available connectivity types!
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> dispose() async {
|
||||||
|
await _connectivitySubscription?.cancel();
|
||||||
|
await _apiEventSubscription.cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -779,8 +779,7 @@ Future<void> _uploadUploadRequest(MediaFileService media) async {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final apiUrl =
|
final apiUrl = '${RustApi.apiBaseUrl(protocol: 'https')}upload';
|
||||||
'http${apiService.apiSecure}://${apiService.apiHost}/api/upload';
|
|
||||||
|
|
||||||
Log.info('Starting upload from ${media.mediaFile.mediaId}');
|
Log.info('Starting upload from ${media.mediaFile.mediaId}');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,17 +127,20 @@ Future<bool> backgroundFetch({
|
||||||
|
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
|
|
||||||
// Issue: Because the background isolate can be reused across multiple periodic tasks,
|
var authenticated = false;
|
||||||
// the API connection state might be stale or disconnected from a previous run.
|
for (var attempt = 0; attempt < 100; attempt++) {
|
||||||
// Explicitly close it here to ensure a clean slate before connecting.
|
final state = await RustApi.connectionState();
|
||||||
await apiService.close(null);
|
if (state == ApiConnectionState.authenticated) {
|
||||||
|
authenticated = true;
|
||||||
if (!await apiService.connect()) {
|
break;
|
||||||
Log.info('Could not connect to the api. Returning early.');
|
}
|
||||||
return false;
|
if (state == ApiConnectionState.permanentlyRejected ||
|
||||||
|
state == ApiConnectionState.suspended) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 100));
|
||||||
}
|
}
|
||||||
|
if (!authenticated) {
|
||||||
if (!apiService.isAuthenticated) {
|
|
||||||
Log.info('Api is not authenticated. Returning early.');
|
Log.info('Api is not authenticated. Returning early.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -164,7 +167,7 @@ Future<bool> backgroundFetch({
|
||||||
await Future.delayed(const Duration(milliseconds: 2000));
|
await Future.delayed(const Duration(milliseconds: 2000));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await apiService.close(() {});
|
await RustApi.close();
|
||||||
stopwatch.stop();
|
stopwatch.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,10 @@ class BackupService {
|
||||||
static final Mutex _protected = Mutex();
|
static final Mutex _protected = Mutex();
|
||||||
|
|
||||||
static String _getIdentityBackupUrl(String backupId) =>
|
static String _getIdentityBackupUrl(String backupId) =>
|
||||||
'${apiService.apiEndpoint}/backup/identity/$backupId';
|
'${RustApi.apiBaseUrl(protocol: 'https')}backup/identity/$backupId';
|
||||||
|
|
||||||
static String _getArchiveBackupUrl(String backupDownloadToken, int? userId) =>
|
static String _getArchiveBackupUrl(String backupDownloadToken, int? userId) =>
|
||||||
'${apiService.apiEndpoint}/backup/archive/${userId == null ? '' : '${userId.toRadixString(16).padLeft(16, '0').toUpperCase()}/'}$backupDownloadToken';
|
'${RustApi.apiBaseUrl(protocol: 'https')}backup/archive/${userId == null ? '' : '${userId.toRadixString(16).padLeft(16, '0').toUpperCase()}/'}$backupDownloadToken';
|
||||||
|
|
||||||
static final _backupUpdateController = StreamController<void>.broadcast();
|
static final _backupUpdateController = StreamController<void>.broadcast();
|
||||||
static Stream<void> get onBackupUpdated => _backupUpdateController.stream;
|
static Stream<void> get onBackupUpdated => _backupUpdateController.stream;
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,8 @@ class FcmNotificationService {
|
||||||
..updateFcmToken = true
|
..updateFcmToken = true
|
||||||
..fcmToken = fcmToken;
|
..fcmToken = fcmToken;
|
||||||
});
|
});
|
||||||
if (apiService.isAuthenticated) {
|
if (await RustApi.connectionState() ==
|
||||||
|
ApiConnectionState.authenticated) {
|
||||||
if (await _uploadFcmToken(fcmToken)) {
|
if (await _uploadFcmToken(fcmToken)) {
|
||||||
await UserService.update((u) {
|
await UserService.update((u) {
|
||||||
u.updateFcmToken = false;
|
u.updateFcmToken = false;
|
||||||
|
|
@ -114,7 +115,8 @@ class FcmNotificationService {
|
||||||
..updateFcmToken = true
|
..updateFcmToken = true
|
||||||
..fcmToken = fcmToken;
|
..fcmToken = fcmToken;
|
||||||
});
|
});
|
||||||
if (apiService.isAuthenticated) {
|
if (await RustApi.connectionState() ==
|
||||||
|
ApiConnectionState.authenticated) {
|
||||||
if (await _uploadFcmToken(fcmToken)) {
|
if (await _uploadFcmToken(fcmToken)) {
|
||||||
await UserService.update((u) {
|
await UserService.update((u) {
|
||||||
u.updateFcmToken = false;
|
u.updateFcmToken = false;
|
||||||
|
|
@ -161,7 +163,10 @@ class FcmNotificationService {
|
||||||
// This is just a workarround until the new Rust decryption is enrolled fully.
|
// This is just a workarround until the new Rust decryption is enrolled fully.
|
||||||
final pushDataString = message.data['push_data'] as String?;
|
final pushDataString = message.data['push_data'] as String?;
|
||||||
if (pushDataString != null) {
|
if (pushDataString != null) {
|
||||||
if (apiService.isConnected) {
|
final apiState = await RustApi.connectionState();
|
||||||
|
if (apiState == ApiConnectionState.connected ||
|
||||||
|
apiState == ApiConnectionState.authenticating ||
|
||||||
|
apiState == ApiConnectionState.authenticated) {
|
||||||
Log.info('Got FCM message, but API is connected...');
|
Log.info('Got FCM message, but API is connected...');
|
||||||
} else {
|
} else {
|
||||||
Log.info('Trying to connect to the API in the background.');
|
Log.info('Trying to connect to the API in the background.');
|
||||||
|
|
|
||||||
|
|
@ -1,99 +1,13 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:clock/clock.dart';
|
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
import 'package:twonly/core/bridge/wrapper/key_manager.dart';
|
import 'package:twonly/core/bridge/wrapper/key_manager.dart';
|
||||||
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
|
||||||
import 'package:twonly/locator.dart';
|
|
||||||
import 'package:twonly/src/database/signal/signal_signed_pre_key_store.dart';
|
import 'package:twonly/src/database/signal/signal_signed_pre_key_store.dart';
|
||||||
import 'package:twonly/src/model/json/signal_identity.model.dart';
|
import 'package:twonly/src/model/json/signal_identity.model.dart';
|
||||||
import 'package:twonly/src/services/signal/consts.signal.dart';
|
import 'package:twonly/src/services/signal/consts.signal.dart';
|
||||||
import 'package:twonly/src/services/signal/protocol_state.signal.dart';
|
|
||||||
import 'package:twonly/src/services/signal/utils.signal.dart';
|
import 'package:twonly/src/services/signal/utils.signal.dart';
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
class SignalIdentityService {
|
|
||||||
static Future<void> onAuthenticated() async {
|
|
||||||
final now = clock.now();
|
|
||||||
final fortyEightHoursAgo = now.subtract(const Duration(hours: 48));
|
|
||||||
final oneWeekAgo = now.subtract(const Duration(days: 7));
|
|
||||||
|
|
||||||
if (userService.currentUser.signalLastSignedPreKeyUpdated == null ||
|
|
||||||
!userService.currentUser.signalLastSignedPreKeyUpdated!.isAfter(
|
|
||||||
fortyEightHoursAgo,
|
|
||||||
)) {
|
|
||||||
final signedPreKey = await _getNewSignalSignedPreKey();
|
|
||||||
if (signedPreKey == null) {
|
|
||||||
Log.error('could not generate a new signed pre key!');
|
|
||||||
} else {
|
|
||||||
await UserService.update((user) {
|
|
||||||
user.signalLastSignedPreKeyUpdated = now;
|
|
||||||
});
|
|
||||||
final res = await rustApiResult(
|
|
||||||
RustApi.updateSignedPreKey(
|
|
||||||
id: signedPreKey.id,
|
|
||||||
key: signedPreKey.getKeyPair().publicKey.serialize(),
|
|
||||||
signature: signedPreKey.signature,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (res.isError) {
|
|
||||||
Log.error('could not update the signed pre key: ${res.error}');
|
|
||||||
await UserService.update((user) {
|
|
||||||
user.signalLastSignedPreKeyUpdated = null;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Log.info('updated signed pre key');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userService.currentUser.signalLastPqcPreKeysUploaded == null ||
|
|
||||||
!userService.currentUser.signalLastPqcPreKeysUploaded!.isAfter(
|
|
||||||
oneWeekAgo,
|
|
||||||
)) {
|
|
||||||
final bundle = await RustSignal.generateBundle();
|
|
||||||
|
|
||||||
final pqcRes = await rustApiResult(
|
|
||||||
RustApi.uploadPqcPreKeys(
|
|
||||||
eccSignedPrekeyId: bundle.signedPreKeyId,
|
|
||||||
eccSignedPrekey: bundle.signedPreKeyPublic,
|
|
||||||
eccSignedPrekeySignature: bundle.signedPreKeySignature,
|
|
||||||
kyberSignedPrekeyId: bundle.kyberPreKeyId,
|
|
||||||
kyberSignedPrekey: bundle.kyberPreKeyPublic,
|
|
||||||
kyberSignedPrekeySignature: bundle.kyberPreKeySignature,
|
|
||||||
prekeys: const [],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (pqcRes.isError) {
|
|
||||||
Log.warn('could not update the pqc signed pre key: ${pqcRes.error}');
|
|
||||||
} else {
|
|
||||||
Log.info('updated pqc signed pre key');
|
|
||||||
await UserService.update((user) {
|
|
||||||
user.signalLastPqcPreKeysUploaded = now;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<PreKeyRecord>> signalGetPreKeys() async {
|
|
||||||
return lockingSignalProtocol.protect(() async {
|
|
||||||
final start = userService.currentUser.currentPreKeyIndexStart;
|
|
||||||
await UserService.update((u) {
|
|
||||||
u.currentPreKeyIndexStart = (u.currentPreKeyIndexStart + 200) % maxValue;
|
|
||||||
});
|
|
||||||
final preKeys = generatePreKeys(start, 200);
|
|
||||||
final signalStore = await getSignalStore();
|
|
||||||
if (signalStore == null) return [];
|
|
||||||
for (final p in preKeys) {
|
|
||||||
await signalStore.preKeyStore.storePreKey(p.id, p);
|
|
||||||
}
|
|
||||||
return preKeys;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SignalIdentity?> getSignalIdentity() async {
|
Future<SignalIdentity?> getSignalIdentity() async {
|
||||||
try {
|
try {
|
||||||
final identity = await RustKeyManager.getSignalIdentity();
|
final identity = await RustKeyManager.getSignalIdentity();
|
||||||
|
|
@ -156,30 +70,3 @@ Future<void> createIfNotExistsSignalIdentity() async {
|
||||||
signedPreKeyStore: const {},
|
signedPreKeyStore: const {},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SignedPreKeyRecord?> _getNewSignalSignedPreKey() async {
|
|
||||||
return lockingSignalProtocol.protect(() async {
|
|
||||||
var identityKeyPair = await getSignalIdentityKeyPair();
|
|
||||||
final signalStore = await getSignalStore();
|
|
||||||
if (identityKeyPair == null || signalStore == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final signedPreKeyId =
|
|
||||||
userService.currentUser.currentSignedPreKeyIndexStart;
|
|
||||||
await UserService.update((user) {
|
|
||||||
user.currentSignedPreKeyIndexStart += 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
final signedPreKey = generateSignedPreKey(
|
|
||||||
identityKeyPair,
|
|
||||||
signedPreKeyId,
|
|
||||||
);
|
|
||||||
|
|
||||||
identityKeyPair = null;
|
|
||||||
|
|
||||||
await signalStore.storeSignedPreKey(signedPreKeyId, signedPreKey);
|
|
||||||
|
|
||||||
return signedPreKey;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -19,33 +19,25 @@ class _AppOutdatedCompState extends State<AppOutdatedComp> {
|
||||||
bool appIsOutdated = false;
|
bool appIsOutdated = false;
|
||||||
bool newDeviceRegistered = false;
|
bool newDeviceRegistered = false;
|
||||||
|
|
||||||
late StreamSubscription<void> _subOutdated;
|
late StreamSubscription<ApiEvent> _apiEventSubscription;
|
||||||
late StreamSubscription<void> _subNewDevice;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_subOutdated.cancel();
|
_apiEventSubscription.cancel();
|
||||||
_subNewDevice.cancel();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_subOutdated = apiService.onAppOutdated.listen((_) async {
|
_apiEventSubscription = apiService.events.listen((event) async {
|
||||||
if (mounted) {
|
if (!mounted) return;
|
||||||
|
if (event.kind == ApiEventKind.appOutdated ||
|
||||||
|
event.kind == ApiEventKind.newDeviceRegistered) {
|
||||||
await context.read<CustomChangeProvider>().updateConnectionState(false);
|
await context.read<CustomChangeProvider>().updateConnectionState(false);
|
||||||
setState(() {
|
setState(() {
|
||||||
appIsOutdated = true;
|
appIsOutdated = event.kind == ApiEventKind.appOutdated;
|
||||||
});
|
newDeviceRegistered = event.kind == ApiEventKind.newDeviceRegistered;
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_subNewDevice = apiService.onNewDeviceRegistered.listen((_) async {
|
|
||||||
if (mounted) {
|
|
||||||
await context.read<CustomChangeProvider>().updateConnectionState(false);
|
|
||||||
setState(() {
|
|
||||||
newDeviceRegistered = true;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -361,8 +361,8 @@ class _ChatListViewState extends State<ChatListView>
|
||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await apiService.close(() {});
|
await RustApi.close();
|
||||||
await apiService.connect();
|
await RustApi.connect();
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ class _RegisterViewState extends State<RegisterView> {
|
||||||
|
|
||||||
unawaited(FcmNotificationService.initAfterUserLoaded());
|
unawaited(FcmNotificationService.initAfterUserLoaded());
|
||||||
|
|
||||||
await apiService.authenticate();
|
await RustApi.reloadConfiguration();
|
||||||
widget.callbackOnSuccess();
|
widget.callbackOnSuccess();
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
Log.error('Error creating new user', error: e, stackTrace: stack);
|
Log.error('Error creating new user', error: e, stackTrace: stack);
|
||||||
|
|
|
||||||
|
|
@ -68,8 +68,7 @@ class _ContactUsState extends State<ContactUsView> {
|
||||||
|
|
||||||
final uploadRequestBytes = uploadRequest.writeToBuffer();
|
final uploadRequestBytes = uploadRequest.writeToBuffer();
|
||||||
|
|
||||||
final apiUrl =
|
final apiUrl = '${RustApi.apiBaseUrl(protocol: 'https')}upload';
|
||||||
'http${apiService.apiSecure}://${apiService.apiHost}/api/upload';
|
|
||||||
|
|
||||||
final requestMultipart = http.MultipartRequest('POST', Uri.parse(apiUrl));
|
final requestMultipart = http.MultipartRequest('POST', Uri.parse(apiUrl));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,6 @@ pub(crate) async fn handle_server_message(
|
||||||
kind: server_to_client::v0::Kind,
|
kind: server_to_client::v0::Kind,
|
||||||
) -> Result<client_to_server::Response> {
|
) -> Result<client_to_server::Response> {
|
||||||
let ok = match kind {
|
let ok = match kind {
|
||||||
// These booleans are presence-only request markers. Their value is not part of
|
|
||||||
// the protocol; the Dart client likewise checks hasRequestNewPreKeys() only.
|
|
||||||
Kind::RequestNewPreKeys(_) => match handle_request_new_prekeys(ctx).await {
|
|
||||||
Ok(response) => response,
|
|
||||||
Err(error) => {
|
|
||||||
tracing::error!("failed to generate requested prekeys: {error}");
|
|
||||||
ok::Ok::None(true)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Kind::RequestNewPqcPreKeys(_) => match handle_request_new_pqc_prekeys(ctx).await {
|
Kind::RequestNewPqcPreKeys(_) => match handle_request_new_pqc_prekeys(ctx).await {
|
||||||
Ok(response) => response,
|
Ok(response) => response,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
|
@ -107,40 +98,6 @@ pub(crate) async fn handle_sealed_message(ctx: &Arc<Context>, bytes: Vec<u8>) ->
|
||||||
handle_decoded_server_message(ctx, payload.from_user_id, message).await
|
handle_decoded_server_message(ctx, payload.from_user_id, message).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn handle_request_new_prekeys(
|
|
||||||
ctx: &Arc<Context>,
|
|
||||||
) -> Result<client_to_server::response::ok::Ok> {
|
|
||||||
let prekeys = match get_callbacks() {
|
|
||||||
Ok(callbacks) => (callbacks.legacy_signal.generate_prekeys)()
|
|
||||||
.await
|
|
||||||
.into_iter()
|
|
||||||
.map(|key| client_to_server::response::PreKey {
|
|
||||||
id: key.id,
|
|
||||||
prekey: key.public_key,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
Err(TwonlyError::MissingCallbackInitialization) => {
|
|
||||||
let engine = ctx.get_signal_engine().lock().await;
|
|
||||||
engine
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(TwonlyError::SignalIdentityNotFound)?
|
|
||||||
.generate_prekeys(200)
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.map(|(id, public_key)| client_to_server::response::PreKey {
|
|
||||||
id: i64::from(id),
|
|
||||||
prekey: public_key,
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
Err(error) => return Err(error),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(client_to_server::response::ok::Ok::Prekeys(
|
|
||||||
client_to_server::response::Prekeys { prekeys },
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn handle_request_new_pqc_prekeys(
|
pub(crate) async fn handle_request_new_pqc_prekeys(
|
||||||
ctx: &Arc<Context>,
|
ctx: &Arc<Context>,
|
||||||
) -> Result<client_to_server::response::ok::Ok> {
|
) -> Result<client_to_server::response::ok::Ok> {
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ impl ApiRuntime {
|
||||||
.ok_or(TwonlyError::Initialization)?;
|
.ok_or(TwonlyError::Initialization)?;
|
||||||
*slot.write().await = replacement;
|
*slot.write().await = replacement;
|
||||||
current.close().await;
|
current.close().await;
|
||||||
Ok(())
|
Self::client(ctx).await?.connect().await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn connect(ctx: &Arc<Context>) -> Result<()> {
|
pub async fn connect(ctx: &Arc<Context>) -> Result<()> {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ pub(super) type PendingRequests = Arc<Mutex<HashMap<u64, oneshot::Sender<Vec<u8>
|
||||||
|
|
||||||
pub(crate) static API_EVENTS: LazyLock<broadcast::Sender<ApiEvent>> =
|
pub(crate) static API_EVENTS: LazyLock<broadcast::Sender<ApiEvent>> =
|
||||||
LazyLock::new(|| broadcast::channel(256).0);
|
LazyLock::new(|| broadcast::channel(256).0);
|
||||||
|
pub(crate) static API_PERMANENTLY_REJECTED: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
pub(crate) struct ApiClient {
|
pub(crate) struct ApiClient {
|
||||||
pub(crate) context: Weak<Context>,
|
pub(crate) context: Weak<Context>,
|
||||||
|
|
@ -65,6 +66,13 @@ impl ApiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn connect(self: &Arc<Self>) -> Result<()> {
|
pub async fn connect(self: &Arc<Self>) -> Result<()> {
|
||||||
|
if API_PERMANENTLY_REJECTED.load(Ordering::Acquire) {
|
||||||
|
self.set_state(ApiConnectionState::PermanentlyRejected)
|
||||||
|
.await;
|
||||||
|
return Err(crate::error::TwonlyError::Generic(
|
||||||
|
"API connection was permanently rejected for this process".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
self.deliberately_closed.store(false, Ordering::Release);
|
self.deliberately_closed.store(false, Ordering::Release);
|
||||||
|
|
||||||
let mut client_guard = self.ws_client.lock().await;
|
let mut client_guard = self.ws_client.lock().await;
|
||||||
|
|
@ -139,7 +147,11 @@ impl ApiClient {
|
||||||
}
|
}
|
||||||
Ok(ConnectionEvent::Disconnected { .. }) => {
|
Ok(ConnectionEvent::Disconnected { .. }) => {
|
||||||
self_clone.is_authenticated.store(false, Ordering::Release);
|
self_clone.is_authenticated.store(false, Ordering::Release);
|
||||||
self_clone.set_state(ApiConnectionState::Stopped).await;
|
if API_PERMANENTLY_REJECTED.load(Ordering::Acquire) {
|
||||||
|
self_clone.set_state(ApiConnectionState::PermanentlyRejected).await;
|
||||||
|
} else {
|
||||||
|
self_clone.set_state(ApiConnectionState::Stopped).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(ConnectionEvent::Connecting { .. }) => {
|
Ok(ConnectionEvent::Connecting { .. }) => {
|
||||||
self_clone.set_state(ApiConnectionState::Connecting).await;
|
self_clone.set_state(ApiConnectionState::Connecting).await;
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,16 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::api::messages::incoming::client2client::messages;
|
use crate::api::messages::incoming::client2client::{messages, recovery};
|
||||||
use crate::api::proto::server_to_client;
|
use crate::api::proto::server_to_client;
|
||||||
use crate::api::runtime::ApiRuntime;
|
use crate::api::runtime::ApiRuntime;
|
||||||
|
use crate::api::Server;
|
||||||
|
|
||||||
use crate::bridge::api::ServerResult;
|
use crate::bridge::api::ServerResult;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::error::{Result, TwonlyError};
|
use crate::error::{Result, TwonlyError};
|
||||||
|
use crate::services::groups::GroupService;
|
||||||
|
use crate::services::mediafiles::MediaFileService;
|
||||||
use prost::Message as ProstMessage;
|
use prost::Message as ProstMessage;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
@ -37,9 +40,6 @@ pub(crate) fn response_error_code(bytes: &[u8]) -> Result<Option<i32>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn schedule_post_authentication(ctx: &Arc<Context>, in_background: bool) {
|
pub(crate) fn schedule_post_authentication(ctx: &Arc<Context>, in_background: bool) {
|
||||||
if in_background {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let ctx = ctx.clone();
|
let ctx = ctx.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
// Wait a bit to let other initial state settle
|
// Wait a bit to let other initial state settle
|
||||||
|
|
@ -50,12 +50,50 @@ pub(crate) fn schedule_post_authentication(ctx: &Arc<Context>, in_background: bo
|
||||||
if let Err(error) = replay.await {
|
if let Err(error) = replay.await {
|
||||||
tracing::warn!("failed to replay API outbox: {error}");
|
tracing::warn!("failed to replay API outbox: {error}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(error) = ApiRuntime::replay_legacy_raw_outbox(&ctx).await {
|
if let Err(error) = ApiRuntime::replay_legacy_raw_outbox(&ctx).await {
|
||||||
tracing::warn!("failed to replay legacy raw-byte outbox: {error}");
|
tracing::warn!("failed to replay legacy raw-byte outbox: {error}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(error) = messages::retransmit_queued_receipts(&ctx).await {
|
if let Err(error) = messages::retransmit_queued_receipts(&ctx).await {
|
||||||
tracing::warn!("failed to retransmit queued receipts: {error}");
|
tracing::warn!("failed to retransmit queued receipts: {error}");
|
||||||
}
|
}
|
||||||
|
if let Err(error) = MediaFileService::new(&ctx).download_pending().await {
|
||||||
|
tracing::warn!("failed to download pending media: {error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if in_background {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(error) = GroupService::new(&ctx).on_connected().await {
|
||||||
|
tracing::warn!("group post-connection maintenance failed: {error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(error) = Server::check_for_deleted_usernames(&ctx).await {
|
||||||
|
tracing::warn!("deleted-username refresh failed: {error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(error) = recovery::perform_heartbeat(&ctx).await {
|
||||||
|
tracing::warn!("passwordless recovery heartbeat failed: {error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(error) = ctx
|
||||||
|
.get_user_discovery()
|
||||||
|
.get()
|
||||||
|
.await
|
||||||
|
.on_connected(&ctx)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::warn!("user-discovery post-connection refresh failed: {error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let signal_engine = ctx.get_signal_engine().lock().await;
|
||||||
|
if let Some(engine) = signal_engine.as_ref() {
|
||||||
|
if let Err(error) = engine.on_connected(&ctx).await {
|
||||||
|
tracing::warn!("Signal key maintenance failed: {error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::client::ApiClient;
|
use super::client::{ApiClient, API_PERMANENTLY_REJECTED};
|
||||||
use crate::api::messages::incoming::handle_server_message;
|
use crate::api::messages::incoming::handle_server_message;
|
||||||
use crate::api::proto::{client_to_server, server_to_client};
|
use crate::api::proto::{client_to_server, server_to_client};
|
||||||
use crate::bridge::api::{ApiConnectionState, ApiEvent, ApiEventKind};
|
use crate::bridge::api::{ApiConnectionState, ApiEvent, ApiEventKind};
|
||||||
|
|
@ -203,6 +203,7 @@ impl ApiClient {
|
||||||
{
|
{
|
||||||
self.set_state(ApiConnectionState::PermanentlyRejected)
|
self.set_state(ApiConnectionState::PermanentlyRejected)
|
||||||
.await;
|
.await;
|
||||||
|
API_PERMANENTLY_REJECTED.store(true, Ordering::Release);
|
||||||
self.deliberately_closed.store(true, Ordering::Release);
|
self.deliberately_closed.store(true, Ordering::Release);
|
||||||
let kind = if code == ErrorCode::AppVersionOutdated as i32 {
|
let kind = if code == ErrorCode::AppVersionOutdated as i32 {
|
||||||
ApiEventKind::AppOutdated
|
ApiEventKind::AppOutdated
|
||||||
|
|
@ -214,8 +215,10 @@ impl ApiClient {
|
||||||
state: Some(ApiConnectionState::PermanentlyRejected),
|
state: Some(ApiConnectionState::PermanentlyRejected),
|
||||||
message: None,
|
message: None,
|
||||||
});
|
});
|
||||||
if let Some(_) = self.ws_client.lock().await.take() {
|
if let Some(client) = self.ws_client.lock().await.take() {
|
||||||
// Drop client to disconnect
|
if let Err(error) = client.shutdown_graceful(Duration::from_secs(5)).await {
|
||||||
|
tracing::warn!(%error, "permanently rejected WebSocket did not shut down cleanly");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if code == ErrorCode::UserIdNotFound as i32 {
|
if code == ErrorCode::UserIdNotFound as i32 {
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,6 @@ pub struct LegacySignalEncryptResult {
|
||||||
pub message_type: i32,
|
pub message_type: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct LegacySignalPreKey {
|
|
||||||
pub id: i64,
|
|
||||||
pub public_key: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will also generate the function init_flutter_callbacks which MUST be called from Flutter to initialize the callbacks
|
// This will also generate the function init_flutter_callbacks which MUST be called from Flutter to initialize the callbacks
|
||||||
callback_generator! {
|
callback_generator! {
|
||||||
FlutterCallbacks {
|
FlutterCallbacks {
|
||||||
|
|
@ -49,8 +43,7 @@ callback_generator! {
|
||||||
},
|
},
|
||||||
LegacySignal legacy_signal {
|
LegacySignal legacy_signal {
|
||||||
decrypt: (i64, Vec<u8>, i32) => LegacySignalDecryptResult,
|
decrypt: (i64, Vec<u8>, i32) => LegacySignalDecryptResult,
|
||||||
encrypt: (i64, Vec<u8>) => Option<LegacySignalEncryptResult>,
|
encrypt: (i64, Vec<u8>) => Option<LegacySignalEncryptResult>
|
||||||
generate_prekeys: () => Vec<LegacySignalPreKey>
|
|
||||||
},
|
},
|
||||||
Api api {
|
Api api {
|
||||||
resync_signal_session: (i64) => (),
|
resync_signal_session: (i64) => (),
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,32 @@ const MAX_FUTURE_TIMESTAMP_SKEW_SECONDS: i64 = 10 * 60;
|
||||||
pub struct Group;
|
pub struct Group;
|
||||||
|
|
||||||
impl Group {
|
impl Group {
|
||||||
|
pub async fn flame_sync_candidates(pool: &sqlx::Pool<Sqlite>) -> Result<Vec<FlameSyncGroup>> {
|
||||||
|
Ok(sqlx::query_as!(
|
||||||
|
FlameSyncGroup,
|
||||||
|
r#"SELECT group_id, total_media_counter, last_flame_counter_change,
|
||||||
|
last_flame_sync, flame_counter
|
||||||
|
FROM groups WHERE last_flame_counter_change IS NOT NULL"#
|
||||||
|
)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_last_flame_sync(
|
||||||
|
pool: &sqlx::Pool<Sqlite>,
|
||||||
|
group_id: &str,
|
||||||
|
timestamp: i64,
|
||||||
|
) -> Result<()> {
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE groups SET last_flame_sync = ? WHERE group_id = ?",
|
||||||
|
timestamp,
|
||||||
|
group_id,
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn ensure_exists(tr: &mut Transaction<'_, Sqlite>, group_id: &str) -> Result<()> {
|
pub async fn ensure_exists(tr: &mut Transaction<'_, Sqlite>, group_id: &str) -> Result<()> {
|
||||||
let exists = sqlx::query_scalar!(
|
let exists = sqlx::query_scalar!(
|
||||||
"SELECT EXISTS(SELECT 1 FROM groups WHERE group_id = ?)",
|
"SELECT EXISTS(SELECT 1 FROM groups WHERE group_id = ?)",
|
||||||
|
|
@ -149,6 +175,14 @@ impl Group {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct FlameSyncGroup {
|
||||||
|
pub group_id: String,
|
||||||
|
pub total_media_counter: i64,
|
||||||
|
pub last_flame_counter_change: Option<i64>,
|
||||||
|
pub last_flame_sync: Option<i64>,
|
||||||
|
pub flame_counter: i64,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(bon::Builder)]
|
#[derive(bon::Builder)]
|
||||||
pub struct InsertGroup {
|
pub struct InsertGroup {
|
||||||
group_id: String,
|
group_id: String,
|
||||||
|
|
@ -462,8 +496,5 @@ impl GetMissingGroupPublicKeys {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_unix_timestamp() -> Result<i64> {
|
fn current_unix_timestamp() -> Result<i64> {
|
||||||
Ok(std::time::SystemTime::now()
|
Ok(crate::utils::current_time().timestamp())
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
|
||||||
.map_err(|error| TwonlyError::Generic(error.to_string()))?
|
|
||||||
.as_secs() as i64)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -334,7 +334,6 @@ fn wire__crate__bridge__callbacks__init_flutter_callbacks_impl(
|
||||||
let api_logging_get_stream_sink = decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
let api_logging_get_stream_sink = decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
||||||
let api_legacy_signal_decrypt = decode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
let api_legacy_signal_decrypt = decode_DartFn_Inputs_i_64_list_prim_u_8_strict_i_32_Output_legacy_signal_decrypt_result_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
||||||
let api_legacy_signal_encrypt = decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
let api_legacy_signal_encrypt = decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_opt_box_autoadd_legacy_signal_encrypt_result_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
||||||
let api_legacy_signal_generate_prekeys = decode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
|
||||||
let api_api_resync_signal_session = decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
let api_api_resync_signal_session = decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
||||||
let api_api_media_action = decode_DartFn_Inputs_String_String_i_64_String_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
let api_api_media_action = decode_DartFn_Inputs_String_String_i_64_String_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
||||||
let api_api_verification_proof = decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
let api_api_verification_proof = decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
||||||
|
|
@ -342,7 +341,7 @@ let api_api_create_push_avatars = decode_DartFn_Inputs_i_64_Output_unit_AnyhowEx
|
||||||
let api_api_media_received = decode_DartFn_Inputs_String_i_64_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
let api_api_media_received = decode_DartFn_Inputs_String_i_64_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));
|
||||||
let api_api_user_config_changed = decode_DartFn_Inputs_user_config_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));deserializer.end(); move |context| {
|
let api_api_user_config_changed = decode_DartFn_Inputs_user_config_Output_unit_AnyhowException(<flutter_rust_bridge::DartOpaque>::sse_decode(&mut deserializer));deserializer.end(); move |context| {
|
||||||
transform_result_sse::<_, ()>((move || {
|
transform_result_sse::<_, ()>((move || {
|
||||||
let output_ok = Result::<_,()>::Ok({ crate::bridge::callbacks::init_flutter_callbacks(api_callback_id, api_logging_get_stream_sink, api_legacy_signal_decrypt, api_legacy_signal_encrypt, api_legacy_signal_generate_prekeys, api_api_resync_signal_session, api_api_media_action, api_api_verification_proof, api_api_create_push_avatars, api_api_media_received, api_api_user_config_changed); })?; Ok(output_ok)
|
let output_ok = Result::<_,()>::Ok({ crate::bridge::callbacks::init_flutter_callbacks(api_callback_id, api_logging_get_stream_sink, api_legacy_signal_decrypt, api_legacy_signal_encrypt, api_api_resync_signal_session, api_api_media_action, api_api_verification_proof, api_api_create_push_avatars, api_api_media_received, api_api_user_config_changed); })?; Ok(output_ok)
|
||||||
})())
|
})())
|
||||||
} })
|
} })
|
||||||
}
|
}
|
||||||
|
|
@ -4202,40 +4201,6 @@ fn decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(
|
||||||
flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body(dart_opaque.clone()))
|
flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body(dart_opaque.clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn decode_DartFn_Inputs__Output_list_legacy_signal_pre_key_AnyhowException(
|
|
||||||
dart_opaque: flutter_rust_bridge::DartOpaque,
|
|
||||||
) -> impl Fn() -> flutter_rust_bridge::DartFnFuture<Vec<crate::bridge::callbacks::LegacySignalPreKey>>
|
|
||||||
{
|
|
||||||
use flutter_rust_bridge::IntoDart;
|
|
||||||
|
|
||||||
async fn body(
|
|
||||||
dart_opaque: flutter_rust_bridge::DartOpaque,
|
|
||||||
) -> Vec<crate::bridge::callbacks::LegacySignalPreKey> {
|
|
||||||
let args = vec![];
|
|
||||||
let message = FLUTTER_RUST_BRIDGE_HANDLER
|
|
||||||
.dart_fn_invoke(dart_opaque, args)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
|
||||||
let action = deserializer.cursor.read_u8().unwrap();
|
|
||||||
let ans = match action {
|
|
||||||
0 => std::result::Result::Ok(
|
|
||||||
<Vec<crate::bridge::callbacks::LegacySignalPreKey>>::sse_decode(&mut deserializer),
|
|
||||||
),
|
|
||||||
1 => std::result::Result::Err(
|
|
||||||
<flutter_rust_bridge::for_generated::anyhow::Error>::sse_decode(&mut deserializer),
|
|
||||||
),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
deserializer.end();
|
|
||||||
let ans = ans.expect("Dart throws exception but Rust side assume it is not failable");
|
|
||||||
ans
|
|
||||||
}
|
|
||||||
|
|
||||||
move || {
|
|
||||||
flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body(dart_opaque.clone()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(
|
fn decode_DartFn_Inputs_i_64_Output_unit_AnyhowException(
|
||||||
dart_opaque: flutter_rust_bridge::DartOpaque,
|
dart_opaque: flutter_rust_bridge::DartOpaque,
|
||||||
) -> impl Fn(i64) -> flutter_rust_bridge::DartFnFuture<()> {
|
) -> impl Fn(i64) -> flutter_rust_bridge::DartFnFuture<()> {
|
||||||
|
|
@ -4728,18 +4693,6 @@ impl SseDecode for crate::bridge::callbacks::LegacySignalEncryptResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseDecode for crate::bridge::callbacks::LegacySignalPreKey {
|
|
||||||
// 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_id = <i64>::sse_decode(deserializer);
|
|
||||||
let mut var_publicKey = <Vec<u8>>::sse_decode(deserializer);
|
|
||||||
return crate::bridge::callbacks::LegacySignalPreKey {
|
|
||||||
id: var_id,
|
|
||||||
public_key: var_publicKey,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseDecode for crate::bridge::wrapper::app_database::LegacyTableMigrationCount {
|
impl SseDecode for crate::bridge::wrapper::app_database::LegacyTableMigrationCount {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
|
@ -4778,20 +4731,6 @@ impl SseDecode for Vec<crate::signal::engine::FrbPqcPreKey> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseDecode for Vec<crate::bridge::callbacks::LegacySignalPreKey> {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
|
||||||
let mut len_ = <i32>::sse_decode(deserializer);
|
|
||||||
let mut ans_ = Vec::with_capacity(len_ as usize);
|
|
||||||
for idx_ in 0..len_ {
|
|
||||||
ans_.push(<crate::bridge::callbacks::LegacySignalPreKey>::sse_decode(
|
|
||||||
deserializer,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return ans_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseDecode for Vec<crate::bridge::wrapper::app_database::LegacyTableMigrationCount> {
|
impl SseDecode for Vec<crate::bridge::wrapper::app_database::LegacyTableMigrationCount> {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||||
|
|
@ -5913,27 +5852,6 @@ impl flutter_rust_bridge::IntoIntoDart<crate::bridge::callbacks::LegacySignalEnc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||||
impl flutter_rust_bridge::IntoDart for crate::bridge::callbacks::LegacySignalPreKey {
|
|
||||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
|
||||||
[
|
|
||||||
self.id.into_into_dart().into_dart(),
|
|
||||||
self.public_key.into_into_dart().into_dart(),
|
|
||||||
]
|
|
||||||
.into_dart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive
|
|
||||||
for crate::bridge::callbacks::LegacySignalPreKey
|
|
||||||
{
|
|
||||||
}
|
|
||||||
impl flutter_rust_bridge::IntoIntoDart<crate::bridge::callbacks::LegacySignalPreKey>
|
|
||||||
for crate::bridge::callbacks::LegacySignalPreKey
|
|
||||||
{
|
|
||||||
fn into_into_dart(self) -> crate::bridge::callbacks::LegacySignalPreKey {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
|
||||||
impl flutter_rust_bridge::IntoDart
|
impl flutter_rust_bridge::IntoDart
|
||||||
for crate::bridge::wrapper::app_database::LegacyTableMigrationCount
|
for crate::bridge::wrapper::app_database::LegacyTableMigrationCount
|
||||||
{
|
{
|
||||||
|
|
@ -6651,14 +6569,6 @@ impl SseEncode for crate::bridge::callbacks::LegacySignalEncryptResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseEncode for crate::bridge::callbacks::LegacySignalPreKey {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
|
||||||
<i64>::sse_encode(self.id, serializer);
|
|
||||||
<Vec<u8>>::sse_encode(self.public_key, serializer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseEncode for crate::bridge::wrapper::app_database::LegacyTableMigrationCount {
|
impl SseEncode for crate::bridge::wrapper::app_database::LegacyTableMigrationCount {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
|
@ -6687,16 +6597,6 @@ impl SseEncode for Vec<crate::signal::engine::FrbPqcPreKey> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SseEncode for Vec<crate::bridge::callbacks::LegacySignalPreKey> {
|
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
|
||||||
<i32>::sse_encode(self.len() as _, serializer);
|
|
||||||
for item in self {
|
|
||||||
<crate::bridge::callbacks::LegacySignalPreKey>::sse_encode(item, serializer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SseEncode for Vec<crate::bridge::wrapper::app_database::LegacyTableMigrationCount> {
|
impl SseEncode for Vec<crate::bridge::wrapper::app_database::LegacyTableMigrationCount> {
|
||||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ use crate::api::server::Server;
|
||||||
use crate::bridge::api::ServerResult;
|
use crate::bridge::api::ServerResult;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::database::app::tables::{
|
use crate::database::app::tables::{
|
||||||
Contact, GetGroupPublicKey, GetMissingGroupPublicKeys, GetUnjoinedGroups, InsertGroup,
|
Contact, GetGroupPublicKey, GetMissingGroupPublicKeys, GetUnjoinedGroups, Group, InsertGroup,
|
||||||
InsertGroupHistory, InsertGroupMember, UpdateContact, UpdateGroup, UpdateGroupMemberState,
|
InsertGroupHistory, InsertGroupMember, UpdateContact, UpdateGroup, UpdateGroupMemberState,
|
||||||
};
|
};
|
||||||
use crate::error::{Result, TwonlyError};
|
use crate::error::{Result, TwonlyError};
|
||||||
|
|
@ -38,6 +38,59 @@ pub struct GroupService {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GroupService {
|
impl GroupService {
|
||||||
|
pub async fn on_connected(&self) -> Result<()> {
|
||||||
|
self.fetch_group_states_for_unjoined_groups().await?;
|
||||||
|
self.fetch_missing_group_public_keys().await?;
|
||||||
|
self.sync_flame_counters().await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sync_flame_counters(&self) -> Result<()> {
|
||||||
|
let db = self.ctx.get_app_database().await;
|
||||||
|
let groups = Group::flame_sync_candidates(&db.pool).await?;
|
||||||
|
|
||||||
|
let Some(best_friend) = groups.iter().max_by_key(|group| group.total_media_counter) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let best_friend_id = best_friend.group_id.clone();
|
||||||
|
let now = current_time().timestamp();
|
||||||
|
let start_today = now - now.rem_euclid(86_400);
|
||||||
|
for group in groups {
|
||||||
|
let Some(changed) = group.last_flame_counter_change else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if changed < start_today
|
||||||
|
|| group
|
||||||
|
.last_flame_sync
|
||||||
|
.is_some_and(|sync| sync >= start_today)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if group.flame_counter <= 2 && group.group_id != best_friend_id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
MessageService::new(&self.ctx)
|
||||||
|
.send_to_group(
|
||||||
|
group.group_id.clone(),
|
||||||
|
EncryptedContent {
|
||||||
|
flame_sync: Some(encrypted_content::FlameSync {
|
||||||
|
flame_counter: group.flame_counter,
|
||||||
|
last_flame_counter_change: changed * 1000,
|
||||||
|
best_friend: group.group_id == best_friend_id,
|
||||||
|
force_update: false,
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.encode_to_vec(),
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Group::set_last_flame_sync(&db.pool, &group.group_id, now).await?;
|
||||||
|
}
|
||||||
|
db.notify_committed(["groups"]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(ctx: &Arc<Context>) -> Self {
|
pub fn new(ctx: &Arc<Context>) -> Self {
|
||||||
Self { ctx: ctx.clone() }
|
Self { ctx: ctx.clone() }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use crate::api::server::Server;
|
||||||
use crate::error::{Result, TwonlyError};
|
use crate::error::{Result, TwonlyError};
|
||||||
|
use crate::user_config::UserConfig;
|
||||||
|
use chrono::{Duration, Utc};
|
||||||
use libsignal_protocol::{
|
use libsignal_protocol::{
|
||||||
message_encrypt, process_prekey_bundle, CiphertextMessageType, DeviceId, GenericSignedPreKey,
|
message_encrypt, process_prekey_bundle, CiphertextMessageType, DeviceId, GenericSignedPreKey,
|
||||||
IdentityKey, IdentityKeyPair, IdentityKeyStore, KyberPreKeyId, KyberPreKeyStore, PreKeyBundle,
|
IdentityKey, IdentityKeyPair, IdentityKeyStore, KyberPreKeyId, KyberPreKeyStore, PreKeyBundle,
|
||||||
|
|
@ -17,6 +20,7 @@ use tokio::sync::Mutex;
|
||||||
use crate::bridge::get_twonly_flutter;
|
use crate::bridge::get_twonly_flutter;
|
||||||
use crate::signal::assert_send::AssertSendFutureExt;
|
use crate::signal::assert_send::AssertSendFutureExt;
|
||||||
use crate::signal::store::DbSignalProtocolStore;
|
use crate::signal::store::DbSignalProtocolStore;
|
||||||
|
use crate::utils::current_time;
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
|
|
||||||
pub struct RustSignalEngine {
|
pub struct RustSignalEngine {
|
||||||
|
|
@ -47,6 +51,53 @@ pub struct FrbPqcPreKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RustSignalEngine {
|
impl RustSignalEngine {
|
||||||
|
pub async fn on_connected(&self, ctx: &Arc<crate::context::Context>) -> Result<()> {
|
||||||
|
let base = UserConfig::load_required_from(ctx)?;
|
||||||
|
let now = current_time().with_timezone(&Utc);
|
||||||
|
|
||||||
|
let refresh_signed = base
|
||||||
|
.signal_last_signed_pre_key_updated
|
||||||
|
.is_none_or(|last| last < now - Duration::hours(48));
|
||||||
|
let refresh_pqc = base
|
||||||
|
.signal_last_pqc_pre_keys_uploaded
|
||||||
|
.is_none_or(|last| last < now - Duration::days(7));
|
||||||
|
|
||||||
|
if !refresh_signed && !refresh_pqc {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let bundle = self.generate_bundle().await?;
|
||||||
|
if refresh_signed {
|
||||||
|
Server::update_signed_pre_key(
|
||||||
|
ctx,
|
||||||
|
i64::from(bundle.signed_pre_key_id),
|
||||||
|
bundle.signed_pre_key_public.clone(),
|
||||||
|
bundle.signed_pre_key_signature.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
UserConfig::update(ctx, |config| {
|
||||||
|
config.signal_last_signed_pre_key_updated = Some(now);
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
if refresh_pqc {
|
||||||
|
Server::upload_pqc_pre_keys(
|
||||||
|
ctx,
|
||||||
|
i64::from(bundle.signed_pre_key_id),
|
||||||
|
bundle.signed_pre_key_public,
|
||||||
|
bundle.signed_pre_key_signature,
|
||||||
|
i64::from(bundle.kyber_pre_key_id),
|
||||||
|
bundle.kyber_pre_key_public,
|
||||||
|
bundle.kyber_pre_key_signature,
|
||||||
|
Vec::new(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
UserConfig::update(ctx, |config| {
|
||||||
|
config.signal_last_pqc_pre_keys_uploaded = Some(now);
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn new(local_name: String) -> Result<Self> {
|
pub async fn new(local_name: String) -> Result<Self> {
|
||||||
let twonly = get_twonly_flutter()?;
|
let twonly = get_twonly_flutter()?;
|
||||||
let pool = twonly.rust_db.read().await.pool.clone();
|
let pool = twonly.rust_db.read().await.pool.clone();
|
||||||
|
|
|
||||||
|
|
@ -372,6 +372,20 @@ impl UserConfig {
|
||||||
Self::save_json_unlocked(context, json)
|
Self::save_json_unlocked(context, json)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Atomically updates the latest persisted configuration with a typed Rust
|
||||||
|
/// mutation. Unlike `update_json`, this does not need a caller snapshot:
|
||||||
|
/// loading, mutation, and saving all happen while holding the write lock.
|
||||||
|
pub(crate) fn update(context: &Context, mutate: impl FnOnce(&mut Self)) -> Result<()> {
|
||||||
|
let _guard = config_lock()
|
||||||
|
.write()
|
||||||
|
.map_err(|_| twonly_error!("user configuration lock was poisoned"))?;
|
||||||
|
let mut config = Self::load_from_unlocked(context)?
|
||||||
|
.ok_or_else(|| twonly_error!("user configuration is unavailable"))?;
|
||||||
|
mutate(&mut config);
|
||||||
|
Self::save_unlocked(context, &config)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Applies only fields changed relative to the caller's original snapshot.
|
/// Applies only fields changed relative to the caller's original snapshot.
|
||||||
/// Concurrent updates from Flutter isolates or Rust therefore do not
|
/// Concurrent updates from Flutter isolates or Rust therefore do not
|
||||||
/// overwrite unrelated fields with stale values.
|
/// overwrite unrelated fields with stale values.
|
||||||
|
|
@ -399,6 +413,10 @@ impl UserConfig {
|
||||||
let config: Self = serde_json::from_str(json).map_err(|error| {
|
let config: Self = serde_json::from_str(json).map_err(|error| {
|
||||||
TwonlyError::Generic(format!("invalid user configuration update: {error}"))
|
TwonlyError::Generic(format!("invalid user configuration update: {error}"))
|
||||||
})?;
|
})?;
|
||||||
|
Self::save_unlocked(context, &config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_unlocked(context: &Context, config: &Self) -> Result<String> {
|
||||||
let normalized = serde_json::to_string(&config)?;
|
let normalized = serde_json::to_string(&config)?;
|
||||||
let path = Self::path(context);
|
let path = Self::path(context);
|
||||||
let parent = path
|
let parent = path
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use crate::api::server::Server;
|
||||||
|
use crate::bridge::api::ServerResult;
|
||||||
|
use crate::context::Context;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use blahaj::{Share, Sharks};
|
use blahaj::{Share, Sharks};
|
||||||
|
|
@ -70,6 +73,55 @@ pub struct UserDiscovery {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserDiscovery {
|
impl UserDiscovery {
|
||||||
|
/// Refreshes server-owned data for announcements after the API connection
|
||||||
|
/// has authenticated. Cryptographic discovery state remains owned here;
|
||||||
|
/// the API runtime only invokes this lifecycle hook.
|
||||||
|
pub async fn on_connected(&self, ctx: &Arc<Context>) -> Result<()> {
|
||||||
|
let database = ctx.get_app_database().await;
|
||||||
|
let announcements = sqlx::query!(
|
||||||
|
r#"SELECT announced_user_id, announced_public_key
|
||||||
|
FROM user_discovery_announced_users WHERE username IS NULL"#
|
||||||
|
)
|
||||||
|
.fetch_all(&database.pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for announcement in announcements {
|
||||||
|
let user = match Server::get_user_by_id(ctx, announcement.announced_user_id).await? {
|
||||||
|
ServerResult::Ok(user) => user,
|
||||||
|
ServerResult::ErrorCode(code) => {
|
||||||
|
tracing::warn!(
|
||||||
|
user_id = announcement.announced_user_id,
|
||||||
|
code,
|
||||||
|
"could not refresh announced user"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if user.public_identity_key.as_deref()
|
||||||
|
!= Some(announcement.announced_public_key.as_slice())
|
||||||
|
{
|
||||||
|
tracing::error!(
|
||||||
|
user_id = announcement.announced_user_id,
|
||||||
|
"server returned a different identity key for announced user"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Some(username) = user.username else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let username = String::from_utf8(username)?;
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE user_discovery_announced_users SET username = ? WHERE announced_user_id = ?",
|
||||||
|
username,
|
||||||
|
announcement.announced_user_id,
|
||||||
|
)
|
||||||
|
.execute(&database.pool)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
database.notify_committed(["user_discovery_announced_users"]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
pub fn new(
|
pub fn new(
|
||||||
data_dir: &str,
|
data_dir: &str,
|
||||||
key_manager: Arc<Mutex<KeyManager>>,
|
key_manager: Arc<Mutex<KeyManager>>,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue