Compare commits

..

No commits in common. "main" and "v0.3.0" have entirely different histories.
main ... v0.3.0

262 changed files with 5926 additions and 90771 deletions

View file

@ -1,46 +1,13 @@
# Changelog
## 0.3.7
## 0.2.30
- Fix: Multiple UI issues
- Fix: Changed minimum threshold for the user discovery to 3
## 0.3.6
- Improve: Visibility of the verification badge
## 0.3.5
- Fix: Performance issue caused by an out-of-sync Signal session
- Fix: Multiple smaller bug fixes
## 0.3.3
- Fix: Multiple UI issues
- Fix: Camera initialization issue
## 0.3.2
- Fix: Multiple smaller performance and UI issues
## 0.3.1
- New: Promotion of sharing contacts when contact is new to twonly
- Improve: Onboarding of new users to the verification badges
- Improve: Better feedback when a QR code is scanned
- Fix: Shared contacts now correctly show the blue verification badge
- Fix: Suppressed link previews for scanned QR codes
- Fix: Black screen on iOS when a link is clicked
- Fix: Fixed size of the typing indicator to prevent the chat from glitching
- Fix: Push notifications are not shown for chats that are already open
- Fix: Multiple smaller performance and UI issues
## 0.3.0
## 0.2.28
- Improved: Design of some UI components
- Improved: Memories viewer shows state for batch operations and has improved performance
- Fix: Issue with background notifications on Android
- Fix: Changed minimum threshold for the user discovery to 3
- Fix: Multiple UI issues
- Fix: Auto-detect if FCM token does not work and trigger a reset
## 0.2.26

View file

@ -35,6 +35,7 @@ If you decide to give twonly a try, please keep in mind that it is still in its
- End-to-End encryption using the [Signal Protocol](https://de.wikipedia.org/wiki/Signal-Protokoll)
- Open Source and can be downloaded directly from GitHub
- No email or phone number required to register
- Privacy friendly - Everything is stored on the device
- The backend is hosted exclusively in Europe
## Roadmap
@ -42,6 +43,7 @@ If you decide to give twonly a try, please keep in mind that it is still in its
### Currently
- Focus on user-friendliness so that people enjoy using the app
- User discovery without a phone number
- Passwordless recovery without a phone number
- Implementation of features so that Snapchat can actually be replaced
- E2EE cloud backup of memories
@ -61,9 +63,36 @@ If you discover a security issue in twonly, please adhere to the coordinated vul
us your report to security@twonly.eu. We also offer for critical security issues a small bug bounties, but we can not
guarantee a bounty currently :/
## Contribution
<!-- ## Contribution
If you have any questions or feature requests, feel free to start a new discussion. Issues are limited to bugs, and for maintainers only. -->
## Development
<details>
<summary>Setup Instructions (macOS)</summary>
## Building
Some dependencies are downloaded directly from the source as there are some new changes which are not yet published on
pub.dev or because they require some special installation.
```bash
git submodule update --init --recursive
cd dependencies/flutter_zxing
git submodule update --init --recursive
./scripts/update_ios_macos_src.s
```
## Debugging files
```bash
run-as eu.twonly.testing ls /data/user/0/eu.twonly.testing/
```
</details>
Currently there are still some core features and rewrites open I want to do like the switch to the MLS protocol involving a huge rewrite in Rust. Because of this, contributions are currently not wanted. You can still view the code (if you find any security issues please contact me!). Also, issues are currently closed. If you find a bug, please use the in-app option as there you can upload your debug log which helps a lot.
## Signing Keys

View file

@ -19,7 +19,6 @@ analyzer:
- "lib/core/**"
- "lib/src/localization/**"
- "rust_builder/"
- "scripts/"
- "build/"
- "dependencies/**"
- "pubspec.yaml"

View file

@ -33,7 +33,7 @@ platform :android do
# Load release notes from CHANGELOG.md
changelog_path = File.expand_path("../CHANGELOG.md", __dir__)
release_notes = "Small bug fixes."
release_notes = "Automated local release via Fastlane"
if File.exist?(changelog_path)
changelog_content = File.read(changelog_path)
escaped_version = Regexp.escape(version)
@ -122,11 +122,6 @@ platform :android do
UI.message("Starting F-Droid deployment...")
FileUtils.mkdir_p(fdroid_repo_dir)
# Delete all APK files in the directory
sh("rm -f #{fdroid_repo_dir}/*.apk")
UI.message("All APK files deleted.")
apks.each do |apk_path|
basename = File.basename(apk_path)
new_name = "eu.twonly_v#{version}-#{basename}"

View file

@ -18,6 +18,33 @@
"version" : "11.2.0"
}
},
{
"identity" : "dkcamera",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zhangao0086/DKCamera",
"state" : {
"branch" : "master",
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
}
},
{
"identity" : "dkimagepickercontroller",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zhangao0086/DKImagePickerController",
"state" : {
"branch" : "4.3.9",
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
}
},
{
"identity" : "dkphotogallery",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
"state" : {
"branch" : "master",
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
@ -126,6 +153,15 @@
"version" : "2.4.0"
}
},
{
"identity" : "sdwebimage",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SDWebImage/SDWebImage",
"state" : {
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
"version" : "5.21.7"
}
},
{
"identity" : "sentry-cocoa",
"kind" : "remoteSourceControl",
@ -134,6 +170,24 @@
"revision" : "16cd512711375fa73f25ae5e373f596bdf4251ae",
"version" : "8.58.0"
}
},
{
"identity" : "swiftygif",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kirualex/SwiftyGif.git",
"state" : {
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
"version" : "5.4.5"
}
},
{
"identity" : "tocropviewcontroller",
"kind" : "remoteSourceControl",
"location" : "https://github.com/TimOliver/TOCropViewController",
"state" : {
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
"version" : "2.8.0"
}
}
],
"version" : 2

View file

@ -18,6 +18,33 @@
"version" : "11.2.0"
}
},
{
"identity" : "dkcamera",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zhangao0086/DKCamera",
"state" : {
"branch" : "master",
"revision" : "5c691d11014b910aff69f960475d70e65d9dcc96"
}
},
{
"identity" : "dkimagepickercontroller",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zhangao0086/DKImagePickerController",
"state" : {
"branch" : "4.3.9",
"revision" : "0bdfeacefa308545adde07bef86e349186335915"
}
},
{
"identity" : "dkphotogallery",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zhangao0086/DKPhotoGallery",
"state" : {
"branch" : "master",
"revision" : "311c1bc7a94f1538f82773a79c84374b12a2ef3d"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
@ -126,6 +153,15 @@
"version" : "2.4.0"
}
},
{
"identity" : "sdwebimage",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SDWebImage/SDWebImage",
"state" : {
"revision" : "2de3a496eaf6df9a1312862adcfd54acd73c39c0",
"version" : "5.21.7"
}
},
{
"identity" : "sentry-cocoa",
"kind" : "remoteSourceControl",
@ -134,6 +170,24 @@
"revision" : "16cd512711375fa73f25ae5e373f596bdf4251ae",
"version" : "8.58.0"
}
},
{
"identity" : "swiftygif",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kirualex/SwiftyGif.git",
"state" : {
"revision" : "4430cbc148baa3907651d40562d96325426f409a",
"version" : "5.4.5"
}
},
{
"identity" : "tocropviewcontroller",
"kind" : "remoteSourceControl",
"location" : "https://github.com/TimOliver/TOCropViewController",
"state" : {
"revision" : "d4a6d8100f4b886fdbc8ae399bf144ff3e9afb7e",
"version" : "2.8.0"
}
}
],
"version" : 2

View file

@ -5,13 +5,9 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/constants/keyvalue.keys.dart';
import 'package:twonly/src/constants/routes.keys.dart';
import 'package:twonly/src/localization/generated/app_localizations.dart';
import 'package:twonly/src/model/json/onboarding_state.model.dart';
import 'package:twonly/src/providers/routing.provider.dart';
import 'package:twonly/src/providers/settings.provider.dart';
import 'package:twonly/src/utils/keyvalue.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/pow.dart';
import 'package:twonly/src/visual/components/app_outdated.comp.dart';
@ -22,7 +18,7 @@ import 'package:twonly/src/visual/views/home.view.dart';
import 'package:twonly/src/visual/views/onboarding/onboarding.view.dart';
import 'package:twonly/src/visual/views/onboarding/register.view.dart';
import 'package:twonly/src/visual/views/onboarding/setup.view.dart';
import 'package:twonly/src/visual/views/recovery_from_secure_storage.view.dart';
import 'package:twonly/src/visual/views/recovery.view.dart';
import 'package:twonly/src/visual/views/unlock_twonly.view.dart';
class App extends StatefulWidget {
@ -155,10 +151,6 @@ class _AppMainWidgetState extends State<AppMainWidget> {
Future<void> initAsync() async {
Log.info('AppWidgetState: initAsync started');
final onboardingState = await KeyValueStore.getModel<OnboardingState>(
KeyValueKeys.onboardingState,
);
_showOnboarding = !onboardingState.hasOnboardingFinished;
if (userService.isUserCreated) {
if (_initialPage != 0) {
final count = await twonlyDB.contactsDao.getContactsCount();
@ -188,19 +180,8 @@ class _AppMainWidgetState extends State<AppMainWidget> {
} else {
_proofOfWork = (null, disabled);
}
if (onboardingState.hasStartedPasswordlessRecovery ||
onboardingState.emailRecoveryRequested) {
WidgetsBinding.instance.addPostFrameCallback((_) {
routerProvider.push(Routes.recoverPasswordless);
});
}
}
// await PasswordlessRecoveryService.handleRecoveryLink(
// 'https://me.twonly.eu/r/#7fdb8f08-0927-4e44-8761-038993414e48/1p7SKEzpxE3wSW9FQw60EUI4OpSW2U4EskdXLw8xg48',
// );
setState(() {
_isLoaded = true;
});
@ -242,13 +223,9 @@ class _AppMainWidgetState extends State<AppMainWidget> {
}
} else if (_showOnboarding) {
child = OnboardingView(
callbackOnSuccess: () async {
await KeyValueStore.update<OnboardingState>(
key: KeyValueKeys.onboardingState,
update: (state) => state.hasOnboardingFinished = true,
);
if (mounted) setState(() => _showOnboarding = false);
},
callbackOnSuccess: () => setState(() {
_showOnboarding = false;
}),
);
} else {
child = RegisterView(

View file

@ -1,173 +0,0 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.12.0.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
import '../../bridge.dart';
import '../../frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
// 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`
class UserDiscoveryStoreFlutter {
const UserDiscoveryStoreFlutter();
Future<AnnouncedUser?> getAnnouncedUserByPublicId({
required PlatformInt64 publicId,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterGetAnnouncedUserByPublicId(
that: this,
publicId: publicId,
);
Future<String> getConfig() => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterGetConfig(
that: this,
);
Future<Uint8List?> getContactPromotion({
required PlatformInt64 contactId,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterGetContactPromotion(
that: this,
contactId: contactId,
);
Future<Uint8List?> getContactVersion({
required PlatformInt64 contactId,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterGetContactVersion(
that: this,
contactId: contactId,
);
Future<List<OtherPromotion>> getOtherPromotionsByPublicId({
required PlatformInt64 publicId,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterGetOtherPromotionsByPublicId(
that: this,
publicId: publicId,
);
Future<List<Uint8List>> getOwnPromotionsAfterVersion({
required int version,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterGetOwnPromotionsAfterVersion(
that: this,
version: version,
);
Future<Uint8List> getShareForContact({
required PlatformInt64 contactId,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterGetShareForContact(
that: this,
contactId: contactId,
);
Future<void> pushNewUserRelation({
required PlatformInt64 fromContactId,
required AnnouncedUser announcedUser,
PlatformInt64? publicKeyVerifiedTimestamp,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterPushNewUserRelation(
that: this,
fromContactId: fromContactId,
announcedUser: announcedUser,
publicKeyVerifiedTimestamp: publicKeyVerifiedTimestamp,
);
Future<void> pushOwnPromotionAndClearOldVersion({
required PlatformInt64 contactId,
required int version,
required List<int> promotion,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterPushOwnPromotionAndClearOldVersion(
that: this,
contactId: contactId,
version: version,
promotion: promotion,
);
Future<void> setContactVersion({
required PlatformInt64 contactId,
required List<int> update,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterSetContactVersion(
that: this,
contactId: contactId,
update: update,
);
Future<void> setShares({required List<Uint8List> shares}) => RustLib
.instance
.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterSetShares(
that: this,
shares: shares,
);
Future<void> storeOtherPromotion({
required OtherPromotion promotion,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterStoreOtherPromotion(
that: this,
promotion: promotion,
);
Future<void> updateConfig({required String update}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryStoreFlutterUpdateConfig(
that: this,
update: update,
);
@override
int get hashCode => 0;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is UserDiscoveryStoreFlutter && runtimeType == other.runtimeType;
}
class UserDiscoveryUtilsFlutter {
const UserDiscoveryUtilsFlutter();
Future<Uint8List> signData({required List<int> inputData}) => RustLib
.instance
.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryUtilsFlutterSignData(
that: this,
inputData: inputData,
);
Future<bool> verifySignature({
required List<int> inputData,
required List<int> pubkey,
required List<int> signature,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryUtilsFlutterVerifySignature(
that: this,
inputData: inputData,
pubkey: pubkey,
signature: signature,
);
Future<bool> verifyStoredPubkey({
required PlatformInt64 fromContactId,
required List<int> pubkey,
}) => RustLib.instance.api
.crateBridgeCallbacksUserDiscoveryUserDiscoveryUtilsFlutterVerifyStoredPubkey(
that: this,
fromContactId: fromContactId,
pubkey: pubkey,
);
@override
int get hashCode => 0;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is UserDiscoveryUtilsFlutter && runtimeType == other.runtimeType;
}

View file

@ -1,37 +0,0 @@
// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.12.0.
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
import '../frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
class RustUtils {
const RustUtils();
static Future<List<Uint8List>> generateShares({
required List<int> secret,
required int total,
required int threshold,
}) => RustLib.instance.api.crateBridgeWrapperRustUtilsGenerateShares(
secret: secret,
total: total,
threshold: threshold,
);
static Future<Uint8List> recoverSecret({
required List<Uint8List> shares,
required int threshold,
}) => RustLib.instance.api.crateBridgeWrapperRustUtilsRecoverSecret(
shares: shares,
threshold: threshold,
);
@override
int get hashCode => 0;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is RustUtils && runtimeType == other.runtimeType;
}

View file

@ -20,12 +20,6 @@ class RustKeyManager {
static Future<PlatformInt64?> getUserId() => RustLib.instance.api
.crateBridgeWrapperKeyManagerRustKeyManagerGetUserId();
static Future<void> importSerialized({required List<int> serializedBytes}) =>
RustLib.instance.api
.crateBridgeWrapperKeyManagerRustKeyManagerImportSerialized(
serializedBytes: serializedBytes,
);
static Future<void> importSignalIdentity({
required List<int> identityKeyPairStructure,
required PlatformInt64 registrationId,
@ -59,10 +53,6 @@ class RustKeyManager {
signedPreKeyId: signedPreKeyId,
);
/// Serialize the key_manager. Needed for the passwordless_recovery feature.
static Future<Uint8List> serialize() => RustLib.instance.api
.crateBridgeWrapperKeyManagerRustKeyManagerSerialize();
static Future<void> setUserId({required PlatformInt64 userId}) => RustLib
.instance
.api

File diff suppressed because it is too large Load diff

View file

@ -5,8 +5,6 @@
import 'bridge.dart';
import 'bridge/callbacks.dart';
import 'bridge/callbacks/user_discovery.dart';
import 'bridge/wrapper.dart';
import 'bridge/wrapper/backup.dart';
import 'bridge/wrapper/key_manager.dart';
import 'bridge/wrapper/user_discovery.dart';
@ -136,19 +134,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
InitConfig dco_decode_box_autoadd_init_config(dynamic raw);
@protected
OtherPromotion dco_decode_box_autoadd_other_promotion(dynamic raw);
@protected
UserDiscoveryStoreFlutter dco_decode_box_autoadd_user_discovery_store_flutter(
dynamic raw,
);
@protected
UserDiscoveryUtilsFlutter dco_decode_box_autoadd_user_discovery_utils_flutter(
dynamic raw,
);
@protected
FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw);
@ -220,9 +205,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
RustKeyManager dco_decode_rust_key_manager(dynamic raw);
@protected
RustUtils dco_decode_rust_utils(dynamic raw);
@protected
int dco_decode_u_32(dynamic raw);
@ -235,16 +217,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void dco_decode_unit(dynamic raw);
@protected
UserDiscoveryStoreFlutter dco_decode_user_discovery_store_flutter(
dynamic raw,
);
@protected
UserDiscoveryUtilsFlutter dco_decode_user_discovery_utils_flutter(
dynamic raw,
);
@protected
BigInt dco_decode_usize(dynamic raw);
@ -294,21 +266,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer);
@protected
OtherPromotion sse_decode_box_autoadd_other_promotion(
SseDeserializer deserializer,
);
@protected
UserDiscoveryStoreFlutter sse_decode_box_autoadd_user_discovery_store_flutter(
SseDeserializer deserializer,
);
@protected
UserDiscoveryUtilsFlutter sse_decode_box_autoadd_user_discovery_utils_flutter(
SseDeserializer deserializer,
);
@protected
FlutterUserDiscovery sse_decode_flutter_user_discovery(
SseDeserializer deserializer,
@ -400,9 +357,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
RustKeyManager sse_decode_rust_key_manager(SseDeserializer deserializer);
@protected
RustUtils sse_decode_rust_utils(SseDeserializer deserializer);
@protected
int sse_decode_u_32(SseDeserializer deserializer);
@ -415,16 +369,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_decode_unit(SseDeserializer deserializer);
@protected
UserDiscoveryStoreFlutter sse_decode_user_discovery_store_flutter(
SseDeserializer deserializer,
);
@protected
UserDiscoveryUtilsFlutter sse_decode_user_discovery_utils_flutter(
SseDeserializer deserializer,
);
@protected
BigInt sse_decode_usize(SseDeserializer deserializer);
@ -573,24 +517,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_box_autoadd_other_promotion(
OtherPromotion self,
SseSerializer serializer,
);
@protected
void sse_encode_box_autoadd_user_discovery_store_flutter(
UserDiscoveryStoreFlutter self,
SseSerializer serializer,
);
@protected
void sse_encode_box_autoadd_user_discovery_utils_flutter(
UserDiscoveryUtilsFlutter self,
SseSerializer serializer,
);
@protected
void sse_encode_flutter_user_discovery(
FlutterUserDiscovery self,
@ -708,9 +634,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_rust_utils(RustUtils self, SseSerializer serializer);
@protected
void sse_encode_u_32(int self, SseSerializer serializer);
@ -723,18 +646,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_unit(void self, SseSerializer serializer);
@protected
void sse_encode_user_discovery_store_flutter(
UserDiscoveryStoreFlutter self,
SseSerializer serializer,
);
@protected
void sse_encode_user_discovery_utils_flutter(
UserDiscoveryUtilsFlutter self,
SseSerializer serializer,
);
@protected
void sse_encode_usize(BigInt self, SseSerializer serializer);

View file

@ -8,8 +8,6 @@
import 'bridge.dart';
import 'bridge/callbacks.dart';
import 'bridge/callbacks/user_discovery.dart';
import 'bridge/wrapper.dart';
import 'bridge/wrapper/backup.dart';
import 'bridge/wrapper/key_manager.dart';
import 'bridge/wrapper/user_discovery.dart';
@ -138,19 +136,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
InitConfig dco_decode_box_autoadd_init_config(dynamic raw);
@protected
OtherPromotion dco_decode_box_autoadd_other_promotion(dynamic raw);
@protected
UserDiscoveryStoreFlutter dco_decode_box_autoadd_user_discovery_store_flutter(
dynamic raw,
);
@protected
UserDiscoveryUtilsFlutter dco_decode_box_autoadd_user_discovery_utils_flutter(
dynamic raw,
);
@protected
FlutterUserDiscovery dco_decode_flutter_user_discovery(dynamic raw);
@ -222,9 +207,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
RustKeyManager dco_decode_rust_key_manager(dynamic raw);
@protected
RustUtils dco_decode_rust_utils(dynamic raw);
@protected
int dco_decode_u_32(dynamic raw);
@ -237,16 +219,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void dco_decode_unit(dynamic raw);
@protected
UserDiscoveryStoreFlutter dco_decode_user_discovery_store_flutter(
dynamic raw,
);
@protected
UserDiscoveryUtilsFlutter dco_decode_user_discovery_utils_flutter(
dynamic raw,
);
@protected
BigInt dco_decode_usize(dynamic raw);
@ -296,21 +268,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
InitConfig sse_decode_box_autoadd_init_config(SseDeserializer deserializer);
@protected
OtherPromotion sse_decode_box_autoadd_other_promotion(
SseDeserializer deserializer,
);
@protected
UserDiscoveryStoreFlutter sse_decode_box_autoadd_user_discovery_store_flutter(
SseDeserializer deserializer,
);
@protected
UserDiscoveryUtilsFlutter sse_decode_box_autoadd_user_discovery_utils_flutter(
SseDeserializer deserializer,
);
@protected
FlutterUserDiscovery sse_decode_flutter_user_discovery(
SseDeserializer deserializer,
@ -402,9 +359,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
RustKeyManager sse_decode_rust_key_manager(SseDeserializer deserializer);
@protected
RustUtils sse_decode_rust_utils(SseDeserializer deserializer);
@protected
int sse_decode_u_32(SseDeserializer deserializer);
@ -417,16 +371,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_decode_unit(SseDeserializer deserializer);
@protected
UserDiscoveryStoreFlutter sse_decode_user_discovery_store_flutter(
SseDeserializer deserializer,
);
@protected
UserDiscoveryUtilsFlutter sse_decode_user_discovery_utils_flutter(
SseDeserializer deserializer,
);
@protected
BigInt sse_decode_usize(SseDeserializer deserializer);
@ -575,24 +519,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_box_autoadd_other_promotion(
OtherPromotion self,
SseSerializer serializer,
);
@protected
void sse_encode_box_autoadd_user_discovery_store_flutter(
UserDiscoveryStoreFlutter self,
SseSerializer serializer,
);
@protected
void sse_encode_box_autoadd_user_discovery_utils_flutter(
UserDiscoveryUtilsFlutter self,
SseSerializer serializer,
);
@protected
void sse_encode_flutter_user_discovery(
FlutterUserDiscovery self,
@ -710,9 +636,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
SseSerializer serializer,
);
@protected
void sse_encode_rust_utils(RustUtils self, SseSerializer serializer);
@protected
void sse_encode_u_32(int self, SseSerializer serializer);
@ -725,18 +648,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
@protected
void sse_encode_unit(void self, SseSerializer serializer);
@protected
void sse_encode_user_discovery_store_flutter(
UserDiscoveryStoreFlutter self,
SseSerializer serializer,
);
@protected
void sse_encode_user_discovery_utils_flutter(
UserDiscoveryUtilsFlutter self,
SseSerializer serializer,
);
@protected
void sse_encode_usize(BigInt self, SseSerializer serializer);

View file

@ -35,6 +35,6 @@ class AppState {
static bool isInBackgroundTask = false;
static bool allowErrorTrackingViaSentry = false;
static bool gotMessageFromServer = false;
static int latestAppVersionId = 117;
static int latestAppVersionId = 116;
static bool hasCameraPermissions = false;
}

View file

@ -1,5 +1,4 @@
import 'dart:async';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mutex/mutex.dart';
@ -33,7 +32,7 @@ import 'package:twonly/src/utils/startup_guard.dart';
final _initMutex = Mutex();
/// This function is used to initialize the absolute minimum so it
/// This function is used to initialized the absolute minimum so it
/// can also be used by the backend without the UI was loaded.
Future<bool> twonlyMinimumInitialization() async {
Log.info('twonlyMinimumInitialization: called');
@ -41,13 +40,16 @@ Future<bool> twonlyMinimumInitialization() async {
lockName: 'init',
mutex: _initMutex,
action: () async {
Log.info('twonlyMinimumInitialization started');
Log.info('twonlyMinimumInitialization: started');
setupLocator();
Log.info('twonlyMinimumInitialization: RustLib.init()');
await RustLib.init();
Log.info('twonlyMinimumInitialization: initFlutterCallbacksForRust()');
await initFlutterCallbacksForRust();
Log.info('twonlyMinimumInitialization: bridge.initializeTwonlyFlutter()');
try {
await bridge.initializeTwonlyFlutter(
config: bridge.InitConfig(
@ -69,21 +71,12 @@ Future<bool> twonlyMinimumInitialization() async {
void main() async {
final binding = SentryWidgetsFlutterBinding.ensureInitialized();
await AppEnvironment.init();
// Preload available cameras in the background to speed up camera tab startup
unawaited(
availableCameras().then((cameras) {
AppEnvironment.cameras = cameras;
}),
);
final stopwatch = Stopwatch()..start();
unawaited(StartupGuard.markAppStartup());
var storageError = await twonlyMinimumInitialization();
await FcmNotificationService.initStartup();
await setupPushNotification();
await initFCMService();
var userExists = false;
@ -116,8 +109,6 @@ void main() async {
unawaited(initFileDownloader());
if (userExists) {
unawaited(FcmNotificationService.initAfterUserLoaded());
if (userService.currentUser.allowErrorTrackingViaSentry) {
AppState.allowErrorTrackingViaSentry = true;
await SentryFlutter.init(
@ -173,6 +164,7 @@ Future<void> postStartupTasks() async {
unawaited(MediaFileService.purgeTempFolder());
// 2. Service initializations
unawaited(setupPushNotification());
unawaited(finishStartedPreprocessing());
unawaited(createPushAvatars());

View file

@ -314,11 +314,9 @@ class UserDiscoveryCallbacks {
static Future<Uint8List?> getContactPromotion(int contactId) async {
try {
final query = twonlyDB.select(twonlyDB.userDiscoveryOwnPromotions)
..where((tbl) => tbl.contactId.equals(contactId))
..orderBy([(tbl) => OrderingTerm.desc(tbl.versionId)])
..limit(1);
final row = await query.getSingleOrNull();
final row = await (twonlyDB.select(
twonlyDB.userDiscoveryOwnPromotions,
)..where((tbl) => tbl.contactId.equals(contactId))).getSingleOrNull();
return row?.promotion;
} catch (e) {
Log.error(e);

View file

@ -36,7 +36,7 @@ abstract class VideoCompressionChannel {
});
return outputPath;
} on PlatformException catch (e) {
Log.warn('Failed to compress video: $e');
Log.error('Failed to compress video: $e');
return null;
} finally {
_currentProgressCallback = null;

View file

@ -3,5 +3,4 @@ class KeyValueKeys {
'last_periodic_task_execution';
static const String currentBackupState = 'current_backup_state';
static const String backupRecoveryState = 'backup_recovery_state';
static const String onboardingState = 'onboarding_state';
}

View file

@ -16,7 +16,6 @@ class Routes {
static String profileContact(int contactId) => '/profile/contact/$contactId';
static const String cameraQRScanner = '/camera/qr_scanner';
static const String recoverPasswordless = '/recover/passwordless';
static const String settings = '/settings';
static const String settingsProfile = '/settings/profile';
@ -36,6 +35,8 @@ class Routes {
'/settings/privacy/block_users';
static const String settingsPrivacyUserDiscovery =
'/settings/privacy/user_discovery';
static const String settingsPrivacyProfileSelection =
'/settings/privacy/profile_selection';
static const String settingsNotification = '/settings/notification';
static const String settingsStorage = '/settings/storage_data';
static const String settingsStorageManage = '/settings/storage_data/manage';

View file

@ -56,18 +56,6 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
return (delete(contacts)..where((t) => t.userId.equals(userId))).go();
}
Future<void> resetRecoveryDataForAllContacts() async {
await update(
contacts,
).write(
const ContactsCompanion(
recoveryIsTrustedFriend: Value(false),
recoveryLastHeartbeat: Value(null),
recoverySecretShare: Value(null),
),
);
}
Future<void> updateContact(
int userId,
ContactsCompanion updatedValues,

View file

@ -128,7 +128,7 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
final result = await _insertGroup(insertGroup);
if (result != null) {
await into(groupMembers).insertOnConflictUpdate(
await into(groupMembers).insert(
GroupMembersCompanion(
groupId: Value(result.groupId),
contactId: Value(

View file

@ -57,70 +57,18 @@ class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
}
Future<bool> isContactVerified(int contactId) async {
final verifierKv = alias(keyVerifications, 'verifierKv');
final query = select(keyVerifications).join([
leftOuterJoin(
verifierKv,
verifierKv.contactId.equalsExp(keyVerifications.verifiedBy),
),
])..where(keyVerifications.contactId.equals(contactId));
final rows = await query.get();
for (final row in rows) {
final kv = row.readTable(keyVerifications);
final hasVerifierKv = row.readTableOrNull(verifierKv) != null;
if (kv.type == VerificationType.contactSharedByVerified) {
if (hasVerifierKv) return true;
} else {
return true;
}
}
return false;
final row =
await (select(keyVerifications)
..where((kv) => kv.contactId.equals(contactId))
..limit(1))
.getSingleOrNull();
return row != null;
}
Stream<List<(KeyVerification, Contact?)>> watchContactVerification(
int contactId,
) {
final verifier = alias(contacts, 'verifier');
final verifierKv = alias(keyVerifications, 'verifierKv');
final query = select(keyVerifications).join([
leftOuterJoin(
verifier,
verifier.userId.equalsExp(keyVerifications.verifiedBy),
),
leftOuterJoin(
verifierKv,
verifierKv.contactId.equalsExp(keyVerifications.verifiedBy),
),
])..where(keyVerifications.contactId.equals(contactId));
return query.watch().map((rows) {
final uniqueKvs =
<int, (KeyVerification, Contact?, bool isVerifierVerified)>{};
for (final row in rows) {
final kv = row.readTable(keyVerifications);
final contact = row.readTableOrNull(verifier);
final hasVerifierKv = row.readTableOrNull(verifierKv) != null;
final existing = uniqueKvs[kv.verificationId];
if (existing == null || hasVerifierKv) {
uniqueKvs[kv.verificationId] = (kv, contact, hasVerifierKv);
}
}
return uniqueKvs.values
.where((item) {
final kv = item.$1;
final isVerifierVerified = item.$3;
if (kv.type == VerificationType.contactSharedByVerified) {
return isVerifierVerified;
}
return true;
})
.map((item) => (item.$1, item.$2))
.toList();
});
Stream<List<KeyVerification>> watchContactVerification(int contactId) {
return (select(
keyVerifications,
)..where((kv) => kv.contactId.equals(contactId))).watch();
}
Future<List<KeyVerification>> getContactVerification(int contactId) async {
@ -259,65 +207,12 @@ class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
});
}
Stream<int> watchUnverifiedGroupMembersCount(String groupId) {
final gm = groupMembers;
final directKv = alias(keyVerifications, 'directKv');
final ur = userDiscoveryUserRelations;
final verifierKv = alias(keyVerifications, 'verifierKv');
final query = select(gm).join([
leftOuterJoin(directKv, directKv.contactId.equalsExp(gm.contactId)),
leftOuterJoin(
ur,
ur.announcedUserId.equalsExp(gm.contactId) &
ur.publicKeyVerifiedTimestamp.isNotNull() &
ur.fromContactId.equalsExp(gm.contactId).not(),
),
leftOuterJoin(
verifierKv,
verifierKv.contactId.equalsExp(ur.fromContactId),
),
])..where(gm.groupId.equals(groupId));
return query.watch().map((rows) {
if (rows.isEmpty) return 0;
final memberTrustMap = <int, ({bool direct, bool partial})>{};
for (final row in rows) {
final contactId = row.readTable(gm).contactId;
final isDirect = row.readTableOrNull(directKv) != null;
final isPartial = row.readTableOrNull(verifierKv) != null;
final current =
memberTrustMap[contactId] ?? (direct: false, partial: false);
memberTrustMap[contactId] = (
direct: current.direct || isDirect,
partial: current.partial || isPartial,
);
}
var count = 0;
for (final trust in memberTrustMap.values) {
if (!trust.direct && !trust.partial) {
count++;
}
}
return count;
});
}
Future<void> addKeyVerification(
int contactId,
VerificationType type, {
int? verifiedBy,
}) async {
Future<void> addKeyVerification(int contactId, VerificationType type) async {
try {
await into(keyVerifications).insertOnConflictUpdate(
KeyVerificationsCompanion(
contactId: Value(contactId),
type: Value(type),
verifiedBy: Value(verifiedBy),
),
);
if (userService.currentUser.isUserDiscoveryEnabled) {

View file

@ -1,13 +1,12 @@
import 'package:drift/drift.dart';
import 'package:hashlib/random.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/database/tables/messages.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/utils/log.dart';
part 'mediafiles.dao.g.dart';
@DriftAccessor(tables: [MediaFiles, Messages])
@DriftAccessor(tables: [MediaFiles])
class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
with _$MediaFilesDaoMixin {
// this constructor is required so that the main database can create an instance
@ -143,9 +142,7 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
final query =
(select(mediaFiles)..where((t) => t.stored.equals(true))).join([])
..groupBy([
const CustomExpression<Object>(
'COALESCE(stored_file_hash, media_id)',
),
const CustomExpression<Object>('COALESCE(stored_file_hash, media_id)')
]);
return query.map((row) => row.readTable(mediaFiles)).watch();
}
@ -157,17 +154,6 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
.watch();
}
Stream<List<MediaFile>> watchMediaFilesForGroup(String groupId) {
final query = select(mediaFiles).join([
innerJoin(
db.messages,
db.messages.mediaId.equalsExp(mediaFiles.mediaId),
useColumns: false,
),
])..where(db.messages.groupId.equals(groupId));
return query.map((row) => row.readTable(mediaFiles)).watch();
}
Future<void> updateAllRetransmissionUploadingState() async {
await (update(mediaFiles)..where(
(t) =>

View file

@ -5,9 +5,6 @@ part of 'mediafiles.dao.dart';
// ignore_for_file: type=lint
mixin _$MediaFilesDaoMixin on DatabaseAccessor<TwonlyDB> {
$MediaFilesTable get mediaFiles => attachedDatabase.mediaFiles;
$GroupsTable get groups => attachedDatabase.groups;
$ContactsTable get contacts => attachedDatabase.contacts;
$MessagesTable get messages => attachedDatabase.messages;
MediaFilesDaoManager get managers => MediaFilesDaoManager(this);
}
@ -16,10 +13,4 @@ class MediaFilesDaoManager {
MediaFilesDaoManager(this._db);
$$MediaFilesTableTableManager get mediaFiles =>
$$MediaFilesTableTableManager(_db.attachedDatabase, _db.mediaFiles);
$$GroupsTableTableManager get groups =>
$$GroupsTableTableManager(_db.attachedDatabase, _db.groups);
$$ContactsTableTableManager get contacts =>
$$ContactsTableTableManager(_db.attachedDatabase, _db.contacts);
$$MessagesTableTableManager get messages =>
$$MessagesTableTableManager(_db.attachedDatabase, _db.messages);
}

View file

@ -32,25 +32,15 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
MessagesDao(super.db);
Stream<List<Message>> watchMessageNotOpened(String groupId) {
final query =
select(messages).join([
leftOuterJoin(
mediaFiles,
mediaFiles.mediaId.equalsExp(messages.mediaId),
),
])
return (select(messages)
..where(
messages.openedAt.isNull() &
messages.groupId.equals(groupId) &
messages.isDeletedFromSender.equals(false) &
(messages.mediaId.isNull() |
mediaFiles.downloadState.isNull() |
mediaFiles.downloadState
.equals(DownloadState.reuploadRequested.name)
.not()),
(t) =>
t.openedAt.isNull() &
t.groupId.equals(groupId) &
t.isDeletedFromSender.equals(false),
)
..orderBy([OrderingTerm.desc(messages.createdAt)]);
return query.map((row) => row.readTable(messages)).watch();
..orderBy([(t) => OrderingTerm.desc(t.createdAt)]))
.watch();
}
Stream<List<Message>> watchMediaNotOpened(String groupId) {
@ -62,10 +52,9 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
),
])
..where(
(mediaFiles.downloadState.isNull() |
mediaFiles.downloadState
.equals(DownloadState.reuploadRequested.name)
.not()) &
mediaFiles.downloadState
.equals(DownloadState.reuploadRequested.name)
.not() &
mediaFiles.type.equals(MediaType.audio.name).not() &
messages.openedAt.isNull() &
messages.groupId.equals(groupId) &
@ -77,24 +66,6 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
return query.map((row) => row.readTable(messages)).watch();
}
Stream<List<MediaFile>> watchUnopenedMediaFiles() {
final query =
select(messages).join([
leftOuterJoin(
mediaFiles,
mediaFiles.mediaId.equalsExp(messages.mediaId),
),
])..where(
messages.openedAt.isNull() &
messages.mediaId.isNotNull() &
messages.type.equals(MessageType.media.name) &
mediaFiles.downloadState.equals(DownloadState.ready.name) &
(mediaFiles.type.equals(MediaType.image.name) |
mediaFiles.type.equals(MediaType.gif.name)),
);
return query.map((row) => row.readTable(mediaFiles)).watch();
}
Future<Stream<Message?>> watchLastMessage(String groupId) async {
final group = await twonlyDB.groupsDao.getGroup(groupId);
final deletionTime = clock.now().subtract(
@ -102,29 +73,19 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
milliseconds: group!.deleteMessagesAfterMilliseconds,
),
);
final query =
select(messages).join([
leftOuterJoin(
mediaFiles,
mediaFiles.mediaId.equalsExp(messages.mediaId),
),
])
return (select(messages)
..where(
messages.groupId.equals(groupId) &
(t) =>
t.groupId.equals(groupId) &
// messages in groups will only be removed in case all members have received it...
// so ensuring that this message is not shown in the messages anymore
(messages.openedAt.isBiggerThanValue(deletionTime) |
messages.openedAt.isNull() |
messages.mediaStored.equals(true)) &
(mediaFiles.downloadState
.equals(DownloadState.reuploadRequested.name)
.not() |
mediaFiles.downloadState.isNull()),
(t.openedAt.isBiggerThanValue(deletionTime) |
t.openedAt.isNull() |
t.mediaStored.equals(true)),
)
..orderBy([OrderingTerm.desc(messages.createdAt)])
..limit(1);
return query.map((row) => row.readTable(messages)).watchSingleOrNull();
..orderBy([(t) => OrderingTerm.desc(t.createdAt)])
..limit(1))
.watchSingleOrNull();
}
Future<Stream<List<Message>>> watchByGroupId(String groupId) async {
@ -284,28 +245,26 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
String text,
DateTime timestamp,
) async {
await transaction(() async {
final msg = await getMessageById(messageId).getSingleOrNull();
if (msg == null || msg.content == null || msg.senderId != contactId) {
return;
}
await into(messageHistories).insert(
MessageHistoriesCompanion(
messageId: Value(messageId),
content: Value(msg.content),
createdAt: Value(timestamp),
),
);
await (update(messages)..where(
(t) => t.messageId.equals(messageId),
))
.write(
MessagesCompanion(
content: Value(text),
modifiedAt: Value(timestamp),
),
);
});
final msg = await getMessageById(messageId).getSingleOrNull();
if (msg == null || msg.content == null || msg.senderId != contactId) {
return;
}
await into(messageHistories).insert(
MessageHistoriesCompanion(
messageId: Value(messageId),
content: Value(msg.content),
createdAt: Value(timestamp),
),
);
await (update(messages)..where(
(t) => t.messageId.equals(messageId),
))
.write(
MessagesCompanion(
content: Value(text),
modifiedAt: Value(timestamp),
),
);
}
Future<void> handleMessagesOpened(
@ -313,37 +272,21 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
List<String> messageIds,
DateTime timestamp,
) async {
if (contactId.present) {
final contactExists = await twonlyDB.contactsDao.getContactById(
contactId.value,
);
if (contactExists == null) {
Log.info(
'handleMessagesOpened: Contact ${contactId.value} does not exist in database, ignoring messages opened action.',
);
return;
}
}
for (final messageId in messageIds) {
try {
await transaction(() async {
final msg = await getMessageById(messageId).getSingleOrNull();
if (msg == null) {
Log.info(
'handleMessagesOpened: Message $messageId does not exist in database, skipping.',
);
return;
}
var ts = timestamp;
if (ts.isBefore(msg.createdAt)) {
Log.warn(
'Receiver clock skew detected for message $messageId. '
'Action timestamp $ts is before message creation ${msg.createdAt}. '
'Clamping to creation time.',
);
ts = msg.createdAt;
}
var actionTimestamp = timestamp;
final msg = await getMessageById(messageId).getSingleOrNull();
if (msg != null && actionTimestamp.isBefore(msg.createdAt)) {
Log.warn(
'Receiver clock skew detected for message $messageId. '
'Action timestamp $actionTimestamp is before message creation ${msg.createdAt}. '
'Clamping to creation time.',
);
actionTimestamp = msg.createdAt;
}
final ts = actionTimestamp;
await transaction(() async {
await into(messageActions).insertOnConflictUpdate(
MessageActionsCompanion(
messageId: Value(messageId),
@ -377,7 +320,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
messages,
)..where((tbl) => tbl.messageId.equals(messageId))).write(
MessagesCompanion(
openedAt: Value(timestamp),
openedAt: Value(actionTimestamp),
),
);
}
@ -386,11 +329,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
'handleMessagesOpened completed for message $messageId',
);
} catch (e) {
Log.warn('handleMessagesOpened failed for $messageId: $e');
Log.error(
'handleMessagesOpened failed for: $e',
onlyIfSentryEnabled: true,
);
Log.error('handleMessagesOpened failed for $messageId: $e');
}
}
}
@ -400,21 +339,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
String messageId,
DateTime timestamp,
) async {
final contactExists = await twonlyDB.contactsDao.getContactById(contactId);
if (contactExists == null) {
Log.info(
'handleMessageAckByServer: Contact $contactId does not exist in database, ignoring message ack.',
);
return;
}
await transaction(() async {
final msg = await getMessageById(messageId).getSingleOrNull();
if (msg == null) {
Log.info(
'handleMessageAckByServer: Message $messageId does not exist in database, skipping.',
);
return;
}
await into(messageActions).insertOnConflictUpdate(
MessageActionsCompanion(
messageId: Value(messageId),
@ -528,15 +453,6 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
.getSingleOrNull();
}
Stream<MessageAction?> watchLastMessageAction(String messageId) {
return (((select(messageActions)..where(
(t) => t.messageId.equals(messageId),
))
..orderBy([(t) => OrderingTerm.desc(t.actionAt)]))
..limit(1))
.watchSingleOrNull();
}
Future<void> deleteMessagesById(String messageId) {
return (delete(messages)..where((t) => t.messageId.equals(messageId))).go();
}

View file

@ -30,15 +30,11 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
.getMessageById(messageId)
.getSingleOrNull();
if (msg == null) {
Log.warn('updateReaction: Message $messageId not found!');
Log.error('updateReaction: Message $messageId not found!');
return;
}
if (msg.groupId != groupId) {
Log.warn('updateReaction: Message groupId ${msg.groupId} != $groupId');
Log.error(
'updateReaction: Message groupId mismatch',
onlyIfSentryEnabled: true,
);
Log.error('updateReaction: Message groupId ${msg.groupId} != $groupId');
return;
}

View file

@ -53,7 +53,7 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
))
.go();
}
Future<void> deleteReceiptsByMessageId(String messageId) async {
await (delete(receipts)..where(
(t) => t.messageId.equals(messageId),
@ -201,8 +201,8 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
);
final updatedReceipt = await getReceiptById(newReceiptId);
if (updatedReceipt == null) {
Log.warn(
'[$oldReceiptId] Tried to change the receipt ID to $newReceiptId, but could not get the updated receipt...',
Log.error(
'Tried to change the receipt ID, but could not get the updated receipt...',
);
}
return updatedReceipt;
@ -253,9 +253,6 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
Future<void> gotReceipt(String receiptId) async {
await into(
receivedReceipts,
).insert(
ReceivedReceiptsCompanion(receiptId: Value(receiptId)),
mode: InsertMode.insertOrIgnore,
);
).insert(ReceivedReceiptsCompanion(receiptId: Value(receiptId)));
}
}

View file

@ -1,5 +1,4 @@
import 'package:drift/drift.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/database/tables/contacts.table.dart';
import 'package:twonly/src/database/tables/user_discovery.table.dart';
import 'package:twonly/src/database/twonly.db.dart';
@ -185,51 +184,23 @@ class UserDiscoveryDao extends DatabaseAccessor<TwonlyDB>
results[user]!.add(relationData);
}
final threshold = userService.currentUser.userDiscoveryThreshold;
results.removeWhere((user, relations) => relations.length < threshold);
return results;
});
}
Stream<int> watchNewAnnouncementsWithDataCount() {
final announcedContact = alias(contacts, 'announcedContact');
final query =
select(userDiscoveryAnnouncedUsers).join([
innerJoin(
userDiscoveryUserRelations,
userDiscoveryUserRelations.announcedUserId.equalsExp(
userDiscoveryAnnouncedUsers.announcedUserId,
),
),
leftOuterJoin(
announcedContact,
announcedContact.userId.equalsExp(
userDiscoveryAnnouncedUsers.announcedUserId,
),
),
])..where(
// Filters: Has a username AND has not been shown to the user yet AND is not an existing contact
userDiscoveryAnnouncedUsers.username.isNotNull() &
userDiscoveryAnnouncedUsers.wasShownToTheUser.equals(false) &
userDiscoveryAnnouncedUsers.isHidden.equals(false) &
(announcedContact.userId.isNull() |
announcedContact.deletedByUser.equals(true)),
);
final countExp = userDiscoveryAnnouncedUsers.announcedUserId.count();
return query.watch().map((rows) {
final relationCounts = <int, int>{};
for (final row in rows) {
final announcedUserId = row
.readTable(userDiscoveryAnnouncedUsers)
.announcedUserId;
relationCounts[announcedUserId] =
(relationCounts[announcedUserId] ?? 0) + 1;
}
final query = selectOnly(userDiscoveryAnnouncedUsers)
..addColumns([countExp])
..where(
// Filters: Has a username AND has not been shown to the user yet
userDiscoveryAnnouncedUsers.username.isNotNull() &
userDiscoveryAnnouncedUsers.wasShownToTheUser.equals(false) &
userDiscoveryAnnouncedUsers.isHidden.equals(false),
);
final threshold = userService.currentUser.userDiscoveryThreshold;
return relationCounts.values.where((count) => count >= threshold).length;
});
return query.watchSingle().map((row) => row.read(countExp) ?? 0);
}
Future<void> markAllValidAnnouncedUsersAsShown() async {
@ -263,12 +234,6 @@ class UserDiscoveryDao extends DatabaseAccessor<TwonlyDB>
)..where((tbl) => tbl.announcedUserId.equals(id))).getSingleOrNull();
}
Stream<UserDiscoveryAnnouncedUser?> watchAnnouncedUser(int id) {
return (select(
userDiscoveryAnnouncedUsers,
)..where((tbl) => tbl.announcedUserId.equals(id))).watchSingleOrNull();
}
Stream<List<UserDiscoveryAnnouncedUser>> watchAllAnnouncedUsers() =>
select(userDiscoveryAnnouncedUsers).watch();

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -42,9 +42,7 @@ class SignalPreKeyStore extends PreKeyStore {
);
try {
await twonlyDB
.into(twonlyDB.signalPreKeyStores)
.insert(preKeyCompanion, mode: InsertMode.insertOrReplace);
await twonlyDB.into(twonlyDB.signalPreKeyStores).insert(preKeyCompanion);
} catch (e) {
Log.error('$e');
}

View file

@ -23,26 +23,15 @@ class Contacts extends Table {
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
// User Discovery
// contact_versions: HashMap<UserID, Vec<u8>>,
BlobColumn get userDiscoveryVersion => blob().nullable()();
BoolColumn get userDiscoveryExcluded =>
boolean().withDefault(const Constant(false))();
BoolColumn get userDiscoveryManualApproved =>
boolean().nullable().withDefault(const Constant(false))();
// Passwordless-Recovery
BoolColumn get recoveryIsTrustedFriend =>
boolean().withDefault(const Constant(false))();
DateTimeColumn get recoveryLastHeartbeat => dateTime().nullable()();
BlobColumn get recoverySecretShare => blob().nullable()();
// This is the share from the contact in case the contact has selected this user as his trusted friend.
BlobColumn get recoveryContactsSecretShare => blob().nullable()();
DateTimeColumn get recoveryContactsLastHeartbeat => dateTime().nullable()();
IntColumn get recoveryContactsThreshold => integer().nullable()();
BoolColumn get askForFriendPromotions => boolean().nullable()();
IntColumn get mediaSendCounter => integer().withDefault(const Constant(0))();
IntColumn get mediaReceivedCounter =>
integer().withDefault(const Constant(0))();
@ -68,11 +57,6 @@ class KeyVerifications extends Table {
onDelete: KeyAction.cascade,
)();
TextColumn get type => textEnum<VerificationType>()();
IntColumn get verifiedBy => integer().nullable().references(
Contacts,
#userId,
onDelete: KeyAction.cascade,
)();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
}

View file

@ -82,7 +82,7 @@ class TwonlyDB extends _$TwonlyDB {
TwonlyDB.forTesting(DatabaseConnection super.connection);
@override
int get schemaVersion => 22;
int get schemaVersion => 17;
static QueryExecutor _openConnection() {
final connection = driftDatabase(
@ -233,48 +233,6 @@ class TwonlyDB extends _$TwonlyDB {
schema.userDiscoveryAnnouncedUsers.wasAskedFriends,
);
},
from17To18: (m, schema) async {
await m.addColumn(
schema.contacts,
schema.contacts.askForFriendPromotions,
);
},
from18To19: (m, schema) async {
await m.addColumn(
schema.keyVerifications,
schema.keyVerifications.verifiedBy,
);
},
from19To20: (m, schema) async {
await m.addColumn(
schema.contacts,
schema.contacts.recoveryIsTrustedFriend,
);
await m.addColumn(
schema.contacts,
schema.contacts.recoveryLastHeartbeat,
);
await m.addColumn(
schema.contacts,
schema.contacts.recoverySecretShare,
);
},
from20To21: (m, schema) async {
await m.addColumn(
schema.contacts,
schema.contacts.recoveryContactsSecretShare,
);
await m.addColumn(
schema.contacts,
schema.contacts.recoveryContactsLastHeartbeat,
);
},
from21To22: (m, schema) async {
await m.addColumn(
schema.contacts,
schema.contacts.recoveryContactsThreshold,
);
},
)(m, from, to);
},
);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -140,6 +140,18 @@ abstract class AppLocalizations {
/// **'Say goodbye to addictive features! twonly was created for sharing moments, free from useless distractions or ads.'**
String get onboardingFocusBody;
/// No description provided for @onboardingSendTwonliesTitle.
///
/// In en, this message translates to:
/// **'Send twonlies'**
String get onboardingSendTwonliesTitle;
/// No description provided for @onboardingSendTwonliesBody.
///
/// In en, this message translates to:
/// **'Share moments securely with your partner. twonly ensures that only your partner can open it, keeping your moments with your partner a two(o)nly thing!'**
String get onboardingSendTwonliesBody;
/// No description provided for @onboardingNotProductTitle.
///
/// In en, this message translates to:
@ -149,9 +161,15 @@ abstract class AppLocalizations {
/// No description provided for @onboardingNotProductBody.
///
/// In en, this message translates to:
/// **'twonly is financed by an optional subscription. Your data will never be sold.'**
/// **'twonly is financed by donations and an optional subscription. Your data will never be sold.'**
String get onboardingNotProductBody;
/// No description provided for @onboardingGetStartedTitle.
///
/// In en, this message translates to:
/// **'Let\'s go!'**
String get onboardingGetStartedTitle;
/// No description provided for @registerUsernameSlogan.
///
/// In en, this message translates to:
@ -302,12 +320,24 @@ abstract class AppLocalizations {
/// **'Username not found'**
String get searchUsernameNotFound;
/// No description provided for @searchUsernameNotFoundBody.
///
/// In en, this message translates to:
/// **'There is no user with the username \"{username}\" registered'**
String searchUsernameNotFoundBody(Object username);
/// No description provided for @searchUsernameNewFollowerTitle.
///
/// In en, this message translates to:
/// **'Open requests'**
String get searchUsernameNewFollowerTitle;
/// No description provided for @chatListViewSearchUserNameBtn.
///
/// In en, this message translates to:
/// **'Add your first twonly contact!'**
String get chatListViewSearchUserNameBtn;
/// No description provided for @chatListDetailInput.
///
/// In en, this message translates to:
@ -482,6 +512,12 @@ abstract class AppLocalizations {
/// **'Store in Gallery'**
String get settingsStorageDataStoreInGTitle;
/// No description provided for @settingsStorageDataStoreInGSubtitle.
///
/// In en, this message translates to:
/// **'Store saved images additional in the systems gallery.'**
String get settingsStorageDataStoreInGSubtitle;
/// No description provided for @settingsStorageDataMediaAutoDownload.
///
/// In en, this message translates to:
@ -575,13 +611,13 @@ abstract class AppLocalizations {
/// No description provided for @settingsPrivacyBlockUsers.
///
/// In en, this message translates to:
/// **'Block contacts'**
/// **'Block users'**
String get settingsPrivacyBlockUsers;
/// No description provided for @settingsPrivacyBlockUsersDesc.
///
/// In en, this message translates to:
/// **'Blocked contacts will not be able to communicate with you. You can unblock a blocked contact at any time.'**
/// **'Blocked users will not be able to communicate with you. You can unblock a blocked user at any time.'**
String get settingsPrivacyBlockUsersDesc;
/// No description provided for @settingsPrivacyBlockUsersCount.
@ -590,6 +626,54 @@ abstract class AppLocalizations {
/// **'{len} contact(s)'**
String settingsPrivacyBlockUsersCount(Object len);
/// No description provided for @settingsPrivacyProfileSelectionTitle.
///
/// In en, this message translates to:
/// **'Security Profile'**
String get settingsPrivacyProfileSelectionTitle;
/// No description provided for @settingsPrivacyProfileSelectionDesc.
///
/// In en, this message translates to:
/// **'Choose your setup path and security configuration'**
String get settingsPrivacyProfileSelectionDesc;
/// No description provided for @securityProfileTitle.
///
/// In en, this message translates to:
/// **'Security Profile'**
String get securityProfileTitle;
/// No description provided for @securityProfileSubtitle.
///
/// In en, this message translates to:
/// **'Choose the level of protection that fits your daily use. This can be changed at any time in your settings.'**
String get securityProfileSubtitle;
/// No description provided for @securityProfileNormalTitle.
///
/// In en, this message translates to:
/// **'Normal Protection'**
String get securityProfileNormalTitle;
/// No description provided for @securityProfileNormalDesc.
///
/// In en, this message translates to:
/// **'Good balance between a convenient mode without bothering you too much.'**
String get securityProfileNormalDesc;
/// No description provided for @securityProfileStrictTitle.
///
/// In en, this message translates to:
/// **'Strict Protection'**
String get securityProfileStrictTitle;
/// No description provided for @securityProfileStrictDesc.
///
/// In en, this message translates to:
/// **'Maximum anti-phishing protection but may be inconvenient.'**
String get securityProfileStrictDesc;
/// No description provided for @settingsNotification.
///
/// In en, this message translates to:
@ -851,63 +935,21 @@ abstract class AppLocalizations {
/// No description provided for @contactVerifyNumberTitle.
///
/// In en, this message translates to:
/// **'Verify contacts'**
/// **'Verify contact'**
String get contactVerifyNumberTitle;
/// No description provided for @verifyUserIdentity.
///
/// In en, this message translates to:
/// **'Verify {username}\'s identity'**
String verifyUserIdentity(Object username);
/// No description provided for @contactVerifyNumberSubtitle.
///
/// In en, this message translates to:
/// **'Verify the identity of your contacts to make sure you are texting the right person.'**
String get contactVerifyNumberSubtitle;
/// No description provided for @inChatContactNotVerified.
///
/// In en, this message translates to:
/// **'Contact not verified.'**
String get inChatContactNotVerified;
/// No description provided for @groupMembersNotVerified.
///
/// In en, this message translates to:
/// **'{count} members are not verified.'**
String groupMembersNotVerified(Object count);
/// No description provided for @userVerifiedTitle.
///
/// In en, this message translates to:
/// **'Contact verified'**
/// **'User verified'**
String get userVerifiedTitle;
/// No description provided for @scanUserQrCode.
///
/// In en, this message translates to:
/// **'Scan {username}\'s QR code'**
String scanUserQrCode(Object username);
/// No description provided for @openOwnQrCode.
///
/// In en, this message translates to:
/// **'Open your own QR code'**
String get openOwnQrCode;
/// No description provided for @contactVerifiedBy.
///
/// In en, this message translates to:
/// **'Verified by {username}'**
String contactVerifiedBy(Object username);
/// No description provided for @contactSharedByUnknown.
///
/// In en, this message translates to:
/// **'Shared by a verified contact (username not available)'**
String get contactSharedByUnknown;
/// No description provided for @verificationTypeQrScanned.
///
/// In en, this message translates to:
@ -971,13 +1013,13 @@ abstract class AppLocalizations {
/// No description provided for @contactBlockBody.
///
/// In en, this message translates to:
/// **'A blocked contact will no longer be able to send you messages and their profile will be hidden from view. To unblock a contact, simply navigate to Settings > Privacy > Blocked Contacts.'**
/// **'A blocked user will no longer be able to send you messages and their profile will be hidden from view. To unblock a user, simply navigate to Settings > Privacy > Blocked Users.'**
String get contactBlockBody;
/// No description provided for @contactRemove.
///
/// In en, this message translates to:
/// **'Remove contact'**
/// **'Remove user'**
String get contactRemove;
/// No description provided for @contactRemoveTitle.
@ -989,7 +1031,7 @@ abstract class AppLocalizations {
/// No description provided for @contactRemoveBody.
///
/// In en, this message translates to:
/// **'Permanently remove the contact. If the contact tries to send you a new message, you will have to accept the contact again first.'**
/// **'Permanently remove the user. If the user tries to send you a new message, you will have to accept the user again first.'**
String get contactRemoveBody;
/// No description provided for @undo.
@ -1592,18 +1634,6 @@ abstract class AppLocalizations {
/// **'Change password'**
String get backupChangePassword;
/// No description provided for @backupChangePasswordAuthReason.
///
/// In en, this message translates to:
/// **'Changing backup password'**
String get backupChangePasswordAuthReason;
/// No description provided for @backupChangePasswordAuthFailed.
///
/// In en, this message translates to:
/// **'You can only change your password after you have authenticated!'**
String get backupChangePasswordAuthFailed;
/// No description provided for @twonlySafeRecoverTitle.
///
/// In en, this message translates to:
@ -1691,7 +1721,7 @@ abstract class AppLocalizations {
/// No description provided for @reportUser.
///
/// In en, this message translates to:
/// **'Report contact'**
/// **'Report user'**
String get reportUser;
/// No description provided for @newDeviceRegistered.
@ -1928,12 +1958,6 @@ abstract class AppLocalizations {
/// **'Already in Group'**
String get alreadyInGroup;
/// No description provided for @contactNotVerified.
///
/// In en, this message translates to:
/// **'Not verified'**
String get contactNotVerified;
/// No description provided for @removeContactFromGroupTitle.
///
/// In en, this message translates to:
@ -2336,6 +2360,18 @@ abstract class AppLocalizations {
/// **'Draft'**
String get draftMessage;
/// No description provided for @exportMemories.
///
/// In en, this message translates to:
/// **'Export memories (Beta)'**
String get exportMemories;
/// No description provided for @importMemories.
///
/// In en, this message translates to:
/// **'Import memories (Beta)'**
String get importMemories;
/// No description provided for @voiceMessageSlideToCancel.
///
/// In en, this message translates to:
@ -2372,12 +2408,6 @@ abstract class AppLocalizations {
/// **'Let a friend scan this QR code to add you'**
String get addContactQrSheetSubtext;
/// No description provided for @letUserScanQrCode.
///
/// In en, this message translates to:
/// **'Let {username} scan this QR code'**
String letUserScanQrCode(Object username);
/// No description provided for @finishSetupCardTitle.
///
/// In en, this message translates to:
@ -2585,7 +2615,7 @@ abstract class AppLocalizations {
/// No description provided for @verifiedPublicKey.
///
/// In en, this message translates to:
/// **'The identity of {username} has been successfully verified.'**
/// **'The public key of {username} has been verified and is valid.'**
String verifiedPublicKey(Object username);
/// No description provided for @memoriesAYearAgo.
@ -2735,13 +2765,13 @@ abstract class AppLocalizations {
/// No description provided for @verificationBadgeGeneralDesc.
///
/// In en, this message translates to:
/// **'The badge gives you the peace of mind that you are messaging the *right person* and your *messages remain confidential*.'**
/// **'The checkmark gives you the certainty that you are messaging the right person. You can verify contacts at any time by scanning their QR code.'**
String get verificationBadgeGeneralDesc;
/// No description provided for @verificationBadgeGreenDesc.
///
/// In en, this message translates to:
/// **'A contact you have *personally verified* using the QR code.'**
/// **'A contact you have *personally* verified.'**
String get verificationBadgeGreenDesc;
/// No description provided for @verificationBadgeYellowDesc.
@ -2756,36 +2786,6 @@ abstract class AppLocalizations {
/// **'A contact whose identity has *not* yet been verified.'**
String get verificationBadgeRedDesc;
/// No description provided for @scanNow.
///
/// In en, this message translates to:
/// **'Scan now'**
String get scanNow;
/// No description provided for @qrScannerVerifyHint.
///
/// In en, this message translates to:
/// **'To verify, the other person must open their QR code (Chat Lists > QR code button at the bottom right)'**
String get qrScannerVerifyHint;
/// No description provided for @qrScannerVerifyUserHint.
///
/// In en, this message translates to:
/// **'To verify, {username} must open their QR code (Chat Lists > QR code button at the bottom right)'**
String qrScannerVerifyUserHint(Object username);
/// No description provided for @openQrCode.
///
/// In en, this message translates to:
/// **'Open QR code'**
String get openQrCode;
/// No description provided for @letFriendScanQrToVerify.
///
/// In en, this message translates to:
/// **'Let a friend scan this QR code to verify you'**
String get letFriendScanQrToVerify;
/// No description provided for @deleteVerificationTitle.
///
/// In en, this message translates to:
@ -2804,18 +2804,18 @@ abstract class AppLocalizations {
/// **'{username} has scanned your QR code and is now verified.'**
String secretQrTokenVerifiedSnackbar(Object username);
/// No description provided for @askForFriendPromotionsPrompt.
///
/// In en, this message translates to:
/// **'Help {username} find familiar faces on twonly by sharing mutual friends.'**
String askForFriendPromotionsPrompt(Object username);
/// No description provided for @mutualGroupsTitle.
///
/// In en, this message translates to:
/// **'{count, plural, =1{1 mutual group} other{{count} mutual groups}}'**
String mutualGroupsTitle(num count);
/// No description provided for @mutualGroupsSentMessages.
///
/// In en, this message translates to:
/// **'{count, plural, =1{1 message sent} other{{count} messages sent}}'**
String mutualGroupsSentMessages(num count);
/// No description provided for @chatEntryFlameRestored.
///
/// In en, this message translates to:
@ -2888,6 +2888,12 @@ abstract class AppLocalizations {
/// **'When the typing indicator is turned off, you can\'t see when others are typing a message.'**
String get settingsTypingIndicationSubtitle;
/// No description provided for @scanQrOrShow.
///
/// In en, this message translates to:
/// **'Scan / Show QR'**
String get scanQrOrShow;
/// No description provided for @contactActionBlock.
///
/// In en, this message translates to:
@ -2942,6 +2948,12 @@ abstract class AppLocalizations {
/// **'Mutual Friends'**
String get userDiscoverySettingsTitle;
/// No description provided for @userDiscoveryWhyThisIsUsed.
///
/// In en, this message translates to:
/// **'Why this is used'**
String get userDiscoveryWhyThisIsUsed;
/// No description provided for @userDiscoveryFeatureOffers.
///
/// In en, this message translates to:
@ -3224,6 +3236,12 @@ abstract class AppLocalizations {
/// **'Back'**
String get back;
/// No description provided for @onboardingExampleLabel.
///
/// In en, this message translates to:
/// **'Example'**
String get onboardingExampleLabel;
/// No description provided for @makerChangedUsername.
///
/// In en, this message translates to:
@ -3392,6 +3410,12 @@ abstract class AppLocalizations {
/// **'Drag to Zoom'**
String get dragToZoom;
/// No description provided for @showUsername.
///
/// In en, this message translates to:
/// **'Show username'**
String get showUsername;
/// No description provided for @onboardingProfileSelectionTitle.
///
/// In en, this message translates to:
@ -3618,6 +3642,30 @@ abstract class AppLocalizations {
/// **'There are no images on your device.'**
String get importGalleryNoImagesFoundDesc;
/// No description provided for @importGalleryShowAllImages.
///
/// In en, this message translates to:
/// **'Show all images'**
String get importGalleryShowAllImages;
/// No description provided for @importGalleryShowTwonlyAlbum.
///
/// In en, this message translates to:
/// **'Show twonly album'**
String get importGalleryShowTwonlyAlbum;
/// No description provided for @importGalleryToggleDescAll.
///
/// In en, this message translates to:
/// **'Viewing all images on your device.'**
String get importGalleryToggleDescAll;
/// No description provided for @importGalleryToggleDescTwonly.
///
/// In en, this message translates to:
/// **'Viewing the \"twonly\" album.'**
String get importGalleryToggleDescTwonly;
/// No description provided for @importGalleryFilterTwonly.
///
/// In en, this message translates to:
@ -3683,456 +3731,6 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Reset'**
String get avatarCustomizeReset;
/// No description provided for @passwordlessRecovery.
///
/// In en, this message translates to:
/// **'Passwordless Recovery'**
String get passwordlessRecovery;
/// No description provided for @passwordlessRecoveryNotConfigured.
///
/// In en, this message translates to:
/// **'Not configured'**
String get passwordlessRecoveryNotConfigured;
/// No description provided for @passwordlessRecoveryTestPin.
///
/// In en, this message translates to:
/// **'Test PIN'**
String get passwordlessRecoveryTestPin;
/// No description provided for @passwordlessRecoveryTestPinTitle.
///
/// In en, this message translates to:
/// **'Test PIN'**
String get passwordlessRecoveryTestPinTitle;
/// No description provided for @passwordlessRecoveryTestPinHint.
///
/// In en, this message translates to:
/// **'Enter your PIN'**
String get passwordlessRecoveryTestPinHint;
/// No description provided for @passwordlessRecoveryTestPinCorrect.
///
/// In en, this message translates to:
/// **'PIN is correct!'**
String get passwordlessRecoveryTestPinCorrect;
/// No description provided for @passwordlessRecoveryTestPinIncorrect.
///
/// In en, this message translates to:
/// **'Incorrect PIN.'**
String get passwordlessRecoveryTestPinIncorrect;
/// No description provided for @passwordlessRecoveryTest.
///
/// In en, this message translates to:
/// **'Test'**
String get passwordlessRecoveryTest;
/// No description provided for @passwordlessRecoverySecondFactorNone.
///
/// In en, this message translates to:
/// **'None'**
String get passwordlessRecoverySecondFactorNone;
/// No description provided for @passwordlessRecoverySecondFactorEmailLabel.
///
/// In en, this message translates to:
/// **'Email ({email})'**
String passwordlessRecoverySecondFactorEmailLabel(Object email);
/// No description provided for @passwordlessRecoverySecondFactorPin.
///
/// In en, this message translates to:
/// **'PIN'**
String get passwordlessRecoverySecondFactorPin;
/// No description provided for @passwordlessRecoverySecondFactorEmail.
///
/// In en, this message translates to:
/// **'Email'**
String get passwordlessRecoverySecondFactorEmail;
/// No description provided for @passwordlessRecoveryModify.
///
/// In en, this message translates to:
/// **'Modify Recovery Settings'**
String get passwordlessRecoveryModify;
/// No description provided for @passwordlessRecoveryModifyDesc.
///
/// In en, this message translates to:
/// **'Update your recovery configuration'**
String get passwordlessRecoveryModifyDesc;
/// No description provided for @passwordlessRecoverySecondFactor.
///
/// In en, this message translates to:
/// **'Second Factor'**
String get passwordlessRecoverySecondFactor;
/// No description provided for @passwordlessRecoveryNoFriendsFound.
///
/// In en, this message translates to:
/// **'No trusted friends found.'**
String get passwordlessRecoveryNoFriendsFound;
/// No description provided for @passwordlessRecoveryActiveFriends.
///
/// In en, this message translates to:
/// **'Active Friends'**
String get passwordlessRecoveryActiveFriends;
/// No description provided for @passwordlessRecoveryActiveFriendsDesc.
///
/// In en, this message translates to:
/// **'These trusted friends are actively using twonly and can probably help you recover your account.'**
String get passwordlessRecoveryActiveFriendsDesc;
/// No description provided for @passwordlessRecoveryInactiveFriends.
///
/// In en, this message translates to:
/// **'Inactive Friends'**
String get passwordlessRecoveryInactiveFriends;
/// No description provided for @passwordlessRecoveryInactiveFriendsDesc.
///
/// In en, this message translates to:
/// **'These friends either have not yet received their share or do not use twonly actively anymore, which could mean that they maybe cannot help.'**
String get passwordlessRecoveryInactiveFriendsDesc;
/// No description provided for @passwordlessRecoveryNotEnoughFriends.
///
/// In en, this message translates to:
/// **'Not enough friends selected. You need at least 3.'**
String get passwordlessRecoveryNotEnoughFriends;
/// No description provided for @passwordlessRecoveryLoading.
///
/// In en, this message translates to:
/// **'Loading...'**
String get passwordlessRecoveryLoading;
/// No description provided for @passwordlessRecoveryEnableSuccess.
///
/// In en, this message translates to:
/// **'Passwordless recovery successfully enabled!'**
String get passwordlessRecoveryEnableSuccess;
/// No description provided for @passwordlessRecoveryEnableFailed.
///
/// In en, this message translates to:
/// **'Failed to enable passwordless recovery.'**
String get passwordlessRecoveryEnableFailed;
/// No description provided for @passwordlessRecoveryNeedAtLeast3.
///
/// In en, this message translates to:
/// **'You need at least 3 trusted friends.'**
String get passwordlessRecoveryNeedAtLeast3;
/// No description provided for @passwordlessRecoveryInvalidPin.
///
/// In en, this message translates to:
/// **'Invalid PIN'**
String get passwordlessRecoveryInvalidPin;
/// No description provided for @passwordlessRecoveryEnterPin.
///
/// In en, this message translates to:
/// **'Please enter a PIN.'**
String get passwordlessRecoveryEnterPin;
/// No description provided for @passwordlessRecoveryPinMinLength.
///
/// In en, this message translates to:
/// **'PIN must be at least 4 digits.'**
String get passwordlessRecoveryPinMinLength;
/// No description provided for @passwordlessRecoveryEnterEmail.
///
/// In en, this message translates to:
/// **'Please enter an email address.'**
String get passwordlessRecoveryEnterEmail;
/// No description provided for @passwordlessRecoveryEnableBtn.
///
/// In en, this message translates to:
/// **'Enable Passwordless Recovery'**
String get passwordlessRecoveryEnableBtn;
/// No description provided for @passwordlessRecoveryRecoverBtn.
///
/// In en, this message translates to:
/// **'Recover passwordless'**
String get passwordlessRecoveryRecoverBtn;
/// No description provided for @passwordlessRecoveryModifyBtn.
///
/// In en, this message translates to:
/// **'Modify Passwordless Recovery'**
String get passwordlessRecoveryModifyBtn;
/// No description provided for @passwordlessRecoveryStatusEnabled.
///
/// In en, this message translates to:
/// **'Enabled • {count} trusted friends'**
String passwordlessRecoveryStatusEnabled(num count);
/// No description provided for @passwordlessRecoveryInfoHowItWorks.
///
/// In en, this message translates to:
/// **'How it works'**
String get passwordlessRecoveryInfoHowItWorks;
/// No description provided for @passwordlessRecoveryInfoHowItWorksDesc.
///
/// In en, this message translates to:
/// **'Because twonly operates without central user accounts or phone numbers to maximize privacy, we rely on a decentralized recovery mechanism. Using Shamir\'s Secret Sharing, your cryptographic identity is split into independent shares and distributed among your trusted friends. To restore access, a predefined threshold of these friends must combine their shares.'**
String get passwordlessRecoveryInfoHowItWorksDesc;
/// No description provided for @passwordlessRecoveryInfoWhySecondFactor.
///
/// In en, this message translates to:
/// **'Why a Second Factor?'**
String get passwordlessRecoveryInfoWhySecondFactor;
/// No description provided for @passwordlessRecoveryInfoWhySecondFactorDesc.
///
/// In en, this message translates to:
/// **'The second factor (Email or PIN) serves as a vital cryptographic safeguard against malicious collusion. If your trusted friends were to coordinate their shares behind your back, they still wouldn\'t be able to decrypt your identity without the second factor key.'**
String get passwordlessRecoveryInfoWhySecondFactorDesc;
/// No description provided for @passwordlessRecoveryInfoGotIt.
///
/// In en, this message translates to:
/// **'Got it'**
String get passwordlessRecoveryInfoGotIt;
/// No description provided for @passwordlessRecoveryMethod.
///
/// In en, this message translates to:
/// **'Second factor method'**
String get passwordlessRecoveryMethod;
/// No description provided for @passwordlessRecoveryMethodNoneDesc.
///
/// In en, this message translates to:
/// **'Without second-factor, your friends could collaborate to recover your account. Therefore, it is recommended to configure a second-factor.'**
String get passwordlessRecoveryMethodNoneDesc;
/// No description provided for @passwordlessRecoveryMethodPinHint.
///
/// In en, this message translates to:
/// **'Enter PIN'**
String get passwordlessRecoveryMethodPinHint;
/// No description provided for @passwordlessRecoveryMethodEmailHint.
///
/// In en, this message translates to:
/// **'Enter recovery email address'**
String get passwordlessRecoveryMethodEmailHint;
/// No description provided for @passwordlessRecoveryMethodEmailDesc.
///
/// In en, this message translates to:
/// **'Your email address is *never stored on the server* and is only sent to it in the event of a recovery.'**
String get passwordlessRecoveryMethodEmailDesc;
/// No description provided for @passwordlessRecoveryThresholdDesc.
///
/// In en, this message translates to:
/// **'To recover your account you need {count} of your selected trusted friends.'**
String passwordlessRecoveryThresholdDesc(num count);
/// No description provided for @passwordlessRecoveryThresholdTitle.
///
/// In en, this message translates to:
/// **'Required trusted friends for recovery'**
String get passwordlessRecoveryThresholdTitle;
/// No description provided for @passwordlessRecoverySelectFriendsNeeded.
///
/// In en, this message translates to:
/// **'Select friends ({count} more needed)'**
String passwordlessRecoverySelectFriendsNeeded(num count);
/// No description provided for @passwordlessRecoverySelectFriends.
///
/// In en, this message translates to:
/// **'Select trusted friends'**
String get passwordlessRecoverySelectFriends;
/// No description provided for @passwordlessRecoveryNoFriendsSelected.
///
/// In en, this message translates to:
/// **'No trusted friends selected yet'**
String get passwordlessRecoveryNoFriendsSelected;
/// No description provided for @passwordlessRecoverySubtitle.
///
/// In en, this message translates to:
/// **'Recover your identity without a password.'**
String get passwordlessRecoverySubtitle;
/// No description provided for @recoverPasswordlessExplanation.
///
/// In en, this message translates to:
/// **'If enabled, you can recover your account by asking your selected friends to help you.'**
String get recoverPasswordlessExplanation;
/// No description provided for @recoverPasswordlessNotificationCardTitle.
///
/// In en, this message translates to:
/// **'Enable notifications'**
String get recoverPasswordlessNotificationCardTitle;
/// No description provided for @recoverPasswordlessNotificationCardSubtitle.
///
/// In en, this message translates to:
/// **'Get notified when a friend has helped you recover your account.'**
String get recoverPasswordlessNotificationCardSubtitle;
/// No description provided for @recoverPasswordlessNotificationCardBtn.
///
/// In en, this message translates to:
/// **'Enable push notifications'**
String get recoverPasswordlessNotificationCardBtn;
/// No description provided for @recoverPasswordlessQrInstructions.
///
/// In en, this message translates to:
/// **'Let friends scan the QR code or share the link.'**
String get recoverPasswordlessQrInstructions;
/// No description provided for @recoverPasswordlessShareBtn.
///
/// In en, this message translates to:
/// **'Share Recovery Link'**
String get recoverPasswordlessShareBtn;
/// No description provided for @recoverPasswordlessCopyBtn.
///
/// In en, this message translates to:
/// **'Copy'**
String get recoverPasswordlessCopyBtn;
/// No description provided for @recoverPasswordlessCopiedSnackbar.
///
/// In en, this message translates to:
/// **'Link copied to clipboard!'**
String get recoverPasswordlessCopiedSnackbar;
/// No description provided for @passwordlessRecoveryNoShareStored.
///
/// In en, this message translates to:
/// **'No recovery share stored for this contact.'**
String get passwordlessRecoveryNoShareStored;
/// No description provided for @passwordlessRecoveryShareSent.
///
/// In en, this message translates to:
/// **'Recovery share successfully sent!'**
String get passwordlessRecoveryShareSent;
/// No description provided for @passwordlessRecoveryNetworkError.
///
/// In en, this message translates to:
/// **'Network error, please ensure you have internet'**
String get passwordlessRecoveryNetworkError;
/// No description provided for @passwordlessRecoveryInvalidEmail.
///
/// In en, this message translates to:
/// **'The email address is invalid.'**
String get passwordlessRecoveryInvalidEmail;
/// No description provided for @passwordlessRecoveryResendEmail.
///
/// In en, this message translates to:
/// **'Resend recovery email'**
String get passwordlessRecoveryResendEmail;
/// No description provided for @passwordlessRecoveryHelpAFriend.
///
/// In en, this message translates to:
/// **'Help a Friend'**
String get passwordlessRecoveryHelpAFriend;
/// No description provided for @passwordlessRecoverySelectContactDesc.
///
/// In en, this message translates to:
/// **'Please select the contact who requested recovery below to send them their recovery key.'**
String get passwordlessRecoverySelectContactDesc;
/// No description provided for @passwordlessRecoverySearchContacts.
///
/// In en, this message translates to:
/// **'Search contacts...'**
String get passwordlessRecoverySearchContacts;
/// No description provided for @passwordlessRecoveryNoContactsFound.
///
/// In en, this message translates to:
/// **'No contacts found'**
String get passwordlessRecoveryNoContactsFound;
/// No description provided for @passwordlessRecoveryCantHelpHim.
///
/// In en, this message translates to:
/// **'You can\'t help him'**
String get passwordlessRecoveryCantHelpHim;
/// No description provided for @passwordlessRecoveryDoesAskedYou.
///
/// In en, this message translates to:
/// **'Does {username} has asked you?'**
String passwordlessRecoveryDoesAskedYou(Object username);
/// No description provided for @passwordlessRecoveryVerifySourceDesc.
///
/// In en, this message translates to:
/// **'Please ensure that you actualy received the link/qr code from your friend! If you are unsure, please verify again and then click again on the link or come back.'**
String get passwordlessRecoveryVerifySourceDesc;
/// No description provided for @passwordlessRecoveryNo.
///
/// In en, this message translates to:
/// **'No'**
String get passwordlessRecoveryNo;
/// No description provided for @passwordlessRecoveryYes.
///
/// In en, this message translates to:
/// **'Yes'**
String get passwordlessRecoveryYes;
/// No description provided for @passwordlessRecoveryYesWithTimer.
///
/// In en, this message translates to:
/// **'Yes ({seconds}s)'**
String passwordlessRecoveryYesWithTimer(Object seconds);
/// No description provided for @recoverPasswordlessBeingRecoveredLabel.
///
/// In en, this message translates to:
/// **'Account being recovered'**
String get recoverPasswordlessBeingRecoveredLabel;
/// No description provided for @recoverPasswordlessSharesReceived.
///
/// In en, this message translates to:
/// **'{received} of {threshold} friends have shared'**
String recoverPasswordlessSharesReceived(Object received, Object threshold);
/// No description provided for @recoverPasswordlessRecoverNowBtn.
///
/// In en, this message translates to:
/// **'Recover now'**
String get recoverPasswordlessRecoverNowBtn;
}
class _AppLocalizationsDelegate

View file

@ -33,12 +33,22 @@ class AppLocalizationsDe extends AppLocalizations {
String get onboardingFocusBody =>
'Verabschiede dich von süchtig machenden Funktionen! twonly wurde für das Teilen von Momenten ohne nutzlose Ablenkungen oder Werbung entwickelt.';
@override
String get onboardingSendTwonliesTitle => 'twonlies senden';
@override
String get onboardingSendTwonliesBody =>
'Teile Momente sicher mit deinem Partner. twonly stellt sicher, dass nur dein Partner sie öffnen kann, sodass deine Momente mit deinem Partner eine two(o)nly Sache bleiben!';
@override
String get onboardingNotProductTitle => 'Du bist nicht das Produkt!';
@override
String get onboardingNotProductBody =>
'twonly wird durch ein optionales Abonnement finanziert. Deine Daten werden niemals verkauft.';
'twonly wird durch Spenden und ein optionales Abonnement finanziert. Deine Daten werden niemals verkauft.';
@override
String get onboardingGetStartedTitle => 'Auf geht\'s';
@override
String get registerUsernameSlogan => 'Konto erstellen';
@ -116,9 +126,18 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get searchUsernameNotFound => 'Benutzername nicht gefunden';
@override
String searchUsernameNotFoundBody(Object username) {
return 'Es wurde kein Benutzer mit dem Benutzernamen \"$username\" gefunden.';
}
@override
String get searchUsernameNewFollowerTitle => 'Offene Anfragen';
@override
String get chatListViewSearchUserNameBtn =>
'Füge deinen ersten twonly-Kontakt hinzu!';
@override
String get chatListDetailInput => 'Nachricht eingeben';
@ -210,6 +229,10 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get settingsStorageDataStoreInGTitle => 'In der Galerie speichern';
@override
String get settingsStorageDataStoreInGSubtitle =>
'Speichere Bilder zusätzlich in der Systemgalerie.';
@override
String get settingsStorageDataMediaAutoDownload =>
'Automatischer Mediendownload';
@ -257,17 +280,45 @@ class AppLocalizationsDe extends AppLocalizations {
String get settingsPrivacy => 'Datenschutz & Sicherheit';
@override
String get settingsPrivacyBlockUsers => 'Kontakte blockieren';
String get settingsPrivacyBlockUsers => 'Benutzer blockieren';
@override
String get settingsPrivacyBlockUsersDesc =>
'Blockierte Kontakte können nicht mit dir kommunizieren. Du kannst einen blockierten Kontakt jederzeit wieder entsperren.';
'Blockierte Benutzer können nicht mit dir kommunizieren. Du kannst einen blockierten Benutzer jederzeit wieder entsperren.';
@override
String settingsPrivacyBlockUsersCount(Object len) {
return '$len Kontakt(e)';
}
@override
String get settingsPrivacyProfileSelectionTitle => 'Sicherheitsprofil';
@override
String get settingsPrivacyProfileSelectionDesc =>
'Wähle deinen Setup-Pfad und deine Sicherheitskonfiguration';
@override
String get securityProfileTitle => 'Sicherheitsprofil';
@override
String get securityProfileSubtitle =>
'Wähle das Schutzniveau, das zu deiner täglichen Nutzung passt. Dies kann jederzeit in den Einstellungen geändert werden.';
@override
String get securityProfileNormalTitle => 'Normaler Schutz';
@override
String get securityProfileNormalDesc =>
'Gute Balance zwischen Komfort und Sicherheit, ohne dich zu sehr einzuschränken.';
@override
String get securityProfileStrictTitle => 'Strikter Schutz';
@override
String get securityProfileStrictDesc =>
'Maximaler Schutz vor Phishing, kann aber unkomfortabel sein.';
@override
String get settingsNotification => 'Benachrichtigung';
@ -380,7 +431,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get contactUsLastWarning =>
'Dies sind die Informationen, die an uns gesendet werden. Bitte prüfe sie und klicke dann auf „Abschicken“.';
'Dies sind die Informationen, die an uns gesendet werden. Bitte prüfen Sie sie und klicke dann auf „Abschicken“.';
@override
String get contactUsSuccess => 'Feedback erfolgreich übermittelt!';
@ -409,45 +460,16 @@ class AppLocalizationsDe extends AppLocalizations {
'Dein Konto wird gelöscht. Es gibt keine Möglichkeit, es wiederherzustellen.';
@override
String get contactVerifyNumberTitle => 'Kontakte verifizieren';
String get contactVerifyNumberTitle => 'Benutzer verifizieren';
@override
String verifyUserIdentity(Object username) {
return 'Identität von $username verifizieren';
}
@override
String get contactVerifyNumberSubtitle =>
'Überprüfe die Identität deiner Kontakte, um sicherzugehen, dass du mit der richtigen Person schreibst.';
@override
String get inChatContactNotVerified => 'Kontakt nicht verifiziert.';
@override
String groupMembersNotVerified(Object count) {
return '$count Mitglieder sind nicht verifiziert.';
}
@override
String get userVerifiedTitle => 'Kontakt verifiziert';
@override
String scanUserQrCode(Object username) {
return 'QR-Code von $username scannen';
}
@override
String get openOwnQrCode => 'Eigenen QR-Code öffnen';
String get userVerifiedTitle => 'Benutzer verifiziert';
@override
String contactVerifiedBy(Object username) {
return 'Verifiziert von $username';
}
@override
String get contactSharedByUnknown =>
'Geteilt von einem verifizierten Kontakt (Benutzername nicht verfügbar)';
@override
String get verificationTypeQrScanned => 'Du hast den QR-Code gescannt.';
@ -486,10 +508,10 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get contactBlockBody =>
'Ein blockierter Kontakt kann dir keine Nachrichten mehr senden, und deren Profil ist nicht mehr sichtbar. Um die Blockierung eines Kontakts aufzuheben, navigiere einfach zu Einstellungen > Datenschutz > Blockierte Kontakte.';
'Ein blockierter Benutzer kann dir keine Nachrichten mehr senden, und deren Profil ist nicht mehr sichtbar. Um die Blockierung eines Benutzers aufzuheben, navigiere einfach zu Einstellungen > Datenschutz > Blockierte Benutzer.';
@override
String get contactRemove => 'Kontakt löschen';
String get contactRemove => 'Benutzer löschen';
@override
String contactRemoveTitle(Object username) {
@ -498,7 +520,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get contactRemoveBody =>
'Den Kontakt dauerhaft entfernen. Wenn der Kontakt versucht, dir eine neue Nachricht zu senden, musst du den Kontakt erst wieder akzeptieren.';
'Den Benutzer dauerhaft entfernen. Wenn der Benutzer versucht, dir eine neue Nachricht zu senden, musst du den Benutzer erst wieder akzeptieren.';
@override
String get undo => 'Rückgängig';
@ -838,14 +860,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get backupTwonlySaveNow => 'Jetzt speichern';
@override
String get backupChangePassword => 'Passwort ändern';
@override
String get backupChangePasswordAuthReason => 'Backup-Passwort ändern';
@override
String get backupChangePasswordAuthFailed =>
'Du kannst dein Passwort nur ändern, wenn du dich authentifiziert hast!';
String get backupChangePassword => 'Password ändern';
@override
String get twonlySafeRecoverTitle => 'Backup wiederherstellen';
@ -895,7 +910,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get reportUserReason => 'Meldegrund';
@override
String get reportUser => 'Kontakt melden';
String get reportUser => 'Benutzer melden';
@override
String get newDeviceRegistered =>
@ -1030,9 +1045,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get alreadyInGroup => 'Bereits Mitglied';
@override
String get contactNotVerified => 'Nicht verifiziert';
@override
String removeContactFromGroupTitle(Object username) {
return '$username aus dieser Gruppe entfernen?';
@ -1295,6 +1307,12 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get draftMessage => 'Entwurf';
@override
String get exportMemories => 'Memories exportieren (Beta)';
@override
String get importMemories => 'Memories importieren (Beta)';
@override
String get voiceMessageSlideToCancel => 'Zum Abbrechen ziehen';
@ -1314,11 +1332,6 @@ class AppLocalizationsDe extends AppLocalizations {
String get addContactQrSheetSubtext =>
'Lass einen Freund diesen QR-Code scannen, um dich hinzuzufügen';
@override
String letUserScanQrCode(Object username) {
return 'Lass $username diesen QR-Code scannen';
}
@override
String get finishSetupCardTitle => 'Profil vervollständigen';
@ -1445,7 +1458,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String verifiedPublicKey(Object username) {
return 'Die Identität von $username wurde erfolgreich überprüft.';
return 'Der öffentliche Schlüssel von $username wurde überprüft und ist gültig.';
}
@override
@ -1544,11 +1557,11 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get verificationBadgeGeneralDesc =>
'Der Haken gibt dir die Sicherheit, dass du mit der *richtigen Person* schreibst und deine *Nachrichten vertraulich* bleiben.';
'Der Haken gibt dir die Sicherheit, dass du mit der richtigen Person schreibst. Du kannst Kontakte jederzeit verifizieren, indem du deren QR-Code scannst.';
@override
String get verificationBadgeGreenDesc =>
'Ein Kontakt, den du über den QR-code *persönlich verifiziert* hast.';
'Ein Kontakt, den du *persönlich verifiziert* hast.';
@override
String get verificationBadgeYellowDesc =>
@ -1558,25 +1571,6 @@ class AppLocalizationsDe extends AppLocalizations {
String get verificationBadgeRedDesc =>
'Ein Kontakt, dessen Identität noch *nicht überprüft* wurde.';
@override
String get scanNow => 'Jetzt scannen';
@override
String get qrScannerVerifyHint =>
'Zum Verifizieren muss die andere Person ihren QR-Code öffnen (Chat-Liste > QR-Code-Button unten rechts)';
@override
String qrScannerVerifyUserHint(Object username) {
return 'Zum Verifizieren muss $username seinen QR-Code öffnen (Chat-Liste > QR-Code-Button unten rechts)';
}
@override
String get openQrCode => 'QR-Code öffnen';
@override
String get letFriendScanQrToVerify =>
'Lass einen Freund diesen QR-Code scannen, um euch zu verifizieren';
@override
String get deleteVerificationTitle => 'Verifizierung löschen?';
@ -1589,11 +1583,6 @@ class AppLocalizationsDe extends AppLocalizations {
return '$username hat deinen QR-Code gescannt und ist nun verifiziert.';
}
@override
String askForFriendPromotionsPrompt(Object username) {
return 'Hilf $username, bekannte Gesichter auf twonly zu finden, indem du gemeinsame Freunde teilst.';
}
@override
String mutualGroupsTitle(num count) {
String _temp0 = intl.Intl.pluralLogic(
@ -1605,6 +1594,17 @@ class AppLocalizationsDe extends AppLocalizations {
return '$_temp0';
}
@override
String mutualGroupsSentMessages(num count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count Nachrichten gesendet',
one: '1 Nachricht gesendet',
);
return '$_temp0';
}
@override
String chatEntryFlameRestored(Object count) {
return '$count Flammen wiederhergestellt';
@ -1650,6 +1650,9 @@ class AppLocalizationsDe extends AppLocalizations {
String get settingsTypingIndicationSubtitle =>
'Bei deaktivierten Tipp-Indikatoren kannst du nicht sehen, wenn andere gerade eine Nachricht tippen.';
@override
String get scanQrOrShow => 'QR scannen / anzeigen';
@override
String get contactActionBlock => 'Blockieren';
@ -1681,6 +1684,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get userDiscoverySettingsTitle => 'Gemeinsame Freunde';
@override
String get userDiscoveryWhyThisIsUsed => 'Warum dies verwendet wird';
@override
String get userDiscoveryFeatureOffers => 'Dein Nutzen auf einen Blick';
@ -1848,6 +1854,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get back => 'Zurück';
@override
String get onboardingExampleLabel => 'Beispiel';
@override
String makerChangedUsername(Object maker, Object oldName, Object newName) {
return '$maker hat den Benutzernamen von $oldName zu $newName geändert.';
@ -1948,6 +1957,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get dragToZoom => 'Zum Zoomen ziehen';
@override
String get showUsername => 'Benutzernamen anzeigen';
@override
String get onboardingProfileSelectionTitle => 'Wähle deinen Setup-Weg';
@ -2082,6 +2094,20 @@ class AppLocalizationsDe extends AppLocalizations {
String get importGalleryNoImagesFoundDesc =>
'Es befinden sich keine Bilder auf deinem Gerät.';
@override
String get importGalleryShowAllImages => 'Alle Bilder anzeigen';
@override
String get importGalleryShowTwonlyAlbum => 'twonly-Album anzeigen';
@override
String get importGalleryToggleDescAll =>
'Es werden alle Bilder auf deinem Gerät angezeigt.';
@override
String get importGalleryToggleDescTwonly =>
'Es wird das \"twonly\"-Album angezeigt.';
@override
String get importGalleryFilterTwonly => 'Nur das twonly-Album anzeigen';
@ -2124,274 +2150,4 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get avatarCustomizeReset => 'Zurücksetzen';
@override
String get passwordlessRecovery => 'Passwortloses Backup';
@override
String get passwordlessRecoveryNotConfigured => 'Nicht konfiguriert';
@override
String get passwordlessRecoveryTestPin => 'PIN testen';
@override
String get passwordlessRecoveryTestPinTitle => 'PIN testen';
@override
String get passwordlessRecoveryTestPinHint => 'Gib deine PIN ein';
@override
String get passwordlessRecoveryTestPinCorrect => 'PIN ist korrekt!';
@override
String get passwordlessRecoveryTestPinIncorrect => 'Falsche PIN.';
@override
String get passwordlessRecoveryTest => 'Testen';
@override
String get passwordlessRecoverySecondFactorNone => 'Keiner';
@override
String passwordlessRecoverySecondFactorEmailLabel(Object email) {
return 'E-Mail ($email)';
}
@override
String get passwordlessRecoverySecondFactorPin => 'PIN';
@override
String get passwordlessRecoverySecondFactorEmail => 'E-Mail';
@override
String get passwordlessRecoveryModify => 'Einstellungen bearbeiten';
@override
String get passwordlessRecoveryModifyDesc =>
'Aktualisiere deine Backupkonfiguration';
@override
String get passwordlessRecoverySecondFactor => 'Zweiter Faktor';
@override
String get passwordlessRecoveryNoFriendsFound => 'Keine Freunde gefunden.';
@override
String get passwordlessRecoveryActiveFriends => 'Aktive Freunde';
@override
String get passwordlessRecoveryActiveFriendsDesc =>
'Diese Freunde nutzen twonly aktiv und können dir wahrscheinlich helfen, dein Konto wiederherzustellen.';
@override
String get passwordlessRecoveryInactiveFriends => 'Inaktive Freunde';
@override
String get passwordlessRecoveryInactiveFriendsDesc =>
'Diese Freunde haben entweder ihren Teil noch nicht erhalten oder nutzen twonly nicht mehr aktiv, was bedeutet, dass sie möglicherweise nicht helfen können.';
@override
String get passwordlessRecoveryNotEnoughFriends =>
'Nicht genügend Freunde ausgewählt. Du brauchst mindestens 3.';
@override
String get passwordlessRecoveryLoading => 'Wird geladen...';
@override
String get passwordlessRecoveryEnableSuccess =>
'Passwortloses Backup erfolgreich aktiviert!';
@override
String get passwordlessRecoveryEnableFailed =>
'Fehler beim Aktivieren des passwortlosen Backups.';
@override
String get passwordlessRecoveryNeedAtLeast3 =>
'Du brauchst mindestens 3 Freunde.';
@override
String get passwordlessRecoveryInvalidPin => 'Ungültige PIN';
@override
String get passwordlessRecoveryEnterPin => 'Bitte gib eine PIN ein.';
@override
String get passwordlessRecoveryPinMinLength =>
'Die PIN muss mindestens 4 Ziffern lang sein.';
@override
String get passwordlessRecoveryEnterEmail =>
'Bitte gib eine E-Mail-Adresse ein.';
@override
String get passwordlessRecoveryEnableBtn => 'Passwortloses Backup aktivieren';
@override
String get passwordlessRecoveryRecoverBtn => 'Passwortlos wiederherstellen';
@override
String get passwordlessRecoveryModifyBtn => 'Passwortloses Backup bearbeiten';
@override
String passwordlessRecoveryStatusEnabled(num count) {
return 'Aktiviert • $count Freunde';
}
@override
String get passwordlessRecoveryInfoHowItWorks => 'Wie es funktioniert';
@override
String get passwordlessRecoveryInfoHowItWorksDesc =>
'Da twonly ohne zentrale Benutzerkonten oder Telefonnummern funktioniert, um deine Privatsphäre zu maximieren, setzen wir auf einen dezentralen Wiederherstellungsmechanismus. Mit Shamir\'s Secret Sharing wird deine kryptografische Identität in unabhängige Teile aufgeteilt und an deine Freunde verteilt. Um den Zugriff wiederherzustellen, muss ein vorher festgelegter Schwellenwert dieser Freunde ihre Teile kombinieren.';
@override
String get passwordlessRecoveryInfoWhySecondFactor =>
'Warum ein zweiter Faktor?';
@override
String get passwordlessRecoveryInfoWhySecondFactorDesc =>
'Der zweite Faktor (E-Mail oder PIN) dient als wichtiger kryptografischer Schutz vor bösartiger Absprache. Wenn sich deine Freunde hinter deinem Rücken absprechen würden, könnten sie deine Identität ohne den Schlüssel für den zweiten Faktor trotzdem nicht entschlüsseln.';
@override
String get passwordlessRecoveryInfoGotIt => 'Verstanden';
@override
String get passwordlessRecoveryMethod => 'Methode für den zweiten Faktor';
@override
String get passwordlessRecoveryMethodNoneDesc =>
'Ohne zweiten Faktor könnten sich deine Freunde absprechen, um dein Konto wiederherzustellen. Daher wird empfohlen, einen zweiten Faktor zu konfigurieren.';
@override
String get passwordlessRecoveryMethodPinHint => 'PIN eingeben';
@override
String get passwordlessRecoveryMethodEmailHint =>
'Wiederherstellungs-E-Mail-Adresse eingeben';
@override
String get passwordlessRecoveryMethodEmailDesc =>
'Deine E-Mail-Adresse wird *niemals auf dem Server gespeichert* und nur im Falle einer Wiederherstellung an ihn gesendet.';
@override
String passwordlessRecoveryThresholdDesc(num count) {
return 'Um dein Konto wiederherzustellen, brauchst du $count deiner ausgewählten Freunde.';
}
@override
String get passwordlessRecoveryThresholdTitle =>
'Benötigte Freunde für das Backup';
@override
String passwordlessRecoverySelectFriendsNeeded(num count) {
return 'Freunde auswählen ($count weitere benötigt)';
}
@override
String get passwordlessRecoverySelectFriends => 'Freunde auswählen';
@override
String get passwordlessRecoveryNoFriendsSelected =>
'Noch keine Freunde ausgewählt';
@override
String get passwordlessRecoverySubtitle =>
'Stelle deine Identität ohne Passwort wieder her.';
@override
String get recoverPasswordlessExplanation =>
'Wenn du die passwortlose Wiederherstellung aktiviert hast, kannst du dein Konto mithilfe deiner Freunde wiederherstellen.';
@override
String get recoverPasswordlessNotificationCardTitle =>
'Benachrichtigungen aktivieren';
@override
String get recoverPasswordlessNotificationCardSubtitle =>
'Lass dich benachrichtigen, wenn ein Freund dir geholfen hat, dein Konto wiederherzustellen.';
@override
String get recoverPasswordlessNotificationCardBtn =>
'Push-Benachrichtigungen aktivieren';
@override
String get recoverPasswordlessQrInstructions =>
'Lass deine Freunde den QR-Code scannen oder teile den Link.';
@override
String get recoverPasswordlessShareBtn => 'Wiederherstellungs-Link teilen';
@override
String get recoverPasswordlessCopyBtn => 'Kopieren';
@override
String get recoverPasswordlessCopiedSnackbar =>
'Link in die Zwischenablage kopiert!';
@override
String get passwordlessRecoveryNoShareStored =>
'Kein Wiederherstellungs-Teil für diesen Kontakt gespeichert.';
@override
String get passwordlessRecoveryShareSent =>
'Wiederherstellungs-Teil erfolgreich gesendet!';
@override
String get passwordlessRecoveryNetworkError =>
'Netzwerkfehler, bitte stelle sicher, dass du Internet hast';
@override
String get passwordlessRecoveryInvalidEmail =>
'Die E-Mail-Adresse ist ungültig.';
@override
String get passwordlessRecoveryResendEmail => 'E-Mail erneut senden';
@override
String get passwordlessRecoveryHelpAFriend => 'Einem Freund helfen';
@override
String get passwordlessRecoverySelectContactDesc =>
'Wähle den Kontakt aus, dem du helfen möchtest.';
@override
String get passwordlessRecoverySearchContacts => 'Kontakte suchen...';
@override
String get passwordlessRecoveryNoContactsFound => 'Keine Kontakte gefunden';
@override
String get passwordlessRecoveryCantHelpHim => 'Du kannst ihm nicht helfen';
@override
String passwordlessRecoveryDoesAskedYou(Object username) {
return 'Hat $username dich gefragt?';
}
@override
String get passwordlessRecoveryVerifySourceDesc =>
'Bitte stelle sicher, dass du den Link/QR-Code tatsächlich von deinem Freund erhalten hast! Wenn du dir unsicher bist, verifiziere dies bitte erneut und klicke dann noch einmal auf den Link oder kehre zurück.';
@override
String get passwordlessRecoveryNo => 'Nein';
@override
String get passwordlessRecoveryYes => 'Ja';
@override
String passwordlessRecoveryYesWithTimer(Object seconds) {
return 'Ja (${seconds}s)';
}
@override
String get recoverPasswordlessBeingRecoveredLabel =>
'Konto wird wiederhergestellt';
@override
String recoverPasswordlessSharesReceived(Object received, Object threshold) {
return '$received von $threshold Freunden haben geteilt';
}
@override
String get recoverPasswordlessRecoverNowBtn => 'Jetzt wiederherstellen';
}

View file

@ -32,12 +32,22 @@ class AppLocalizationsEn extends AppLocalizations {
String get onboardingFocusBody =>
'Say goodbye to addictive features! twonly was created for sharing moments, free from useless distractions or ads.';
@override
String get onboardingSendTwonliesTitle => 'Send twonlies';
@override
String get onboardingSendTwonliesBody =>
'Share moments securely with your partner. twonly ensures that only your partner can open it, keeping your moments with your partner a two(o)nly thing!';
@override
String get onboardingNotProductTitle => 'You are not the product!';
@override
String get onboardingNotProductBody =>
'twonly is financed by an optional subscription. Your data will never be sold.';
'twonly is financed by donations and an optional subscription. Your data will never be sold.';
@override
String get onboardingGetStartedTitle => 'Let\'s go!';
@override
String get registerUsernameSlogan => 'Create your account';
@ -115,9 +125,17 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get searchUsernameNotFound => 'Username not found';
@override
String searchUsernameNotFoundBody(Object username) {
return 'There is no user with the username \"$username\" registered';
}
@override
String get searchUsernameNewFollowerTitle => 'Open requests';
@override
String get chatListViewSearchUserNameBtn => 'Add your first twonly contact!';
@override
String get chatListDetailInput => 'Type a message';
@ -208,6 +226,10 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get settingsStorageDataStoreInGTitle => 'Store in Gallery';
@override
String get settingsStorageDataStoreInGSubtitle =>
'Store saved images additional in the systems gallery.';
@override
String get settingsStorageDataMediaAutoDownload => 'Media auto-download';
@ -254,17 +276,45 @@ class AppLocalizationsEn extends AppLocalizations {
String get settingsPrivacy => 'Privacy & Security';
@override
String get settingsPrivacyBlockUsers => 'Block contacts';
String get settingsPrivacyBlockUsers => 'Block users';
@override
String get settingsPrivacyBlockUsersDesc =>
'Blocked contacts will not be able to communicate with you. You can unblock a blocked contact at any time.';
'Blocked users will not be able to communicate with you. You can unblock a blocked user at any time.';
@override
String settingsPrivacyBlockUsersCount(Object len) {
return '$len contact(s)';
}
@override
String get settingsPrivacyProfileSelectionTitle => 'Security Profile';
@override
String get settingsPrivacyProfileSelectionDesc =>
'Choose your setup path and security configuration';
@override
String get securityProfileTitle => 'Security Profile';
@override
String get securityProfileSubtitle =>
'Choose the level of protection that fits your daily use. This can be changed at any time in your settings.';
@override
String get securityProfileNormalTitle => 'Normal Protection';
@override
String get securityProfileNormalDesc =>
'Good balance between a convenient mode without bothering you too much.';
@override
String get securityProfileStrictTitle => 'Strict Protection';
@override
String get securityProfileStrictDesc =>
'Maximum anti-phishing protection but may be inconvenient.';
@override
String get settingsNotification => 'Notification';
@ -405,45 +455,16 @@ class AppLocalizationsEn extends AppLocalizations {
'Your account will be deleted. There is no change to restore it.';
@override
String get contactVerifyNumberTitle => 'Verify contacts';
String get contactVerifyNumberTitle => 'Verify contact';
@override
String verifyUserIdentity(Object username) {
return 'Verify $username\'s identity';
}
@override
String get contactVerifyNumberSubtitle =>
'Verify the identity of your contacts to make sure you are texting the right person.';
@override
String get inChatContactNotVerified => 'Contact not verified.';
@override
String groupMembersNotVerified(Object count) {
return '$count members are not verified.';
}
@override
String get userVerifiedTitle => 'Contact verified';
@override
String scanUserQrCode(Object username) {
return 'Scan $username\'s QR code';
}
@override
String get openOwnQrCode => 'Open your own QR code';
String get userVerifiedTitle => 'User verified';
@override
String contactVerifiedBy(Object username) {
return 'Verified by $username';
}
@override
String get contactSharedByUnknown =>
'Shared by a verified contact (username not available)';
@override
String get verificationTypeQrScanned => 'You scanned their QR code.';
@ -482,10 +503,10 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get contactBlockBody =>
'A blocked contact will no longer be able to send you messages and their profile will be hidden from view. To unblock a contact, simply navigate to Settings > Privacy > Blocked Contacts.';
'A blocked user will no longer be able to send you messages and their profile will be hidden from view. To unblock a user, simply navigate to Settings > Privacy > Blocked Users.';
@override
String get contactRemove => 'Remove contact';
String get contactRemove => 'Remove user';
@override
String contactRemoveTitle(Object username) {
@ -494,7 +515,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get contactRemoveBody =>
'Permanently remove the contact. If the contact tries to send you a new message, you will have to accept the contact again first.';
'Permanently remove the user. If the user tries to send you a new message, you will have to accept the user again first.';
@override
String get undo => 'Undo';
@ -835,13 +856,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get backupChangePassword => 'Change password';
@override
String get backupChangePasswordAuthReason => 'Changing backup password';
@override
String get backupChangePasswordAuthFailed =>
'You can only change your password after you have authenticated!';
@override
String get twonlySafeRecoverTitle => 'Restore backup';
@ -890,7 +904,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get reportUserReason => 'Reporting reason';
@override
String get reportUser => 'Report contact';
String get reportUser => 'Report user';
@override
String get newDeviceRegistered =>
@ -1024,9 +1038,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get alreadyInGroup => 'Already in Group';
@override
String get contactNotVerified => 'Not verified';
@override
String removeContactFromGroupTitle(Object username) {
return 'Remove $username from this group?';
@ -1287,6 +1298,12 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get draftMessage => 'Draft';
@override
String get exportMemories => 'Export memories (Beta)';
@override
String get importMemories => 'Import memories (Beta)';
@override
String get voiceMessageSlideToCancel => 'Slide to cancel';
@ -1306,11 +1323,6 @@ class AppLocalizationsEn extends AppLocalizations {
String get addContactQrSheetSubtext =>
'Let a friend scan this QR code to add you';
@override
String letUserScanQrCode(Object username) {
return 'Let $username scan this QR code';
}
@override
String get finishSetupCardTitle => 'Complete your profile';
@ -1434,7 +1446,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String verifiedPublicKey(Object username) {
return 'The identity of $username has been successfully verified.';
return 'The public key of $username has been verified and is valid.';
}
@override
@ -1531,11 +1543,11 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get verificationBadgeGeneralDesc =>
'The badge gives you the peace of mind that you are messaging the *right person* and your *messages remain confidential*.';
'The checkmark gives you the certainty that you are messaging the right person. You can verify contacts at any time by scanning their QR code.';
@override
String get verificationBadgeGreenDesc =>
'A contact you have *personally verified* using the QR code.';
'A contact you have *personally* verified.';
@override
String get verificationBadgeYellowDesc =>
@ -1545,25 +1557,6 @@ class AppLocalizationsEn extends AppLocalizations {
String get verificationBadgeRedDesc =>
'A contact whose identity has *not* yet been verified.';
@override
String get scanNow => 'Scan now';
@override
String get qrScannerVerifyHint =>
'To verify, the other person must open their QR code (Chat Lists > QR code button at the bottom right)';
@override
String qrScannerVerifyUserHint(Object username) {
return 'To verify, $username must open their QR code (Chat Lists > QR code button at the bottom right)';
}
@override
String get openQrCode => 'Open QR code';
@override
String get letFriendScanQrToVerify =>
'Let a friend scan this QR code to verify you';
@override
String get deleteVerificationTitle => 'Delete verification?';
@ -1576,11 +1569,6 @@ class AppLocalizationsEn extends AppLocalizations {
return '$username has scanned your QR code and is now verified.';
}
@override
String askForFriendPromotionsPrompt(Object username) {
return 'Help $username find familiar faces on twonly by sharing mutual friends.';
}
@override
String mutualGroupsTitle(num count) {
String _temp0 = intl.Intl.pluralLogic(
@ -1592,6 +1580,17 @@ class AppLocalizationsEn extends AppLocalizations {
return '$_temp0';
}
@override
String mutualGroupsSentMessages(num count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: '$count messages sent',
one: '1 message sent',
);
return '$_temp0';
}
@override
String chatEntryFlameRestored(Object count) {
return '$count flames restored';
@ -1637,6 +1636,9 @@ class AppLocalizationsEn extends AppLocalizations {
String get settingsTypingIndicationSubtitle =>
'When the typing indicator is turned off, you can\'t see when others are typing a message.';
@override
String get scanQrOrShow => 'Scan / Show QR';
@override
String get contactActionBlock => 'Block';
@ -1668,6 +1670,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get userDiscoverySettingsTitle => 'Mutual Friends';
@override
String get userDiscoveryWhyThisIsUsed => 'Why this is used';
@override
String get userDiscoveryFeatureOffers => 'Your benefits at a glance';
@ -1835,6 +1840,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get back => 'Back';
@override
String get onboardingExampleLabel => 'Example';
@override
String makerChangedUsername(Object maker, Object oldName, Object newName) {
return '$maker changed their username from $oldName to $newName.';
@ -1934,6 +1942,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get dragToZoom => 'Drag to Zoom';
@override
String get showUsername => 'Show username';
@override
String get onboardingProfileSelectionTitle => 'Choose your setup path';
@ -2068,6 +2079,18 @@ class AppLocalizationsEn extends AppLocalizations {
String get importGalleryNoImagesFoundDesc =>
'There are no images on your device.';
@override
String get importGalleryShowAllImages => 'Show all images';
@override
String get importGalleryShowTwonlyAlbum => 'Show twonly album';
@override
String get importGalleryToggleDescAll => 'Viewing all images on your device.';
@override
String get importGalleryToggleDescTwonly => 'Viewing the \"twonly\" album.';
@override
String get importGalleryFilterTwonly => 'Only show the twonly-Album';
@ -2109,270 +2132,4 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get avatarCustomizeReset => 'Reset';
@override
String get passwordlessRecovery => 'Passwordless Recovery';
@override
String get passwordlessRecoveryNotConfigured => 'Not configured';
@override
String get passwordlessRecoveryTestPin => 'Test PIN';
@override
String get passwordlessRecoveryTestPinTitle => 'Test PIN';
@override
String get passwordlessRecoveryTestPinHint => 'Enter your PIN';
@override
String get passwordlessRecoveryTestPinCorrect => 'PIN is correct!';
@override
String get passwordlessRecoveryTestPinIncorrect => 'Incorrect PIN.';
@override
String get passwordlessRecoveryTest => 'Test';
@override
String get passwordlessRecoverySecondFactorNone => 'None';
@override
String passwordlessRecoverySecondFactorEmailLabel(Object email) {
return 'Email ($email)';
}
@override
String get passwordlessRecoverySecondFactorPin => 'PIN';
@override
String get passwordlessRecoverySecondFactorEmail => 'Email';
@override
String get passwordlessRecoveryModify => 'Modify Recovery Settings';
@override
String get passwordlessRecoveryModifyDesc =>
'Update your recovery configuration';
@override
String get passwordlessRecoverySecondFactor => 'Second Factor';
@override
String get passwordlessRecoveryNoFriendsFound => 'No trusted friends found.';
@override
String get passwordlessRecoveryActiveFriends => 'Active Friends';
@override
String get passwordlessRecoveryActiveFriendsDesc =>
'These trusted friends are actively using twonly and can probably help you recover your account.';
@override
String get passwordlessRecoveryInactiveFriends => 'Inactive Friends';
@override
String get passwordlessRecoveryInactiveFriendsDesc =>
'These friends either have not yet received their share or do not use twonly actively anymore, which could mean that they maybe cannot help.';
@override
String get passwordlessRecoveryNotEnoughFriends =>
'Not enough friends selected. You need at least 3.';
@override
String get passwordlessRecoveryLoading => 'Loading...';
@override
String get passwordlessRecoveryEnableSuccess =>
'Passwordless recovery successfully enabled!';
@override
String get passwordlessRecoveryEnableFailed =>
'Failed to enable passwordless recovery.';
@override
String get passwordlessRecoveryNeedAtLeast3 =>
'You need at least 3 trusted friends.';
@override
String get passwordlessRecoveryInvalidPin => 'Invalid PIN';
@override
String get passwordlessRecoveryEnterPin => 'Please enter a PIN.';
@override
String get passwordlessRecoveryPinMinLength =>
'PIN must be at least 4 digits.';
@override
String get passwordlessRecoveryEnterEmail => 'Please enter an email address.';
@override
String get passwordlessRecoveryEnableBtn => 'Enable Passwordless Recovery';
@override
String get passwordlessRecoveryRecoverBtn => 'Recover passwordless';
@override
String get passwordlessRecoveryModifyBtn => 'Modify Passwordless Recovery';
@override
String passwordlessRecoveryStatusEnabled(num count) {
return 'Enabled • $count trusted friends';
}
@override
String get passwordlessRecoveryInfoHowItWorks => 'How it works';
@override
String get passwordlessRecoveryInfoHowItWorksDesc =>
'Because twonly operates without central user accounts or phone numbers to maximize privacy, we rely on a decentralized recovery mechanism. Using Shamir\'s Secret Sharing, your cryptographic identity is split into independent shares and distributed among your trusted friends. To restore access, a predefined threshold of these friends must combine their shares.';
@override
String get passwordlessRecoveryInfoWhySecondFactor => 'Why a Second Factor?';
@override
String get passwordlessRecoveryInfoWhySecondFactorDesc =>
'The second factor (Email or PIN) serves as a vital cryptographic safeguard against malicious collusion. If your trusted friends were to coordinate their shares behind your back, they still wouldn\'t be able to decrypt your identity without the second factor key.';
@override
String get passwordlessRecoveryInfoGotIt => 'Got it';
@override
String get passwordlessRecoveryMethod => 'Second factor method';
@override
String get passwordlessRecoveryMethodNoneDesc =>
'Without second-factor, your friends could collaborate to recover your account. Therefore, it is recommended to configure a second-factor.';
@override
String get passwordlessRecoveryMethodPinHint => 'Enter PIN';
@override
String get passwordlessRecoveryMethodEmailHint =>
'Enter recovery email address';
@override
String get passwordlessRecoveryMethodEmailDesc =>
'Your email address is *never stored on the server* and is only sent to it in the event of a recovery.';
@override
String passwordlessRecoveryThresholdDesc(num count) {
return 'To recover your account you need $count of your selected trusted friends.';
}
@override
String get passwordlessRecoveryThresholdTitle =>
'Required trusted friends for recovery';
@override
String passwordlessRecoverySelectFriendsNeeded(num count) {
return 'Select friends ($count more needed)';
}
@override
String get passwordlessRecoverySelectFriends => 'Select trusted friends';
@override
String get passwordlessRecoveryNoFriendsSelected =>
'No trusted friends selected yet';
@override
String get passwordlessRecoverySubtitle =>
'Recover your identity without a password.';
@override
String get recoverPasswordlessExplanation =>
'If enabled, you can recover your account by asking your selected friends to help you.';
@override
String get recoverPasswordlessNotificationCardTitle => 'Enable notifications';
@override
String get recoverPasswordlessNotificationCardSubtitle =>
'Get notified when a friend has helped you recover your account.';
@override
String get recoverPasswordlessNotificationCardBtn =>
'Enable push notifications';
@override
String get recoverPasswordlessQrInstructions =>
'Let friends scan the QR code or share the link.';
@override
String get recoverPasswordlessShareBtn => 'Share Recovery Link';
@override
String get recoverPasswordlessCopyBtn => 'Copy';
@override
String get recoverPasswordlessCopiedSnackbar => 'Link copied to clipboard!';
@override
String get passwordlessRecoveryNoShareStored =>
'No recovery share stored for this contact.';
@override
String get passwordlessRecoveryShareSent =>
'Recovery share successfully sent!';
@override
String get passwordlessRecoveryNetworkError =>
'Network error, please ensure you have internet';
@override
String get passwordlessRecoveryInvalidEmail =>
'The email address is invalid.';
@override
String get passwordlessRecoveryResendEmail => 'Resend recovery email';
@override
String get passwordlessRecoveryHelpAFriend => 'Help a Friend';
@override
String get passwordlessRecoverySelectContactDesc =>
'Please select the contact who requested recovery below to send them their recovery key.';
@override
String get passwordlessRecoverySearchContacts => 'Search contacts...';
@override
String get passwordlessRecoveryNoContactsFound => 'No contacts found';
@override
String get passwordlessRecoveryCantHelpHim => 'You can\'t help him';
@override
String passwordlessRecoveryDoesAskedYou(Object username) {
return 'Does $username has asked you?';
}
@override
String get passwordlessRecoveryVerifySourceDesc =>
'Please ensure that you actualy received the link/qr code from your friend! If you are unsure, please verify again and then click again on the link or come back.';
@override
String get passwordlessRecoveryNo => 'No';
@override
String get passwordlessRecoveryYes => 'Yes';
@override
String passwordlessRecoveryYesWithTimer(Object seconds) {
return 'Yes (${seconds}s)';
}
@override
String get recoverPasswordlessBeingRecoveredLabel =>
'Account being recovered';
@override
String recoverPasswordlessSharesReceived(Object received, Object threshold) {
return '$received of $threshold friends have shared';
}
@override
String get recoverPasswordlessRecoverNowBtn => 'Recover now';
}

@ -1 +1 @@
Subproject commit b7d703c82a5ac0b6776847cb4eb6d637620f6585
Subproject commit c95e98ca929d630ead028d84e13934b30dbeba3b

View file

@ -1,65 +0,0 @@
import 'package:json_annotation/json_annotation.dart';
part 'onboarding_state.model.g.dart';
/// Holds information about a single trusted friend who has already shared their
/// recovery part.
@JsonSerializable()
class ReceivedRecoveryShare {
ReceivedRecoveryShare({
required this.messageId,
required this.trustedFriendDisplayName,
required this.myDisplayName,
required this.myUserId,
required this.myAvatarSvg,
required this.threshold,
required this.sharedSecretDataBytes,
});
factory ReceivedRecoveryShare.fromJson(Map<String, dynamic> json) =>
_$ReceivedRecoveryShareFromJson(json);
final int messageId;
final String trustedFriendDisplayName;
final String myDisplayName;
final int myUserId;
final List<int>? myAvatarSvg;
final int threshold;
final List<int> sharedSecretDataBytes;
Map<String, dynamic> toJson() => _$ReceivedRecoveryShareToJson(this);
}
@JsonSerializable()
class OnboardingState {
OnboardingState({
this.hasOnboardingFinished = false,
this.hasStartedPasswordlessRecovery = false,
this.notificationId,
this.downloadAuthToken,
this.serverRegistered = false,
this.encryptionKey,
this.emailRecoveryRequested = false,
});
factory OnboardingState.fromJson(Map<String, dynamic> json) =>
_$OnboardingStateFromJson(json);
bool hasOnboardingFinished;
bool hasStartedPasswordlessRecovery;
String? notificationId;
List<int>? downloadAuthToken;
bool serverRegistered;
List<int>? encryptionKey;
bool emailRecoveryRequested;
List<ReceivedRecoveryShare> receivedShares = [];
Map<String, dynamic> toJson() => _$OnboardingStateToJson(this);
}

View file

@ -1,67 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'onboarding_state.model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ReceivedRecoveryShare _$ReceivedRecoveryShareFromJson(
Map<String, dynamic> json,
) => ReceivedRecoveryShare(
messageId: (json['messageId'] as num).toInt(),
trustedFriendDisplayName: json['trustedFriendDisplayName'] as String,
myDisplayName: json['myDisplayName'] as String,
myUserId: (json['myUserId'] as num).toInt(),
myAvatarSvg: (json['myAvatarSvg'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList(),
threshold: (json['threshold'] as num).toInt(),
sharedSecretDataBytes: (json['sharedSecretDataBytes'] as List<dynamic>)
.map((e) => (e as num).toInt())
.toList(),
);
Map<String, dynamic> _$ReceivedRecoveryShareToJson(
ReceivedRecoveryShare instance,
) => <String, dynamic>{
'messageId': instance.messageId,
'trustedFriendDisplayName': instance.trustedFriendDisplayName,
'myDisplayName': instance.myDisplayName,
'myUserId': instance.myUserId,
'myAvatarSvg': instance.myAvatarSvg,
'threshold': instance.threshold,
'sharedSecretDataBytes': instance.sharedSecretDataBytes,
};
OnboardingState _$OnboardingStateFromJson(Map<String, dynamic> json) =>
OnboardingState(
hasOnboardingFinished: json['hasOnboardingFinished'] as bool? ?? false,
hasStartedPasswordlessRecovery:
json['hasStartedPasswordlessRecovery'] as bool? ?? false,
notificationId: json['notificationId'] as String?,
downloadAuthToken: (json['downloadAuthToken'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList(),
serverRegistered: json['serverRegistered'] as bool? ?? false,
encryptionKey: (json['encryptionKey'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList(),
emailRecoveryRequested:
json['emailRecoveryRequested'] as bool? ?? false,
)
..receivedShares = (json['receivedShares'] as List<dynamic>)
.map((e) => ReceivedRecoveryShare.fromJson(e as Map<String, dynamic>))
.toList();
Map<String, dynamic> _$OnboardingStateToJson(OnboardingState instance) =>
<String, dynamic>{
'hasOnboardingFinished': instance.hasOnboardingFinished,
'hasStartedPasswordlessRecovery': instance.hasStartedPasswordlessRecovery,
'notificationId': instance.notificationId,
'downloadAuthToken': instance.downloadAuthToken,
'serverRegistered': instance.serverRegistered,
'encryptionKey': instance.encryptionKey,
'emailRecoveryRequested': instance.emailRecoveryRequested,
'receivedShares': instance.receivedShares,
};

View file

@ -40,6 +40,9 @@ class UserData {
@JsonKey(defaultValue: SetupProfile.standard)
SetupProfile setupProfile = SetupProfile.standard;
@JsonKey(defaultValue: SecurityProfile.normal)
SecurityProfile securityProfile = SecurityProfile.normal;
// --- SUBSCRIPTION DTA ---
@JsonKey(defaultValue: 'Free')
@ -125,9 +128,6 @@ class UserData {
// -- Custom DATA --
@JsonKey(defaultValue: true)
bool askForFriendPromotions = true;
@JsonKey(defaultValue: 100_000)
int currentPreKeyIndexStart = 100_000;
@ -153,8 +153,6 @@ class UserData {
@JsonKey(defaultValue: false)
bool isBackupEnabled = false;
PasswordLessRecovery? passwordLessRecovery;
// Used for push notifcation via FCM.
String? fcmToken;
@ -202,34 +200,3 @@ class TwonlySafeBackup {
List<int> encryptionKey;
Map<String, dynamic> toJson() => _$TwonlySafeBackupToJson(this);
}
@JsonSerializable()
class PasswordLessRecovery {
PasswordLessRecovery(this.threshold);
factory PasswordLessRecovery.fromJson(Map<String, dynamic> json) =>
_$PasswordLessRecoveryFromJson(json);
// Only stored, so the user can see his deposit email address...
String? email;
// <--
// Data shared with trusted friends
int threshold;
// Trusted friends are able to brute-force the pin -> Server delets after X tries
List<int>? pinSeed;
List<int>? pinUnlockToken;
// Stored not on the server, so the server is unable to link a email to a user until the actuall recovery or can
// brute-force the pin
List<int>? encryptedServerKeyNonce;
// --->
// Checking with the server that the server data is valid and not delted throug the pin protection for example.
DateTime? lastServerHeartbeat;
DateTime? lastContactHeartbeat;
List<int>? encryptedServerKey;
Map<String, dynamic> toJson() => _$PasswordLessRecoveryToJson(this);
}

View file

@ -23,6 +23,12 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
..setupProfile =
$enumDecodeNullable(_$SetupProfileEnumMap, json['setupProfile']) ??
SetupProfile.standard
..securityProfile =
$enumDecodeNullable(
_$SecurityProfileEnumMap,
json['securityProfile'],
) ??
SecurityProfile.normal
..subscriptionPlanIdStore = json['subscriptionPlanIdStore'] as String?
..lastImageSend = json['lastImageSend'] == null
? null
@ -79,7 +85,6 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
json['userDiscoverySharePromotion'] as bool? ?? true
..userDiscoveryInitializationError =
json['userDiscoveryInitializationError'] as bool? ?? false
..askForFriendPromotions = json['askForFriendPromotions'] as bool? ?? true
..currentPreKeyIndexStart =
(json['currentPreKeyIndexStart'] as num?)?.toInt() ?? 100000
..currentSignedPreKeyIndexStart =
@ -97,11 +102,6 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
json['twonlySafeBackup'] as Map<String, dynamic>,
)
..isBackupEnabled = json['isBackupEnabled'] as bool? ?? false
..passwordLessRecovery = json['passwordLessRecovery'] == null
? null
: PasswordLessRecovery.fromJson(
json['passwordLessRecovery'] as Map<String, dynamic>,
)
..fcmToken = json['fcmToken'] as String?
..askedForUserStudyPermission =
json['askedForUserStudyPermission'] as bool? ?? false
@ -126,6 +126,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
'isDeveloper': instance.isDeveloper,
'deviceId': instance.deviceId,
'setupProfile': _$SetupProfileEnumMap[instance.setupProfile]!,
'securityProfile': _$SecurityProfileEnumMap[instance.securityProfile]!,
'subscriptionPlan': instance.subscriptionPlan,
'subscriptionPlanIdStore': instance.subscriptionPlanIdStore,
'lastImageSend': instance.lastImageSend?.toIso8601String(),
@ -160,7 +161,6 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
instance.userDiscoveryRequiresManualApproval,
'userDiscoverySharePromotion': instance.userDiscoverySharePromotion,
'userDiscoveryInitializationError': instance.userDiscoveryInitializationError,
'askForFriendPromotions': instance.askForFriendPromotions,
'currentPreKeyIndexStart': instance.currentPreKeyIndexStart,
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
'lastChangeLogHash': instance.lastChangeLogHash,
@ -169,7 +169,6 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
'canUseLoginTokenForAuth': instance.canUseLoginTokenForAuth,
'twonlySafeBackup': instance.twonlySafeBackup,
'isBackupEnabled': instance.isBackupEnabled,
'passwordLessRecovery': instance.passwordLessRecovery,
'fcmToken': instance.fcmToken,
'askedForUserStudyPermission': instance.askedForUserStudyPermission,
'userStudyParticipantsToken': instance.userStudyParticipantsToken,
@ -185,6 +184,12 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
const _$SetupProfileEnumMap = {
SetupProfile.standard: 'standard',
SetupProfile.customized: 'customized',
SetupProfile.maximum: 'maximum',
};
const _$SecurityProfileEnumMap = {
SecurityProfile.normal: 'normal',
SecurityProfile.strict: 'strict',
};
const _$ThemeModeEnumMap = {
@ -227,40 +232,3 @@ const _$LastBackupUploadStateEnumMap = {
LastBackupUploadState.failed: 'failed',
LastBackupUploadState.success: 'success',
};
PasswordLessRecovery _$PasswordLessRecoveryFromJson(
Map<String, dynamic> json,
) => PasswordLessRecovery((json['threshold'] as num).toInt())
..email = json['email'] as String?
..pinSeed = (json['pinSeed'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList()
..pinUnlockToken = (json['pinUnlockToken'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList()
..encryptedServerKeyNonce =
(json['encryptedServerKeyNonce'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList()
..lastServerHeartbeat = json['lastServerHeartbeat'] == null
? null
: DateTime.parse(json['lastServerHeartbeat'] as String)
..lastContactHeartbeat = json['lastContactHeartbeat'] == null
? null
: DateTime.parse(json['lastContactHeartbeat'] as String)
..encryptedServerKey = (json['encryptedServerKey'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList();
Map<String, dynamic> _$PasswordLessRecoveryToJson(
PasswordLessRecovery instance,
) => <String, dynamic>{
'email': instance.email,
'threshold': instance.threshold,
'pinSeed': instance.pinSeed,
'pinUnlockToken': instance.pinUnlockToken,
'encryptedServerKeyNonce': instance.encryptedServerKeyNonce,
'lastServerHeartbeat': instance.lastServerHeartbeat?.toIso8601String(),
'lastContactHeartbeat': instance.lastContactHeartbeat?.toIso8601String(),
'encryptedServerKey': instance.encryptedServerKey,
};

View file

@ -809,301 +809,6 @@ class Handshake_AuthenticateWithLoginToken extends $pb.GeneratedMessage {
void clearInBackground() => $_clearField(5);
}
class Handshake_GetServerKeyForPasswordLessRecovery
extends $pb.GeneratedMessage {
factory Handshake_GetServerKeyForPasswordLessRecovery({
$fixnum.Int64? userId,
$core.List<$core.int>? encryptedServerKeyNone,
$core.List<$core.int>? pinUnlockToken,
$core.List<$core.int>? pinProtectionKey,
$core.String? email,
}) {
final result = create();
if (userId != null) result.userId = userId;
if (encryptedServerKeyNone != null)
result.encryptedServerKeyNone = encryptedServerKeyNone;
if (pinUnlockToken != null) result.pinUnlockToken = pinUnlockToken;
if (pinProtectionKey != null) result.pinProtectionKey = pinProtectionKey;
if (email != null) result.email = email;
return result;
}
Handshake_GetServerKeyForPasswordLessRecovery._();
factory Handshake_GetServerKeyForPasswordLessRecovery.fromBuffer(
$core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory Handshake_GetServerKeyForPasswordLessRecovery.fromJson(
$core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'Handshake.GetServerKeyForPasswordLessRecovery',
package:
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
createEmptyInstance: create)
..aInt64(1, _omitFieldNames ? '' : 'userId')
..a<$core.List<$core.int>>(
2, _omitFieldNames ? '' : 'encryptedServerKeyNone', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(
3, _omitFieldNames ? '' : 'pinUnlockToken', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(
4, _omitFieldNames ? '' : 'pinProtectionKey', $pb.PbFieldType.OY)
..aOS(5, _omitFieldNames ? '' : 'email')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
Handshake_GetServerKeyForPasswordLessRecovery clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
Handshake_GetServerKeyForPasswordLessRecovery copyWith(
void Function(Handshake_GetServerKeyForPasswordLessRecovery)
updates) =>
super.copyWith((message) =>
updates(message as Handshake_GetServerKeyForPasswordLessRecovery))
as Handshake_GetServerKeyForPasswordLessRecovery;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static Handshake_GetServerKeyForPasswordLessRecovery create() =>
Handshake_GetServerKeyForPasswordLessRecovery._();
@$core.override
Handshake_GetServerKeyForPasswordLessRecovery createEmptyInstance() =>
create();
@$core.pragma('dart2js:noInline')
static Handshake_GetServerKeyForPasswordLessRecovery getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
Handshake_GetServerKeyForPasswordLessRecovery>(create);
static Handshake_GetServerKeyForPasswordLessRecovery? _defaultInstance;
@$pb.TagNumber(1)
$fixnum.Int64 get userId => $_getI64(0);
@$pb.TagNumber(1)
set userId($fixnum.Int64 value) => $_setInt64(0, value);
@$pb.TagNumber(1)
$core.bool hasUserId() => $_has(0);
@$pb.TagNumber(1)
void clearUserId() => $_clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get encryptedServerKeyNone => $_getN(1);
@$pb.TagNumber(2)
set encryptedServerKeyNone($core.List<$core.int> value) =>
$_setBytes(1, value);
@$pb.TagNumber(2)
$core.bool hasEncryptedServerKeyNone() => $_has(1);
@$pb.TagNumber(2)
void clearEncryptedServerKeyNone() => $_clearField(2);
@$pb.TagNumber(3)
$core.List<$core.int> get pinUnlockToken => $_getN(2);
@$pb.TagNumber(3)
set pinUnlockToken($core.List<$core.int> value) => $_setBytes(2, value);
@$pb.TagNumber(3)
$core.bool hasPinUnlockToken() => $_has(2);
@$pb.TagNumber(3)
void clearPinUnlockToken() => $_clearField(3);
@$pb.TagNumber(4)
$core.List<$core.int> get pinProtectionKey => $_getN(3);
@$pb.TagNumber(4)
set pinProtectionKey($core.List<$core.int> value) => $_setBytes(3, value);
@$pb.TagNumber(4)
$core.bool hasPinProtectionKey() => $_has(3);
@$pb.TagNumber(4)
void clearPinProtectionKey() => $_clearField(4);
@$pb.TagNumber(5)
$core.String get email => $_getSZ(4);
@$pb.TagNumber(5)
set email($core.String value) => $_setString(4, value);
@$pb.TagNumber(5)
$core.bool hasEmail() => $_has(4);
@$pb.TagNumber(5)
void clearEmail() => $_clearField(5);
}
class Handshake_RegisterPasswordlessNotification extends $pb.GeneratedMessage {
factory Handshake_RegisterPasswordlessNotification({
$core.String? notificationId,
$core.List<$core.int>? downloadAuthToken,
$core.String? langCode,
$core.String? googleFcm,
}) {
final result = create();
if (notificationId != null) result.notificationId = notificationId;
if (downloadAuthToken != null) result.downloadAuthToken = downloadAuthToken;
if (langCode != null) result.langCode = langCode;
if (googleFcm != null) result.googleFcm = googleFcm;
return result;
}
Handshake_RegisterPasswordlessNotification._();
factory Handshake_RegisterPasswordlessNotification.fromBuffer(
$core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory Handshake_RegisterPasswordlessNotification.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'Handshake.RegisterPasswordlessNotification',
package:
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'notificationId')
..a<$core.List<$core.int>>(
2, _omitFieldNames ? '' : 'downloadAuthToken', $pb.PbFieldType.OY)
..aOS(3, _omitFieldNames ? '' : 'langCode')
..aOS(4, _omitFieldNames ? '' : 'googleFcm')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
Handshake_RegisterPasswordlessNotification clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
Handshake_RegisterPasswordlessNotification copyWith(
void Function(Handshake_RegisterPasswordlessNotification) updates) =>
super.copyWith((message) =>
updates(message as Handshake_RegisterPasswordlessNotification))
as Handshake_RegisterPasswordlessNotification;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static Handshake_RegisterPasswordlessNotification create() =>
Handshake_RegisterPasswordlessNotification._();
@$core.override
Handshake_RegisterPasswordlessNotification createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static Handshake_RegisterPasswordlessNotification getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
Handshake_RegisterPasswordlessNotification>(create);
static Handshake_RegisterPasswordlessNotification? _defaultInstance;
@$pb.TagNumber(1)
$core.String get notificationId => $_getSZ(0);
@$pb.TagNumber(1)
set notificationId($core.String value) => $_setString(0, value);
@$pb.TagNumber(1)
$core.bool hasNotificationId() => $_has(0);
@$pb.TagNumber(1)
void clearNotificationId() => $_clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get downloadAuthToken => $_getN(1);
@$pb.TagNumber(2)
set downloadAuthToken($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(2)
$core.bool hasDownloadAuthToken() => $_has(1);
@$pb.TagNumber(2)
void clearDownloadAuthToken() => $_clearField(2);
@$pb.TagNumber(3)
$core.String get langCode => $_getSZ(2);
@$pb.TagNumber(3)
set langCode($core.String value) => $_setString(2, value);
@$pb.TagNumber(3)
$core.bool hasLangCode() => $_has(2);
@$pb.TagNumber(3)
void clearLangCode() => $_clearField(3);
@$pb.TagNumber(4)
$core.String get googleFcm => $_getSZ(3);
@$pb.TagNumber(4)
set googleFcm($core.String value) => $_setString(3, value);
@$pb.TagNumber(4)
$core.bool hasGoogleFcm() => $_has(3);
@$pb.TagNumber(4)
void clearGoogleFcm() => $_clearField(4);
}
class Handshake_CheckForPasswordlessNotification extends $pb.GeneratedMessage {
factory Handshake_CheckForPasswordlessNotification({
$core.String? notificationId,
$core.List<$core.int>? downloadAuthToken,
$core.Iterable<$fixnum.Int64>? alreadyReceivedMessageIds,
}) {
final result = create();
if (notificationId != null) result.notificationId = notificationId;
if (downloadAuthToken != null) result.downloadAuthToken = downloadAuthToken;
if (alreadyReceivedMessageIds != null)
result.alreadyReceivedMessageIds.addAll(alreadyReceivedMessageIds);
return result;
}
Handshake_CheckForPasswordlessNotification._();
factory Handshake_CheckForPasswordlessNotification.fromBuffer(
$core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory Handshake_CheckForPasswordlessNotification.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'Handshake.CheckForPasswordlessNotification',
package:
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'notificationId')
..a<$core.List<$core.int>>(
2, _omitFieldNames ? '' : 'downloadAuthToken', $pb.PbFieldType.OY)
..p<$fixnum.Int64>(3, _omitFieldNames ? '' : 'alreadyReceivedMessageIds',
$pb.PbFieldType.K6)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
Handshake_CheckForPasswordlessNotification clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
Handshake_CheckForPasswordlessNotification copyWith(
void Function(Handshake_CheckForPasswordlessNotification) updates) =>
super.copyWith((message) =>
updates(message as Handshake_CheckForPasswordlessNotification))
as Handshake_CheckForPasswordlessNotification;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static Handshake_CheckForPasswordlessNotification create() =>
Handshake_CheckForPasswordlessNotification._();
@$core.override
Handshake_CheckForPasswordlessNotification createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static Handshake_CheckForPasswordlessNotification getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
Handshake_CheckForPasswordlessNotification>(create);
static Handshake_CheckForPasswordlessNotification? _defaultInstance;
@$pb.TagNumber(1)
$core.String get notificationId => $_getSZ(0);
@$pb.TagNumber(1)
set notificationId($core.String value) => $_setString(0, value);
@$pb.TagNumber(1)
$core.bool hasNotificationId() => $_has(0);
@$pb.TagNumber(1)
void clearNotificationId() => $_clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get downloadAuthToken => $_getN(1);
@$pb.TagNumber(2)
set downloadAuthToken($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(2)
$core.bool hasDownloadAuthToken() => $_has(1);
@$pb.TagNumber(2)
void clearDownloadAuthToken() => $_clearField(2);
@$pb.TagNumber(3)
$pb.PbList<$fixnum.Int64> get alreadyReceivedMessageIds => $_getList(2);
}
enum Handshake_Handshake {
register,
getAuthChallenge,
@ -1112,9 +817,6 @@ enum Handshake_Handshake {
requestPOW,
authenticateWithLoginToken,
getUseridByUsername,
getServerKeyForPasswordlessRecovery,
registerPasswordlessNotification,
checkForPasswordlessNotification,
notSet
}
@ -1127,12 +829,6 @@ class Handshake extends $pb.GeneratedMessage {
Handshake_RequestPOW? requestPOW,
Handshake_AuthenticateWithLoginToken? authenticateWithLoginToken,
Handshake_GetUserIdByUsername? getUseridByUsername,
Handshake_GetServerKeyForPasswordLessRecovery?
getServerKeyForPasswordlessRecovery,
Handshake_RegisterPasswordlessNotification?
registerPasswordlessNotification,
Handshake_CheckForPasswordlessNotification?
checkForPasswordlessNotification,
}) {
final result = create();
if (register != null) result.register = register;
@ -1144,15 +840,6 @@ class Handshake extends $pb.GeneratedMessage {
result.authenticateWithLoginToken = authenticateWithLoginToken;
if (getUseridByUsername != null)
result.getUseridByUsername = getUseridByUsername;
if (getServerKeyForPasswordlessRecovery != null)
result.getServerKeyForPasswordlessRecovery =
getServerKeyForPasswordlessRecovery;
if (registerPasswordlessNotification != null)
result.registerPasswordlessNotification =
registerPasswordlessNotification;
if (checkForPasswordlessNotification != null)
result.checkForPasswordlessNotification =
checkForPasswordlessNotification;
return result;
}
@ -1174,9 +861,6 @@ class Handshake extends $pb.GeneratedMessage {
5: Handshake_Handshake.requestPOW,
6: Handshake_Handshake.authenticateWithLoginToken,
7: Handshake_Handshake.getUseridByUsername,
8: Handshake_Handshake.getServerKeyForPasswordlessRecovery,
9: Handshake_Handshake.registerPasswordlessNotification,
10: Handshake_Handshake.checkForPasswordlessNotification,
0: Handshake_Handshake.notSet
};
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
@ -1184,7 +868,7 @@ class Handshake extends $pb.GeneratedMessage {
package:
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
createEmptyInstance: create)
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
..oo(0, [1, 2, 3, 4, 5, 6, 7])
..aOM<Handshake_Register>(1, _omitFieldNames ? '' : 'register',
subBuilder: Handshake_Register.create)
..aOM<Handshake_GetAuthChallenge>(
@ -1203,15 +887,6 @@ class Handshake extends $pb.GeneratedMessage {
..aOM<Handshake_GetUserIdByUsername>(
7, _omitFieldNames ? '' : 'getUseridByUsername',
subBuilder: Handshake_GetUserIdByUsername.create)
..aOM<Handshake_GetServerKeyForPasswordLessRecovery>(
8, _omitFieldNames ? '' : 'getServerKeyForPasswordlessRecovery',
subBuilder: Handshake_GetServerKeyForPasswordLessRecovery.create)
..aOM<Handshake_RegisterPasswordlessNotification>(
9, _omitFieldNames ? '' : 'registerPasswordlessNotification',
subBuilder: Handshake_RegisterPasswordlessNotification.create)
..aOM<Handshake_CheckForPasswordlessNotification>(
10, _omitFieldNames ? '' : 'checkForPasswordlessNotification',
subBuilder: Handshake_CheckForPasswordlessNotification.create)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@ -1239,9 +914,6 @@ class Handshake extends $pb.GeneratedMessage {
@$pb.TagNumber(5)
@$pb.TagNumber(6)
@$pb.TagNumber(7)
@$pb.TagNumber(8)
@$pb.TagNumber(9)
@$pb.TagNumber(10)
Handshake_Handshake whichHandshake() =>
_Handshake_HandshakeByTag[$_whichOneof(0)]!;
@$pb.TagNumber(1)
@ -1251,9 +923,6 @@ class Handshake extends $pb.GeneratedMessage {
@$pb.TagNumber(5)
@$pb.TagNumber(6)
@$pb.TagNumber(7)
@$pb.TagNumber(8)
@$pb.TagNumber(9)
@$pb.TagNumber(10)
void clearHandshake() => $_clearField($_whichOneof(0));
@$pb.TagNumber(1)
@ -1337,51 +1006,6 @@ class Handshake extends $pb.GeneratedMessage {
void clearGetUseridByUsername() => $_clearField(7);
@$pb.TagNumber(7)
Handshake_GetUserIdByUsername ensureGetUseridByUsername() => $_ensure(6);
@$pb.TagNumber(8)
Handshake_GetServerKeyForPasswordLessRecovery
get getServerKeyForPasswordlessRecovery => $_getN(7);
@$pb.TagNumber(8)
set getServerKeyForPasswordlessRecovery(
Handshake_GetServerKeyForPasswordLessRecovery value) =>
$_setField(8, value);
@$pb.TagNumber(8)
$core.bool hasGetServerKeyForPasswordlessRecovery() => $_has(7);
@$pb.TagNumber(8)
void clearGetServerKeyForPasswordlessRecovery() => $_clearField(8);
@$pb.TagNumber(8)
Handshake_GetServerKeyForPasswordLessRecovery
ensureGetServerKeyForPasswordlessRecovery() => $_ensure(7);
@$pb.TagNumber(9)
Handshake_RegisterPasswordlessNotification
get registerPasswordlessNotification => $_getN(8);
@$pb.TagNumber(9)
set registerPasswordlessNotification(
Handshake_RegisterPasswordlessNotification value) =>
$_setField(9, value);
@$pb.TagNumber(9)
$core.bool hasRegisterPasswordlessNotification() => $_has(8);
@$pb.TagNumber(9)
void clearRegisterPasswordlessNotification() => $_clearField(9);
@$pb.TagNumber(9)
Handshake_RegisterPasswordlessNotification
ensureRegisterPasswordlessNotification() => $_ensure(8);
@$pb.TagNumber(10)
Handshake_CheckForPasswordlessNotification
get checkForPasswordlessNotification => $_getN(9);
@$pb.TagNumber(10)
set checkForPasswordlessNotification(
Handshake_CheckForPasswordlessNotification value) =>
$_setField(10, value);
@$pb.TagNumber(10)
$core.bool hasCheckForPasswordlessNotification() => $_has(9);
@$pb.TagNumber(10)
void clearCheckForPasswordlessNotification() => $_clearField(10);
@$pb.TagNumber(10)
Handshake_CheckForPasswordlessNotification
ensureCheckForPasswordlessNotification() => $_ensure(9);
}
class ApplicationData_TextMessage extends $pb.GeneratedMessage {
@ -2558,160 +2182,6 @@ class ApplicationData_Deprecated extends $pb.GeneratedMessage {
static ApplicationData_Deprecated? _defaultInstance;
}
class ApplicationData_RegisterPasswordLessRecovery
extends $pb.GeneratedMessage {
factory ApplicationData_RegisterPasswordLessRecovery({
$core.List<$core.int>? encryptedServerKey,
$core.List<$core.int>? pinUnlockToken,
}) {
final result = create();
if (encryptedServerKey != null)
result.encryptedServerKey = encryptedServerKey;
if (pinUnlockToken != null) result.pinUnlockToken = pinUnlockToken;
return result;
}
ApplicationData_RegisterPasswordLessRecovery._();
factory ApplicationData_RegisterPasswordLessRecovery.fromBuffer(
$core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory ApplicationData_RegisterPasswordLessRecovery.fromJson(
$core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'ApplicationData.RegisterPasswordLessRecovery',
package:
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
createEmptyInstance: create)
..a<$core.List<$core.int>>(
1, _omitFieldNames ? '' : 'encryptedServerKey', $pb.PbFieldType.OY,
protoName: 'encryptedServerKey')
..a<$core.List<$core.int>>(
2, _omitFieldNames ? '' : 'pinUnlockToken', $pb.PbFieldType.OY,
protoName: 'pinUnlockToken')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
ApplicationData_RegisterPasswordLessRecovery clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
ApplicationData_RegisterPasswordLessRecovery copyWith(
void Function(ApplicationData_RegisterPasswordLessRecovery)
updates) =>
super.copyWith((message) =>
updates(message as ApplicationData_RegisterPasswordLessRecovery))
as ApplicationData_RegisterPasswordLessRecovery;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static ApplicationData_RegisterPasswordLessRecovery create() =>
ApplicationData_RegisterPasswordLessRecovery._();
@$core.override
ApplicationData_RegisterPasswordLessRecovery createEmptyInstance() =>
create();
@$core.pragma('dart2js:noInline')
static ApplicationData_RegisterPasswordLessRecovery getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
ApplicationData_RegisterPasswordLessRecovery>(create);
static ApplicationData_RegisterPasswordLessRecovery? _defaultInstance;
@$pb.TagNumber(1)
$core.List<$core.int> get encryptedServerKey => $_getN(0);
@$pb.TagNumber(1)
set encryptedServerKey($core.List<$core.int> value) => $_setBytes(0, value);
@$pb.TagNumber(1)
$core.bool hasEncryptedServerKey() => $_has(0);
@$pb.TagNumber(1)
void clearEncryptedServerKey() => $_clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get pinUnlockToken => $_getN(1);
@$pb.TagNumber(2)
set pinUnlockToken($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(2)
$core.bool hasPinUnlockToken() => $_has(1);
@$pb.TagNumber(2)
void clearPinUnlockToken() => $_clearField(2);
}
class ApplicationData_PasswordlessNotification extends $pb.GeneratedMessage {
factory ApplicationData_PasswordlessNotification({
$core.String? notificationId,
$core.List<$core.int>? encryptedMessage,
}) {
final result = create();
if (notificationId != null) result.notificationId = notificationId;
if (encryptedMessage != null) result.encryptedMessage = encryptedMessage;
return result;
}
ApplicationData_PasswordlessNotification._();
factory ApplicationData_PasswordlessNotification.fromBuffer(
$core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory ApplicationData_PasswordlessNotification.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'ApplicationData.PasswordlessNotification',
package:
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'notificationId')
..a<$core.List<$core.int>>(
2, _omitFieldNames ? '' : 'encryptedMessage', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
ApplicationData_PasswordlessNotification clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
ApplicationData_PasswordlessNotification copyWith(
void Function(ApplicationData_PasswordlessNotification) updates) =>
super.copyWith((message) =>
updates(message as ApplicationData_PasswordlessNotification))
as ApplicationData_PasswordlessNotification;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static ApplicationData_PasswordlessNotification create() =>
ApplicationData_PasswordlessNotification._();
@$core.override
ApplicationData_PasswordlessNotification createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static ApplicationData_PasswordlessNotification getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
ApplicationData_PasswordlessNotification>(create);
static ApplicationData_PasswordlessNotification? _defaultInstance;
@$pb.TagNumber(1)
$core.String get notificationId => $_getSZ(0);
@$pb.TagNumber(1)
set notificationId($core.String value) => $_setString(0, value);
@$pb.TagNumber(1)
$core.bool hasNotificationId() => $_has(0);
@$pb.TagNumber(1)
void clearNotificationId() => $_clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get encryptedMessage => $_getN(1);
@$pb.TagNumber(2)
set encryptedMessage($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(2)
$core.bool hasEncryptedMessage() => $_has(1);
@$pb.TagNumber(2)
void clearEncryptedMessage() => $_clearField(2);
}
enum ApplicationData_ApplicationData {
textMessage,
getUserByUsername,
@ -2739,8 +2209,6 @@ enum ApplicationData_ApplicationData {
ipaForceCheck,
addAdditionalUser,
setLoginToken,
registerPasswordlessRecovery,
passwordlessNotification,
notSet
}
@ -2772,8 +2240,6 @@ class ApplicationData extends $pb.GeneratedMessage {
ApplicationData_IPAForceCheck? ipaForceCheck,
ApplicationData_AddAdditionalUser? addAdditionalUser,
ApplicationData_SetLoginToken? setLoginToken,
ApplicationData_RegisterPasswordLessRecovery? registerPasswordlessRecovery,
ApplicationData_PasswordlessNotification? passwordlessNotification,
}) {
final result = create();
if (textMessage != null) result.textMessage = textMessage;
@ -2808,10 +2274,6 @@ class ApplicationData extends $pb.GeneratedMessage {
if (ipaForceCheck != null) result.ipaForceCheck = ipaForceCheck;
if (addAdditionalUser != null) result.addAdditionalUser = addAdditionalUser;
if (setLoginToken != null) result.setLoginToken = setLoginToken;
if (registerPasswordlessRecovery != null)
result.registerPasswordlessRecovery = registerPasswordlessRecovery;
if (passwordlessNotification != null)
result.passwordlessNotification = passwordlessNotification;
return result;
}
@ -2852,8 +2314,6 @@ class ApplicationData extends $pb.GeneratedMessage {
28: ApplicationData_ApplicationData.ipaForceCheck,
29: ApplicationData_ApplicationData.addAdditionalUser,
30: ApplicationData_ApplicationData.setLoginToken,
31: ApplicationData_ApplicationData.registerPasswordlessRecovery,
32: ApplicationData_ApplicationData.passwordlessNotification,
0: ApplicationData_ApplicationData.notSet
};
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
@ -2887,9 +2347,7 @@ class ApplicationData extends $pb.GeneratedMessage {
27,
28,
29,
30,
31,
32
30
])
..aOM<ApplicationData_TextMessage>(1, _omitFieldNames ? '' : 'textMessage',
protoName: 'textMessage',
@ -2981,12 +2439,6 @@ class ApplicationData extends $pb.GeneratedMessage {
..aOM<ApplicationData_SetLoginToken>(
30, _omitFieldNames ? '' : 'setLoginToken',
subBuilder: ApplicationData_SetLoginToken.create)
..aOM<ApplicationData_RegisterPasswordLessRecovery>(
31, _omitFieldNames ? '' : 'registerPasswordlessRecovery',
subBuilder: ApplicationData_RegisterPasswordLessRecovery.create)
..aOM<ApplicationData_PasswordlessNotification>(
32, _omitFieldNames ? '' : 'passwordlessNotification',
subBuilder: ApplicationData_PasswordlessNotification.create)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@ -3034,8 +2486,6 @@ class ApplicationData extends $pb.GeneratedMessage {
@$pb.TagNumber(28)
@$pb.TagNumber(29)
@$pb.TagNumber(30)
@$pb.TagNumber(31)
@$pb.TagNumber(32)
ApplicationData_ApplicationData whichApplicationData() =>
_ApplicationData_ApplicationDataByTag[$_whichOneof(0)]!;
@$pb.TagNumber(1)
@ -3064,8 +2514,6 @@ class ApplicationData extends $pb.GeneratedMessage {
@$pb.TagNumber(28)
@$pb.TagNumber(29)
@$pb.TagNumber(30)
@$pb.TagNumber(31)
@$pb.TagNumber(32)
void clearApplicationData() => $_clearField($_whichOneof(0));
@$pb.TagNumber(1)
@ -3371,36 +2819,6 @@ class ApplicationData extends $pb.GeneratedMessage {
void clearSetLoginToken() => $_clearField(30);
@$pb.TagNumber(30)
ApplicationData_SetLoginToken ensureSetLoginToken() => $_ensure(25);
@$pb.TagNumber(31)
ApplicationData_RegisterPasswordLessRecovery
get registerPasswordlessRecovery => $_getN(26);
@$pb.TagNumber(31)
set registerPasswordlessRecovery(
ApplicationData_RegisterPasswordLessRecovery value) =>
$_setField(31, value);
@$pb.TagNumber(31)
$core.bool hasRegisterPasswordlessRecovery() => $_has(26);
@$pb.TagNumber(31)
void clearRegisterPasswordlessRecovery() => $_clearField(31);
@$pb.TagNumber(31)
ApplicationData_RegisterPasswordLessRecovery
ensureRegisterPasswordlessRecovery() => $_ensure(26);
@$pb.TagNumber(32)
ApplicationData_PasswordlessNotification get passwordlessNotification =>
$_getN(27);
@$pb.TagNumber(32)
set passwordlessNotification(
ApplicationData_PasswordlessNotification value) =>
$_setField(32, value);
@$pb.TagNumber(32)
$core.bool hasPasswordlessNotification() => $_has(27);
@$pb.TagNumber(32)
void clearPasswordlessNotification() => $_clearField(32);
@$pb.TagNumber(32)
ApplicationData_PasswordlessNotification ensurePasswordlessNotification() =>
$_ensure(27);
}
class Response_PreKey extends $pb.GeneratedMessage {

View file

@ -152,33 +152,6 @@ const Handshake$json = {
'9': 0,
'10': 'getUseridByUsername'
},
{
'1': 'get_server_key_for_passwordless_recovery',
'3': 8,
'4': 1,
'5': 11,
'6': '.client_to_server.Handshake.GetServerKeyForPasswordLessRecovery',
'9': 0,
'10': 'getServerKeyForPasswordlessRecovery'
},
{
'1': 'register_passwordless_notification',
'3': 9,
'4': 1,
'5': 11,
'6': '.client_to_server.Handshake.RegisterPasswordlessNotification',
'9': 0,
'10': 'registerPasswordlessNotification'
},
{
'1': 'check_for_passwordless_notification',
'3': 10,
'4': 1,
'5': 11,
'6': '.client_to_server.Handshake.CheckForPasswordlessNotification',
'9': 0,
'10': 'checkForPasswordlessNotification'
},
],
'3': [
Handshake_RequestPOW$json,
@ -187,10 +160,7 @@ const Handshake$json = {
Handshake_GetUserIdByUsername$json,
Handshake_GetAuthToken$json,
Handshake_Authenticate$json,
Handshake_AuthenticateWithLoginToken$json,
Handshake_GetServerKeyForPasswordLessRecovery$json,
Handshake_RegisterPasswordlessNotification$json,
Handshake_CheckForPasswordlessNotification$json
Handshake_AuthenticateWithLoginToken$json
],
'8': [
{'1': 'Handshake'},
@ -333,95 +303,6 @@ const Handshake_AuthenticateWithLoginToken$json = {
],
};
@$core.Deprecated('Use handshakeDescriptor instead')
const Handshake_GetServerKeyForPasswordLessRecovery$json = {
'1': 'GetServerKeyForPasswordLessRecovery',
'2': [
{'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'},
{
'1': 'encrypted_server_key_none',
'3': 2,
'4': 1,
'5': 12,
'10': 'encryptedServerKeyNone'
},
{
'1': 'pin_unlock_token',
'3': 3,
'4': 1,
'5': 12,
'9': 0,
'10': 'pinUnlockToken',
'17': true
},
{
'1': 'pin_protection_key',
'3': 4,
'4': 1,
'5': 12,
'9': 1,
'10': 'pinProtectionKey',
'17': true
},
{'1': 'email', '3': 5, '4': 1, '5': 9, '9': 2, '10': 'email', '17': true},
],
'8': [
{'1': '_pin_unlock_token'},
{'1': '_pin_protection_key'},
{'1': '_email'},
],
};
@$core.Deprecated('Use handshakeDescriptor instead')
const Handshake_RegisterPasswordlessNotification$json = {
'1': 'RegisterPasswordlessNotification',
'2': [
{'1': 'notification_id', '3': 1, '4': 1, '5': 9, '10': 'notificationId'},
{
'1': 'download_auth_token',
'3': 2,
'4': 1,
'5': 12,
'10': 'downloadAuthToken'
},
{'1': 'lang_code', '3': 3, '4': 1, '5': 9, '10': 'langCode'},
{
'1': 'google_fcm',
'3': 4,
'4': 1,
'5': 9,
'9': 0,
'10': 'googleFcm',
'17': true
},
],
'8': [
{'1': '_google_fcm'},
],
};
@$core.Deprecated('Use handshakeDescriptor instead')
const Handshake_CheckForPasswordlessNotification$json = {
'1': 'CheckForPasswordlessNotification',
'2': [
{'1': 'notification_id', '3': 1, '4': 1, '5': 9, '10': 'notificationId'},
{
'1': 'download_auth_token',
'3': 2,
'4': 1,
'5': 12,
'10': 'downloadAuthToken'
},
{
'1': 'already_received_message_ids',
'3': 3,
'4': 3,
'5': 3,
'10': 'alreadyReceivedMessageIds'
},
],
};
/// Descriptor for `Handshake`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List handshakeDescriptor = $convert.base64Decode(
'CglIYW5kc2hha2USQgoIcmVnaXN0ZXIYASABKAsyJC5jbGllbnRfdG9fc2VydmVyLkhhbmRzaG'
@ -435,47 +316,26 @@ final $typed_data.Uint8List handshakeDescriptor = $convert.base64Decode(
'MjYuY2xpZW50X3RvX3NlcnZlci5IYW5kc2hha2UuQXV0aGVudGljYXRlV2l0aExvZ2luVG9rZW'
'5IAFIaYXV0aGVudGljYXRlV2l0aExvZ2luVG9rZW4SZgoWZ2V0X3VzZXJpZF9ieV91c2VybmFt'
'ZRgHIAEoCzIvLmNsaWVudF90b19zZXJ2ZXIuSGFuZHNoYWtlLkdldFVzZXJJZEJ5VXNlcm5hbW'
'VIAFITZ2V0VXNlcmlkQnlVc2VybmFtZRKYAQooZ2V0X3NlcnZlcl9rZXlfZm9yX3Bhc3N3b3Jk'
'bGVzc19yZWNvdmVyeRgIIAEoCzI/LmNsaWVudF90b19zZXJ2ZXIuSGFuZHNoYWtlLkdldFNlcn'
'ZlcktleUZvclBhc3N3b3JkTGVzc1JlY292ZXJ5SABSI2dldFNlcnZlcktleUZvclBhc3N3b3Jk'
'bGVzc1JlY292ZXJ5EowBCiJyZWdpc3Rlcl9wYXNzd29yZGxlc3Nfbm90aWZpY2F0aW9uGAkgAS'
'gLMjwuY2xpZW50X3RvX3NlcnZlci5IYW5kc2hha2UuUmVnaXN0ZXJQYXNzd29yZGxlc3NOb3Rp'
'ZmljYXRpb25IAFIgcmVnaXN0ZXJQYXNzd29yZGxlc3NOb3RpZmljYXRpb24SjQEKI2NoZWNrX2'
'Zvcl9wYXNzd29yZGxlc3Nfbm90aWZpY2F0aW9uGAogASgLMjwuY2xpZW50X3RvX3NlcnZlci5I'
'YW5kc2hha2UuQ2hlY2tGb3JQYXNzd29yZGxlc3NOb3RpZmljYXRpb25IAFIgY2hlY2tGb3JQYX'
'Nzd29yZGxlc3NOb3RpZmljYXRpb24aDAoKUmVxdWVzdFBPVxrKAwoIUmVnaXN0ZXISGgoIdXNl'
'cm5hbWUYASABKAlSCHVzZXJuYW1lEiQKC2ludml0ZV9jb2RlGAIgASgJSABSCmludml0ZUNvZG'
'WIAQESLgoTcHVibGljX2lkZW50aXR5X2tleRgDIAEoDFIRcHVibGljSWRlbnRpdHlLZXkSIwoN'
'c2lnbmVkX3ByZWtleRgEIAEoDFIMc2lnbmVkUHJla2V5EjYKF3NpZ25lZF9wcmVrZXlfc2lnbm'
'F0dXJlGAUgASgMUhVzaWduZWRQcmVrZXlTaWduYXR1cmUSKAoQc2lnbmVkX3ByZWtleV9pZBgG'
'IAEoA1IOc2lnbmVkUHJla2V5SWQSJwoPcmVnaXN0cmF0aW9uX2lkGAcgASgDUg5yZWdpc3RyYX'
'Rpb25JZBIVCgZpc19pb3MYCCABKAhSBWlzSW9zEhsKCWxhbmdfY29kZRgJIAEoCVIIbGFuZ0Nv'
'ZGUSIgoNcHJvb2Zfb2Zfd29yaxgKIAEoA1ILcHJvb2ZPZldvcmsSJAoLbG9naW5fdG9rZW4YCy'
'ABKAxIAVIKbG9naW5Ub2tlbogBAUIOCgxfaW52aXRlX2NvZGVCDgoMX2xvZ2luX3Rva2VuGhIK'
'EEdldEF1dGhDaGFsbGVuZ2UaMQoTR2V0VXNlcklkQnlVc2VybmFtZRIaCgh1c2VybmFtZRgBIA'
'EoCVIIdXNlcm5hbWUaQwoMR2V0QXV0aFRva2VuEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIa'
'CghyZXNwb25zZRgCIAEoDFIIcmVzcG9uc2Ua6AEKDEF1dGhlbnRpY2F0ZRIXCgd1c2VyX2lkGA'
'EgASgDUgZ1c2VySWQSHQoKYXV0aF90b2tlbhgCIAEoDFIJYXV0aFRva2VuEiQKC2FwcF92ZXJz'
'aW9uGAMgASgJSABSCmFwcFZlcnNpb26IAQESIAoJZGV2aWNlX2lkGAQgASgDSAFSCGRldmljZU'
'lkiAEBEigKDWluX2JhY2tncm91bmQYBSABKAhIAlIMaW5CYWNrZ3JvdW5kiAEBQg4KDF9hcHBf'
'dmVyc2lvbkIMCgpfZGV2aWNlX2lkQhAKDl9pbl9iYWNrZ3JvdW5kGsYBChpBdXRoZW50aWNhdG'
'VXaXRoTG9naW5Ub2tlbhIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSLAoSc2VjcmV0X2xvZ2lu'
'X3Rva2VuGAIgASgMUhBzZWNyZXRMb2dpblRva2VuEh8KC2FwcF92ZXJzaW9uGAMgASgJUgphcH'
'BWZXJzaW9uEhsKCWRldmljZV9pZBgEIAEoA1IIZGV2aWNlSWQSIwoNaW5fYmFja2dyb3VuZBgF'
'IAEoCFIMaW5CYWNrZ3JvdW5kGqwCCiNHZXRTZXJ2ZXJLZXlGb3JQYXNzd29yZExlc3NSZWNvdm'
'VyeRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSOQoZZW5jcnlwdGVkX3NlcnZlcl9rZXlfbm9u'
'ZRgCIAEoDFIWZW5jcnlwdGVkU2VydmVyS2V5Tm9uZRItChBwaW5fdW5sb2NrX3Rva2VuGAMgAS'
'gMSABSDnBpblVubG9ja1Rva2VuiAEBEjEKEnBpbl9wcm90ZWN0aW9uX2tleRgEIAEoDEgBUhBw'
'aW5Qcm90ZWN0aW9uS2V5iAEBEhkKBWVtYWlsGAUgASgJSAJSBWVtYWlsiAEBQhMKEV9waW5fdW'
'5sb2NrX3Rva2VuQhUKE19waW5fcHJvdGVjdGlvbl9rZXlCCAoGX2VtYWlsGssBCiBSZWdpc3Rl'
'clBhc3N3b3JkbGVzc05vdGlmaWNhdGlvbhInCg9ub3RpZmljYXRpb25faWQYASABKAlSDm5vdG'
'lmaWNhdGlvbklkEi4KE2Rvd25sb2FkX2F1dGhfdG9rZW4YAiABKAxSEWRvd25sb2FkQXV0aFRv'
'a2VuEhsKCWxhbmdfY29kZRgDIAEoCVIIbGFuZ0NvZGUSIgoKZ29vZ2xlX2ZjbRgEIAEoCUgAUg'
'lnb29nbGVGY22IAQFCDQoLX2dvb2dsZV9mY20avAEKIENoZWNrRm9yUGFzc3dvcmRsZXNzTm90'
'aWZpY2F0aW9uEicKD25vdGlmaWNhdGlvbl9pZBgBIAEoCVIObm90aWZpY2F0aW9uSWQSLgoTZG'
'93bmxvYWRfYXV0aF90b2tlbhgCIAEoDFIRZG93bmxvYWRBdXRoVG9rZW4SPwocYWxyZWFkeV9y'
'ZWNlaXZlZF9tZXNzYWdlX2lkcxgDIAMoA1IZYWxyZWFkeVJlY2VpdmVkTWVzc2FnZUlkc0ILCg'
'lIYW5kc2hha2U=');
'VIAFITZ2V0VXNlcmlkQnlVc2VybmFtZRoMCgpSZXF1ZXN0UE9XGsoDCghSZWdpc3RlchIaCgh1'
'c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUSJAoLaW52aXRlX2NvZGUYAiABKAlIAFIKaW52aXRlQ2'
'9kZYgBARIuChNwdWJsaWNfaWRlbnRpdHlfa2V5GAMgASgMUhFwdWJsaWNJZGVudGl0eUtleRIj'
'Cg1zaWduZWRfcHJla2V5GAQgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaW'
'duYXR1cmUYBSABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRIoChBzaWduZWRfcHJla2V5X2lk'
'GAYgASgDUg5zaWduZWRQcmVrZXlJZBInCg9yZWdpc3RyYXRpb25faWQYByABKANSDnJlZ2lzdH'
'JhdGlvbklkEhUKBmlzX2lvcxgIIAEoCFIFaXNJb3MSGwoJbGFuZ19jb2RlGAkgASgJUghsYW5n'
'Q29kZRIiCg1wcm9vZl9vZl93b3JrGAogASgDUgtwcm9vZk9mV29yaxIkCgtsb2dpbl90b2tlbh'
'gLIAEoDEgBUgpsb2dpblRva2VuiAEBQg4KDF9pbnZpdGVfY29kZUIOCgxfbG9naW5fdG9rZW4a'
'EgoQR2V0QXV0aENoYWxsZW5nZRoxChNHZXRVc2VySWRCeVVzZXJuYW1lEhoKCHVzZXJuYW1lGA'
'EgASgJUgh1c2VybmFtZRpDCgxHZXRBdXRoVG9rZW4SFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklk'
'EhoKCHJlc3BvbnNlGAIgASgMUghyZXNwb25zZRroAQoMQXV0aGVudGljYXRlEhcKB3VzZXJfaW'
'QYASABKANSBnVzZXJJZBIdCgphdXRoX3Rva2VuGAIgASgMUglhdXRoVG9rZW4SJAoLYXBwX3Zl'
'cnNpb24YAyABKAlIAFIKYXBwVmVyc2lvbogBARIgCglkZXZpY2VfaWQYBCABKANIAVIIZGV2aW'
'NlSWSIAQESKAoNaW5fYmFja2dyb3VuZBgFIAEoCEgCUgxpbkJhY2tncm91bmSIAQFCDgoMX2Fw'
'cF92ZXJzaW9uQgwKCl9kZXZpY2VfaWRCEAoOX2luX2JhY2tncm91bmQaxgEKGkF1dGhlbnRpY2'
'F0ZVdpdGhMb2dpblRva2VuEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIsChJzZWNyZXRfbG9n'
'aW5fdG9rZW4YAiABKAxSEHNlY3JldExvZ2luVG9rZW4SHwoLYXBwX3ZlcnNpb24YAyABKAlSCm'
'FwcFZlcnNpb24SGwoJZGV2aWNlX2lkGAQgASgDUghkZXZpY2VJZBIjCg1pbl9iYWNrZ3JvdW5k'
'GAUgASgIUgxpbkJhY2tncm91bmRCCwoJSGFuZHNoYWtl');
@$core.Deprecated('Use applicationDataDescriptor instead')
const ApplicationData$json = {
@ -715,24 +575,6 @@ const ApplicationData$json = {
'9': 0,
'10': 'setLoginToken'
},
{
'1': 'register_passwordless_recovery',
'3': 31,
'4': 1,
'5': 11,
'6': '.client_to_server.ApplicationData.RegisterPasswordLessRecovery',
'9': 0,
'10': 'registerPasswordlessRecovery'
},
{
'1': 'passwordless_notification',
'3': 32,
'4': 1,
'5': 11,
'6': '.client_to_server.ApplicationData.PasswordlessNotification',
'9': 0,
'10': 'passwordlessNotification'
},
],
'3': [
ApplicationData_TextMessage$json,
@ -754,9 +596,7 @@ const ApplicationData$json = {
ApplicationData_DeleteAccount$json,
ApplicationData_AddAdditionalUser$json,
ApplicationData_SetLoginToken$json,
ApplicationData_Deprecated$json,
ApplicationData_RegisterPasswordLessRecovery$json,
ApplicationData_PasswordlessNotification$json
ApplicationData_Deprecated$json
],
'8': [
{'1': 'ApplicationData'},
@ -935,47 +775,6 @@ const ApplicationData_Deprecated$json = {
'1': 'Deprecated',
};
@$core.Deprecated('Use applicationDataDescriptor instead')
const ApplicationData_RegisterPasswordLessRecovery$json = {
'1': 'RegisterPasswordLessRecovery',
'2': [
{
'1': 'encryptedServerKey',
'3': 1,
'4': 1,
'5': 12,
'10': 'encryptedServerKey'
},
{
'1': 'pinUnlockToken',
'3': 2,
'4': 1,
'5': 12,
'9': 0,
'10': 'pinUnlockToken',
'17': true
},
],
'8': [
{'1': '_pinUnlockToken'},
],
};
@$core.Deprecated('Use applicationDataDescriptor instead')
const ApplicationData_PasswordlessNotification$json = {
'1': 'PasswordlessNotification',
'2': [
{'1': 'notification_id', '3': 1, '4': 1, '5': 9, '10': 'notificationId'},
{
'1': 'encrypted_message',
'3': 2,
'4': 1,
'5': 12,
'10': 'encryptedMessage'
},
],
};
/// Descriptor for `ApplicationData`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
'Cg9BcHBsaWNhdGlvbkRhdGESUQoLdGV4dE1lc3NhZ2UYASABKAsyLS5jbGllbnRfdG9fc2Vydm'
@ -1021,36 +820,27 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
'dmVBZGRpdGlvbmFsVXNlchJjChFhZGRBZGRpdGlvbmFsVXNlchgdIAEoCzIzLmNsaWVudF90b1'
'9zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkFkZEFkZGl0aW9uYWxVc2VySABSEWFkZEFkZGl0aW9u'
'YWxVc2VyElkKD3NldF9sb2dpbl90b2tlbhgeIAEoCzIvLmNsaWVudF90b19zZXJ2ZXIuQXBwbG'
'ljYXRpb25EYXRhLlNldExvZ2luVG9rZW5IAFINc2V0TG9naW5Ub2tlbhKGAQoecmVnaXN0ZXJf'
'cGFzc3dvcmRsZXNzX3JlY292ZXJ5GB8gASgLMj4uY2xpZW50X3RvX3NlcnZlci5BcHBsaWNhdG'
'lvbkRhdGEuUmVnaXN0ZXJQYXNzd29yZExlc3NSZWNvdmVyeUgAUhxyZWdpc3RlclBhc3N3b3Jk'
'bGVzc1JlY292ZXJ5EnkKGXBhc3N3b3JkbGVzc19ub3RpZmljYXRpb24YICABKAsyOi5jbGllbn'
'RfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5QYXNzd29yZGxlc3NOb3RpZmljYXRpb25IAFIY'
'cGFzc3dvcmRsZXNzTm90aWZpY2F0aW9uGmoKC1RleHRNZXNzYWdlEhcKB3VzZXJfaWQYASABKA'
'NSBnVzZXJJZBISCgRib2R5GAMgASgMUgRib2R5EiAKCXB1c2hfZGF0YRgEIAEoDEgAUghwdXNo'
'RGF0YYgBAUIMCgpfcHVzaF9kYXRhGi8KEUdldFVzZXJCeVVzZXJuYW1lEhoKCHVzZXJuYW1lGA'
'EgASgJUgh1c2VybmFtZRosCg5DaGFuZ2VVc2VybmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNl'
'cm5hbWUaNQoUVXBkYXRlR29vZ2xlRmNtVG9rZW4SHQoKZ29vZ2xlX2ZjbRgBIAEoCVIJZ29vZ2'
'xlRmNtGiYKC0dldFVzZXJCeUlkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBoTChFHZXRBdmFp'
'bGFibGVQbGFucxoXChVHZXRBZGRBY2NvdW50c0ludml0ZXMaFQoTR2V0Q3VycmVudFBsYW5Jbm'
'ZvcxovChRSZW1vdmVBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaLQoS'
'R2V0UHJla2V5c0J5VXNlcklkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBoyChdHZXRTaWduZW'
'RQcmVLZXlCeVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQamwEKElVwZGF0ZVNpZ25l'
'ZFByZUtleRIoChBzaWduZWRfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZXlJZBIjCg1zaW'
'duZWRfcHJla2V5GAIgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaWduYXR1'
'cmUYAyABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRo1CgxEb3dubG9hZERvbmUSJQoOZG93bm'
'xvYWRfdG9rZW4YASABKAxSDWRvd25sb2FkVG9rZW4aTgoKUmVwb3J0VXNlchIoChByZXBvcnRl'
'ZF91c2VyX2lkGAEgASgDUg5yZXBvcnRlZFVzZXJJZBIWCgZyZWFzb24YAiABKAlSBnJlYXNvbh'
'pxCgtJUEFQdXJjaGFzZRIdCgpwcm9kdWN0X2lkGAEgASgJUglwcm9kdWN0SWQSFgoGc291cmNl'
'GAIgASgJUgZzb3VyY2USKwoRdmVyaWZpY2F0aW9uX2RhdGEYAyABKAlSEHZlcmlmaWNhdGlvbk'
'RhdGEaDwoNSVBBRm9yY2VDaGVjaxoPCg1EZWxldGVBY2NvdW50GiwKEUFkZEFkZGl0aW9uYWxV'
'c2VyEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBowCg1TZXRMb2dpblRva2VuEh8KC2xvZ2luX3'
'Rva2VuGAEgASgMUgpsb2dpblRva2VuGgwKCkRlcHJlY2F0ZWQajgEKHFJlZ2lzdGVyUGFzc3dv'
'cmRMZXNzUmVjb3ZlcnkSLgoSZW5jcnlwdGVkU2VydmVyS2V5GAEgASgMUhJlbmNyeXB0ZWRTZX'
'J2ZXJLZXkSKwoOcGluVW5sb2NrVG9rZW4YAiABKAxIAFIOcGluVW5sb2NrVG9rZW6IAQFCEQoP'
'X3BpblVubG9ja1Rva2VuGnAKGFBhc3N3b3JkbGVzc05vdGlmaWNhdGlvbhInCg9ub3RpZmljYX'
'Rpb25faWQYASABKAlSDm5vdGlmaWNhdGlvbklkEisKEWVuY3J5cHRlZF9tZXNzYWdlGAIgASgM'
'UhBlbmNyeXB0ZWRNZXNzYWdlQhEKD0FwcGxpY2F0aW9uRGF0YQ==');
'ljYXRpb25EYXRhLlNldExvZ2luVG9rZW5IAFINc2V0TG9naW5Ub2tlbhpqCgtUZXh0TWVzc2Fn'
'ZRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSEgoEYm9keRgDIAEoDFIEYm9keRIgCglwdXNoX2'
'RhdGEYBCABKAxIAFIIcHVzaERhdGGIAQFCDAoKX3B1c2hfZGF0YRovChFHZXRVc2VyQnlVc2Vy'
'bmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaLAoOQ2hhbmdlVXNlcm5hbWUSGgoIdX'
'Nlcm5hbWUYASABKAlSCHVzZXJuYW1lGjUKFFVwZGF0ZUdvb2dsZUZjbVRva2VuEh0KCmdvb2ds'
'ZV9mY20YASABKAlSCWdvb2dsZUZjbRomCgtHZXRVc2VyQnlJZBIXCgd1c2VyX2lkGAEgASgDUg'
'Z1c2VySWQaEwoRR2V0QXZhaWxhYmxlUGxhbnMaFwoVR2V0QWRkQWNjb3VudHNJbnZpdGVzGhUK'
'E0dldEN1cnJlbnRQbGFuSW5mb3MaLwoUUmVtb3ZlQWRkaXRpb25hbFVzZXISFwoHdXNlcl9pZB'
'gBIAEoA1IGdXNlcklkGi0KEkdldFByZWtleXNCeVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1'
'c2VySWQaMgoXR2V0U2lnbmVkUHJlS2V5QnlVc2VySWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlck'
'lkGpsBChJVcGRhdGVTaWduZWRQcmVLZXkSKAoQc2lnbmVkX3ByZWtleV9pZBgBIAEoA1IOc2ln'
'bmVkUHJla2V5SWQSIwoNc2lnbmVkX3ByZWtleRgCIAEoDFIMc2lnbmVkUHJla2V5EjYKF3NpZ2'
'5lZF9wcmVrZXlfc2lnbmF0dXJlGAMgASgMUhVzaWduZWRQcmVrZXlTaWduYXR1cmUaNQoMRG93'
'bmxvYWREb25lEiUKDmRvd25sb2FkX3Rva2VuGAEgASgMUg1kb3dubG9hZFRva2VuGk4KClJlcG'
'9ydFVzZXISKAoQcmVwb3J0ZWRfdXNlcl9pZBgBIAEoA1IOcmVwb3J0ZWRVc2VySWQSFgoGcmVh'
'c29uGAIgASgJUgZyZWFzb24acQoLSVBBUHVyY2hhc2USHQoKcHJvZHVjdF9pZBgBIAEoCVIJcH'
'JvZHVjdElkEhYKBnNvdXJjZRgCIAEoCVIGc291cmNlEisKEXZlcmlmaWNhdGlvbl9kYXRhGAMg'
'ASgJUhB2ZXJpZmljYXRpb25EYXRhGg8KDUlQQUZvcmNlQ2hlY2saDwoNRGVsZXRlQWNjb3VudB'
'osChFBZGRBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaMAoNU2V0TG9n'
'aW5Ub2tlbhIfCgtsb2dpbl90b2tlbhgBIAEoDFIKbG9naW5Ub2tlbhoMCgpEZXByZWNhdGVkQh'
'EKD0FwcGxpY2F0aW9uRGF0YQ==');
@$core.Deprecated('Use responseDescriptor instead')
const Response$json = {

View file

@ -93,10 +93,6 @@ class ErrorCode extends $pb.ProtobufEnum {
ErrorCode._(1035, _omitEnumNames ? '' : 'UserIsNotInFreePlan');
static const ErrorCode ForegroundSessionConnected =
ErrorCode._(1036, _omitEnumNames ? '' : 'ForegroundSessionConnected');
static const ErrorCode NoRecoveryData =
ErrorCode._(1037, _omitEnumNames ? '' : 'NoRecoveryData');
static const ErrorCode InvalidRecoveryData =
ErrorCode._(1038, _omitEnumNames ? '' : 'InvalidRecoveryData');
static const $core.List<ErrorCode> values = <ErrorCode>[
Unknown,
@ -138,8 +134,6 @@ class ErrorCode extends $pb.ProtobufEnum {
IPAPaymentExpired,
UserIsNotInFreePlan,
ForegroundSessionConnected,
NoRecoveryData,
InvalidRecoveryData,
];
static final $core.Map<$core.int, ErrorCode> _byValue =

View file

@ -58,8 +58,6 @@ const ErrorCode$json = {
{'1': 'IPAPaymentExpired', '2': 1034},
{'1': 'UserIsNotInFreePlan', '2': 1035},
{'1': 'ForegroundSessionConnected', '2': 1036},
{'1': 'NoRecoveryData', '2': 1037},
{'1': 'InvalidRecoveryData', '2': 1038},
],
};
@ -82,5 +80,4 @@ final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode(
'EIUIEhcKEkFwcFZlcnNpb25PdXRkYXRlZBCGCBIYChNOZXdEZXZpY2VSZWdpc3RlcmVkEIcIEh'
'cKEkludmFsaWRQcm9vZk9mV29yaxCICBIZChRSZWdpc3RyYXRpb25EaXNhYmxlZBCJCBIWChFJ'
'UEFQYXltZW50RXhwaXJlZBCKCBIYChNVc2VySXNOb3RJbkZyZWVQbGFuEIsIEh8KGkZvcmVncm'
'91bmRTZXNzaW9uQ29ubmVjdGVkEIwIEhMKDk5vUmVjb3ZlcnlEYXRhEI0IEhgKE0ludmFsaWRS'
'ZWNvdmVyeURhdGEQjgg=');
'91bmRTZXNzaW9uQ29ubmVjdGVkEIwI');

View file

@ -1505,136 +1505,6 @@ class Response_ProofOfWork extends $pb.GeneratedMessage {
void clearDifficulty() => $_clearField(2);
}
class Response_PasswordlessNotificationMessage extends $pb.GeneratedMessage {
factory Response_PasswordlessNotificationMessage({
$fixnum.Int64? id,
$core.List<$core.int>? encryptedMessage,
}) {
final result = create();
if (id != null) result.id = id;
if (encryptedMessage != null) result.encryptedMessage = encryptedMessage;
return result;
}
Response_PasswordlessNotificationMessage._();
factory Response_PasswordlessNotificationMessage.fromBuffer(
$core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory Response_PasswordlessNotificationMessage.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'Response.PasswordlessNotificationMessage',
package:
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
createEmptyInstance: create)
..aInt64(1, _omitFieldNames ? '' : 'id')
..a<$core.List<$core.int>>(
2, _omitFieldNames ? '' : 'encryptedMessage', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
Response_PasswordlessNotificationMessage clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
Response_PasswordlessNotificationMessage copyWith(
void Function(Response_PasswordlessNotificationMessage) updates) =>
super.copyWith((message) =>
updates(message as Response_PasswordlessNotificationMessage))
as Response_PasswordlessNotificationMessage;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static Response_PasswordlessNotificationMessage create() =>
Response_PasswordlessNotificationMessage._();
@$core.override
Response_PasswordlessNotificationMessage createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static Response_PasswordlessNotificationMessage getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
Response_PasswordlessNotificationMessage>(create);
static Response_PasswordlessNotificationMessage? _defaultInstance;
@$pb.TagNumber(1)
$fixnum.Int64 get id => $_getI64(0);
@$pb.TagNumber(1)
set id($fixnum.Int64 value) => $_setInt64(0, value);
@$pb.TagNumber(1)
$core.bool hasId() => $_has(0);
@$pb.TagNumber(1)
void clearId() => $_clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get encryptedMessage => $_getN(1);
@$pb.TagNumber(2)
set encryptedMessage($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(2)
$core.bool hasEncryptedMessage() => $_has(1);
@$pb.TagNumber(2)
void clearEncryptedMessage() => $_clearField(2);
}
class Response_PasswordlessNotificationMessages extends $pb.GeneratedMessage {
factory Response_PasswordlessNotificationMessages({
$core.Iterable<Response_PasswordlessNotificationMessage>? messages,
}) {
final result = create();
if (messages != null) result.messages.addAll(messages);
return result;
}
Response_PasswordlessNotificationMessages._();
factory Response_PasswordlessNotificationMessages.fromBuffer(
$core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory Response_PasswordlessNotificationMessages.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'Response.PasswordlessNotificationMessages',
package:
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
createEmptyInstance: create)
..pPM<Response_PasswordlessNotificationMessage>(
1, _omitFieldNames ? '' : 'messages',
subBuilder: Response_PasswordlessNotificationMessage.create)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
Response_PasswordlessNotificationMessages clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
Response_PasswordlessNotificationMessages copyWith(
void Function(Response_PasswordlessNotificationMessages) updates) =>
super.copyWith((message) =>
updates(message as Response_PasswordlessNotificationMessages))
as Response_PasswordlessNotificationMessages;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static Response_PasswordlessNotificationMessages create() =>
Response_PasswordlessNotificationMessages._();
@$core.override
Response_PasswordlessNotificationMessages createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static Response_PasswordlessNotificationMessages getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
Response_PasswordlessNotificationMessages>(create);
static Response_PasswordlessNotificationMessages? _defaultInstance;
@$pb.TagNumber(1)
$pb.PbList<Response_PasswordlessNotificationMessage> get messages =>
$_getList(0);
}
enum Response_Ok_Ok {
none,
userid,
@ -1651,8 +1521,6 @@ enum Response_Ok_Ok {
downloadtokens,
signedprekey,
proofOfWork,
passwordlessRecoveryServerKey,
passwordlessNotificationMessages,
notSet
}
@ -1673,8 +1541,6 @@ class Response_Ok extends $pb.GeneratedMessage {
Response_DownloadTokens? downloadtokens,
Response_SignedPreKey? signedprekey,
Response_ProofOfWork? proofOfWork,
$core.List<$core.int>? passwordlessRecoveryServerKey,
Response_PasswordlessNotificationMessages? passwordlessNotificationMessages,
}) {
final result = create();
if (none != null) result.none = none;
@ -1693,11 +1559,6 @@ class Response_Ok extends $pb.GeneratedMessage {
if (downloadtokens != null) result.downloadtokens = downloadtokens;
if (signedprekey != null) result.signedprekey = signedprekey;
if (proofOfWork != null) result.proofOfWork = proofOfWork;
if (passwordlessRecoveryServerKey != null)
result.passwordlessRecoveryServerKey = passwordlessRecoveryServerKey;
if (passwordlessNotificationMessages != null)
result.passwordlessNotificationMessages =
passwordlessNotificationMessages;
return result;
}
@ -1726,8 +1587,6 @@ class Response_Ok extends $pb.GeneratedMessage {
13: Response_Ok_Ok.downloadtokens,
14: Response_Ok_Ok.signedprekey,
15: Response_Ok_Ok.proofOfWork,
16: Response_Ok_Ok.passwordlessRecoveryServerKey,
17: Response_Ok_Ok.passwordlessNotificationMessages,
0: Response_Ok_Ok.notSet
};
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
@ -1735,7 +1594,7 @@ class Response_Ok extends $pb.GeneratedMessage {
package:
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
createEmptyInstance: create)
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17])
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
..aOB(1, _omitFieldNames ? '' : 'None', protoName: 'None')
..aInt64(2, _omitFieldNames ? '' : 'userid')
..a<$core.List<$core.int>>(
@ -1765,13 +1624,6 @@ class Response_Ok extends $pb.GeneratedMessage {
subBuilder: Response_SignedPreKey.create)
..aOM<Response_ProofOfWork>(15, _omitFieldNames ? '' : 'proofOfWork',
protoName: 'proofOfWork', subBuilder: Response_ProofOfWork.create)
..a<$core.List<$core.int>>(
16,
_omitFieldNames ? '' : 'passwordlessRecoveryServerKey',
$pb.PbFieldType.OY)
..aOM<Response_PasswordlessNotificationMessages>(
17, _omitFieldNames ? '' : 'passwordlessNotificationMessages',
subBuilder: Response_PasswordlessNotificationMessages.create)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@ -1808,8 +1660,6 @@ class Response_Ok extends $pb.GeneratedMessage {
@$pb.TagNumber(13)
@$pb.TagNumber(14)
@$pb.TagNumber(15)
@$pb.TagNumber(16)
@$pb.TagNumber(17)
Response_Ok_Ok whichOk() => _Response_Ok_OkByTag[$_whichOneof(0)]!;
@$pb.TagNumber(1)
@$pb.TagNumber(2)
@ -1826,8 +1676,6 @@ class Response_Ok extends $pb.GeneratedMessage {
@$pb.TagNumber(13)
@$pb.TagNumber(14)
@$pb.TagNumber(15)
@$pb.TagNumber(16)
@$pb.TagNumber(17)
void clearOk() => $_clearField($_whichOneof(0));
@$pb.TagNumber(1)
@ -1987,31 +1835,6 @@ class Response_Ok extends $pb.GeneratedMessage {
void clearProofOfWork() => $_clearField(15);
@$pb.TagNumber(15)
Response_ProofOfWork ensureProofOfWork() => $_ensure(14);
@$pb.TagNumber(16)
$core.List<$core.int> get passwordlessRecoveryServerKey => $_getN(15);
@$pb.TagNumber(16)
set passwordlessRecoveryServerKey($core.List<$core.int> value) =>
$_setBytes(15, value);
@$pb.TagNumber(16)
$core.bool hasPasswordlessRecoveryServerKey() => $_has(15);
@$pb.TagNumber(16)
void clearPasswordlessRecoveryServerKey() => $_clearField(16);
@$pb.TagNumber(17)
Response_PasswordlessNotificationMessages
get passwordlessNotificationMessages => $_getN(16);
@$pb.TagNumber(17)
set passwordlessNotificationMessages(
Response_PasswordlessNotificationMessages value) =>
$_setField(17, value);
@$pb.TagNumber(17)
$core.bool hasPasswordlessNotificationMessages() => $_has(16);
@$pb.TagNumber(17)
void clearPasswordlessNotificationMessages() => $_clearField(17);
@$pb.TagNumber(17)
Response_PasswordlessNotificationMessages
ensurePasswordlessNotificationMessages() => $_ensure(16);
}
enum Response_Response { ok, error, notSet }

View file

@ -176,8 +176,6 @@ const Response$json = {
Response_UploadToken$json,
Response_DownloadTokens$json,
Response_ProofOfWork$json,
Response_PasswordlessNotificationMessage$json,
Response_PasswordlessNotificationMessages$json,
Response_Ok$json
],
'8': [
@ -511,36 +509,6 @@ const Response_ProofOfWork$json = {
],
};
@$core.Deprecated('Use responseDescriptor instead')
const Response_PasswordlessNotificationMessage$json = {
'1': 'PasswordlessNotificationMessage',
'2': [
{'1': 'id', '3': 1, '4': 1, '5': 3, '10': 'id'},
{
'1': 'encrypted_message',
'3': 2,
'4': 1,
'5': 12,
'10': 'encryptedMessage'
},
],
};
@$core.Deprecated('Use responseDescriptor instead')
const Response_PasswordlessNotificationMessages$json = {
'1': 'PasswordlessNotificationMessages',
'2': [
{
'1': 'messages',
'3': 1,
'4': 3,
'5': 11,
'6': '.server_to_client.Response.PasswordlessNotificationMessage',
'10': 'messages'
},
],
};
@$core.Deprecated('Use responseDescriptor instead')
const Response_Ok$json = {
'1': 'Ok',
@ -655,23 +623,6 @@ const Response_Ok$json = {
'9': 0,
'10': 'proofOfWork'
},
{
'1': 'passwordless_recovery_server_key',
'3': 16,
'4': 1,
'5': 12,
'9': 0,
'10': 'passwordlessRecoveryServerKey'
},
{
'1': 'passwordless_notification_messages',
'3': 17,
'4': 1,
'5': 11,
'6': '.server_to_client.Response.PasswordlessNotificationMessages',
'9': 0,
'10': 'passwordlessNotificationMessages'
},
],
'8': [
{'1': 'Ok'},
@ -726,29 +677,22 @@ final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
'cKD2Rvd25sb2FkX3Rva2VucxgCIAMoDFIOZG93bmxvYWRUb2tlbnMaOQoORG93bmxvYWRUb2tl'
'bnMSJwoPZG93bmxvYWRfdG9rZW5zGAEgAygMUg5kb3dubG9hZFRva2VucxpFCgtQcm9vZk9mV2'
'9yaxIWCgZwcmVmaXgYASABKAlSBnByZWZpeBIeCgpkaWZmaWN1bHR5GAIgASgDUgpkaWZmaWN1'
'bHR5Gl4KH1Bhc3N3b3JkbGVzc05vdGlmaWNhdGlvbk1lc3NhZ2USDgoCaWQYASABKANSAmlkEi'
'sKEWVuY3J5cHRlZF9tZXNzYWdlGAIgASgMUhBlbmNyeXB0ZWRNZXNzYWdlGnoKIFBhc3N3b3Jk'
'bGVzc05vdGlmaWNhdGlvbk1lc3NhZ2VzElYKCG1lc3NhZ2VzGAEgAygLMjouc2VydmVyX3RvX2'
'NsaWVudC5SZXNwb25zZS5QYXNzd29yZGxlc3NOb3RpZmljYXRpb25NZXNzYWdlUghtZXNzYWdl'
'cxqwCQoCT2sSFAoETm9uZRgBIAEoCEgAUgROb25lEhgKBnVzZXJpZBgCIAEoA0gAUgZ1c2VyaW'
'QSJgoNYXV0aGNoYWxsZW5nZRgDIAEoDEgAUg1hdXRoY2hhbGxlbmdlEkoKC3VwbG9hZHRva2Vu'
'GAQgASgLMiYuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5VcGxvYWRUb2tlbkgAUgt1cGxvYW'
'R0b2tlbhJBCgh1c2VyZGF0YRgFIAEoCzIjLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuVXNl'
'ckRhdGFIAFIIdXNlcmRhdGESHgoJYXV0aHRva2VuGAYgASgMSABSCWF1dGh0b2tlbhJKCgxkZX'
'ByZWNhdGVkXzcYByABKAsyJS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkRlcHJlY2F0ZWRI'
'AFILZGVwcmVjYXRlZDcSUAoNYXV0aGVudGljYXRlZBgIIAEoCzIoLnNlcnZlcl90b19jbGllbn'
'QuUmVzcG9uc2UuQXV0aGVudGljYXRlZEgAUg1hdXRoZW50aWNhdGVkEjgKBXBsYW5zGAkgASgL'
'MiAuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5QbGFuc0gAUgVwbGFucxJNCgxwbGFuYmFsbG'
'FuY2UYCiABKAsyJy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBsYW5CYWxsYW5jZUgAUgxw'
'bGFuYmFsbGFuY2USTAoNZGVwcmVjYXRlZF8xMRgLIAEoCzIlLnNlcnZlcl90b19jbGllbnQuUm'
'VzcG9uc2UuRGVwcmVjYXRlZEgAUgxkZXByZWNhdGVkMTESXwoSYWRkYWNjb3VudHNpbnZpdGVz'
'GAwgASgLMi0uc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5BZGRBY2NvdW50c0ludml0ZXNIAF'
'ISYWRkYWNjb3VudHNpbnZpdGVzElMKDmRvd25sb2FkdG9rZW5zGA0gASgLMikuc2VydmVyX3Rv'
'X2NsaWVudC5SZXNwb25zZS5Eb3dubG9hZFRva2Vuc0gAUg5kb3dubG9hZHRva2VucxJNCgxzaW'
'duZWRwcmVrZXkYDiABKAsyJy5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlNpZ25lZFByZUtl'
'eUgAUgxzaWduZWRwcmVrZXkSSgoLcHJvb2ZPZldvcmsYDyABKAsyJi5zZXJ2ZXJfdG9fY2xpZW'
'50LlJlc3BvbnNlLlByb29mT2ZXb3JrSABSC3Byb29mT2ZXb3JrEkkKIHBhc3N3b3JkbGVzc19y'
'ZWNvdmVyeV9zZXJ2ZXJfa2V5GBAgASgMSABSHXBhc3N3b3JkbGVzc1JlY292ZXJ5U2VydmVyS2'
'V5EosBCiJwYXNzd29yZGxlc3Nfbm90aWZpY2F0aW9uX21lc3NhZ2VzGBEgASgLMjsuc2VydmVy'
'X3RvX2NsaWVudC5SZXNwb25zZS5QYXNzd29yZGxlc3NOb3RpZmljYXRpb25NZXNzYWdlc0gAUi'
'BwYXNzd29yZGxlc3NOb3RpZmljYXRpb25NZXNzYWdlc0IECgJPa0IKCghSZXNwb25zZQ==');
'bHR5GtcHCgJPaxIUCgROb25lGAEgASgISABSBE5vbmUSGAoGdXNlcmlkGAIgASgDSABSBnVzZX'
'JpZBImCg1hdXRoY2hhbGxlbmdlGAMgASgMSABSDWF1dGhjaGFsbGVuZ2USSgoLdXBsb2FkdG9r'
'ZW4YBCABKAsyJi5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlVwbG9hZFRva2VuSABSC3VwbG'
'9hZHRva2VuEkEKCHVzZXJkYXRhGAUgASgLMiMuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5V'
'c2VyRGF0YUgAUgh1c2VyZGF0YRIeCglhdXRodG9rZW4YBiABKAxIAFIJYXV0aHRva2VuEkoKDG'
'RlcHJlY2F0ZWRfNxgHIAEoCzIlLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuRGVwcmVjYXRl'
'ZEgAUgtkZXByZWNhdGVkNxJQCg1hdXRoZW50aWNhdGVkGAggASgLMiguc2VydmVyX3RvX2NsaW'
'VudC5SZXNwb25zZS5BdXRoZW50aWNhdGVkSABSDWF1dGhlbnRpY2F0ZWQSOAoFcGxhbnMYCSAB'
'KAsyIC5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBsYW5zSABSBXBsYW5zEk0KDHBsYW5iYW'
'xsYW5jZRgKIAEoCzInLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUGxhbkJhbGxhbmNlSABS'
'DHBsYW5iYWxsYW5jZRJMCg1kZXByZWNhdGVkXzExGAsgASgLMiUuc2VydmVyX3RvX2NsaWVudC'
'5SZXNwb25zZS5EZXByZWNhdGVkSABSDGRlcHJlY2F0ZWQxMRJfChJhZGRhY2NvdW50c2ludml0'
'ZXMYDCABKAsyLS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkFkZEFjY291bnRzSW52aXRlc0'
'gAUhJhZGRhY2NvdW50c2ludml0ZXMSUwoOZG93bmxvYWR0b2tlbnMYDSABKAsyKS5zZXJ2ZXJf'
'dG9fY2xpZW50LlJlc3BvbnNlLkRvd25sb2FkVG9rZW5zSABSDmRvd25sb2FkdG9rZW5zEk0KDH'
'NpZ25lZHByZWtleRgOIAEoCzInLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuU2lnbmVkUHJl'
'S2V5SABSDHNpZ25lZHByZWtleRJKCgtwcm9vZk9mV29yaxgPIAEoCzImLnNlcnZlcl90b19jbG'
'llbnQuUmVzcG9uc2UuUHJvb2ZPZldvcmtIAFILcHJvb2ZPZldvcmtCBAoCT2tCCgoIUmVzcG9u'
'c2U=');

View file

@ -368,12 +368,10 @@ class EncryptedContent_GroupCreate extends $pb.GeneratedMessage {
factory EncryptedContent_GroupCreate({
$core.List<$core.int>? stateKey,
$core.List<$core.int>? groupPublicKey,
$core.String? groupName,
}) {
final result = create();
if (stateKey != null) result.stateKey = stateKey;
if (groupPublicKey != null) result.groupPublicKey = groupPublicKey;
if (groupName != null) result.groupName = groupName;
return result;
}
@ -393,7 +391,6 @@ class EncryptedContent_GroupCreate extends $pb.GeneratedMessage {
3, _omitFieldNames ? '' : 'stateKey', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(
4, _omitFieldNames ? '' : 'groupPublicKey', $pb.PbFieldType.OY)
..aOS(5, _omitFieldNames ? '' : 'groupName')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@ -436,15 +433,6 @@ class EncryptedContent_GroupCreate extends $pb.GeneratedMessage {
$core.bool hasGroupPublicKey() => $_has(1);
@$pb.TagNumber(4)
void clearGroupPublicKey() => $_clearField(4);
@$pb.TagNumber(5)
$core.String get groupName => $_getSZ(2);
@$pb.TagNumber(5)
set groupName($core.String value) => $_setString(2, value);
@$pb.TagNumber(5)
$core.bool hasGroupName() => $_has(2);
@$pb.TagNumber(5)
void clearGroupName() => $_clearField(5);
}
class EncryptedContent_GroupJoin extends $pb.GeneratedMessage {
@ -1839,154 +1827,6 @@ class EncryptedContent_KeyVerificationProof extends $pb.GeneratedMessage {
void clearCalculatedMac() => $_clearField(1);
}
class EncryptedContent_PasswordLessRecovery extends $pb.GeneratedMessage {
factory EncryptedContent_PasswordLessRecovery({
$core.List<$core.int>? recoverySecretShare,
$core.bool? delete,
$fixnum.Int64? threshold,
}) {
final result = create();
if (recoverySecretShare != null)
result.recoverySecretShare = recoverySecretShare;
if (delete != null) result.delete = delete;
if (threshold != null) result.threshold = threshold;
return result;
}
EncryptedContent_PasswordLessRecovery._();
factory EncryptedContent_PasswordLessRecovery.fromBuffer(
$core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory EncryptedContent_PasswordLessRecovery.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'EncryptedContent.PasswordLessRecovery',
createEmptyInstance: create)
..a<$core.List<$core.int>>(
1, _omitFieldNames ? '' : 'recoverySecretShare', $pb.PbFieldType.OY,
protoName: 'recoverySecretShare')
..aOB(2, _omitFieldNames ? '' : 'delete')
..aInt64(3, _omitFieldNames ? '' : 'threshold')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
EncryptedContent_PasswordLessRecovery clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
EncryptedContent_PasswordLessRecovery copyWith(
void Function(EncryptedContent_PasswordLessRecovery) updates) =>
super.copyWith((message) =>
updates(message as EncryptedContent_PasswordLessRecovery))
as EncryptedContent_PasswordLessRecovery;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EncryptedContent_PasswordLessRecovery create() =>
EncryptedContent_PasswordLessRecovery._();
@$core.override
EncryptedContent_PasswordLessRecovery createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static EncryptedContent_PasswordLessRecovery getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
EncryptedContent_PasswordLessRecovery>(create);
static EncryptedContent_PasswordLessRecovery? _defaultInstance;
@$pb.TagNumber(1)
$core.List<$core.int> get recoverySecretShare => $_getN(0);
@$pb.TagNumber(1)
set recoverySecretShare($core.List<$core.int> value) => $_setBytes(0, value);
@$pb.TagNumber(1)
$core.bool hasRecoverySecretShare() => $_has(0);
@$pb.TagNumber(1)
void clearRecoverySecretShare() => $_clearField(1);
@$pb.TagNumber(2)
$core.bool get delete => $_getBF(1);
@$pb.TagNumber(2)
set delete($core.bool value) => $_setBool(1, value);
@$pb.TagNumber(2)
$core.bool hasDelete() => $_has(1);
@$pb.TagNumber(2)
void clearDelete() => $_clearField(2);
@$pb.TagNumber(3)
$fixnum.Int64 get threshold => $_getI64(2);
@$pb.TagNumber(3)
set threshold($fixnum.Int64 value) => $_setInt64(2, value);
@$pb.TagNumber(3)
$core.bool hasThreshold() => $_has(2);
@$pb.TagNumber(3)
void clearThreshold() => $_clearField(3);
}
class EncryptedContent_PasswordLessRecoveryHeartbeat
extends $pb.GeneratedMessage {
factory EncryptedContent_PasswordLessRecoveryHeartbeat({
$core.List<$core.int>? hash,
}) {
final result = create();
if (hash != null) result.hash = hash;
return result;
}
EncryptedContent_PasswordLessRecoveryHeartbeat._();
factory EncryptedContent_PasswordLessRecoveryHeartbeat.fromBuffer(
$core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory EncryptedContent_PasswordLessRecoveryHeartbeat.fromJson(
$core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'EncryptedContent.PasswordLessRecoveryHeartbeat',
createEmptyInstance: create)
..a<$core.List<$core.int>>(
1, _omitFieldNames ? '' : 'hash', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
EncryptedContent_PasswordLessRecoveryHeartbeat clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
EncryptedContent_PasswordLessRecoveryHeartbeat copyWith(
void Function(EncryptedContent_PasswordLessRecoveryHeartbeat)
updates) =>
super.copyWith((message) => updates(
message as EncryptedContent_PasswordLessRecoveryHeartbeat))
as EncryptedContent_PasswordLessRecoveryHeartbeat;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EncryptedContent_PasswordLessRecoveryHeartbeat create() =>
EncryptedContent_PasswordLessRecoveryHeartbeat._();
@$core.override
EncryptedContent_PasswordLessRecoveryHeartbeat createEmptyInstance() =>
create();
@$core.pragma('dart2js:noInline')
static EncryptedContent_PasswordLessRecoveryHeartbeat getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
EncryptedContent_PasswordLessRecoveryHeartbeat>(create);
static EncryptedContent_PasswordLessRecoveryHeartbeat? _defaultInstance;
@$pb.TagNumber(1)
$core.List<$core.int> get hash => $_getN(0);
@$pb.TagNumber(1)
set hash($core.List<$core.int> value) => $_setBytes(0, value);
@$pb.TagNumber(1)
$core.bool hasHash() => $_has(0);
@$pb.TagNumber(1)
void clearHash() => $_clearField(1);
}
class EncryptedContent extends $pb.GeneratedMessage {
factory EncryptedContent({
$core.String? groupId,
@ -2012,10 +1852,6 @@ class EncryptedContent extends $pb.GeneratedMessage {
EncryptedContent_UserDiscoveryRequest? userDiscoveryRequest,
EncryptedContent_UserDiscoveryUpdate? userDiscoveryUpdate,
EncryptedContent_KeyVerificationProof? keyVerificationProof,
$core.bool? askForFriendPromotions,
EncryptedContent_PasswordLessRecovery? passwordlessRecovery,
EncryptedContent_PasswordLessRecoveryHeartbeat?
passwordlessRecoveryHeartbeat,
}) {
final result = create();
if (groupId != null) result.groupId = groupId;
@ -2048,12 +1884,6 @@ class EncryptedContent extends $pb.GeneratedMessage {
result.userDiscoveryUpdate = userDiscoveryUpdate;
if (keyVerificationProof != null)
result.keyVerificationProof = keyVerificationProof;
if (askForFriendPromotions != null)
result.askForFriendPromotions = askForFriendPromotions;
if (passwordlessRecovery != null)
result.passwordlessRecovery = passwordlessRecovery;
if (passwordlessRecoveryHeartbeat != null)
result.passwordlessRecoveryHeartbeat = passwordlessRecoveryHeartbeat;
return result;
}
@ -2125,13 +1955,6 @@ class EncryptedContent extends $pb.GeneratedMessage {
..aOM<EncryptedContent_KeyVerificationProof>(
24, _omitFieldNames ? '' : 'keyVerificationProof',
subBuilder: EncryptedContent_KeyVerificationProof.create)
..aOB(25, _omitFieldNames ? '' : 'askForFriendPromotions')
..aOM<EncryptedContent_PasswordLessRecovery>(
26, _omitFieldNames ? '' : 'passwordlessRecovery',
subBuilder: EncryptedContent_PasswordLessRecovery.create)
..aOM<EncryptedContent_PasswordLessRecoveryHeartbeat>(
27, _omitFieldNames ? '' : 'passwordlessRecoveryHeartbeat',
subBuilder: EncryptedContent_PasswordLessRecoveryHeartbeat.create)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@ -2415,43 +2238,6 @@ class EncryptedContent extends $pb.GeneratedMessage {
@$pb.TagNumber(24)
EncryptedContent_KeyVerificationProof ensureKeyVerificationProof() =>
$_ensure(22);
@$pb.TagNumber(25)
$core.bool get askForFriendPromotions => $_getBF(23);
@$pb.TagNumber(25)
set askForFriendPromotions($core.bool value) => $_setBool(23, value);
@$pb.TagNumber(25)
$core.bool hasAskForFriendPromotions() => $_has(23);
@$pb.TagNumber(25)
void clearAskForFriendPromotions() => $_clearField(25);
@$pb.TagNumber(26)
EncryptedContent_PasswordLessRecovery get passwordlessRecovery => $_getN(24);
@$pb.TagNumber(26)
set passwordlessRecovery(EncryptedContent_PasswordLessRecovery value) =>
$_setField(26, value);
@$pb.TagNumber(26)
$core.bool hasPasswordlessRecovery() => $_has(24);
@$pb.TagNumber(26)
void clearPasswordlessRecovery() => $_clearField(26);
@$pb.TagNumber(26)
EncryptedContent_PasswordLessRecovery ensurePasswordlessRecovery() =>
$_ensure(24);
@$pb.TagNumber(27)
EncryptedContent_PasswordLessRecoveryHeartbeat
get passwordlessRecoveryHeartbeat => $_getN(25);
@$pb.TagNumber(27)
set passwordlessRecoveryHeartbeat(
EncryptedContent_PasswordLessRecoveryHeartbeat value) =>
$_setField(27, value);
@$pb.TagNumber(27)
$core.bool hasPasswordlessRecoveryHeartbeat() => $_has(25);
@$pb.TagNumber(27)
void clearPasswordlessRecoveryHeartbeat() => $_clearField(27);
@$pb.TagNumber(27)
EncryptedContent_PasswordLessRecoveryHeartbeat
ensurePasswordlessRecoveryHeartbeat() => $_ensure(25);
}
const $core.bool _omitFieldNames =

View file

@ -79,20 +79,16 @@ class EncryptedContent_ErrorMessages_Type extends $pb.ProtobufEnum {
static const EncryptedContent_ErrorMessages_Type SESSION_OUT_OF_SYNC =
EncryptedContent_ErrorMessages_Type._(
3, _omitEnumNames ? '' : 'SESSION_OUT_OF_SYNC');
static const EncryptedContent_ErrorMessages_Type
GROUP_NOT_FOUND_OR_NOT_A_MEMBER = EncryptedContent_ErrorMessages_Type._(
4, _omitEnumNames ? '' : 'GROUP_NOT_FOUND_OR_NOT_A_MEMBER');
static const $core.List<EncryptedContent_ErrorMessages_Type> values =
<EncryptedContent_ErrorMessages_Type>[
ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD,
UNKNOWN_MESSAGE_TYPE,
SESSION_OUT_OF_SYNC,
GROUP_NOT_FOUND_OR_NOT_A_MEMBER,
];
static final $core.List<EncryptedContent_ErrorMessages_Type?> _byValue =
$pb.ProtobufEnum.$_initByValueList(values, 4);
$pb.ProtobufEnum.$_initByValueList(values, 3);
static EncryptedContent_ErrorMessages_Type? valueOf($core.int value) =>
value < 0 || value >= _byValue.length ? null : _byValue[value];

View file

@ -186,22 +186,13 @@ const EncryptedContent$json = {
'10': 'senderUserDiscoveryVersion',
'17': true
},
{
'1': 'ask_for_friend_promotions',
'3': 25,
'4': 1,
'5': 8,
'9': 4,
'10': 'askForFriendPromotions',
'17': true
},
{
'1': 'message_update',
'3': 5,
'4': 1,
'5': 11,
'6': '.EncryptedContent.MessageUpdate',
'9': 5,
'9': 4,
'10': 'messageUpdate',
'17': true
},
@ -211,7 +202,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.Media',
'9': 6,
'9': 5,
'10': 'media',
'17': true
},
@ -221,7 +212,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.MediaUpdate',
'9': 7,
'9': 6,
'10': 'mediaUpdate',
'17': true
},
@ -231,7 +222,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.ContactUpdate',
'9': 8,
'9': 7,
'10': 'contactUpdate',
'17': true
},
@ -241,7 +232,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.ContactRequest',
'9': 9,
'9': 8,
'10': 'contactRequest',
'17': true
},
@ -251,7 +242,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.FlameSync',
'9': 10,
'9': 9,
'10': 'flameSync',
'17': true
},
@ -261,7 +252,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.PushKeys',
'9': 11,
'9': 10,
'10': 'pushKeys',
'17': true
},
@ -271,7 +262,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.Reaction',
'9': 12,
'9': 11,
'10': 'reaction',
'17': true
},
@ -281,7 +272,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.TextMessage',
'9': 13,
'9': 12,
'10': 'textMessage',
'17': true
},
@ -291,7 +282,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.GroupCreate',
'9': 14,
'9': 13,
'10': 'groupCreate',
'17': true
},
@ -301,7 +292,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.GroupJoin',
'9': 15,
'9': 14,
'10': 'groupJoin',
'17': true
},
@ -311,7 +302,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.GroupUpdate',
'9': 16,
'9': 15,
'10': 'groupUpdate',
'17': true
},
@ -321,7 +312,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.ResendGroupPublicKey',
'9': 17,
'9': 16,
'10': 'resendGroupPublicKey',
'17': true
},
@ -331,7 +322,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.ErrorMessages',
'9': 18,
'9': 17,
'10': 'errorMessages',
'17': true
},
@ -341,7 +332,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.AdditionalDataMessage',
'9': 19,
'9': 18,
'10': 'additionalDataMessage',
'17': true
},
@ -351,7 +342,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.TypingIndicator',
'9': 20,
'9': 19,
'10': 'typingIndicator',
'17': true
},
@ -361,7 +352,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.UserDiscoveryRequest',
'9': 21,
'9': 20,
'10': 'userDiscoveryRequest',
'17': true
},
@ -371,7 +362,7 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.UserDiscoveryUpdate',
'9': 22,
'9': 21,
'10': 'userDiscoveryUpdate',
'17': true
},
@ -381,30 +372,10 @@ const EncryptedContent$json = {
'4': 1,
'5': 11,
'6': '.EncryptedContent.KeyVerificationProof',
'9': 23,
'9': 22,
'10': 'keyVerificationProof',
'17': true
},
{
'1': 'passwordless_recovery',
'3': 26,
'4': 1,
'5': 11,
'6': '.EncryptedContent.PasswordLessRecovery',
'9': 24,
'10': 'passwordlessRecovery',
'17': true
},
{
'1': 'passwordless_recovery_heartbeat',
'3': 27,
'4': 1,
'5': 11,
'6': '.EncryptedContent.PasswordLessRecoveryHeartbeat',
'9': 25,
'10': 'passwordlessRecoveryHeartbeat',
'17': true
},
],
'3': [
EncryptedContent_ErrorMessages$json,
@ -425,16 +396,13 @@ const EncryptedContent$json = {
EncryptedContent_TypingIndicator$json,
EncryptedContent_UserDiscoveryRequest$json,
EncryptedContent_UserDiscoveryUpdate$json,
EncryptedContent_KeyVerificationProof$json,
EncryptedContent_PasswordLessRecovery$json,
EncryptedContent_PasswordLessRecoveryHeartbeat$json
EncryptedContent_KeyVerificationProof$json
],
'8': [
{'1': '_group_id'},
{'1': '_is_direct_chat'},
{'1': '_sender_profile_counter'},
{'1': '_sender_user_discovery_version'},
{'1': '_ask_for_friend_promotions'},
{'1': '_message_update'},
{'1': '_media'},
{'1': '_media_update'},
@ -454,8 +422,6 @@ const EncryptedContent$json = {
{'1': '_user_discovery_request'},
{'1': '_user_discovery_update'},
{'1': '_key_verification_proof'},
{'1': '_passwordless_recovery'},
{'1': '_passwordless_recovery_heartbeat'},
],
};
@ -489,7 +455,6 @@ const EncryptedContent_ErrorMessages_Type$json = {
{'1': 'ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD', '2': 0},
{'1': 'UNKNOWN_MESSAGE_TYPE', '2': 2},
{'1': 'SESSION_OUT_OF_SYNC', '2': 3},
{'1': 'GROUP_NOT_FOUND_OR_NOT_A_MEMBER', '2': 4},
],
};
@ -499,18 +464,6 @@ const EncryptedContent_GroupCreate$json = {
'2': [
{'1': 'state_key', '3': 3, '4': 1, '5': 12, '10': 'stateKey'},
{'1': 'group_public_key', '3': 4, '4': 1, '5': 12, '10': 'groupPublicKey'},
{
'1': 'group_name',
'3': 5,
'4': 1,
'5': 9,
'9': 0,
'10': 'groupName',
'17': true
},
],
'8': [
{'1': '_group_name'},
],
};
@ -979,150 +932,109 @@ const EncryptedContent_KeyVerificationProof$json = {
],
};
@$core.Deprecated('Use encryptedContentDescriptor instead')
const EncryptedContent_PasswordLessRecovery$json = {
'1': 'PasswordLessRecovery',
'2': [
{
'1': 'recoverySecretShare',
'3': 1,
'4': 1,
'5': 12,
'9': 0,
'10': 'recoverySecretShare',
'17': true
},
{'1': 'delete', '3': 2, '4': 1, '5': 8, '10': 'delete'},
{'1': 'threshold', '3': 3, '4': 1, '5': 3, '10': 'threshold'},
],
'8': [
{'1': '_recoverySecretShare'},
],
};
@$core.Deprecated('Use encryptedContentDescriptor instead')
const EncryptedContent_PasswordLessRecoveryHeartbeat$json = {
'1': 'PasswordLessRecoveryHeartbeat',
'2': [
{'1': 'hash', '3': 1, '4': 1, '5': 12, '10': 'hash'},
],
};
/// Descriptor for `EncryptedContent`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List encryptedContentDescriptor = $convert.base64Decode(
'ChBFbmNyeXB0ZWRDb250ZW50Eh4KCGdyb3VwX2lkGAIgASgJSABSB2dyb3VwSWSIAQESKQoOaX'
'NfZGlyZWN0X2NoYXQYAyABKAhIAVIMaXNEaXJlY3RDaGF0iAEBEjkKFnNlbmRlcl9wcm9maWxl'
'X2NvdW50ZXIYBCABKANIAlIUc2VuZGVyUHJvZmlsZUNvdW50ZXKIAQESRgodc2VuZGVyX3VzZX'
'JfZGlzY292ZXJ5X3ZlcnNpb24YFSABKAxIA1Iac2VuZGVyVXNlckRpc2NvdmVyeVZlcnNpb26I'
'AQESPgoZYXNrX2Zvcl9mcmllbmRfcHJvbW90aW9ucxgZIAEoCEgEUhZhc2tGb3JGcmllbmRQcm'
'9tb3Rpb25ziAEBEksKDm1lc3NhZ2VfdXBkYXRlGAUgASgLMh8uRW5jcnlwdGVkQ29udGVudC5N'
'ZXNzYWdlVXBkYXRlSAVSDW1lc3NhZ2VVcGRhdGWIAQESMgoFbWVkaWEYBiABKAsyFy5FbmNyeX'
'B0ZWRDb250ZW50Lk1lZGlhSAZSBW1lZGlhiAEBEkUKDG1lZGlhX3VwZGF0ZRgHIAEoCzIdLkVu'
'Y3J5cHRlZENvbnRlbnQuTWVkaWFVcGRhdGVIB1ILbWVkaWFVcGRhdGWIAQESSwoOY29udGFjdF'
'91cGRhdGUYCCABKAsyHy5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RVcGRhdGVICFINY29udGFj'
'dFVwZGF0ZYgBARJOCg9jb250YWN0X3JlcXVlc3QYCSABKAsyIC5FbmNyeXB0ZWRDb250ZW50Lk'
'NvbnRhY3RSZXF1ZXN0SAlSDmNvbnRhY3RSZXF1ZXN0iAEBEj8KCmZsYW1lX3N5bmMYCiABKAsy'
'Gy5FbmNyeXB0ZWRDb250ZW50LkZsYW1lU3luY0gKUglmbGFtZVN5bmOIAQESPAoJcHVzaF9rZX'
'lzGAsgASgLMhouRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5c0gLUghwdXNoS2V5c4gBARI7Cghy'
'ZWFjdGlvbhgMIAEoCzIaLkVuY3J5cHRlZENvbnRlbnQuUmVhY3Rpb25IDFIIcmVhY3Rpb26IAQ'
'ESRQoMdGV4dF9tZXNzYWdlGA0gASgLMh0uRW5jcnlwdGVkQ29udGVudC5UZXh0TWVzc2FnZUgN'
'Ugt0ZXh0TWVzc2FnZYgBARJFCgxncm91cF9jcmVhdGUYDiABKAsyHS5FbmNyeXB0ZWRDb250ZW'
'50Lkdyb3VwQ3JlYXRlSA5SC2dyb3VwQ3JlYXRliAEBEj8KCmdyb3VwX2pvaW4YDyABKAsyGy5F'
'bmNyeXB0ZWRDb250ZW50Lkdyb3VwSm9pbkgPUglncm91cEpvaW6IAQESRQoMZ3JvdXBfdXBkYX'
'RlGBAgASgLMh0uRW5jcnlwdGVkQ29udGVudC5Hcm91cFVwZGF0ZUgQUgtncm91cFVwZGF0ZYgB'
'ARJiChdyZXNlbmRfZ3JvdXBfcHVibGljX2tleRgRIAEoCzImLkVuY3J5cHRlZENvbnRlbnQuUm'
'VzZW5kR3JvdXBQdWJsaWNLZXlIEVIUcmVzZW5kR3JvdXBQdWJsaWNLZXmIAQESSwoOZXJyb3Jf'
'bWVzc2FnZXMYEiABKAsyHy5FbmNyeXB0ZWRDb250ZW50LkVycm9yTWVzc2FnZXNIElINZXJyb3'
'JNZXNzYWdlc4gBARJkChdhZGRpdGlvbmFsX2RhdGFfbWVzc2FnZRgTIAEoCzInLkVuY3J5cHRl'
'ZENvbnRlbnQuQWRkaXRpb25hbERhdGFNZXNzYWdlSBNSFWFkZGl0aW9uYWxEYXRhTWVzc2FnZY'
'gBARJRChB0eXBpbmdfaW5kaWNhdG9yGBQgASgLMiEuRW5jcnlwdGVkQ29udGVudC5UeXBpbmdJ'
'bmRpY2F0b3JIFFIPdHlwaW5nSW5kaWNhdG9yiAEBEmEKFnVzZXJfZGlzY292ZXJ5X3JlcXVlc3'
'QYFiABKAsyJi5FbmNyeXB0ZWRDb250ZW50LlVzZXJEaXNjb3ZlcnlSZXF1ZXN0SBVSFHVzZXJE'
'aXNjb3ZlcnlSZXF1ZXN0iAEBEl4KFXVzZXJfZGlzY292ZXJ5X3VwZGF0ZRgXIAEoCzIlLkVuY3'
'J5cHRlZENvbnRlbnQuVXNlckRpc2NvdmVyeVVwZGF0ZUgWUhN1c2VyRGlzY292ZXJ5VXBkYXRl'
'iAEBEmEKFmtleV92ZXJpZmljYXRpb25fcHJvb2YYGCABKAsyJi5FbmNyeXB0ZWRDb250ZW50Lk'
'tleVZlcmlmaWNhdGlvblByb29mSBdSFGtleVZlcmlmaWNhdGlvblByb29miAEBEmAKFXBhc3N3'
'b3JkbGVzc19yZWNvdmVyeRgaIAEoCzImLkVuY3J5cHRlZENvbnRlbnQuUGFzc3dvcmRMZXNzUm'
'Vjb3ZlcnlIGFIUcGFzc3dvcmRsZXNzUmVjb3ZlcnmIAQESfAofcGFzc3dvcmRsZXNzX3JlY292'
'ZXJ5X2hlYXJ0YmVhdBgbIAEoCzIvLkVuY3J5cHRlZENvbnRlbnQuUGFzc3dvcmRMZXNzUmVjb3'
'ZlcnlIZWFydGJlYXRIGVIdcGFzc3dvcmRsZXNzUmVjb3ZlcnlIZWFydGJlYXSIAQEalgIKDUVy'
'cm9yTWVzc2FnZXMSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNzYW'
'dlcy5UeXBlUgR0eXBlEiwKEnJlbGF0ZWRfcmVjZWlwdF9pZBgCIAEoCVIQcmVsYXRlZFJlY2Vp'
'cHRJZCKcAQoEVHlwZRI8CjhFUlJPUl9QUk9DRVNTSU5HX01FU1NBR0VfQ1JFQVRFRF9BQ0NPVU'
'5UX1JFUVVFU1RfSU5TVEVBRBAAEhgKFFVOS05PV05fTUVTU0FHRV9UWVBFEAISFwoTU0VTU0lP'
'Tl9PVVRfT0ZfU1lOQxADEiMKH0dST1VQX05PVF9GT1VORF9PUl9OT1RfQV9NRU1CRVIQBBqHAQ'
'oLR3JvdXBDcmVhdGUSGwoJc3RhdGVfa2V5GAMgASgMUghzdGF0ZUtleRIoChBncm91cF9wdWJs'
'aWNfa2V5GAQgASgMUg5ncm91cFB1YmxpY0tleRIiCgpncm91cF9uYW1lGAUgASgJSABSCWdyb3'
'VwTmFtZYgBAUINCgtfZ3JvdXBfbmFtZRo1CglHcm91cEpvaW4SKAoQZ3JvdXBfcHVibGljX2tl'
'eRgBIAEoDFIOZ3JvdXBQdWJsaWNLZXkaFgoUUmVzZW5kR3JvdXBQdWJsaWNLZXkayAIKC0dyb3'
'VwVXBkYXRlEioKEWdyb3VwX2FjdGlvbl90eXBlGAEgASgJUg9ncm91cEFjdGlvblR5cGUSMwoT'
'YWZmZWN0ZWRfY29udGFjdF9pZBgCIAEoA0gAUhFhZmZlY3RlZENvbnRhY3RJZIgBARIpCg5uZX'
'dfZ3JvdXBfbmFtZRgDIAEoCUgBUgxuZXdHcm91cE5hbWWIAQESVwombmV3X2RlbGV0ZV9tZXNz'
'YWdlc19hZnRlcl9taWxsaXNlY29uZHMYBCABKANIAlIibmV3RGVsZXRlTWVzc2FnZXNBZnRlck'
'1pbGxpc2Vjb25kc4gBAUIWChRfYWZmZWN0ZWRfY29udGFjdF9pZEIRCg9fbmV3X2dyb3VwX25h'
'bWVCKQonX25ld19kZWxldGVfbWVzc2FnZXNfYWZ0ZXJfbWlsbGlzZWNvbmRzGq8BCgtUZXh0TW'
'Vzc2FnZRIqChFzZW5kZXJfbWVzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2FnZUlkEhIKBHRl'
'eHQYAiABKAlSBHRleHQSHAoJdGltZXN0YW1wGAMgASgDUgl0aW1lc3RhbXASLQoQcXVvdGVfbW'
'Vzc2FnZV9pZBgEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBAUITChFfcXVvdGVfbWVzc2FnZV9p'
'ZBrOAQoVQWRkaXRpb25hbERhdGFNZXNzYWdlEioKEXNlbmRlcl9tZXNzYWdlX2lkGAEgASgJUg'
'9zZW5kZXJNZXNzYWdlSWQSHAoJdGltZXN0YW1wGAIgASgDUgl0aW1lc3RhbXASEgoEdHlwZRgD'
'IAEoCVIEdHlwZRI7ChdhZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRgEIAEoDEgAUhVhZGRpdGlvbm'
'FsTWVzc2FnZURhdGGIAQFCGgoYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGmQKCFJlYWN0aW9u'
'EioKEXRhcmdldF9tZXNzYWdlX2lkGAEgASgJUg90YXJnZXRNZXNzYWdlSWQSFAoFZW1vamkYAi'
'ABKAlSBWVtb2ppEhYKBnJlbW92ZRgDIAEoCFIGcmVtb3ZlGr4CCg1NZXNzYWdlVXBkYXRlEjgK'
'BHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50Lk1lc3NhZ2VVcGRhdGUuVHlwZVIEdHlwZR'
'IvChFzZW5kZXJfbWVzc2FnZV9pZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSWSIAQESPQobbXVs'
'dGlwbGVfdGFyZ2V0X21lc3NhZ2VfaWRzGAMgAygJUhhtdWx0aXBsZVRhcmdldE1lc3NhZ2VJZH'
'MSFwoEdGV4dBgEIAEoCUgBUgR0ZXh0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1IJdGltZXN0YW1w'
'Ii0KBFR5cGUSCgoGREVMRVRFEAASDQoJRURJVF9URVhUEAESCgoGT1BFTkVEEAJCFAoSX3Nlbm'
'Rlcl9tZXNzYWdlX2lkQgcKBV90ZXh0GoUGCgVNZWRpYRIqChFzZW5kZXJfbWVzc2FnZV9pZBgB'
'IAEoCVIPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiABKA4yHC5FbmNyeXB0ZWRDb250ZW50Lk'
'1lZGlhLlR5cGVSBHR5cGUSRgodZGlzcGxheV9saW1pdF9pbl9taWxsaXNlY29uZHMYAyABKANI'
'AFIaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQESNwoXcmVxdWlyZXNfYXV0aGVudGljYX'
'Rpb24YBCABKAhSFnJlcXVpcmVzQXV0aGVudGljYXRpb24SHAoJdGltZXN0YW1wGAUgASgDUgl0'
'aW1lc3RhbXASLQoQcXVvdGVfbWVzc2FnZV9pZBgGIAEoCUgBUg5xdW90ZU1lc3NhZ2VJZIgBAR'
'IqCg5kb3dubG9hZF90b2tlbhgHIAEoDEgCUg1kb3dubG9hZFRva2VuiAEBEioKDmVuY3J5cHRp'
'b25fa2V5GAggASgMSANSDWVuY3J5cHRpb25LZXmIAQESKgoOZW5jcnlwdGlvbl9tYWMYCSABKA'
'xIBFINZW5jcnlwdGlvbk1hY4gBARIuChBlbmNyeXB0aW9uX25vbmNlGAogASgMSAVSD2VuY3J5'
'cHRpb25Ob25jZYgBARI7ChdhZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRgLIAEoDEgGUhVhZGRpdG'
'lvbmFsTWVzc2FnZURhdGGIAQEiPgoEVHlwZRIMCghSRVVQTE9BRBAAEgkKBUlNQUdFEAESCQoF'
'VklERU8QAhIHCgNHSUYQAxIJCgVBVURJTxAEQiAKHl9kaXNwbGF5X2xpbWl0X2luX21pbGxpc2'
'Vjb25kc0ITChFfcXVvdGVfbWVzc2FnZV9pZEIRCg9fZG93bmxvYWRfdG9rZW5CEQoPX2VuY3J5'
'cHRpb25fa2V5QhEKD19lbmNyeXB0aW9uX21hY0ITChFfZW5jcnlwdGlvbl9ub25jZUIaChhfYW'
'RkaXRpb25hbF9tZXNzYWdlX2RhdGEaqQEKC01lZGlhVXBkYXRlEjYKBHR5cGUYASABKA4yIi5F'
'bmNyeXB0ZWRDb250ZW50Lk1lZGlhVXBkYXRlLlR5cGVSBHR5cGUSKgoRdGFyZ2V0X21lc3NhZ2'
'VfaWQYAiABKAlSD3RhcmdldE1lc3NhZ2VJZCI2CgRUeXBlEgwKCFJFT1BFTkVEEAASCgoGU1RP'
'UkVEEAESFAoQREVDUllQVElPTl9FUlJPUhACGngKDkNvbnRhY3RSZXF1ZXN0EjkKBHR5cGUYAS'
'ABKA4yJS5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RSZXF1ZXN0LlR5cGVSBHR5cGUiKwoEVHlw'
'ZRILCgdSRVFVRVNUEAASCgoGUkVKRUNUEAESCgoGQUNDRVBUEAIapAIKDUNvbnRhY3RVcGRhdG'
'USOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFVwZGF0ZS5UeXBlUgR0'
'eXBlEjcKFWF2YXRhcl9zdmdfY29tcHJlc3NlZBgCIAEoDEgAUhNhdmF0YXJTdmdDb21wcmVzc2'
'VkiAEBEh8KCHVzZXJuYW1lGAMgASgJSAFSCHVzZXJuYW1liAEBEiYKDGRpc3BsYXlfbmFtZRgE'
'IAEoCUgCUgtkaXNwbGF5TmFtZYgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIKCgZVUERBVEUQAU'
'IYChZfYXZhdGFyX3N2Z19jb21wcmVzc2VkQgsKCV91c2VybmFtZUIPCg1fZGlzcGxheV9uYW1l'
'GtkBCghQdXNoS2V5cxIzCgR0eXBlGAEgASgOMh8uRW5jcnlwdGVkQ29udGVudC5QdXNoS2V5cy'
'5UeXBlUgR0eXBlEhoKBmtleV9pZBgCIAEoA0gAUgVrZXlJZIgBARIVCgNrZXkYAyABKAxIAVID'
'a2V5iAEBEiIKCmNyZWF0ZWRfYXQYBCABKANIAlIJY3JlYXRlZEF0iAEBIh8KBFR5cGUSCwoHUk'
'VRVUVTVBAAEgoKBlVQREFURRABQgkKB19rZXlfaWRCBgoEX2tleUINCgtfY3JlYXRlZF9hdBqv'
'AQoJRmxhbWVTeW5jEiMKDWZsYW1lX2NvdW50ZXIYASABKANSDGZsYW1lQ291bnRlchI5ChlsYX'
'N0X2ZsYW1lX2NvdW50ZXJfY2hhbmdlGAIgASgDUhZsYXN0RmxhbWVDb3VudGVyQ2hhbmdlEh8K'
'C2Jlc3RfZnJpZW5kGAMgASgIUgpiZXN0RnJpZW5kEiEKDGZvcmNlX3VwZGF0ZRgEIAEoCFILZm'
'9yY2VVcGRhdGUaTQoPVHlwaW5nSW5kaWNhdG9yEhsKCWlzX3R5cGluZxgBIAEoCFIIaXNUeXBp'
'bmcSHQoKY3JlYXRlZF9hdBgCIAEoA1IJY3JlYXRlZEF0Gj8KFFVzZXJEaXNjb3ZlcnlSZXF1ZX'
'N0EicKD2N1cnJlbnRfdmVyc2lvbhgBIAEoDFIOY3VycmVudFZlcnNpb24aMQoTVXNlckRpc2Nv'
'dmVyeVVwZGF0ZRIaCghtZXNzYWdlcxgBIAMoDFIIbWVzc2FnZXMaPQoUS2V5VmVyaWZpY2F0aW'
'9uUHJvb2YSJQoOY2FsY3VsYXRlZF9tYWMYASABKAxSDWNhbGN1bGF0ZWRNYWMamwEKFFBhc3N3'
'b3JkTGVzc1JlY292ZXJ5EjUKE3JlY292ZXJ5U2VjcmV0U2hhcmUYASABKAxIAFITcmVjb3Zlcn'
'lTZWNyZXRTaGFyZYgBARIWCgZkZWxldGUYAiABKAhSBmRlbGV0ZRIcCgl0aHJlc2hvbGQYAyAB'
'KANSCXRocmVzaG9sZEIWChRfcmVjb3ZlcnlTZWNyZXRTaGFyZRozCh1QYXNzd29yZExlc3NSZW'
'NvdmVyeUhlYXJ0YmVhdBISCgRoYXNoGAEgASgMUgRoYXNoQgsKCV9ncm91cF9pZEIRCg9faXNf'
'ZGlyZWN0X2NoYXRCGQoXX3NlbmRlcl9wcm9maWxlX2NvdW50ZXJCIAoeX3NlbmRlcl91c2VyX2'
'Rpc2NvdmVyeV92ZXJzaW9uQhwKGl9hc2tfZm9yX2ZyaWVuZF9wcm9tb3Rpb25zQhEKD19tZXNz'
'YWdlX3VwZGF0ZUIICgZfbWVkaWFCDwoNX21lZGlhX3VwZGF0ZUIRCg9fY29udGFjdF91cGRhdG'
'VCEgoQX2NvbnRhY3RfcmVxdWVzdEINCgtfZmxhbWVfc3luY0IMCgpfcHVzaF9rZXlzQgsKCV9y'
'ZWFjdGlvbkIPCg1fdGV4dF9tZXNzYWdlQg8KDV9ncm91cF9jcmVhdGVCDQoLX2dyb3VwX2pvaW'
'5CDwoNX2dyb3VwX3VwZGF0ZUIaChhfcmVzZW5kX2dyb3VwX3B1YmxpY19rZXlCEQoPX2Vycm9y'
'X21lc3NhZ2VzQhoKGF9hZGRpdGlvbmFsX2RhdGFfbWVzc2FnZUITChFfdHlwaW5nX2luZGljYX'
'RvckIZChdfdXNlcl9kaXNjb3ZlcnlfcmVxdWVzdEIYChZfdXNlcl9kaXNjb3ZlcnlfdXBkYXRl'
'QhkKF19rZXlfdmVyaWZpY2F0aW9uX3Byb29mQhgKFl9wYXNzd29yZGxlc3NfcmVjb3ZlcnlCIg'
'ogX3Bhc3N3b3JkbGVzc19yZWNvdmVyeV9oZWFydGJlYXQ=');
'AQESSwoObWVzc2FnZV91cGRhdGUYBSABKAsyHy5FbmNyeXB0ZWRDb250ZW50Lk1lc3NhZ2VVcG'
'RhdGVIBFINbWVzc2FnZVVwZGF0ZYgBARIyCgVtZWRpYRgGIAEoCzIXLkVuY3J5cHRlZENvbnRl'
'bnQuTWVkaWFIBVIFbWVkaWGIAQESRQoMbWVkaWFfdXBkYXRlGAcgASgLMh0uRW5jcnlwdGVkQ2'
'9udGVudC5NZWRpYVVwZGF0ZUgGUgttZWRpYVVwZGF0ZYgBARJLCg5jb250YWN0X3VwZGF0ZRgI'
'IAEoCzIfLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFVwZGF0ZUgHUg1jb250YWN0VXBkYXRliA'
'EBEk4KD2NvbnRhY3RfcmVxdWVzdBgJIAEoCzIgLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFJl'
'cXVlc3RICFIOY29udGFjdFJlcXVlc3SIAQESPwoKZmxhbWVfc3luYxgKIAEoCzIbLkVuY3J5cH'
'RlZENvbnRlbnQuRmxhbWVTeW5jSAlSCWZsYW1lU3luY4gBARI8CglwdXNoX2tleXMYCyABKAsy'
'Gi5FbmNyeXB0ZWRDb250ZW50LlB1c2hLZXlzSApSCHB1c2hLZXlziAEBEjsKCHJlYWN0aW9uGA'
'wgASgLMhouRW5jcnlwdGVkQ29udGVudC5SZWFjdGlvbkgLUghyZWFjdGlvbogBARJFCgx0ZXh0'
'X21lc3NhZ2UYDSABKAsyHS5FbmNyeXB0ZWRDb250ZW50LlRleHRNZXNzYWdlSAxSC3RleHRNZX'
'NzYWdliAEBEkUKDGdyb3VwX2NyZWF0ZRgOIAEoCzIdLkVuY3J5cHRlZENvbnRlbnQuR3JvdXBD'
'cmVhdGVIDVILZ3JvdXBDcmVhdGWIAQESPwoKZ3JvdXBfam9pbhgPIAEoCzIbLkVuY3J5cHRlZE'
'NvbnRlbnQuR3JvdXBKb2luSA5SCWdyb3VwSm9pbogBARJFCgxncm91cF91cGRhdGUYECABKAsy'
'HS5FbmNyeXB0ZWRDb250ZW50Lkdyb3VwVXBkYXRlSA9SC2dyb3VwVXBkYXRliAEBEmIKF3Jlc2'
'VuZF9ncm91cF9wdWJsaWNfa2V5GBEgASgLMiYuRW5jcnlwdGVkQ29udGVudC5SZXNlbmRHcm91'
'cFB1YmxpY0tleUgQUhRyZXNlbmRHcm91cFB1YmxpY0tleYgBARJLCg5lcnJvcl9tZXNzYWdlcx'
'gSIAEoCzIfLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNzYWdlc0gRUg1lcnJvck1lc3NhZ2Vz'
'iAEBEmQKF2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdlGBMgASgLMicuRW5jcnlwdGVkQ29udGVudC'
'5BZGRpdGlvbmFsRGF0YU1lc3NhZ2VIElIVYWRkaXRpb25hbERhdGFNZXNzYWdliAEBElEKEHR5'
'cGluZ19pbmRpY2F0b3IYFCABKAsyIS5FbmNyeXB0ZWRDb250ZW50LlR5cGluZ0luZGljYXRvck'
'gTUg90eXBpbmdJbmRpY2F0b3KIAQESYQoWdXNlcl9kaXNjb3ZlcnlfcmVxdWVzdBgWIAEoCzIm'
'LkVuY3J5cHRlZENvbnRlbnQuVXNlckRpc2NvdmVyeVJlcXVlc3RIFFIUdXNlckRpc2NvdmVyeV'
'JlcXVlc3SIAQESXgoVdXNlcl9kaXNjb3ZlcnlfdXBkYXRlGBcgASgLMiUuRW5jcnlwdGVkQ29u'
'dGVudC5Vc2VyRGlzY292ZXJ5VXBkYXRlSBVSE3VzZXJEaXNjb3ZlcnlVcGRhdGWIAQESYQoWa2'
'V5X3ZlcmlmaWNhdGlvbl9wcm9vZhgYIAEoCzImLkVuY3J5cHRlZENvbnRlbnQuS2V5VmVyaWZp'
'Y2F0aW9uUHJvb2ZIFlIUa2V5VmVyaWZpY2F0aW9uUHJvb2aIAQEa8AEKDUVycm9yTWVzc2FnZX'
'MSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuRXJyb3JNZXNzYWdlcy5UeXBlUgR0'
'eXBlEiwKEnJlbGF0ZWRfcmVjZWlwdF9pZBgCIAEoCVIQcmVsYXRlZFJlY2VpcHRJZCJ3CgRUeX'
'BlEjwKOEVSUk9SX1BST0NFU1NJTkdfTUVTU0FHRV9DUkVBVEVEX0FDQ09VTlRfUkVRVUVTVF9J'
'TlNURUFEEAASGAoUVU5LTk9XTl9NRVNTQUdFX1RZUEUQAhIXChNTRVNTSU9OX09VVF9PRl9TWU'
'5DEAMaVAoLR3JvdXBDcmVhdGUSGwoJc3RhdGVfa2V5GAMgASgMUghzdGF0ZUtleRIoChBncm91'
'cF9wdWJsaWNfa2V5GAQgASgMUg5ncm91cFB1YmxpY0tleRo1CglHcm91cEpvaW4SKAoQZ3JvdX'
'BfcHVibGljX2tleRgBIAEoDFIOZ3JvdXBQdWJsaWNLZXkaFgoUUmVzZW5kR3JvdXBQdWJsaWNL'
'ZXkayAIKC0dyb3VwVXBkYXRlEioKEWdyb3VwX2FjdGlvbl90eXBlGAEgASgJUg9ncm91cEFjdG'
'lvblR5cGUSMwoTYWZmZWN0ZWRfY29udGFjdF9pZBgCIAEoA0gAUhFhZmZlY3RlZENvbnRhY3RJ'
'ZIgBARIpCg5uZXdfZ3JvdXBfbmFtZRgDIAEoCUgBUgxuZXdHcm91cE5hbWWIAQESVwombmV3X2'
'RlbGV0ZV9tZXNzYWdlc19hZnRlcl9taWxsaXNlY29uZHMYBCABKANIAlIibmV3RGVsZXRlTWVz'
'c2FnZXNBZnRlck1pbGxpc2Vjb25kc4gBAUIWChRfYWZmZWN0ZWRfY29udGFjdF9pZEIRCg9fbm'
'V3X2dyb3VwX25hbWVCKQonX25ld19kZWxldGVfbWVzc2FnZXNfYWZ0ZXJfbWlsbGlzZWNvbmRz'
'Gq8BCgtUZXh0TWVzc2FnZRIqChFzZW5kZXJfbWVzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2'
'FnZUlkEhIKBHRleHQYAiABKAlSBHRleHQSHAoJdGltZXN0YW1wGAMgASgDUgl0aW1lc3RhbXAS'
'LQoQcXVvdGVfbWVzc2FnZV9pZBgEIAEoCUgAUg5xdW90ZU1lc3NhZ2VJZIgBAUITChFfcXVvdG'
'VfbWVzc2FnZV9pZBrOAQoVQWRkaXRpb25hbERhdGFNZXNzYWdlEioKEXNlbmRlcl9tZXNzYWdl'
'X2lkGAEgASgJUg9zZW5kZXJNZXNzYWdlSWQSHAoJdGltZXN0YW1wGAIgASgDUgl0aW1lc3RhbX'
'ASEgoEdHlwZRgDIAEoCVIEdHlwZRI7ChdhZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRgEIAEoDEgA'
'UhVhZGRpdGlvbmFsTWVzc2FnZURhdGGIAQFCGgoYX2FkZGl0aW9uYWxfbWVzc2FnZV9kYXRhGm'
'QKCFJlYWN0aW9uEioKEXRhcmdldF9tZXNzYWdlX2lkGAEgASgJUg90YXJnZXRNZXNzYWdlSWQS'
'FAoFZW1vamkYAiABKAlSBWVtb2ppEhYKBnJlbW92ZRgDIAEoCFIGcmVtb3ZlGr4CCg1NZXNzYW'
'dlVXBkYXRlEjgKBHR5cGUYASABKA4yJC5FbmNyeXB0ZWRDb250ZW50Lk1lc3NhZ2VVcGRhdGUu'
'VHlwZVIEdHlwZRIvChFzZW5kZXJfbWVzc2FnZV9pZBgCIAEoCUgAUg9zZW5kZXJNZXNzYWdlSW'
'SIAQESPQobbXVsdGlwbGVfdGFyZ2V0X21lc3NhZ2VfaWRzGAMgAygJUhhtdWx0aXBsZVRhcmdl'
'dE1lc3NhZ2VJZHMSFwoEdGV4dBgEIAEoCUgBUgR0ZXh0iAEBEhwKCXRpbWVzdGFtcBgFIAEoA1'
'IJdGltZXN0YW1wIi0KBFR5cGUSCgoGREVMRVRFEAASDQoJRURJVF9URVhUEAESCgoGT1BFTkVE'
'EAJCFAoSX3NlbmRlcl9tZXNzYWdlX2lkQgcKBV90ZXh0GoUGCgVNZWRpYRIqChFzZW5kZXJfbW'
'Vzc2FnZV9pZBgBIAEoCVIPc2VuZGVyTWVzc2FnZUlkEjAKBHR5cGUYAiABKA4yHC5FbmNyeXB0'
'ZWRDb250ZW50Lk1lZGlhLlR5cGVSBHR5cGUSRgodZGlzcGxheV9saW1pdF9pbl9taWxsaXNlY2'
'9uZHMYAyABKANIAFIaZGlzcGxheUxpbWl0SW5NaWxsaXNlY29uZHOIAQESNwoXcmVxdWlyZXNf'
'YXV0aGVudGljYXRpb24YBCABKAhSFnJlcXVpcmVzQXV0aGVudGljYXRpb24SHAoJdGltZXN0YW'
'1wGAUgASgDUgl0aW1lc3RhbXASLQoQcXVvdGVfbWVzc2FnZV9pZBgGIAEoCUgBUg5xdW90ZU1l'
'c3NhZ2VJZIgBARIqCg5kb3dubG9hZF90b2tlbhgHIAEoDEgCUg1kb3dubG9hZFRva2VuiAEBEi'
'oKDmVuY3J5cHRpb25fa2V5GAggASgMSANSDWVuY3J5cHRpb25LZXmIAQESKgoOZW5jcnlwdGlv'
'bl9tYWMYCSABKAxIBFINZW5jcnlwdGlvbk1hY4gBARIuChBlbmNyeXB0aW9uX25vbmNlGAogAS'
'gMSAVSD2VuY3J5cHRpb25Ob25jZYgBARI7ChdhZGRpdGlvbmFsX21lc3NhZ2VfZGF0YRgLIAEo'
'DEgGUhVhZGRpdGlvbmFsTWVzc2FnZURhdGGIAQEiPgoEVHlwZRIMCghSRVVQTE9BRBAAEgkKBU'
'lNQUdFEAESCQoFVklERU8QAhIHCgNHSUYQAxIJCgVBVURJTxAEQiAKHl9kaXNwbGF5X2xpbWl0'
'X2luX21pbGxpc2Vjb25kc0ITChFfcXVvdGVfbWVzc2FnZV9pZEIRCg9fZG93bmxvYWRfdG9rZW'
'5CEQoPX2VuY3J5cHRpb25fa2V5QhEKD19lbmNyeXB0aW9uX21hY0ITChFfZW5jcnlwdGlvbl9u'
'b25jZUIaChhfYWRkaXRpb25hbF9tZXNzYWdlX2RhdGEaqQEKC01lZGlhVXBkYXRlEjYKBHR5cG'
'UYASABKA4yIi5FbmNyeXB0ZWRDb250ZW50Lk1lZGlhVXBkYXRlLlR5cGVSBHR5cGUSKgoRdGFy'
'Z2V0X21lc3NhZ2VfaWQYAiABKAlSD3RhcmdldE1lc3NhZ2VJZCI2CgRUeXBlEgwKCFJFT1BFTk'
'VEEAASCgoGU1RPUkVEEAESFAoQREVDUllQVElPTl9FUlJPUhACGngKDkNvbnRhY3RSZXF1ZXN0'
'EjkKBHR5cGUYASABKA4yJS5FbmNyeXB0ZWRDb250ZW50LkNvbnRhY3RSZXF1ZXN0LlR5cGVSBH'
'R5cGUiKwoEVHlwZRILCgdSRVFVRVNUEAASCgoGUkVKRUNUEAESCgoGQUNDRVBUEAIapAIKDUNv'
'bnRhY3RVcGRhdGUSOAoEdHlwZRgBIAEoDjIkLkVuY3J5cHRlZENvbnRlbnQuQ29udGFjdFVwZG'
'F0ZS5UeXBlUgR0eXBlEjcKFWF2YXRhcl9zdmdfY29tcHJlc3NlZBgCIAEoDEgAUhNhdmF0YXJT'
'dmdDb21wcmVzc2VkiAEBEh8KCHVzZXJuYW1lGAMgASgJSAFSCHVzZXJuYW1liAEBEiYKDGRpc3'
'BsYXlfbmFtZRgEIAEoCUgCUgtkaXNwbGF5TmFtZYgBASIfCgRUeXBlEgsKB1JFUVVFU1QQABIK'
'CgZVUERBVEUQAUIYChZfYXZhdGFyX3N2Z19jb21wcmVzc2VkQgsKCV91c2VybmFtZUIPCg1fZG'
'lzcGxheV9uYW1lGtkBCghQdXNoS2V5cxIzCgR0eXBlGAEgASgOMh8uRW5jcnlwdGVkQ29udGVu'
'dC5QdXNoS2V5cy5UeXBlUgR0eXBlEhoKBmtleV9pZBgCIAEoA0gAUgVrZXlJZIgBARIVCgNrZX'
'kYAyABKAxIAVIDa2V5iAEBEiIKCmNyZWF0ZWRfYXQYBCABKANIAlIJY3JlYXRlZEF0iAEBIh8K'
'BFR5cGUSCwoHUkVRVUVTVBAAEgoKBlVQREFURRABQgkKB19rZXlfaWRCBgoEX2tleUINCgtfY3'
'JlYXRlZF9hdBqvAQoJRmxhbWVTeW5jEiMKDWZsYW1lX2NvdW50ZXIYASABKANSDGZsYW1lQ291'
'bnRlchI5ChlsYXN0X2ZsYW1lX2NvdW50ZXJfY2hhbmdlGAIgASgDUhZsYXN0RmxhbWVDb3VudG'
'VyQ2hhbmdlEh8KC2Jlc3RfZnJpZW5kGAMgASgIUgpiZXN0RnJpZW5kEiEKDGZvcmNlX3VwZGF0'
'ZRgEIAEoCFILZm9yY2VVcGRhdGUaTQoPVHlwaW5nSW5kaWNhdG9yEhsKCWlzX3R5cGluZxgBIA'
'EoCFIIaXNUeXBpbmcSHQoKY3JlYXRlZF9hdBgCIAEoA1IJY3JlYXRlZEF0Gj8KFFVzZXJEaXNj'
'b3ZlcnlSZXF1ZXN0EicKD2N1cnJlbnRfdmVyc2lvbhgBIAEoDFIOY3VycmVudFZlcnNpb24aMQ'
'oTVXNlckRpc2NvdmVyeVVwZGF0ZRIaCghtZXNzYWdlcxgBIAMoDFIIbWVzc2FnZXMaPQoUS2V5'
'VmVyaWZpY2F0aW9uUHJvb2YSJQoOY2FsY3VsYXRlZF9tYWMYASABKAxSDWNhbGN1bGF0ZWRNYW'
'NCCwoJX2dyb3VwX2lkQhEKD19pc19kaXJlY3RfY2hhdEIZChdfc2VuZGVyX3Byb2ZpbGVfY291'
'bnRlckIgCh5fc2VuZGVyX3VzZXJfZGlzY292ZXJ5X3ZlcnNpb25CEQoPX21lc3NhZ2VfdXBkYX'
'RlQggKBl9tZWRpYUIPCg1fbWVkaWFfdXBkYXRlQhEKD19jb250YWN0X3VwZGF0ZUISChBfY29u'
'dGFjdF9yZXF1ZXN0Qg0KC19mbGFtZV9zeW5jQgwKCl9wdXNoX2tleXNCCwoJX3JlYWN0aW9uQg'
'8KDV90ZXh0X21lc3NhZ2VCDwoNX2dyb3VwX2NyZWF0ZUINCgtfZ3JvdXBfam9pbkIPCg1fZ3Jv'
'dXBfdXBkYXRlQhoKGF9yZXNlbmRfZ3JvdXBfcHVibGljX2tleUIRCg9fZXJyb3JfbWVzc2FnZX'
'NCGgoYX2FkZGl0aW9uYWxfZGF0YV9tZXNzYWdlQhMKEV90eXBpbmdfaW5kaWNhdG9yQhkKF191'
'c2VyX2Rpc2NvdmVyeV9yZXF1ZXN0QhgKFl91c2VyX2Rpc2NvdmVyeV91cGRhdGVCGQoXX2tleV'
'92ZXJpZmljYXRpb25fcHJvb2Y=');

View file

@ -1,544 +0,0 @@
// This is a generated file - do not edit.
//
// Generated from passwordless_recovery.proto.
// @dart = 3.3
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
import 'dart:core' as $core;
import 'package:fixnum/fixnum.dart' as $fixnum;
import 'package:protobuf/protobuf.dart' as $pb;
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
/// Send from the person who tries to recover their account.
/// This can be done via a link, which will then be opend in the app of the contact.
/// The contact than has to manualy select from which user he got the request.
/// -> Using this phishing is harder, as the user has to manualy select the user to recovery
/// -> The user who wants to recover his account does not need to remember her old username
class RecoveryRequest extends $pb.GeneratedMessage {
factory RecoveryRequest({
$core.String? notificationId,
$core.List<$core.int>? publicKey,
}) {
final result = create();
if (notificationId != null) result.notificationId = notificationId;
if (publicKey != null) result.publicKey = publicKey;
return result;
}
RecoveryRequest._();
factory RecoveryRequest.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory RecoveryRequest.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'RecoveryRequest',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'notificationId')
..a<$core.List<$core.int>>(
2, _omitFieldNames ? '' : 'publicKey', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
RecoveryRequest clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
RecoveryRequest copyWith(void Function(RecoveryRequest) updates) =>
super.copyWith((message) => updates(message as RecoveryRequest))
as RecoveryRequest;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static RecoveryRequest create() => RecoveryRequest._();
@$core.override
RecoveryRequest createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static RecoveryRequest getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<RecoveryRequest>(create);
static RecoveryRequest? _defaultInstance;
@$pb.TagNumber(1)
$core.String get notificationId => $_getSZ(0);
@$pb.TagNumber(1)
set notificationId($core.String value) => $_setString(0, value);
@$pb.TagNumber(1)
$core.bool hasNotificationId() => $_has(0);
@$pb.TagNumber(1)
void clearNotificationId() => $_clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get publicKey => $_getN(1);
@$pb.TagNumber(2)
set publicKey($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(2)
$core.bool hasPublicKey() => $_has(1);
@$pb.TagNumber(2)
void clearPublicKey() => $_clearField(2);
}
/// Used as envelope for TrustedFriendShare and RecoveryData
class EncryptedEnvelope extends $pb.GeneratedMessage {
factory EncryptedEnvelope({
$core.List<$core.int>? encryptedData,
$core.List<$core.int>? iv,
$core.List<$core.int>? mac,
}) {
final result = create();
if (encryptedData != null) result.encryptedData = encryptedData;
if (iv != null) result.iv = iv;
if (mac != null) result.mac = mac;
return result;
}
EncryptedEnvelope._();
factory EncryptedEnvelope.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory EncryptedEnvelope.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'EncryptedEnvelope',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..a<$core.List<$core.int>>(
1, _omitFieldNames ? '' : 'encryptedData', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(
2, _omitFieldNames ? '' : 'iv', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(
3, _omitFieldNames ? '' : 'mac', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
EncryptedEnvelope clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
EncryptedEnvelope copyWith(void Function(EncryptedEnvelope) updates) =>
super.copyWith((message) => updates(message as EncryptedEnvelope))
as EncryptedEnvelope;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EncryptedEnvelope create() => EncryptedEnvelope._();
@$core.override
EncryptedEnvelope createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static EncryptedEnvelope getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<EncryptedEnvelope>(create);
static EncryptedEnvelope? _defaultInstance;
@$pb.TagNumber(1)
$core.List<$core.int> get encryptedData => $_getN(0);
@$pb.TagNumber(1)
set encryptedData($core.List<$core.int> value) => $_setBytes(0, value);
@$pb.TagNumber(1)
$core.bool hasEncryptedData() => $_has(0);
@$pb.TagNumber(1)
void clearEncryptedData() => $_clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get iv => $_getN(1);
@$pb.TagNumber(2)
set iv($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(2)
$core.bool hasIv() => $_has(1);
@$pb.TagNumber(2)
void clearIv() => $_clearField(2);
@$pb.TagNumber(3)
$core.List<$core.int> get mac => $_getN(2);
@$pb.TagNumber(3)
set mac($core.List<$core.int> value) => $_setBytes(2, value);
@$pb.TagNumber(3)
$core.bool hasMac() => $_has(2);
@$pb.TagNumber(3)
void clearMac() => $_clearField(3);
}
class TrustedFriendShare_User extends $pb.GeneratedMessage {
factory TrustedFriendShare_User({
$fixnum.Int64? userId,
$core.String? displayName,
$core.List<$core.int>? avatar,
}) {
final result = create();
if (userId != null) result.userId = userId;
if (displayName != null) result.displayName = displayName;
if (avatar != null) result.avatar = avatar;
return result;
}
TrustedFriendShare_User._();
factory TrustedFriendShare_User.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory TrustedFriendShare_User.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'TrustedFriendShare.User',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..aInt64(1, _omitFieldNames ? '' : 'userId')
..aOS(2, _omitFieldNames ? '' : 'displayName')
..a<$core.List<$core.int>>(
3, _omitFieldNames ? '' : 'avatar', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
TrustedFriendShare_User clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
TrustedFriendShare_User copyWith(
void Function(TrustedFriendShare_User) updates) =>
super.copyWith((message) => updates(message as TrustedFriendShare_User))
as TrustedFriendShare_User;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static TrustedFriendShare_User create() => TrustedFriendShare_User._();
@$core.override
TrustedFriendShare_User createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static TrustedFriendShare_User getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<TrustedFriendShare_User>(create);
static TrustedFriendShare_User? _defaultInstance;
@$pb.TagNumber(1)
$fixnum.Int64 get userId => $_getI64(0);
@$pb.TagNumber(1)
set userId($fixnum.Int64 value) => $_setInt64(0, value);
@$pb.TagNumber(1)
$core.bool hasUserId() => $_has(0);
@$pb.TagNumber(1)
void clearUserId() => $_clearField(1);
@$pb.TagNumber(2)
$core.String get displayName => $_getSZ(1);
@$pb.TagNumber(2)
set displayName($core.String value) => $_setString(1, value);
@$pb.TagNumber(2)
$core.bool hasDisplayName() => $_has(1);
@$pb.TagNumber(2)
void clearDisplayName() => $_clearField(2);
@$pb.TagNumber(3)
$core.List<$core.int> get avatar => $_getN(2);
@$pb.TagNumber(3)
set avatar($core.List<$core.int> value) => $_setBytes(2, value);
@$pb.TagNumber(3)
$core.bool hasAvatar() => $_has(2);
@$pb.TagNumber(3)
void clearAvatar() => $_clearField(3);
}
/// Send from the trusted friend to
/// This is encrypted with the received public key.
class TrustedFriendShare extends $pb.GeneratedMessage {
factory TrustedFriendShare({
TrustedFriendShare_User? trustedFriend,
TrustedFriendShare_User? shareUser,
$core.int? threshold,
$core.List<$core.int>? sharedSecretData,
}) {
final result = create();
if (trustedFriend != null) result.trustedFriend = trustedFriend;
if (shareUser != null) result.shareUser = shareUser;
if (threshold != null) result.threshold = threshold;
if (sharedSecretData != null) result.sharedSecretData = sharedSecretData;
return result;
}
TrustedFriendShare._();
factory TrustedFriendShare.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory TrustedFriendShare.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'TrustedFriendShare',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..aOM<TrustedFriendShare_User>(1, _omitFieldNames ? '' : 'trustedFriend',
subBuilder: TrustedFriendShare_User.create)
..aOM<TrustedFriendShare_User>(2, _omitFieldNames ? '' : 'shareUser',
subBuilder: TrustedFriendShare_User.create)
..aI(3, _omitFieldNames ? '' : 'threshold')
..a<$core.List<$core.int>>(
4, _omitFieldNames ? '' : 'sharedSecretData', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
TrustedFriendShare clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
TrustedFriendShare copyWith(void Function(TrustedFriendShare) updates) =>
super.copyWith((message) => updates(message as TrustedFriendShare))
as TrustedFriendShare;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static TrustedFriendShare create() => TrustedFriendShare._();
@$core.override
TrustedFriendShare createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static TrustedFriendShare getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<TrustedFriendShare>(create);
static TrustedFriendShare? _defaultInstance;
/// This allows to display the user which user has send him his recovery data.
@$pb.TagNumber(1)
TrustedFriendShare_User get trustedFriend => $_getN(0);
@$pb.TagNumber(1)
set trustedFriend(TrustedFriendShare_User value) => $_setField(1, value);
@$pb.TagNumber(1)
$core.bool hasTrustedFriend() => $_has(0);
@$pb.TagNumber(1)
void clearTrustedFriend() => $_clearField(1);
@$pb.TagNumber(1)
TrustedFriendShare_User ensureTrustedFriend() => $_ensure(0);
/// This allows to display the userdata, showing that he is recovering the correct person.
@$pb.TagNumber(2)
TrustedFriendShare_User get shareUser => $_getN(1);
@$pb.TagNumber(2)
set shareUser(TrustedFriendShare_User value) => $_setField(2, value);
@$pb.TagNumber(2)
$core.bool hasShareUser() => $_has(1);
@$pb.TagNumber(2)
void clearShareUser() => $_clearField(2);
@$pb.TagNumber(2)
TrustedFriendShare_User ensureShareUser() => $_ensure(1);
/// The minimum threshold required to decrypte the shares.
@$pb.TagNumber(3)
$core.int get threshold => $_getIZ(2);
@$pb.TagNumber(3)
set threshold($core.int value) => $_setSignedInt32(2, value);
@$pb.TagNumber(3)
$core.bool hasThreshold() => $_has(2);
@$pb.TagNumber(3)
void clearThreshold() => $_clearField(3);
/// The actual share which will become: SharedSecretData
@$pb.TagNumber(4)
$core.List<$core.int> get sharedSecretData => $_getN(3);
@$pb.TagNumber(4)
set sharedSecretData($core.List<$core.int> value) => $_setBytes(3, value);
@$pb.TagNumber(4)
$core.bool hasSharedSecretData() => $_has(3);
@$pb.TagNumber(4)
void clearSharedSecretData() => $_clearField(4);
}
/// The user identity keys. Can be used to restore the data backup.
class RecoveryData extends $pb.GeneratedMessage {
factory RecoveryData({
$fixnum.Int64? userId,
$core.List<$core.int>? keyManager,
}) {
final result = create();
if (userId != null) result.userId = userId;
if (keyManager != null) result.keyManager = keyManager;
return result;
}
RecoveryData._();
factory RecoveryData.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory RecoveryData.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'RecoveryData',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..aInt64(1, _omitFieldNames ? '' : 'userId')
..a<$core.List<$core.int>>(
3, _omitFieldNames ? '' : 'keyManager', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
RecoveryData clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
RecoveryData copyWith(void Function(RecoveryData) updates) =>
super.copyWith((message) => updates(message as RecoveryData))
as RecoveryData;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static RecoveryData create() => RecoveryData._();
@$core.override
RecoveryData createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static RecoveryData getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<RecoveryData>(create);
static RecoveryData? _defaultInstance;
@$pb.TagNumber(1)
$fixnum.Int64 get userId => $_getI64(0);
@$pb.TagNumber(1)
set userId($fixnum.Int64 value) => $_setInt64(0, value);
@$pb.TagNumber(1)
$core.bool hasUserId() => $_has(0);
@$pb.TagNumber(1)
void clearUserId() => $_clearField(1);
@$pb.TagNumber(3)
$core.List<$core.int> get keyManager => $_getN(1);
@$pb.TagNumber(3)
set keyManager($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(3)
$core.bool hasKeyManager() => $_has(1);
@$pb.TagNumber(3)
void clearKeyManager() => $_clearField(3);
}
/// After received all shares this is decrypted by the user restoring its own
class SharedSecretData extends $pb.GeneratedMessage {
factory SharedSecretData({
$core.List<$core.int>? recoveryData,
$core.List<$core.int>? encryptedServerKeyNonce,
$core.List<$core.int>? pinSeed,
$core.List<$core.int>? pinUnlockToken,
$core.String? emailHint,
}) {
final result = create();
if (recoveryData != null) result.recoveryData = recoveryData;
if (encryptedServerKeyNonce != null)
result.encryptedServerKeyNonce = encryptedServerKeyNonce;
if (pinSeed != null) result.pinSeed = pinSeed;
if (pinUnlockToken != null) result.pinUnlockToken = pinUnlockToken;
if (emailHint != null) result.emailHint = emailHint;
return result;
}
SharedSecretData._();
factory SharedSecretData.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory SharedSecretData.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'SharedSecretData',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..a<$core.List<$core.int>>(
1, _omitFieldNames ? '' : 'recoveryData', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(
3, _omitFieldNames ? '' : 'encryptedServerKeyNonce', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(
4, _omitFieldNames ? '' : 'pinSeed', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(
5, _omitFieldNames ? '' : 'pinUnlockToken', $pb.PbFieldType.OY)
..aOS(6, _omitFieldNames ? '' : 'emailHint')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
SharedSecretData clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
SharedSecretData copyWith(void Function(SharedSecretData) updates) =>
super.copyWith((message) => updates(message as SharedSecretData))
as SharedSecretData;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static SharedSecretData create() => SharedSecretData._();
@$core.override
SharedSecretData createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static SharedSecretData getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<SharedSecretData>(create);
static SharedSecretData? _defaultInstance;
/// The recovery data is encrypted in case a second factor was chosen.
@$pb.TagNumber(1)
$core.List<$core.int> get recoveryData => $_getN(0);
@$pb.TagNumber(1)
set recoveryData($core.List<$core.int> value) => $_setBytes(0, value);
@$pb.TagNumber(1)
$core.bool hasRecoveryData() => $_has(0);
@$pb.TagNumber(1)
void clearRecoveryData() => $_clearField(1);
@$pb.TagNumber(3)
$core.List<$core.int> get encryptedServerKeyNonce => $_getN(1);
@$pb.TagNumber(3)
set encryptedServerKeyNonce($core.List<$core.int> value) =>
$_setBytes(1, value);
@$pb.TagNumber(3)
$core.bool hasEncryptedServerKeyNonce() => $_has(1);
@$pb.TagNumber(3)
void clearEncryptedServerKeyNonce() => $_clearField(3);
@$pb.TagNumber(4)
$core.List<$core.int> get pinSeed => $_getN(2);
@$pb.TagNumber(4)
set pinSeed($core.List<$core.int> value) => $_setBytes(2, value);
@$pb.TagNumber(4)
$core.bool hasPinSeed() => $_has(2);
@$pb.TagNumber(4)
void clearPinSeed() => $_clearField(4);
@$pb.TagNumber(5)
$core.List<$core.int> get pinUnlockToken => $_getN(3);
@$pb.TagNumber(5)
set pinUnlockToken($core.List<$core.int> value) => $_setBytes(3, value);
@$pb.TagNumber(5)
$core.bool hasPinUnlockToken() => $_has(3);
@$pb.TagNumber(5)
void clearPinUnlockToken() => $_clearField(5);
@$pb.TagNumber(6)
$core.String get emailHint => $_getSZ(4);
@$pb.TagNumber(6)
set emailHint($core.String value) => $_setString(4, value);
@$pb.TagNumber(6)
$core.bool hasEmailHint() => $_has(4);
@$pb.TagNumber(6)
void clearEmailHint() => $_clearField(6);
}
const $core.bool _omitFieldNames =
$core.bool.fromEnvironment('protobuf.omit_field_names');
const $core.bool _omitMessageNames =
$core.bool.fromEnvironment('protobuf.omit_message_names');

View file

@ -1,11 +0,0 @@
// This is a generated file - do not edit.
//
// Generated from passwordless_recovery.proto.
// @dart = 3.3
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports

View file

@ -1,170 +0,0 @@
// This is a generated file - do not edit.
//
// Generated from passwordless_recovery.proto.
// @dart = 3.3
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
// ignore_for_file: unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use recoveryRequestDescriptor instead')
const RecoveryRequest$json = {
'1': 'RecoveryRequest',
'2': [
{'1': 'notification_id', '3': 1, '4': 1, '5': 9, '10': 'notificationId'},
{'1': 'public_key', '3': 2, '4': 1, '5': 12, '10': 'publicKey'},
],
};
/// Descriptor for `RecoveryRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List recoveryRequestDescriptor = $convert.base64Decode(
'Cg9SZWNvdmVyeVJlcXVlc3QSJwoPbm90aWZpY2F0aW9uX2lkGAEgASgJUg5ub3RpZmljYXRpb2'
'5JZBIdCgpwdWJsaWNfa2V5GAIgASgMUglwdWJsaWNLZXk=');
@$core.Deprecated('Use encryptedEnvelopeDescriptor instead')
const EncryptedEnvelope$json = {
'1': 'EncryptedEnvelope',
'2': [
{'1': 'encrypted_data', '3': 1, '4': 1, '5': 12, '10': 'encryptedData'},
{'1': 'iv', '3': 2, '4': 1, '5': 12, '10': 'iv'},
{'1': 'mac', '3': 3, '4': 1, '5': 12, '10': 'mac'},
],
};
/// Descriptor for `EncryptedEnvelope`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List encryptedEnvelopeDescriptor = $convert.base64Decode(
'ChFFbmNyeXB0ZWRFbnZlbG9wZRIlCg5lbmNyeXB0ZWRfZGF0YRgBIAEoDFINZW5jcnlwdGVkRG'
'F0YRIOCgJpdhgCIAEoDFICaXYSEAoDbWFjGAMgASgMUgNtYWM=');
@$core.Deprecated('Use trustedFriendShareDescriptor instead')
const TrustedFriendShare$json = {
'1': 'TrustedFriendShare',
'2': [
{
'1': 'trusted_friend',
'3': 1,
'4': 1,
'5': 11,
'6': '.passwordless_recovery.TrustedFriendShare.User',
'10': 'trustedFriend'
},
{
'1': 'share_user',
'3': 2,
'4': 1,
'5': 11,
'6': '.passwordless_recovery.TrustedFriendShare.User',
'10': 'shareUser'
},
{'1': 'threshold', '3': 3, '4': 1, '5': 5, '10': 'threshold'},
{
'1': 'shared_secret_data',
'3': 4,
'4': 1,
'5': 12,
'10': 'sharedSecretData'
},
],
'3': [TrustedFriendShare_User$json],
};
@$core.Deprecated('Use trustedFriendShareDescriptor instead')
const TrustedFriendShare_User$json = {
'1': 'User',
'2': [
{'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'},
{'1': 'display_name', '3': 2, '4': 1, '5': 9, '10': 'displayName'},
{'1': 'avatar', '3': 3, '4': 1, '5': 12, '10': 'avatar'},
],
};
/// Descriptor for `TrustedFriendShare`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List trustedFriendShareDescriptor = $convert.base64Decode(
'ChJUcnVzdGVkRnJpZW5kU2hhcmUSVQoOdHJ1c3RlZF9mcmllbmQYASABKAsyLi5wYXNzd29yZG'
'xlc3NfcmVjb3ZlcnkuVHJ1c3RlZEZyaWVuZFNoYXJlLlVzZXJSDXRydXN0ZWRGcmllbmQSTQoK'
'c2hhcmVfdXNlchgCIAEoCzIuLnBhc3N3b3JkbGVzc19yZWNvdmVyeS5UcnVzdGVkRnJpZW5kU2'
'hhcmUuVXNlclIJc2hhcmVVc2VyEhwKCXRocmVzaG9sZBgDIAEoBVIJdGhyZXNob2xkEiwKEnNo'
'YXJlZF9zZWNyZXRfZGF0YRgEIAEoDFIQc2hhcmVkU2VjcmV0RGF0YRpaCgRVc2VyEhcKB3VzZX'
'JfaWQYASABKANSBnVzZXJJZBIhCgxkaXNwbGF5X25hbWUYAiABKAlSC2Rpc3BsYXlOYW1lEhYK'
'BmF2YXRhchgDIAEoDFIGYXZhdGFy');
@$core.Deprecated('Use recoveryDataDescriptor instead')
const RecoveryData$json = {
'1': 'RecoveryData',
'2': [
{'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'},
{'1': 'key_manager', '3': 3, '4': 1, '5': 12, '10': 'keyManager'},
],
};
/// Descriptor for `RecoveryData`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List recoveryDataDescriptor = $convert.base64Decode(
'CgxSZWNvdmVyeURhdGESFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEh8KC2tleV9tYW5hZ2VyGA'
'MgASgMUgprZXlNYW5hZ2Vy');
@$core.Deprecated('Use sharedSecretDataDescriptor instead')
const SharedSecretData$json = {
'1': 'SharedSecretData',
'2': [
{'1': 'recovery_data', '3': 1, '4': 1, '5': 12, '10': 'recoveryData'},
{
'1': 'encrypted_server_key_nonce',
'3': 3,
'4': 1,
'5': 12,
'9': 0,
'10': 'encryptedServerKeyNonce',
'17': true
},
{
'1': 'pin_seed',
'3': 4,
'4': 1,
'5': 12,
'9': 1,
'10': 'pinSeed',
'17': true
},
{
'1': 'pin_unlock_token',
'3': 5,
'4': 1,
'5': 12,
'9': 2,
'10': 'pinUnlockToken',
'17': true
},
{
'1': 'email_hint',
'3': 6,
'4': 1,
'5': 9,
'9': 3,
'10': 'emailHint',
'17': true
},
],
'8': [
{'1': '_encrypted_server_key_nonce'},
{'1': '_pin_seed'},
{'1': '_pin_unlock_token'},
{'1': '_email_hint'},
],
};
/// Descriptor for `SharedSecretData`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List sharedSecretDataDescriptor = $convert.base64Decode(
'ChBTaGFyZWRTZWNyZXREYXRhEiMKDXJlY292ZXJ5X2RhdGEYASABKAxSDHJlY292ZXJ5RGF0YR'
'JAChplbmNyeXB0ZWRfc2VydmVyX2tleV9ub25jZRgDIAEoDEgAUhdlbmNyeXB0ZWRTZXJ2ZXJL'
'ZXlOb25jZYgBARIeCghwaW5fc2VlZBgEIAEoDEgBUgdwaW5TZWVkiAEBEi0KEHBpbl91bmxvY2'
'tfdG9rZW4YBSABKAxIAlIOcGluVW5sb2NrVG9rZW6IAQESIgoKZW1haWxfaGludBgGIAEoCUgD'
'UgllbWFpbEhpbnSIAQFCHQobX2VuY3J5cHRlZF9zZXJ2ZXJfa2V5X25vbmNlQgsKCV9waW5fc2'
'VlZEITChFfcGluX3VubG9ja190b2tlbkINCgtfZW1haWxfaGludA==');

View file

@ -1,670 +0,0 @@
// This is a generated file - do not edit.
//
// Generated from types.proto.
// @dart = 3.3
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
import 'dart:core' as $core;
import 'package:fixnum/fixnum.dart' as $fixnum;
import 'package:protobuf/protobuf.dart' as $pb;
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
/// Send from the person who tries to recover their account.
/// This can be done via a link, which will then be opend in the app of the contact.
/// The contact than has to manualy select from which user he got the request.
/// -> Using this phishing is harder, as the user has to manualy select the user to recovery
/// -> The user who wants to recover his account does not need to remember her old username
class RecoveryRequest extends $pb.GeneratedMessage {
factory RecoveryRequest({
$fixnum.Int64? tempId,
$core.List<$core.int>? publicKey,
}) {
final result = create();
if (tempId != null) result.tempId = tempId;
if (publicKey != null) result.publicKey = publicKey;
return result;
}
RecoveryRequest._();
factory RecoveryRequest.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory RecoveryRequest.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'RecoveryRequest',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..aInt64(1, _omitFieldNames ? '' : 'tempId')
..a<$core.List<$core.int>>(
2, _omitFieldNames ? '' : 'publicKey', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
RecoveryRequest clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
RecoveryRequest copyWith(void Function(RecoveryRequest) updates) =>
super.copyWith((message) => updates(message as RecoveryRequest))
as RecoveryRequest;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static RecoveryRequest create() => RecoveryRequest._();
@$core.override
RecoveryRequest createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static RecoveryRequest getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<RecoveryRequest>(create);
static RecoveryRequest? _defaultInstance;
@$pb.TagNumber(1)
$fixnum.Int64 get tempId => $_getI64(0);
@$pb.TagNumber(1)
set tempId($fixnum.Int64 value) => $_setInt64(0, value);
@$pb.TagNumber(1)
$core.bool hasTempId() => $_has(0);
@$pb.TagNumber(1)
void clearTempId() => $_clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get publicKey => $_getN(1);
@$pb.TagNumber(2)
set publicKey($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(2)
$core.bool hasPublicKey() => $_has(1);
@$pb.TagNumber(2)
void clearPublicKey() => $_clearField(2);
}
/// Used as envelope for TrustedFriendShare and RecoveryData
class EncryptedEnvelope extends $pb.GeneratedMessage {
factory EncryptedEnvelope({
$core.List<$core.int>? encryptedData,
$core.List<$core.int>? iv,
$core.List<$core.int>? mac,
}) {
final result = create();
if (encryptedData != null) result.encryptedData = encryptedData;
if (iv != null) result.iv = iv;
if (mac != null) result.mac = mac;
return result;
}
EncryptedEnvelope._();
factory EncryptedEnvelope.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory EncryptedEnvelope.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'EncryptedEnvelope',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..a<$core.List<$core.int>>(
1, _omitFieldNames ? '' : 'encryptedData', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(
2, _omitFieldNames ? '' : 'iv', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(
3, _omitFieldNames ? '' : 'mac', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
EncryptedEnvelope clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
EncryptedEnvelope copyWith(void Function(EncryptedEnvelope) updates) =>
super.copyWith((message) => updates(message as EncryptedEnvelope))
as EncryptedEnvelope;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EncryptedEnvelope create() => EncryptedEnvelope._();
@$core.override
EncryptedEnvelope createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static EncryptedEnvelope getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<EncryptedEnvelope>(create);
static EncryptedEnvelope? _defaultInstance;
@$pb.TagNumber(1)
$core.List<$core.int> get encryptedData => $_getN(0);
@$pb.TagNumber(1)
set encryptedData($core.List<$core.int> value) => $_setBytes(0, value);
@$pb.TagNumber(1)
$core.bool hasEncryptedData() => $_has(0);
@$pb.TagNumber(1)
void clearEncryptedData() => $_clearField(1);
@$pb.TagNumber(2)
$core.List<$core.int> get iv => $_getN(1);
@$pb.TagNumber(2)
set iv($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(2)
$core.bool hasIv() => $_has(1);
@$pb.TagNumber(2)
void clearIv() => $_clearField(2);
@$pb.TagNumber(3)
$core.List<$core.int> get mac => $_getN(2);
@$pb.TagNumber(3)
set mac($core.List<$core.int> value) => $_setBytes(2, value);
@$pb.TagNumber(3)
$core.bool hasMac() => $_has(2);
@$pb.TagNumber(3)
void clearMac() => $_clearField(3);
}
class TrustedFriendShare_User extends $pb.GeneratedMessage {
factory TrustedFriendShare_User({
$fixnum.Int64? userId,
$core.String? displayName,
$core.List<$core.int>? avatar,
}) {
final result = create();
if (userId != null) result.userId = userId;
if (displayName != null) result.displayName = displayName;
if (avatar != null) result.avatar = avatar;
return result;
}
TrustedFriendShare_User._();
factory TrustedFriendShare_User.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory TrustedFriendShare_User.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'TrustedFriendShare.User',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..aInt64(1, _omitFieldNames ? '' : 'userId')
..aOS(2, _omitFieldNames ? '' : 'displayName')
..a<$core.List<$core.int>>(
3, _omitFieldNames ? '' : 'avatar', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
TrustedFriendShare_User clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
TrustedFriendShare_User copyWith(
void Function(TrustedFriendShare_User) updates) =>
super.copyWith((message) => updates(message as TrustedFriendShare_User))
as TrustedFriendShare_User;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static TrustedFriendShare_User create() => TrustedFriendShare_User._();
@$core.override
TrustedFriendShare_User createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static TrustedFriendShare_User getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<TrustedFriendShare_User>(create);
static TrustedFriendShare_User? _defaultInstance;
@$pb.TagNumber(1)
$fixnum.Int64 get userId => $_getI64(0);
@$pb.TagNumber(1)
set userId($fixnum.Int64 value) => $_setInt64(0, value);
@$pb.TagNumber(1)
$core.bool hasUserId() => $_has(0);
@$pb.TagNumber(1)
void clearUserId() => $_clearField(1);
@$pb.TagNumber(2)
$core.String get displayName => $_getSZ(1);
@$pb.TagNumber(2)
set displayName($core.String value) => $_setString(1, value);
@$pb.TagNumber(2)
$core.bool hasDisplayName() => $_has(1);
@$pb.TagNumber(2)
void clearDisplayName() => $_clearField(2);
@$pb.TagNumber(3)
$core.List<$core.int> get avatar => $_getN(2);
@$pb.TagNumber(3)
set avatar($core.List<$core.int> value) => $_setBytes(2, value);
@$pb.TagNumber(3)
$core.bool hasAvatar() => $_has(2);
@$pb.TagNumber(3)
void clearAvatar() => $_clearField(3);
}
/// Send from the trusted friend to
/// This is encrypted with the received public key.
class TrustedFriendShare extends $pb.GeneratedMessage {
factory TrustedFriendShare({
TrustedFriendShare_User? trustedFriend,
TrustedFriendShare_User? shareUser,
$core.int? threshold,
$core.List<$core.int>? sharedSecretData,
}) {
final result = create();
if (trustedFriend != null) result.trustedFriend = trustedFriend;
if (shareUser != null) result.shareUser = shareUser;
if (threshold != null) result.threshold = threshold;
if (sharedSecretData != null) result.sharedSecretData = sharedSecretData;
return result;
}
TrustedFriendShare._();
factory TrustedFriendShare.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory TrustedFriendShare.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'TrustedFriendShare',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..aOM<TrustedFriendShare_User>(1, _omitFieldNames ? '' : 'trustedFriend',
subBuilder: TrustedFriendShare_User.create)
..aOM<TrustedFriendShare_User>(2, _omitFieldNames ? '' : 'shareUser',
subBuilder: TrustedFriendShare_User.create)
..aI(3, _omitFieldNames ? '' : 'threshold')
..a<$core.List<$core.int>>(
4, _omitFieldNames ? '' : 'sharedSecretData', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
TrustedFriendShare clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
TrustedFriendShare copyWith(void Function(TrustedFriendShare) updates) =>
super.copyWith((message) => updates(message as TrustedFriendShare))
as TrustedFriendShare;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static TrustedFriendShare create() => TrustedFriendShare._();
@$core.override
TrustedFriendShare createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static TrustedFriendShare getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<TrustedFriendShare>(create);
static TrustedFriendShare? _defaultInstance;
/// This allows to display the user which user has send him his recovery data.
@$pb.TagNumber(1)
TrustedFriendShare_User get trustedFriend => $_getN(0);
@$pb.TagNumber(1)
set trustedFriend(TrustedFriendShare_User value) => $_setField(1, value);
@$pb.TagNumber(1)
$core.bool hasTrustedFriend() => $_has(0);
@$pb.TagNumber(1)
void clearTrustedFriend() => $_clearField(1);
@$pb.TagNumber(1)
TrustedFriendShare_User ensureTrustedFriend() => $_ensure(0);
/// This allows to display the userdata, showing that he is recovering the correct person.
@$pb.TagNumber(2)
TrustedFriendShare_User get shareUser => $_getN(1);
@$pb.TagNumber(2)
set shareUser(TrustedFriendShare_User value) => $_setField(2, value);
@$pb.TagNumber(2)
$core.bool hasShareUser() => $_has(1);
@$pb.TagNumber(2)
void clearShareUser() => $_clearField(2);
@$pb.TagNumber(2)
TrustedFriendShare_User ensureShareUser() => $_ensure(1);
/// The minimum threshold required to decrypte the shares.
@$pb.TagNumber(3)
$core.int get threshold => $_getIZ(2);
@$pb.TagNumber(3)
set threshold($core.int value) => $_setSignedInt32(2, value);
@$pb.TagNumber(3)
$core.bool hasThreshold() => $_has(2);
@$pb.TagNumber(3)
void clearThreshold() => $_clearField(3);
/// The actual share which will become: SharedSecretData
@$pb.TagNumber(4)
$core.List<$core.int> get sharedSecretData => $_getN(3);
@$pb.TagNumber(4)
set sharedSecretData($core.List<$core.int> value) => $_setBytes(3, value);
@$pb.TagNumber(4)
$core.bool hasSharedSecretData() => $_has(3);
@$pb.TagNumber(4)
void clearSharedSecretData() => $_clearField(4);
}
class SharedSecretData_SecondFactorPin extends $pb.GeneratedMessage {
factory SharedSecretData_SecondFactorPin({
$core.List<$core.int>? unlockToken,
$core.List<$core.int>? pinSeed,
}) {
final result = create();
if (unlockToken != null) result.unlockToken = unlockToken;
if (pinSeed != null) result.pinSeed = pinSeed;
return result;
}
SharedSecretData_SecondFactorPin._();
factory SharedSecretData_SecondFactorPin.fromBuffer(
$core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory SharedSecretData_SecondFactorPin.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'SharedSecretData.SecondFactorPin',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..a<$core.List<$core.int>>(
1, _omitFieldNames ? '' : 'unlockToken', $pb.PbFieldType.OY)
..a<$core.List<$core.int>>(
2, _omitFieldNames ? '' : 'pinSeed', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
SharedSecretData_SecondFactorPin clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
SharedSecretData_SecondFactorPin copyWith(
void Function(SharedSecretData_SecondFactorPin) updates) =>
super.copyWith(
(message) => updates(message as SharedSecretData_SecondFactorPin))
as SharedSecretData_SecondFactorPin;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static SharedSecretData_SecondFactorPin create() =>
SharedSecretData_SecondFactorPin._();
@$core.override
SharedSecretData_SecondFactorPin createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static SharedSecretData_SecondFactorPin getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<SharedSecretData_SecondFactorPin>(
create);
static SharedSecretData_SecondFactorPin? _defaultInstance;
/// Required to try the PIN to get the share from the server.
/// This prevents that someone else can lock the pin, as the server only
/// allows 3 tries then after 1 day again 3 tries until the key is deleted.
@$pb.TagNumber(1)
$core.List<$core.int> get unlockToken => $_getN(0);
@$pb.TagNumber(1)
set unlockToken($core.List<$core.int> value) => $_setBytes(0, value);
@$pb.TagNumber(1)
$core.bool hasUnlockToken() => $_has(0);
@$pb.TagNumber(1)
void clearUnlockToken() => $_clearField(1);
/// This never is send to the server but used to hash the pin before sending it to the server.
/// This prevents that the server every knows the shot 4-diget PIN.
@$pb.TagNumber(2)
$core.List<$core.int> get pinSeed => $_getN(1);
@$pb.TagNumber(2)
set pinSeed($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(2)
$core.bool hasPinSeed() => $_has(1);
@$pb.TagNumber(2)
void clearPinSeed() => $_clearField(2);
}
class SharedSecretData_SecondFactorMail extends $pb.GeneratedMessage {
factory SharedSecretData_SecondFactorMail() => create();
SharedSecretData_SecondFactorMail._();
factory SharedSecretData_SecondFactorMail.fromBuffer(
$core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory SharedSecretData_SecondFactorMail.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'SharedSecretData.SecondFactorMail',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
SharedSecretData_SecondFactorMail clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
SharedSecretData_SecondFactorMail copyWith(
void Function(SharedSecretData_SecondFactorMail) updates) =>
super.copyWith((message) =>
updates(message as SharedSecretData_SecondFactorMail))
as SharedSecretData_SecondFactorMail;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static SharedSecretData_SecondFactorMail create() =>
SharedSecretData_SecondFactorMail._();
@$core.override
SharedSecretData_SecondFactorMail createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static SharedSecretData_SecondFactorMail getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<SharedSecretData_SecondFactorMail>(
create);
static SharedSecretData_SecondFactorMail? _defaultInstance;
}
/// After received all shares this is decrypted by the user restoring its own
class SharedSecretData extends $pb.GeneratedMessage {
factory SharedSecretData({
RecoveryData? recoveryData,
SharedSecretData_SecondFactorMail? secondFactorMail,
SharedSecretData_SecondFactorPin? secondFactorPin,
$core.List<$core.int>? recoveryDataEncrypted,
}) {
final result = create();
if (recoveryData != null) result.recoveryData = recoveryData;
if (secondFactorMail != null) result.secondFactorMail = secondFactorMail;
if (secondFactorPin != null) result.secondFactorPin = secondFactorPin;
if (recoveryDataEncrypted != null)
result.recoveryDataEncrypted = recoveryDataEncrypted;
return result;
}
SharedSecretData._();
factory SharedSecretData.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory SharedSecretData.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'SharedSecretData',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..aOM<RecoveryData>(1, _omitFieldNames ? '' : 'recoveryData',
subBuilder: RecoveryData.create)
..aOM<SharedSecretData_SecondFactorMail>(
2, _omitFieldNames ? '' : 'secondFactorMail',
subBuilder: SharedSecretData_SecondFactorMail.create)
..aOM<SharedSecretData_SecondFactorPin>(
3, _omitFieldNames ? '' : 'secondFactorPin',
subBuilder: SharedSecretData_SecondFactorPin.create)
..a<$core.List<$core.int>>(
4, _omitFieldNames ? '' : 'recoveryDataEncrypted', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
SharedSecretData clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
SharedSecretData copyWith(void Function(SharedSecretData) updates) =>
super.copyWith((message) => updates(message as SharedSecretData))
as SharedSecretData;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static SharedSecretData create() => SharedSecretData._();
@$core.override
SharedSecretData createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static SharedSecretData getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<SharedSecretData>(create);
static SharedSecretData? _defaultInstance;
/// No second factor was selected
@$pb.TagNumber(1)
RecoveryData get recoveryData => $_getN(0);
@$pb.TagNumber(1)
set recoveryData(RecoveryData value) => $_setField(1, value);
@$pb.TagNumber(1)
$core.bool hasRecoveryData() => $_has(0);
@$pb.TagNumber(1)
void clearRecoveryData() => $_clearField(1);
@$pb.TagNumber(1)
RecoveryData ensureRecoveryData() => $_ensure(0);
/// Server has
@$pb.TagNumber(2)
SharedSecretData_SecondFactorMail get secondFactorMail => $_getN(1);
@$pb.TagNumber(2)
set secondFactorMail(SharedSecretData_SecondFactorMail value) =>
$_setField(2, value);
@$pb.TagNumber(2)
$core.bool hasSecondFactorMail() => $_has(1);
@$pb.TagNumber(2)
void clearSecondFactorMail() => $_clearField(2);
@$pb.TagNumber(2)
SharedSecretData_SecondFactorMail ensureSecondFactorMail() => $_ensure(1);
@$pb.TagNumber(3)
SharedSecretData_SecondFactorPin get secondFactorPin => $_getN(2);
@$pb.TagNumber(3)
set secondFactorPin(SharedSecretData_SecondFactorPin value) =>
$_setField(3, value);
@$pb.TagNumber(3)
$core.bool hasSecondFactorPin() => $_has(2);
@$pb.TagNumber(3)
void clearSecondFactorPin() => $_clearField(3);
@$pb.TagNumber(3)
SharedSecretData_SecondFactorPin ensureSecondFactorPin() => $_ensure(2);
/// The recovery data in case a second factor was used
/// The decryption key is loaded from the server either using the PIN or the MAIL
@$pb.TagNumber(4)
$core.List<$core.int> get recoveryDataEncrypted => $_getN(3);
@$pb.TagNumber(4)
set recoveryDataEncrypted($core.List<$core.int> value) =>
$_setBytes(3, value);
@$pb.TagNumber(4)
$core.bool hasRecoveryDataEncrypted() => $_has(3);
@$pb.TagNumber(4)
void clearRecoveryDataEncrypted() => $_clearField(4);
}
/// The data which is recovered at the end.
/// The backup_master_key allows to recover the actual backup uploaded in the background to the server.
/// In case the backup is not available any more the user can use its user_id and his private_key to requister as a new user.
class RecoveryData extends $pb.GeneratedMessage {
factory RecoveryData({
$fixnum.Int64? userId,
$core.List<$core.int>? keyManager,
}) {
final result = create();
if (userId != null) result.userId = userId;
if (keyManager != null) result.keyManager = keyManager;
return result;
}
RecoveryData._();
factory RecoveryData.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory RecoveryData.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'RecoveryData',
package: const $pb.PackageName(
_omitMessageNames ? '' : 'passwordless_recovery'),
createEmptyInstance: create)
..aInt64(1, _omitFieldNames ? '' : 'userId')
..a<$core.List<$core.int>>(
3, _omitFieldNames ? '' : 'keyManager', $pb.PbFieldType.OY)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
RecoveryData clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
RecoveryData copyWith(void Function(RecoveryData) updates) =>
super.copyWith((message) => updates(message as RecoveryData))
as RecoveryData;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static RecoveryData create() => RecoveryData._();
@$core.override
RecoveryData createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static RecoveryData getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<RecoveryData>(create);
static RecoveryData? _defaultInstance;
@$pb.TagNumber(1)
$fixnum.Int64 get userId => $_getI64(0);
@$pb.TagNumber(1)
set userId($fixnum.Int64 value) => $_setInt64(0, value);
@$pb.TagNumber(1)
$core.bool hasUserId() => $_has(0);
@$pb.TagNumber(1)
void clearUserId() => $_clearField(1);
@$pb.TagNumber(3)
$core.List<$core.int> get keyManager => $_getN(1);
@$pb.TagNumber(3)
set keyManager($core.List<$core.int> value) => $_setBytes(1, value);
@$pb.TagNumber(3)
$core.bool hasKeyManager() => $_has(1);
@$pb.TagNumber(3)
void clearKeyManager() => $_clearField(3);
}
const $core.bool _omitFieldNames =
$core.bool.fromEnvironment('protobuf.omit_field_names');
const $core.bool _omitMessageNames =
$core.bool.fromEnvironment('protobuf.omit_message_names');

View file

@ -1,11 +0,0 @@
// This is a generated file - do not edit.
//
// Generated from types.proto.
// @dart = 3.3
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports

View file

@ -1,195 +0,0 @@
// This is a generated file - do not edit.
//
// Generated from types.proto.
// @dart = 3.3
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
// ignore_for_file: unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use recoveryRequestDescriptor instead')
const RecoveryRequest$json = {
'1': 'RecoveryRequest',
'2': [
{'1': 'temp_id', '3': 1, '4': 1, '5': 3, '10': 'tempId'},
{'1': 'public_key', '3': 2, '4': 1, '5': 12, '10': 'publicKey'},
],
};
/// Descriptor for `RecoveryRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List recoveryRequestDescriptor = $convert.base64Decode(
'Cg9SZWNvdmVyeVJlcXVlc3QSFwoHdGVtcF9pZBgBIAEoA1IGdGVtcElkEh0KCnB1YmxpY19rZX'
'kYAiABKAxSCXB1YmxpY0tleQ==');
@$core.Deprecated('Use encryptedEnvelopeDescriptor instead')
const EncryptedEnvelope$json = {
'1': 'EncryptedEnvelope',
'2': [
{'1': 'encrypted_data', '3': 1, '4': 1, '5': 12, '10': 'encryptedData'},
{'1': 'iv', '3': 2, '4': 1, '5': 12, '10': 'iv'},
{'1': 'mac', '3': 3, '4': 1, '5': 12, '10': 'mac'},
],
};
/// Descriptor for `EncryptedEnvelope`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List encryptedEnvelopeDescriptor = $convert.base64Decode(
'ChFFbmNyeXB0ZWRFbnZlbG9wZRIlCg5lbmNyeXB0ZWRfZGF0YRgBIAEoDFINZW5jcnlwdGVkRG'
'F0YRIOCgJpdhgCIAEoDFICaXYSEAoDbWFjGAMgASgMUgNtYWM=');
@$core.Deprecated('Use trustedFriendShareDescriptor instead')
const TrustedFriendShare$json = {
'1': 'TrustedFriendShare',
'2': [
{
'1': 'trusted_friend',
'3': 1,
'4': 1,
'5': 11,
'6': '.passwordless_recovery.TrustedFriendShare.User',
'10': 'trustedFriend'
},
{
'1': 'share_user',
'3': 2,
'4': 1,
'5': 11,
'6': '.passwordless_recovery.TrustedFriendShare.User',
'10': 'shareUser'
},
{'1': 'threshold', '3': 3, '4': 1, '5': 5, '10': 'threshold'},
{
'1': 'shared_secret_data',
'3': 4,
'4': 1,
'5': 12,
'10': 'sharedSecretData'
},
],
'3': [TrustedFriendShare_User$json],
};
@$core.Deprecated('Use trustedFriendShareDescriptor instead')
const TrustedFriendShare_User$json = {
'1': 'User',
'2': [
{'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'},
{'1': 'display_name', '3': 2, '4': 1, '5': 9, '10': 'displayName'},
{'1': 'avatar', '3': 3, '4': 1, '5': 12, '10': 'avatar'},
],
};
/// Descriptor for `TrustedFriendShare`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List trustedFriendShareDescriptor = $convert.base64Decode(
'ChJUcnVzdGVkRnJpZW5kU2hhcmUSVQoOdHJ1c3RlZF9mcmllbmQYASABKAsyLi5wYXNzd29yZG'
'xlc3NfcmVjb3ZlcnkuVHJ1c3RlZEZyaWVuZFNoYXJlLlVzZXJSDXRydXN0ZWRGcmllbmQSTQoK'
'c2hhcmVfdXNlchgCIAEoCzIuLnBhc3N3b3JkbGVzc19yZWNvdmVyeS5UcnVzdGVkRnJpZW5kU2'
'hhcmUuVXNlclIJc2hhcmVVc2VyEhwKCXRocmVzaG9sZBgDIAEoBVIJdGhyZXNob2xkEiwKEnNo'
'YXJlZF9zZWNyZXRfZGF0YRgEIAEoDFIQc2hhcmVkU2VjcmV0RGF0YRpaCgRVc2VyEhcKB3VzZX'
'JfaWQYASABKANSBnVzZXJJZBIhCgxkaXNwbGF5X25hbWUYAiABKAlSC2Rpc3BsYXlOYW1lEhYK'
'BmF2YXRhchgDIAEoDFIGYXZhdGFy');
@$core.Deprecated('Use sharedSecretDataDescriptor instead')
const SharedSecretData$json = {
'1': 'SharedSecretData',
'2': [
{
'1': 'recovery_data',
'3': 1,
'4': 1,
'5': 11,
'6': '.passwordless_recovery.RecoveryData',
'9': 0,
'10': 'recoveryData',
'17': true
},
{
'1': 'second_factor_mail',
'3': 2,
'4': 1,
'5': 11,
'6': '.passwordless_recovery.SharedSecretData.SecondFactorMail',
'9': 1,
'10': 'secondFactorMail',
'17': true
},
{
'1': 'second_factor_pin',
'3': 3,
'4': 1,
'5': 11,
'6': '.passwordless_recovery.SharedSecretData.SecondFactorPin',
'9': 2,
'10': 'secondFactorPin',
'17': true
},
{
'1': 'recovery_data_encrypted',
'3': 4,
'4': 1,
'5': 12,
'9': 3,
'10': 'recoveryDataEncrypted',
'17': true
},
],
'3': [
SharedSecretData_SecondFactorPin$json,
SharedSecretData_SecondFactorMail$json
],
'8': [
{'1': '_recovery_data'},
{'1': '_second_factor_mail'},
{'1': '_second_factor_pin'},
{'1': '_recovery_data_encrypted'},
],
};
@$core.Deprecated('Use sharedSecretDataDescriptor instead')
const SharedSecretData_SecondFactorPin$json = {
'1': 'SecondFactorPin',
'2': [
{'1': 'unlock_token', '3': 1, '4': 1, '5': 12, '10': 'unlockToken'},
{'1': 'pin_seed', '3': 2, '4': 1, '5': 12, '10': 'pinSeed'},
],
};
@$core.Deprecated('Use sharedSecretDataDescriptor instead')
const SharedSecretData_SecondFactorMail$json = {
'1': 'SecondFactorMail',
};
/// Descriptor for `SharedSecretData`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List sharedSecretDataDescriptor = $convert.base64Decode(
'ChBTaGFyZWRTZWNyZXREYXRhEk0KDXJlY292ZXJ5X2RhdGEYASABKAsyIy5wYXNzd29yZGxlc3'
'NfcmVjb3ZlcnkuUmVjb3ZlcnlEYXRhSABSDHJlY292ZXJ5RGF0YYgBARJrChJzZWNvbmRfZmFj'
'dG9yX21haWwYAiABKAsyOC5wYXNzd29yZGxlc3NfcmVjb3ZlcnkuU2hhcmVkU2VjcmV0RGF0YS'
'5TZWNvbmRGYWN0b3JNYWlsSAFSEHNlY29uZEZhY3Rvck1haWyIAQESaAoRc2Vjb25kX2ZhY3Rv'
'cl9waW4YAyABKAsyNy5wYXNzd29yZGxlc3NfcmVjb3ZlcnkuU2hhcmVkU2VjcmV0RGF0YS5TZW'
'NvbmRGYWN0b3JQaW5IAlIPc2Vjb25kRmFjdG9yUGluiAEBEjsKF3JlY292ZXJ5X2RhdGFfZW5j'
'cnlwdGVkGAQgASgMSANSFXJlY292ZXJ5RGF0YUVuY3J5cHRlZIgBARpPCg9TZWNvbmRGYWN0b3'
'JQaW4SIQoMdW5sb2NrX3Rva2VuGAEgASgMUgt1bmxvY2tUb2tlbhIZCghwaW5fc2VlZBgCIAEo'
'DFIHcGluU2VlZBoSChBTZWNvbmRGYWN0b3JNYWlsQhAKDl9yZWNvdmVyeV9kYXRhQhUKE19zZW'
'NvbmRfZmFjdG9yX21haWxCFAoSX3NlY29uZF9mYWN0b3JfcGluQhoKGF9yZWNvdmVyeV9kYXRh'
'X2VuY3J5cHRlZA==');
@$core.Deprecated('Use recoveryDataDescriptor instead')
const RecoveryData$json = {
'1': 'RecoveryData',
'2': [
{'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'},
{'1': 'key_manager', '3': 3, '4': 1, '5': 12, '10': 'keyManager'},
],
};
/// Descriptor for `RecoveryData`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List recoveryDataDescriptor = $convert.base64Decode(
'CgxSZWNvdmVyeURhdGESFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEh8KC2tleV9tYW5hZ2VyGA'
'MgASgMUgprZXlNYW5hZ2Vy');

View file

@ -37,7 +37,6 @@ message EncryptedContent {
/// This can be added, so the receiver can check weather he is up to date with the current profile
optional int64 sender_profile_counter = 4;
optional bytes sender_user_discovery_version = 21;
optional bool ask_for_friend_promotions = 25;
optional MessageUpdate message_update = 5;
optional Media media = 6;
@ -58,15 +57,12 @@ message EncryptedContent {
optional UserDiscoveryRequest user_discovery_request = 22;
optional UserDiscoveryUpdate user_discovery_update = 23;
optional KeyVerificationProof key_verification_proof = 24;
optional PasswordLessRecovery passwordless_recovery = 26;
optional PasswordLessRecoveryHeartbeat passwordless_recovery_heartbeat = 27;
message ErrorMessages {
enum Type {
ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD = 0;
UNKNOWN_MESSAGE_TYPE = 2;
SESSION_OUT_OF_SYNC = 3;
GROUP_NOT_FOUND_OR_NOT_A_MEMBER = 4;
}
Type type = 1;
string related_receipt_id = 2;
@ -74,9 +70,8 @@ message EncryptedContent {
message GroupCreate {
// key for the state stored on the server
bytes state_key = 3;
bytes group_public_key = 4;
optional string group_name = 5;
bytes state_key = 3;
bytes group_public_key = 4;
}
message GroupJoin {
@ -84,7 +79,9 @@ message EncryptedContent {
bytes group_public_key = 1;
}
message ResendGroupPublicKey {}
message ResendGroupPublicKey {
}
message GroupUpdate {
string group_action_type = 1; // GroupActionType.name
@ -217,14 +214,4 @@ message EncryptedContent {
bytes calculated_mac = 1;
}
message PasswordLessRecovery {
optional bytes recoverySecretShare = 1;
bool delete = 2;
int64 threshold = 3;
}
message PasswordLessRecoveryHeartbeat {
bytes hash = 1;
}
}

View file

@ -13,8 +13,7 @@ import 'package:twonly/src/visual/views/contact/add_new_contact.view.dart';
import 'package:twonly/src/visual/views/contact/contact.view.dart';
import 'package:twonly/src/visual/views/groups/group.view.dart';
import 'package:twonly/src/visual/views/groups/group_create_select_members.view.dart';
import 'package:twonly/src/visual/views/onboarding/recover_password.view.dart';
import 'package:twonly/src/visual/views/onboarding/recover_passwordless.view.dart';
import 'package:twonly/src/visual/views/onboarding/recover.view.dart';
import 'package:twonly/src/visual/views/public_profile.view.dart';
import 'package:twonly/src/visual/views/settings/account.view.dart';
import 'package:twonly/src/visual/views/settings/appearance.view.dart';
@ -40,6 +39,7 @@ import 'package:twonly/src/visual/views/settings/help/help.view.dart';
import 'package:twonly/src/visual/views/settings/notification.view.dart';
import 'package:twonly/src/visual/views/settings/privacy.view.dart';
import 'package:twonly/src/visual/views/settings/privacy/block_users.view.dart';
import 'package:twonly/src/visual/views/settings/privacy/profile_selection.view.dart';
import 'package:twonly/src/visual/views/settings/privacy/user_discovery.view.dart';
import 'package:twonly/src/visual/views/settings/profile/modify_avatar.view.dart';
import 'package:twonly/src/visual/views/settings/profile/profile.view.dart';
@ -58,13 +58,6 @@ final routerProvider = GoRouter(
path: Routes.home,
builder: (context, state) => const AppMainWidget(initialPage: 1),
),
GoRoute(
path: Routes.recoverPasswordless,
builder: (context, state) {
final token = state.extra as String?;
return RecoverPasswordless(initialEmailToken: token);
},
),
// Chats
GoRoute(
@ -142,13 +135,7 @@ final routerProvider = GoRouter(
GoRoute(
path: Routes.cameraQRScanner,
builder: (context, state) {
final extra = state.extra as Map<String, dynamic>?;
final contact = extra?['contact'] as Contact?;
final openToVerify = extra?['openToVerify'] as bool? ?? false;
return QrCodeScannerView(
contact: contact,
openToVerify: openToVerify,
);
return const QrCodeScannerView();
},
),
@ -219,6 +206,10 @@ final routerProvider = GoRouter(
path: 'user_discovery',
builder: (context, state) => const UserDiscoverySettingsView(),
),
GoRoute(
path: 'profile_selection',
builder: (context, state) => const ProfileSelectionSettingsView(),
),
],
),
GoRoute(
@ -249,9 +240,7 @@ final routerProvider = GoRouter(
routes: [
GoRoute(
path: 'verifybadge',
builder: (context, state) => VerificationBadeFaqView(
contact: state.extra as Contact?,
),
builder: (context, state) => const VerificationBadeFaqView(),
),
],
),

View file

@ -21,8 +21,7 @@ import 'package:twonly/locator.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pbserver.dart';
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
as server;
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart' as server;
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pbserver.dart';
import 'package:twonly/src/services/api/client2client/user_discovery.c2c.dart';
import 'package:twonly/src/services/api/mediafiles/download.api.dart';
@ -34,7 +33,6 @@ import 'package:twonly/src/services/flame.service.dart';
import 'package:twonly/src/services/group.service.dart';
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
import 'package:twonly/src/services/passwordless_recovery.service.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/utils.signal.dart';
@ -67,15 +65,13 @@ class ApiService {
Stream<SubscriptionPlan> get onPlanUpdated => _planUpdateController.stream;
final _connectionStateController = StreamController<bool>.broadcast();
Stream<bool> get onConnectionStateUpdated =>
_connectionStateController.stream;
Stream<bool> get onConnectionStateUpdated => _connectionStateController.stream;
final _appOutdatedController = StreamController<void>.broadcast();
Stream<void> get onAppOutdated => _appOutdatedController.stream;
final _newDeviceRegisteredController = StreamController<void>.broadcast();
Stream<void> get onNewDeviceRegistered =>
_newDeviceRegisteredController.stream;
Stream<void> get onNewDeviceRegistered => _newDeviceRegisteredController.stream;
bool appIsOutdated = false;
bool isAuthenticated = false;
@ -84,8 +80,7 @@ class ApiService {
Timer? reconnectionTimer;
int _reconnectionDelay = 5;
final HashMap<Int64, Completer<server.ServerToClient?>> _pendingRequests =
HashMap();
final HashMap<Int64, Completer<server.ServerToClient?>> _pendingRequests = HashMap();
IOWebSocketChannel? _channel;
// ignore: cancel_subscriptions
StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
@ -117,7 +112,7 @@ class ApiService {
// Function is called after the user is authenticated at the server
Future<void> onAuthenticated() async {
await FcmNotificationService.initFCMAfterAuthenticated();
await initFCMAfterAuthenticated();
_connectionStateController.add(true);
if (AppState.isInBackgroundTask) {
@ -140,7 +135,6 @@ class ApiService {
unawaited(fetchGroupStatesForUnjoinedGroups());
unawaited(fetchMissingGroupPublicKey());
unawaited(checkForDeletedUsernames());
unawaited(PasswordlessRecoveryService.performHeartbeat());
unawaited(UserDiscoveryService.checkForNewAnnouncedUsers());
@ -424,9 +418,7 @@ class ApiService {
}
if (res.error == ErrorCode.UserIdNotFound && contactId != null) {
Log.warn('Contact deleted their account $contactId.');
final contact = await twonlyDB.contactsDao
.getContactByUserId(contactId)
.getSingleOrNull();
final contact = await twonlyDB.contactsDao.getContactByUserId(contactId).getSingleOrNull();
if (contact != null) {
await twonlyDB.contactsDao.updateContact(
contactId,
@ -491,8 +483,7 @@ class ApiService {
return true;
}
if (result.isError) {
if (result.error != ErrorCode.AuthTokenNotValid &&
result.error != ErrorCode.ForegroundSessionConnected) {
if (result.error != ErrorCode.AuthTokenNotValid && result.error != ErrorCode.ForegroundSessionConnected) {
Log.error(
'got error while authenticating to the server: ${result.error}',
);
@ -530,8 +521,7 @@ class ApiService {
return true;
}
if (result.isError) {
if (result.error != ErrorCode.AuthTokenNotValid &&
result.error != ErrorCode.ForegroundSessionConnected) {
if (result.error != ErrorCode.AuthTokenNotValid && result.error != ErrorCode.ForegroundSessionConnected) {
Log.error(
'got error while authenticating to the server: ${result.error}',
);
@ -563,8 +553,7 @@ class ApiService {
return;
}
final handshake = Handshake()
..getAuthChallenge = Handshake_GetAuthChallenge();
final handshake = Handshake()..getAuthChallenge = Handshake_GetAuthChallenge();
final req = createClientToServerFromHandshake(handshake);
final result = await sendRequestSync(req, authenticated: false);
@ -629,9 +618,7 @@ class ApiService {
final register = Handshake_Register()
..username = username
..publicIdentityKey = (await signalStore.getIdentityKeyPair())
.getPublicKey()
.serialize()
..publicIdentityKey = (await signalStore.getIdentityKeyPair()).getPublicKey().serialize()
..registrationId = Int64(signalIdentity.registrationId)
..signedPrekey = signedPreKey.getKeyPair().publicKey.serialize()
..signedPrekeySignature = signedPreKey.signature
@ -686,11 +673,11 @@ class ApiService {
final req = createClientToServerFromHandshake(handshake);
final result = await sendRequestSync(req, authenticated: false);
if (result.isError) {
Log.error('could not request proof of work params', error: result);
Log.error('could not request proof of work params', result);
if (result.error == ErrorCode.RegistrationDisabled) {
return (null, true);
}
Log.error('could not request proof of work params', error: result);
Log.error('could not request proof of work params', result);
return (null, false);
}
return (result.value.proofOfWork as Response_ProofOfWork, false);
@ -760,85 +747,6 @@ class ApiService {
return sendRequestSync(req, contactId: userId.toInt());
}
Future<Result> registerPasswordLessRecovery(
List<int> encryptedServerKey,
List<int>? pinUnlockToken,
) async {
final req = createClientToServerFromApplicationData(
ApplicationData(
registerPasswordlessRecovery:
ApplicationData_RegisterPasswordLessRecovery(
encryptedServerKey: encryptedServerKey,
pinUnlockToken: pinUnlockToken,
),
),
);
return sendRequestSync(req);
}
Future<Result> getServerKeyForPasswordlessRecovery({
required int userId,
List<int>? encryptedServerKeyNone,
List<int>? pinUnlockToken,
List<int>? pinProtectionKey,
String? email,
}) async {
final get = Handshake_GetServerKeyForPasswordLessRecovery()
..userId = Int64(userId);
if (encryptedServerKeyNone != null) {
get.encryptedServerKeyNone = encryptedServerKeyNone;
}
if (pinUnlockToken != null) {
get.pinUnlockToken = pinUnlockToken;
}
if (pinProtectionKey != null) {
get.pinProtectionKey = pinProtectionKey;
}
if (email != null) {
get.email = email;
}
final handshake = Handshake()..getServerKeyForPasswordlessRecovery = get;
final req = createClientToServerFromHandshake(handshake);
return sendRequestSync(req, authenticated: false);
}
Future<Result> submitRecoveryShare({
required String notificationId,
required List<int> encryptedMessage,
}) async {
final req = createClientToServerFromApplicationData(
ApplicationData(
passwordlessNotification: ApplicationData_PasswordlessNotification(
notificationId: notificationId,
encryptedMessage: encryptedMessage,
),
),
);
return sendRequestSync(req);
}
Future<Result> registerPasswordlessNotification({
required String notificationId,
required List<int> downloadAuthToken,
required String langCode,
required String? googleFcm,
}) async {
final registerNotif = Handshake_RegisterPasswordlessNotification()
..notificationId = notificationId
..downloadAuthToken = downloadAuthToken
..langCode = langCode;
if (googleFcm != null) {
registerNotif.googleFcm = googleFcm;
}
final handshake = Handshake()
..registerPasswordlessNotification = registerNotif;
final req = createClientToServerFromHandshake(handshake);
return sendRequestSync(req, authenticated: false);
}
Future<Result> addAdditionalUser(Int64 userId) async {
final get = ApplicationData_AddAdditionalUser()..userId = userId;
final appData = ApplicationData()..addAdditionalUser = get;
@ -951,30 +859,4 @@ class ApiService {
final req = createClientToServerFromApplicationData(appData);
return sendRequestSync(req, contactId: target);
}
/// Polls the server for new passwordless recovery notification messages.
/// [alreadyReceivedIds] prevents the server from sending duplicates.
Future<server.Response_PasswordlessNotificationMessages?> checkForPasswordlessNotification({
required String notificationId,
required List<int> downloadAuthToken,
List<Int64>? alreadyReceivedIds,
}) async {
final check = Handshake_CheckForPasswordlessNotification()
..notificationId = notificationId
..downloadAuthToken = downloadAuthToken;
if (alreadyReceivedIds != null && alreadyReceivedIds.isNotEmpty) {
check.alreadyReceivedMessageIds.addAll(alreadyReceivedIds);
}
final handshake = Handshake()..checkForPasswordlessNotification = check;
final req = createClientToServerFromHandshake(handshake);
final res = await sendRequestSync(req, authenticated: false);
if (res.isSuccess) {
final ok = res.value as server.Response_Ok;
if (ok.hasPasswordlessNotificationMessages()) {
return ok.passwordlessNotificationMessages;
}
}
return null;
}
}

View file

@ -2,11 +2,8 @@ import 'package:clock/clock.dart' show clock;
import 'package:drift/drift.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart'
as pb_data;
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/api/utils.api.dart';
import 'package:twonly/src/services/key_verification.service.dart';
import 'package:twonly/src/utils/log.dart';
Future<void> handleAdditionalDataMessage(
@ -31,25 +28,6 @@ Future<void> handleAdditionalDataMessage(
return;
}
try {
final additionalData = pb_data.AdditionalMessageData.fromBuffer(
message.additionalMessageData,
);
if (additionalData.type == pb_data.AdditionalMessageData_Type.CONTACTS) {
for (final sharedContact in additionalData.contacts) {
await KeyVerificationService.verifySharedContact(
contactId: sharedContact.userId.toInt(),
sharedPublicIdentityKey: sharedContact.publicIdentityKey,
senderId: fromUserId,
);
}
}
} catch (e) {
Log.error(
'Failed to parse additional message data or verify shared contacts: $e',
);
}
final msg = await twonlyDB.messagesDao.insertMessage(
MessagesCompanion(
messageId: Value(message.senderMessageId),
@ -68,8 +46,6 @@ Future<void> handleAdditionalDataMessage(
fromTimestamp(message.timestamp),
);
if (msg != null) {
Log.info(
'[$receiptId] Inserted a new text message with ID: ${msg.messageId}',
);
Log.info('[$receiptId] Inserted a new text message with ID: ${msg.messageId}');
}
}

View file

@ -1,23 +1,16 @@
import 'package:clock/clock.dart';
import 'package:drift/drift.dart' show Value;
import 'package:fixnum/fixnum.dart' show Int64;
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'
show IdentityKeyPair;
import 'package:twonly/locator.dart';
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart';
import 'package:twonly/src/services/api/messages.api.dart'
show sendCipherText, tryToSendCompleteMessage;
import 'package:twonly/src/services/group.service.dart' show fetchGroupState;
import 'package:twonly/src/utils/log.dart';
Future<void> handleErrorMessage(
int fromUserId,
EncryptedContent_ErrorMessages error,
String receiptId, {
String? groupId,
}) async {
Log.warn('[$receiptId] Got error from $fromUserId: $error');
String receiptId,
) async {
Log.error('[$receiptId] Got error from $fromUserId: $error');
switch (error.type) {
case EncryptedContent_ErrorMessages_Type
@ -36,59 +29,6 @@ Future<void> handleErrorMessage(
);
case EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC:
break; // The other user initiated a new signal session, so ignore the error in this case, as the new session works...
case EncryptedContent_ErrorMessages_Type.GROUP_NOT_FOUND_OR_NOT_A_MEMBER:
if (groupId == null) {
Log.warn(
'[$receiptId] GROUP_NOT_FOUND_OR_NOT_A_MEMBER error received, but groupId is null.',
);
return;
}
final group = await twonlyDB.groupsDao.getGroup(groupId);
if (group == null) {
Log.warn(
'[$receiptId] GROUP_NOT_FOUND_OR_NOT_A_MEMBER error received, but group $groupId is not found in database.',
);
return;
}
// Update group state from the server to ensure the user is still part of the group...
final updatedState = await fetchGroupState(group);
if (updatedState != null) {
final (_, state) = updatedState;
final isStillMember = state.memberIds.contains(Int64(fromUserId));
if (isStillMember) {
final keyPair = IdentityKeyPair.fromSerialized(
group.myGroupPrivateKey!,
);
await sendCipherText(
fromUserId,
EncryptedContent(
groupId: groupId,
groupCreate: EncryptedContent_GroupCreate(
stateKey: group.stateEncryptionKey,
groupPublicKey: keyPair.getPublicKey().serialize(),
),
),
);
}
final r = await twonlyDB.receiptsDao.getReceiptById(
error.relatedReceiptId,
);
if (r != null) {
await twonlyDB.receiptsDao.updateReceiptWidthUserId(
fromUserId,
error.relatedReceiptId,
ReceiptsCompanion(
markForRetry: Value(clock.now()),
retryCount: Value(r.retryCount + 1),
),
);
}
// then resend: error.relatedReceiptId
await tryToSendCompleteMessage(
receiptId: error.relatedReceiptId,
blocking: false,
);
} else {}
// ignore: no_default_cases
default:
break;

View file

@ -17,9 +17,7 @@ Future<void> handleGroupCreate(
EncryptedContent_GroupCreate newGroup,
String receiptId,
) async {
final user = await twonlyDB.contactsDao
.getContactByUserId(fromUserId)
.getSingleOrNull();
final user = await twonlyDB.contactsDao.getContactByUserId(fromUserId).getSingleOrNull();
if (user == null) {
// Only contacts can invite other contacts, so this can (via the UI) not happen.
Log.error(
@ -45,26 +43,11 @@ Future<void> handleGroupCreate(
stateVersionId: const Value(0),
stateEncryptionKey: Value(Uint8List.fromList(newGroup.stateKey)),
myGroupPrivateKey: Value(myGroupKey.serialize()),
groupName: Value(newGroup.hasGroupName() ? newGroup.groupName : ''),
groupName: const Value(''),
joinedGroup: const Value(false),
),
);
} else {
// In this case make a group state update and check if the fromUserId is still a admin. otherwise return with an log error message
final updatedState = await fetchGroupState(group);
if (updatedState == null) {
Log.error(
'[$receiptId] Received group invite/create for $groupId, but failed to fetch group state from server.',
);
return;
}
final (_, state) = updatedState;
if (!state.adminIds.any((id) => id.toInt() == fromUserId)) {
Log.error(
'[$receiptId] Received group invite/create for $groupId from $fromUserId, but they are not an admin of this group.',
);
return;
}
// User was already in the group, so update leftGroup back to false
await twonlyDB.groupsDao.updateGroup(
groupId,
@ -72,6 +55,7 @@ Future<void> handleGroupCreate(
stateVersionId: const Value(0),
stateEncryptionKey: Value(Uint8List.fromList(newGroup.stateKey)),
myGroupPrivateKey: Value(myGroupKey.serialize()),
groupName: const Value(''),
joinedGroup: const Value(false),
leftGroup: const Value(false),
deletedContent: const Value(false),
@ -95,8 +79,18 @@ Future<void> handleGroupCreate(
),
);
// Load group members from the server as this is the single source of truth.
// This can be done in the background, so the WebSocket message can be ACKed.
await twonlyDB.groupsDao.insertOrUpdateGroupMember(
GroupMembersCompanion(
groupId: Value(groupId),
contactId: Value(fromUserId),
memberState: const Value(
MemberState.admin, // is the group creator, so must be admin...
),
groupPublicKey: Value(Uint8List.fromList(newGroup.groupPublicKey)),
),
);
// can be done in the background -> websocket message can be ACK
unawaited(fetchGroupStatesForUnjoinedGroups());
await sendCipherTextToGroup(
@ -119,9 +113,7 @@ Future<void> handleGroupUpdate(
final actionType = groupActionTypeFromString(update.groupActionType);
if (actionType == null) {
Log.error(
'[$receiptId] Group action ${update.groupActionType} is unknown ignoring.',
);
Log.error('[$receiptId] Group action ${update.groupActionType} is unknown ignoring.');
return;
}

View file

@ -14,7 +14,7 @@ import 'package:twonly/src/services/flame.service.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/log.dart';
Future<bool> handleMedia(
Future<void> handleMedia(
int fromUserId,
String groupId,
EncryptedContent_Media media,
@ -36,7 +36,7 @@ Future<bool> handleMedia(
Log.warn(
'[$receiptId] Got reupload for a message that either does not exists (${message == null}) or senderId = ${message?.senderId}',
);
return false;
return;
}
// in case there was already a downloaded file delete it...
@ -64,7 +64,7 @@ Future<bool> handleMedia(
unawaited(startDownloadMedia(mediaFile, false));
}
return true;
return;
case EncryptedContent_Media_Type.IMAGE:
mediaType = MediaType.image;
case EncryptedContent_Media_Type.VIDEO:
@ -85,13 +85,13 @@ Future<bool> handleMedia(
Log.warn(
'[$receiptId] $fromUserId tried to modify the message from ${messageTmp.senderId}.',
);
return false;
return;
}
if (messageTmp.mediaId == null) {
Log.warn(
'[$receiptId] This message already exit without a mediaId. Message is dropped.',
);
return false;
return;
}
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
messageTmp.mediaId!,
@ -100,7 +100,7 @@ Future<bool> handleMedia(
Log.warn(
'[$receiptId] This message and media file already exit and was not requested again. Dropping it.',
);
return false;
return;
}
if (mediaFile != null) {
@ -145,13 +145,9 @@ Future<bool> handleMedia(
if (mediaFile == null) {
Log.error('[$receiptId] Could not insert media file into database');
return false;
return;
}
Log.info(
'[$receiptId] Inserting media message: messageId=${media.senderMessageId}, mediaId=${mediaFile!.mediaId}',
);
message = await twonlyDB.messagesDao.insertMessage(
MessagesCompanion(
messageId: Value(media.senderMessageId),
@ -190,7 +186,6 @@ Future<bool> handleMedia(
);
unawaited(startDownloadMedia(mediaFile!, false));
return true;
} else {
if (mediaFile == null && message == null) {
Log.error(
@ -205,7 +200,6 @@ Future<bool> handleMedia(
'[$receiptId] Could not insert new message as the message is empty.',
);
}
return false;
}
}

View file

@ -7,7 +7,7 @@ import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/services/api/utils.api.dart';
import 'package:twonly/src/utils/log.dart';
Future<bool> handleTextMessage(
Future<void> handleTextMessage(
int fromUserId,
String groupId,
EncryptedContent_TextMessage textMessage,
@ -26,7 +26,7 @@ Future<bool> handleTextMessage(
Log.warn(
'[$receiptId] $fromUserId tried to overwrite message from ${existing.senderId}. Dropping.',
);
return false;
return;
}
final message = await twonlyDB.messagesDao.insertMessage(
@ -50,5 +50,4 @@ Future<bool> handleTextMessage(
if (message != null) {
Log.info('[$receiptId] Inserted a new text message with ID: ${message.messageId}');
}
return message != null;
}

View file

@ -143,12 +143,8 @@ Future<void> handleDownloadStatusUpdate(TaskStatusUpdate update) async {
failed = false;
} else {
failed = true;
Log.warn(
'[$mediaId] Got invalid response status code: ${update.responseStatusCode}',
);
Log.error(
'Got invalid response status code: ${update.responseStatusCode}',
onlyIfSentryEnabled: true,
);
}
} else {
@ -231,7 +227,7 @@ Future<void> startDownloadMedia(MediaFile media, bool force) async {
try {
await downloadFileFast(media, apiUrl, mediaService.encryptedPath);
} catch (e) {
Log.warn('Fast download failed: $e');
Log.error('Fast download failed: $e');
await FileDownloader().enqueue(task);
}
} catch (e) {
@ -255,12 +251,8 @@ Future<void> downloadFileFast(
return;
} else {
if (response.statusCode == 404 || response.statusCode == 403) {
Log.warn(
'Got ${response.statusCode} from server for media ID ${media.mediaId}. Requesting upload again',
);
Log.error(
'Got ${response.statusCode} from server for media ID.',
onlyIfSentryEnabled: true,
'Got ${response.statusCode} from server for media ID ${media.mediaId}. Requesting upload again',
);
// Message was deleted from the server. Requesting it again from the sender to upload it again...
await requestMediaReupload(media.mediaId);
@ -272,13 +264,6 @@ Future<void> downloadFileFast(
}
Future<void> requestMediaReupload(String mediaId) async {
await twonlyDB.mediaFilesDao.updateMedia(
mediaId,
const MediaFilesCompanion(
downloadState: Value(DownloadState.reuploadRequested),
),
);
final messages = await twonlyDB.messagesDao.getMessagesByMediaId(mediaId);
for (final message in messages) {
@ -292,6 +277,12 @@ Future<void> requestMediaReupload(String mediaId) async {
),
),
);
await twonlyDB.mediaFilesDao.updateMedia(
mediaId,
const MediaFilesCompanion(
downloadState: Value(DownloadState.reuploadRequested),
),
);
}
}
@ -302,7 +293,7 @@ Future<void> handleEncryptedFile(String mediaId) async {
action: () async {
final mediaService = await MediaFileService.fromMediaId(mediaId);
if (mediaService == null) {
Log.warn('[$mediaId] Media file not found in database.');
Log.error('Media file not found in database.');
return;
}

View file

@ -80,12 +80,8 @@ Future<void> handleUploadStatusUpdate(TaskStatusUpdate update) async {
await markUploadAsSuccessful(media);
return;
}
Log.warn(
'Got HTTP error ${update.responseStatusCode} for $mediaId',
);
Log.error(
'Got HTTP error ${update.responseStatusCode} for media.',
onlyIfSentryEnabled: true,
'Got HTTP error ${update.responseStatusCode} for $mediaId',
);
}
@ -122,12 +118,8 @@ Future<void> handleUploadStatusUpdate(TaskStatusUpdate update) async {
if (update.status == TaskStatus.failed ||
update.status == TaskStatus.canceled) {
Log.warn(
'Background upload failed for $mediaId with status ${update.status} and ${update.responseStatusCode}. ',
);
Log.error(
'Background upload failed with status ${update.status} and ${update.responseStatusCode}.',
onlyIfSentryEnabled: true,
'Background upload failed for $mediaId with status ${update.status} and ${update.responseStatusCode}. ',
);
final mediaService = MediaFileService(media);

View file

@ -227,54 +227,46 @@ Future<void> finishStartedPreprocessing() async {
if (!service.originalPath.existsSync() &&
!service.uploadRequestPath.existsSync()) {
if (service.storedPath.existsSync()) {
// media file was stored, we can recover tempPath from storedPath and upload it.
try {
if (!service.tempPath.existsSync()) {
service.storedPath.copySync(service.tempPath.path);
}
} catch (e) {
Log.error('Error recovering tempPath from storedPath: $e');
continue;
}
} else {
if (mediaFile.reuploadRequestedBy != null) {
Log.warn(
'Reupload requested for ${mediaFile.mediaId} but files are missing. Cancelling reupload but keeping record.',
);
await twonlyDB.mediaFilesDao.updateMedia(
mediaFile.mediaId,
const MediaFilesCompanion(
uploadState: Value(UploadState.uploaded),
reuploadRequestedBy: Value(null),
),
);
continue;
}
final messages = await twonlyDB.messagesDao.getMessagesByMediaId(
mediaFile.mediaId,
);
if (messages.isEmpty) {
Log.info(
'Deleted media files ${mediaFile.mediaId} as originalPath and uploadRequestPath both do not exists and no messages reference it.',
);
// the file does not exists anymore and no messages reference it.
await twonlyDB.mediaFilesDao.deleteMediaFile(mediaFile.mediaId);
} else {
Log.warn(
'Media files ${mediaFile.mediaId} missing but messages still reference it. Keeping record to avoid broken chat history.',
);
// Just mark as uploaded to stop preprocessing attempts
await twonlyDB.mediaFilesDao.updateMedia(
mediaFile.mediaId,
const MediaFilesCompanion(
uploadState: Value(UploadState.uploaded),
),
);
}
// media files was just stored..
continue;
}
if (mediaFile.reuploadRequestedBy != null) {
Log.warn(
'Reupload requested for ${mediaFile.mediaId} but files are missing. Cancelling reupload but keeping record.',
);
await twonlyDB.mediaFilesDao.updateMedia(
mediaFile.mediaId,
const MediaFilesCompanion(
uploadState: Value(UploadState.uploaded),
reuploadRequestedBy: Value(null),
),
);
continue;
}
final messages = await twonlyDB.messagesDao.getMessagesByMediaId(
mediaFile.mediaId,
);
if (messages.isEmpty) {
Log.info(
'Deleted media files ${mediaFile.mediaId} as originalPath and uploadRequestPath both do not exists and no messages reference it.',
);
// the file does not exists anymore and no messages reference it.
await twonlyDB.mediaFilesDao.deleteMediaFile(mediaFile.mediaId);
} else {
Log.warn(
'Media files ${mediaFile.mediaId} missing but messages still reference it. Keeping record to avoid broken chat history.',
);
// Just mark as uploaded to stop preprocessing attempts
await twonlyDB.mediaFilesDao.updateMedia(
mediaFile.mediaId,
const MediaFilesCompanion(
uploadState: Value(UploadState.uploaded),
),
);
}
continue;
}
Log.info(
'Finishing started preprocessing of ${mediaFile.mediaId} in state ${mediaFile.uploadState}.',
@ -439,45 +431,17 @@ Future<void> _startBackgroundMediaUploadInternal(
// Refresh the media file state inside the mutex
await mediaService.updateFromDB();
if (mediaService.mediaFile.uploadState == UploadState.uploading) {
await _checkAndRecoverMissingUploadRequest(mediaService);
}
if (mediaService.mediaFile.uploadState == UploadState.initialized ||
mediaService.mediaFile.uploadState == UploadState.preprocessing) {
Log.info(
'Handling media file ${mediaService.mediaFile.mediaId} in ${mediaService.mediaFile.uploadState}',
'Hanlding media file ${mediaService.mediaFile.mediaId} in ${mediaService.mediaFile.uploadState}',
);
await mediaService.setUploadState(UploadState.preprocessing);
if (!mediaService.tempPath.existsSync()) {
if (mediaService.storedPath.existsSync()) {
try {
mediaService.storedPath.copySync(mediaService.tempPath.path);
} catch (e) {
Log.error('Error copying storedPath to tempPath: $e');
}
} else {
await mediaService.compressMedia();
}
await mediaService.compressMedia();
if (!mediaService.tempPath.existsSync()) {
final messages = await twonlyDB.messagesDao.getMessagesByMediaId(
mediaService.mediaFile.mediaId,
);
if (messages.isEmpty) {
Log.warn(
'Media files ${mediaService.mediaFile.mediaId} has no original, temp, or stored path. Removing it from DB as files are not existent.',
);
await twonlyDB.mediaFilesDao.deleteMediaFile(
mediaService.mediaFile.mediaId,
);
} else {
Log.warn(
'Media files ${mediaService.mediaFile.mediaId} has no original, temp, or stored path, but messages still reference it. Marking as uploaded to stop retries.',
);
await mediaService.setUploadState(UploadState.uploaded);
}
return;
}
}
@ -676,31 +640,7 @@ Future<void> _createUploadRequest(MediaFileService media) async {
await media.uploadRequestPath.writeAsBytes(uploadRequestBytes);
}
Future<bool> _checkAndRecoverMissingUploadRequest(
MediaFileService media, {
bool triggerBackgroundUpload = false,
}) async {
if (!media.uploadRequestPath.existsSync()) {
Log.warn(
'UploadRequestPath for media ${media.mediaFile.mediaId} does not exist. Reverting to preprocessing.',
);
await media.setUploadState(UploadState.preprocessing);
if (triggerBackgroundUpload) {
unawaited(startBackgroundMediaUpload(media));
}
return true;
}
return false;
}
Future<void> _uploadUploadRequest(MediaFileService media) async {
if (await _checkAndRecoverMissingUploadRequest(
media,
triggerBackgroundUpload: true,
)) {
return;
}
final currentMedia = await twonlyDB.mediaFilesDao.getMediaFileById(
media.mediaFile.mediaId,
);
@ -750,13 +690,6 @@ Future<void> uploadFileFastOrEnqueue(
UploadTask task,
MediaFileService media,
) async {
if (await _checkAndRecoverMissingUploadRequest(
media,
triggerBackgroundUpload: true,
)) {
return;
}
final requestMultipart = http.MultipartRequest(
'POST',
Uri.parse(task.url),

View file

@ -19,7 +19,6 @@ import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
import 'package:twonly/src/services/signal/encryption.signal.dart';
import 'package:twonly/src/services/signal/session.signal.dart';
import 'package:twonly/src/services/user.service.dart' show UserService;
import 'package:twonly/src/services/user_discovery.service.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
@ -101,18 +100,14 @@ Future<(Uint8List, Uint8List?)?> _tryToSendCompleteMessageInternal({
if (receiptId == null && receipt == null) return null;
try {
final targetReceiptId = receipt?.receiptId ?? receiptId!;
final loadedReceipt = await twonlyDB.receiptsDao.getReceiptById(
targetReceiptId,
);
if (loadedReceipt == null) {
Log.info(
'[$targetReceiptId] Receipt not found (might have been processed or deleted).',
);
return null;
if (receipt == null) {
// ignore: parameter_assignments
receipt = await twonlyDB.receiptsDao.getReceiptById(receiptId!);
if (receipt == null) {
Log.warn('[$receiptId] Receipt not found.');
return null;
}
}
// ignore: parameter_assignments
receipt = loadedReceipt;
if (receipt.retryCount >= 2) {
// After two retries, change the receiptId. This addresses a bug where the receiver received the message and marked it as received,
@ -142,7 +137,7 @@ Future<(Uint8List, Uint8List?)?> _tryToSendCompleteMessageInternal({
if (!onlyReturnEncryptedData &&
receipt.ackByServerAt != null &&
receipt.markForRetry == null) {
Log.info('Message already uploaded and mark for retry is not set.');
Log.error('Message already uploaded and mark for retry is not set!');
return null;
}
@ -181,17 +176,7 @@ Future<(Uint8List, Uint8List?)?> _tryToSendCompleteMessageInternal({
Uint8List.fromList(message.encryptedContent),
);
if (cipherText == null) {
Log.error(
'[${receipt.receiptId}] Could not encrypt the message for user ${receipt.contactId}. Aborting and trying again.',
);
if (receipt.messageId != null) {
await twonlyDB.messagesDao.handleMessageAckByServer(
receipt.contactId,
receipt.messageId!,
clock.now(),
);
}
await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId);
Log.error('Could not encrypt the message. Aborting and trying again.');
return null;
}
message.encryptedContent = cipherText.serialize();
@ -449,7 +434,7 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
final openReceipts = await twonlyDB.receiptsDao.getReceiptCountForContact(
contactId,
);
if (openReceipts > 10) {
if (openReceipts > 6) {
// this prevents that these types of messages are send in case the receiver is offline
return null;
}
@ -458,26 +443,12 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
userService.currentUser.avatarCounter,
);
{
if (userService.currentUser.askForFriendPromotions) {
final contacts = await twonlyDB.contactsDao.getAllContacts();
final contactCount = contacts.where((c) => c.accepted).length;
if (contactCount > 5) {
await UserService.update((u) {
u.askForFriendPromotions = false;
});
} else {
encryptedContent.askForFriendPromotions = true;
}
}
if (userService.currentUser.isUserDiscoveryEnabled && messageId != null) {
final contact = await twonlyDB.contactsDao.getContactById(contactId);
if (UserDiscoveryService.isContactAllowed(contact)) {
final version = await UserDiscoveryService.getCurrentVersion();
if (version != null) {
encryptedContent.senderUserDiscoveryVersion = version;
}
if (userService.currentUser.isUserDiscoveryEnabled && messageId != null) {
final contact = await twonlyDB.contactsDao.getContactById(contactId);
if (UserDiscoveryService.isContactAllowed(contact)) {
final version = await UserDiscoveryService.getCurrentVersion();
if (version != null) {
encryptedContent.senderUserDiscoveryVersion = version;
}
}
}

View file

@ -5,7 +5,6 @@ import 'package:clock/clock.dart';
import 'package:drift/drift.dart';
import 'package:hashlib/random.dart';
import 'package:mutex/mutex.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart';
@ -33,7 +32,6 @@ import 'package:twonly/src/services/group.service.dart';
import 'package:twonly/src/services/key_verification.service.dart';
import 'package:twonly/src/services/notifications/background.notifications.dart';
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
import 'package:twonly/src/services/passwordless_recovery.service.dart';
import 'package:twonly/src/services/signal/encryption.signal.dart';
import 'package:twonly/src/services/signal/session.signal.dart';
import 'package:twonly/src/utils/log.dart';
@ -56,13 +54,9 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
Log.info(
'Got ${msg.v0.newMessages.newMessages.length} messages from the server.',
);
final brokenSessionsInCurrentBatch = <int>{};
for (final newMessage in msg.v0.newMessages.newMessages) {
try {
await handleClient2ClientMessage(
newMessage,
brokenSessionsInCurrentBatch: brokenSessionsInCurrentBatch,
);
await handleClient2ClientMessage(newMessage);
} catch (e) {
Log.error(e);
}
@ -87,10 +81,7 @@ DateTime lastPushKeyRequest = clock.now().subtract(const Duration(hours: 1));
final Map<String, Mutex> _messageLocks = {};
Future<void> handleClient2ClientMessage(
NewMessage newMessage, {
Set<int>? brokenSessionsInCurrentBatch,
}) async {
Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
final body = Uint8List.fromList(newMessage.body);
final message = Message.fromBuffer(body);
final receiptId = message.receiptId;
@ -104,11 +95,7 @@ Future<void> handleClient2ClientMessage(
}
await mutex.protect(() async {
try {
await _handleClient2ClientMessage(
newMessage,
message,
brokenSessionsInCurrentBatch: brokenSessionsInCurrentBatch,
);
await _handleClient2ClientMessage(newMessage, message);
} finally {
_messageLocks.remove(receiptId);
}
@ -117,27 +104,11 @@ Future<void> handleClient2ClientMessage(
Future<void> _handleClient2ClientMessage(
NewMessage newMessage,
Message message, {
Set<int>? brokenSessionsInCurrentBatch,
}) async {
Message message,
) async {
final fromUserId = newMessage.fromUserId.toInt();
final receiptId = message.receiptId;
if (brokenSessionsInCurrentBatch?.contains(fromUserId) == true) {
// This happens when a session goes out of sync (e.g. wrong message order).
// We skip the remaining messages in the batch because each failed decryption
// attempt is extremely slow (~1.2s) and would otherwise freeze the app.
// By returning early, we skip gotReceipt() and error responses.
// The server still deletes the batch since we ACK the entire batch later.
// The sender keeps the message unacknowledged. Once they process our SESSION_OUT_OF_SYNC
// error and establish a new session, their retry logic will automatically re-encrypt
// and re-send these messages with the new keys.
Log.info(
'Skipping message from $fromUserId - session known broken in this batch',
);
return;
}
if (await twonlyDB.receiptsDao.isDuplicated(receiptId)) {
return;
}
@ -227,7 +198,6 @@ Future<void> _handleClient2ClientMessage(
encryptedContentRaw,
message.type,
receiptId,
brokenSessionsInCurrentBatch: brokenSessionsInCurrentBatch,
);
if (plainTextContent != null) {
response = Message(
@ -240,8 +210,6 @@ Future<void> _handleClient2ClientMessage(
type: Message_Type.CIPHERTEXT,
encryptedContent: encryptedContent.writeToBuffer(),
);
// Use Value.absent() for CIPHERTEXT messages so that insertReceipt generates a new UUID.
// This prevents receipt ID collisions and ensures the recipient's ACK is tracked correctly.
receiptIdDB = const Value.absent();
} else {
// Message was successful processed
@ -250,9 +218,8 @@ Future<void> _handleClient2ClientMessage(
response ??= Message(type: Message_Type.SENDER_DELIVERY_RECEIPT);
String? targetReceiptId;
try {
final inserted = await twonlyDB.receiptsDao.insertReceipt(
await twonlyDB.receiptsDao.insertReceipt(
ReceiptsCompanion(
receiptId: receiptIdDB ?? Value(receiptId),
contactId: Value(fromUserId),
@ -260,18 +227,10 @@ Future<void> _handleClient2ClientMessage(
contactWillSendsReceipt: const Value(false),
),
);
// Use the inserted receipt's ID because for CIPHERTEXT messages we generate a new UUID
// (receiptIdDB is Value.absent()) to avoid ID collisions and properly track individual ACKs.
targetReceiptId = inserted?.receiptId;
} catch (e) {
Log.warn('[$receiptId] Error inserting receipt: $e');
}
if (targetReceiptId != null) {
await tryToSendCompleteMessage(
receiptId: targetReceiptId,
blocking: false,
);
}
await tryToSendCompleteMessage(receiptId: receiptId, blocking: false);
}
case Message_Type.TEST_NOTIFICATION:
break;
@ -281,11 +240,7 @@ Future<void> _handleClient2ClientMessage(
await twonlyDB.receiptsDao.gotReceipt(receiptId);
Log.info('[$receiptId] Finished processing');
} catch (e) {
Log.warn('[$receiptId] Error marking message as received: $e');
Log.error(
'Error marking message as received: $e',
onlyIfSentryEnabled: true,
);
Log.error('[$receiptId] Error marking message as received: $e');
}
}
@ -293,15 +248,13 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessageRaw(
int fromUserId,
Uint8List encryptedContentRaw,
Message_Type messageType,
String receiptId, {
Set<int>? brokenSessionsInCurrentBatch,
}) async {
String receiptId,
) async {
Log.info('[$receiptId] calling signalDecryptMessage');
var (encryptedContent, decryptionErrorType) = await signalDecryptMessage(
fromUserId,
encryptedContentRaw,
messageType.value,
brokenSessionsInCurrentBatch: brokenSessionsInCurrentBatch,
);
if (encryptedContent == null) {
@ -318,7 +271,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessageRaw(
Log.info('[$receiptId] Calling handleEncryptedMessage');
final result = await handleEncryptedMessage(
final (a, b) = await handleEncryptedMessage(
fromUserId,
encryptedContent,
messageType,
@ -327,9 +280,9 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessageRaw(
Log.info('[$receiptId] Finished handleEncryptedMessage');
if (result.responseCipherText == null && result.responsePlaintext == null) {
unawaited(FcmNotificationService.updateLastServerMessageTimestamp());
if (Platform.isAndroid && result.showPushNotification) {
if (a == null && b == null) {
unawaited(updateLastServerMessageTimestamp());
if (Platform.isAndroid) {
// Message was handled without any error. Show push notification to the user for Android.
await showPushNotificationFromServerMessages(
fromUserId,
@ -338,10 +291,10 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessageRaw(
}
}
return (result.responseCipherText, result.responsePlaintext);
return (a, b);
}
Future<DecryptedMessageResult> handleEncryptedMessage(
Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
int fromUserId,
EncryptedContent content,
Message_Type messageType,
@ -366,28 +319,19 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
);
}
if (content.hasAskForFriendPromotions() && content.askForFriendPromotions) {
final contact = await twonlyDB.contactsDao.getContactById(fromUserId);
if (contact != null && contact.askForFriendPromotions == null) {
await twonlyDB.contactsDao.updateContact(
fromUserId,
const ContactsCompanion(askForFriendPromotions: Value(true)),
);
}
}
if (content.hasContactRequest()) {
if (!await handleContactRequest(
fromUserId,
content.contactRequest,
receiptId,
)) {
return DecryptedMessageResult(
responsePlaintext: PlaintextContent()
return (
null,
PlaintextContent()
..retryControlError = PlaintextContent_RetryErrorMessage(),
);
}
return const DecryptedMessageResult();
return (null, null);
}
if (content.hasErrorMessages()) {
@ -395,27 +339,8 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
fromUserId,
content.errorMessages,
receiptId,
groupId: content.hasGroupId() ? content.groupId : null,
);
return const DecryptedMessageResult(showPushNotification: false);
}
if (content.hasPasswordlessRecovery()) {
await PasswordlessRecoveryService.handlePasswordlessRecovery(
fromUserId,
content.passwordlessRecovery,
receiptId,
);
return const DecryptedMessageResult();
}
if (content.hasPasswordlessRecoveryHeartbeat()) {
await PasswordlessRecoveryService.handlePasswordlessRecoveryHeartbeat(
fromUserId,
content.passwordlessRecoveryHeartbeat,
receiptId,
);
return const DecryptedMessageResult();
return (null, null);
}
if (content.hasContactUpdate()) {
@ -425,7 +350,7 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
senderProfileCounter,
receiptId,
);
return const DecryptedMessageResult(showPushNotification: false);
return (null, null);
}
if (content.hasUserDiscoveryRequest()) {
@ -434,7 +359,7 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
content.userDiscoveryRequest,
receiptId,
);
return const DecryptedMessageResult(showPushNotification: false);
return (null, null);
}
if (content.hasUserDiscoveryUpdate()) {
@ -443,12 +368,12 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
content.userDiscoveryUpdate,
receiptId,
);
return const DecryptedMessageResult();
return (null, null);
}
if (content.hasPushKeys()) {
await handlePushKey(fromUserId, content.pushKeys, receiptId);
return const DecryptedMessageResult();
return (null, null);
}
if (content.hasMessageUpdate()) {
@ -457,7 +382,7 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
content.messageUpdate,
receiptId,
);
return const DecryptedMessageResult();
return (null, null);
}
if (content.hasKeyVerificationProof()) {
@ -465,7 +390,7 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
fromUserId,
content.keyVerificationProof.calculatedMac,
);
return const DecryptedMessageResult();
return (null, null);
}
if (content.hasMediaUpdate()) {
@ -474,19 +399,12 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
content.mediaUpdate,
receiptId,
);
return const DecryptedMessageResult();
return (null, null);
}
if (!content.hasGroupId()) {
final type = _getEncryptedContentType(content);
Log.warn(
'[$receiptId] Messages should have a groupId $fromUserId. Type: $type',
);
Log.error(
'Messages should have a groupId. Type: $type',
onlyIfSentryEnabled: true,
);
return const DecryptedMessageResult();
Log.error('[$receiptId] Messages should have a groupId $fromUserId.');
return (null, null);
}
if (content.hasGroupCreate()) {
@ -496,12 +414,11 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
content.groupCreate,
receiptId,
);
return const DecryptedMessageResult();
return (null, null);
}
/// Verify that the user is (still) in that group...
if (!await twonlyDB.groupsDao.isContactInGroup(fromUserId, content.groupId)) {
// Check if this is a direct chat...
if (getUUIDforDirectChat(userService.currentUser.userId, fromUserId) ==
content.groupId) {
final contact = await twonlyDB.contactsDao
@ -512,17 +429,18 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
);
if (contact == null || !contact.accepted || contact.deletedByUser) {
await handleNewContactRequest(fromUserId);
Log.warn(
Log.error(
'[$receiptId] User tries to send message to direct chat while the user does not exist!',
);
return DecryptedMessageResult(
responseCipherText: EncryptedContent(
return (
EncryptedContent(
errorMessages: EncryptedContent_ErrorMessages(
type: EncryptedContent_ErrorMessages_Type
.ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD,
relatedReceiptId: receiptId,
),
),
null,
);
}
Log.info(
@ -536,35 +454,27 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
);
} else {
if (content.hasGroupJoin()) {
Log.warn(
Log.error(
'[$receiptId] Got group join message, but group does not exist yet, retry later. As probably the GroupCreate was not yet received.',
);
// In case the group join was received before the GroupCreate the sender should send it later again.
return DecryptedMessageResult(
responsePlaintext: PlaintextContent()
return (
null,
PlaintextContent()
..retryControlError = PlaintextContent_RetryErrorMessage(),
);
}
Log.warn(
'[$receiptId] User $fromUserId tried to access group ${content.groupId}. Sending GROUP_NOT_FOUND_OR_NOT_A_MEMBER error.',
);
return DecryptedMessageResult(
responseCipherText: EncryptedContent(
groupId: content.groupId,
errorMessages: EncryptedContent_ErrorMessages(
type: EncryptedContent_ErrorMessages_Type
.GROUP_NOT_FOUND_OR_NOT_A_MEMBER,
relatedReceiptId: receiptId,
),
),
Log.error(
'[$receiptId] User $fromUserId tried to access group ${content.groupId}.',
);
return (null, null);
}
}
if (content.hasFlameSync()) {
await handleFlameSync(content.groupId, content.flameSync, receiptId);
return const DecryptedMessageResult();
return (null, null);
}
if (content.hasGroupUpdate()) {
@ -574,7 +484,7 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
content.groupUpdate,
receiptId,
);
return const DecryptedMessageResult();
return (null, null);
}
if (content.hasGroupJoin()) {
@ -584,12 +494,13 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
content.groupJoin,
receiptId,
)) {
return DecryptedMessageResult(
responsePlaintext: PlaintextContent()
return (
null,
PlaintextContent()
..retryControlError = PlaintextContent_RetryErrorMessage(),
);
}
return const DecryptedMessageResult();
return (null, null);
}
if (content.hasResendGroupPublicKey()) {
@ -599,7 +510,7 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
content.groupJoin,
receiptId,
);
return const DecryptedMessageResult();
return (null, null);
}
if (content.hasAdditionalDataMessage()) {
@ -609,17 +520,17 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
content.additionalDataMessage,
receiptId,
);
return const DecryptedMessageResult();
return (null, null);
}
if (content.hasTextMessage()) {
final isNewText = await handleTextMessage(
await handleTextMessage(
fromUserId,
content.groupId,
content.textMessage,
receiptId,
);
return DecryptedMessageResult(showPushNotification: isNewText);
return (null, null);
}
if (content.hasReaction()) {
@ -629,17 +540,17 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
content.reaction,
receiptId,
);
return const DecryptedMessageResult();
return (null, null);
}
if (content.hasMedia()) {
final isNewMedia = await handleMedia(
await handleMedia(
fromUserId,
content.groupId,
content.media,
receiptId,
);
return DecryptedMessageResult(showPushNotification: isNewMedia);
return (null, null);
}
if (content.hasTypingIndicator()) {
@ -651,39 +562,5 @@ Future<DecryptedMessageResult> handleEncryptedMessage(
);
}
return const DecryptedMessageResult();
}
String _getEncryptedContentType(EncryptedContent content) {
if (content.hasMessageUpdate()) return 'messageUpdate';
if (content.hasMedia()) return 'media';
if (content.hasMediaUpdate()) return 'mediaUpdate';
if (content.hasContactUpdate()) return 'contactUpdate';
if (content.hasContactRequest()) return 'contactRequest';
if (content.hasFlameSync()) return 'flameSync';
if (content.hasPushKeys()) return 'pushKeys';
if (content.hasReaction()) return 'reaction';
if (content.hasTextMessage()) return 'textMessage';
if (content.hasGroupCreate()) return 'groupCreate';
if (content.hasGroupJoin()) return 'groupJoin';
if (content.hasGroupUpdate()) return 'groupUpdate';
if (content.hasResendGroupPublicKey()) return 'resendGroupPublicKey';
if (content.hasErrorMessages()) return 'errorMessages';
if (content.hasAdditionalDataMessage()) return 'additionalDataMessage';
if (content.hasTypingIndicator()) return 'typingIndicator';
if (content.hasUserDiscoveryRequest()) return 'userDiscoveryRequest';
if (content.hasUserDiscoveryUpdate()) return 'userDiscoveryUpdate';
if (content.hasKeyVerificationProof()) return 'keyVerificationProof';
return 'unknown';
}
class DecryptedMessageResult {
const DecryptedMessageResult({
this.responseCipherText,
this.responsePlaintext,
this.showPushNotification = true,
});
final EncryptedContent? responseCipherText;
final PlaintextContent? responsePlaintext;
final bool showPushNotification;
return (null, null);
}

View file

@ -105,9 +105,8 @@ class BackupService {
))) {
final backupId = await RustBackupIdentity.getBackupId();
if (backupId == null) {
Log.warn('No backup password was set by the user.');
Log.error('No backup password was set by the user.');
backup.identityState = LastBackupUploadState.failed;
await UserService.update((u) => u.isBackupEnabled = false);
} else {
Log.info('Performing a identity backup.');
final encryptedBackup =
@ -163,7 +162,7 @@ class BackupService {
(backupDownloadToken, backupArchive) =
await RustBackupArchive.createBackupArchive();
} catch (e) {
Log.warn('Creating archive backup failed: $e');
Log.error(e);
return;
}
Log.info(
@ -325,26 +324,6 @@ class BackupService {
return _nextBackupStage();
}
static Future<RecoveryError?> startPasswordlessBackupRecovery(
int userId,
String username,
Uint8List keyManagerBytes,
) async {
final state = BackupRecovery(
username: username,
password: '',
userId: userId,
)..state = BackupRecoveryState.archiveBackupStarted;
await deleteLocalUserData();
// Import KeyManager keys into secure storage & in-memory key manager
await RustKeyManager.importSerialized(serializedBytes: keyManagerBytes);
await KeyValueStore.put(KeyValueKeys.backupRecoveryState, state.toJson());
return _nextBackupStage();
}
static Future<(Uint8List?, RecoveryError?)> _downloadBackup(
String backupServerUrl,
) async {
@ -358,7 +337,7 @@ class BackupService {
},
);
} catch (e) {
Log.warn('Error fetching backup: $e');
Log.error('Error fetching backup: $e');
return (null, RecoveryError.noInternet);
}

View file

@ -139,7 +139,6 @@ Future<bool> createNewGroup(String groupName, List<Contact> members) async {
groupCreate: EncryptedContent_GroupCreate(
stateKey: stateEncryptionKey,
groupPublicKey: myGroupKey.getPublicKey().serialize(),
groupName: group.groupName,
),
),
);
@ -771,7 +770,6 @@ Future<bool> addNewGroupMembers(
groupCreate: EncryptedContent_GroupCreate(
stateKey: group.stateEncryptionKey,
groupPublicKey: keyPair.getPublicKey().serialize(),
groupName: group.groupName,
),
),
);

View file

@ -1,9 +1,9 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
import 'package:flutter_sharing_intent/model/sharing_file.dart';
@ -13,8 +13,6 @@ import 'package:twonly/src/constants/routes.keys.dart';
import 'package:twonly/src/database/tables/contacts.table.dart';
import 'package:twonly/src/database/tables/mediafiles.table.dart';
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
import 'package:twonly/src/services/passwordless_recovery.service.dart'
show PasswordlessRecoveryService;
import 'package:twonly/src/services/signal/session.signal.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
@ -32,12 +30,6 @@ Future<bool> handleIntentUrl(BuildContext context, Uri uri) async {
// Check if this is the QR code link which was
// therefore scanned with the system camera
if (kDebugMode &&
uri.toString().startsWith(PasswordlessRecoveryService.linkPrefix)) {
await PasswordlessRecoveryService.handleRecoveryLink(uri.toString());
return true;
}
if (uri.toString().startsWith(QrCodeUtils.linkPrefix)) {
final result = await QrCodeUtils.handleQrCodeLink(uri.toString());

View file

@ -1,4 +1,3 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:collection/collection.dart';
@ -14,7 +13,7 @@ import 'package:twonly/src/services/signal/identity.signal.dart';
import 'package:twonly/src/services/signal/session.signal.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/components/verification_success_dialog.comp.dart';
import 'package:twonly/src/visual/components/snackbar.dart';
class KeyVerificationService {
static Future<List<int>> getNewSecretVerificationToken() async {
@ -78,14 +77,12 @@ class KeyVerificationService {
final contact = await twonlyDB.contactsDao.getContactById(fromUserId);
final context = rootNavigatorKey.currentContext;
if (context != null && context.mounted && contact != null) {
unawaited(
VerificationSuccessDialog.show(
context,
contact,
message: context.lang.secretQrTokenVerifiedSnackbar(
getContactDisplayName(contact),
),
showSnackbar(
context,
context.lang.secretQrTokenVerifiedSnackbar(
getContactDisplayName(contact),
),
level: SnackbarLevel.success,
);
}
return;
@ -94,29 +91,6 @@ class KeyVerificationService {
Log.error('No valid secret token could be found...');
}
static Future<void> verifySharedContact({
required int contactId,
required List<int> sharedPublicIdentityKey,
required int senderId,
}) async {
final publicIdentityKey = await getPublicKeyFromContact(contactId);
if (publicIdentityKey == null) {
Log.info('No public key stored for contact $contactId');
return;
}
if (publicIdentityKey.equals(sharedPublicIdentityKey)) {
Log.info('Verified a user which was shared by a contact');
await twonlyDB.keyVerificationDao.addKeyVerification(
contactId,
VerificationType.contactSharedByVerified,
verifiedBy: senderId,
);
} else {
Log.error('Public identity keys do not match for contact $contactId');
}
}
}
Future<List<int>> _createVerificationBytes(

View file

@ -33,18 +33,17 @@ Future<void> compressImage(
Log.info('Compressed images size in bytes: ${compressedBytes.length}');
if (compressedBytes.length >= 2 * 1000 * 1000) {
if (compressedBytes.length >= 1 * 1000 * 1000) {
// if the media file is over 1MB compress it with 60%
final tmpCompressedBytes = await FlutterImageCompress.compressWithFile(
sourceFile.path,
format: CompressFormat.webp,
quality: 60,
);
if (tmpCompressedBytes == null) {
if (tmpCompressedBytes != null) {
Log.error(
'Could not compress media file with 60%: $sourceFile. Sending original 90% compressed file.',
);
} else {
compressedBytes = tmpCompressedBytes;
}
}
@ -107,11 +106,11 @@ Future<void> compressAndOverlayVideo(MediaFileService media) async {
},
);
} catch (e) {
Log.warn('during video compression: $e');
Log.error('during video compression: $e');
}
if (compressedPath == null) {
Log.warn('Could not compress video using original video.');
Log.error('Could not compress video using original video.');
// as a fall back use the non compressed version
media.ffmpegOutputPath.copySync(media.tempPath.path);
}

View file

@ -252,7 +252,7 @@ class MediaFileService {
Future<void> compressMedia() async {
if (!originalPath.existsSync()) {
Log.warn('Could not compress as original media does not exists.');
Log.error('Could not compress as original media does not exists.');
return;
}
@ -296,8 +296,7 @@ class MediaFileService {
(thumbnailPath.existsSync() && thumbnailPath.lengthSync() > 0) ||
mediaFile.type == MediaType.audio ||
((mediaFile.type == MediaType.image || mediaFile.type == MediaType.gif) &&
storedPath.existsSync() &&
storedPath.lengthSync() > 0);
storedPath.existsSync() && storedPath.lengthSync() > 0);
Future<void> storeMediaFile() async {
Log.info('Storing media file ${mediaFile.mediaId}');
@ -315,21 +314,17 @@ class MediaFileService {
await tempPath.copy(storedPath.path);
if (userService.currentUser.storeMediaFilesInGallery) {
if (mediaFile.type == MediaType.video) {
await saveVideoToGallery(
storedPath.path,
name: mediaFile.mediaId,
);
await saveVideoToGallery(storedPath.path);
} else {
await saveImageToGallery(
storedPath.readAsBytesSync(),
createdAt: mediaFile.createdAt,
name: mediaFile.mediaId,
);
}
}
} else {
Log.warn(
'Could not store image locally as ${tempPath.path} does not exist.',
Log.error(
'Could not store image neither as ${tempPath.path} does not exists.',
);
}
unawaited(createThumbnail());
@ -470,7 +465,7 @@ class MediaFileService {
format: CompressFormat.webp,
quality: 90,
);
if (webpBytes.isNotEmpty) {
await storedPath.writeAsBytes(webpBytes);
} else {
@ -504,7 +499,7 @@ class MediaFileService {
);
await updateFromDB();
} catch (e) {
Log.warn(
Log.error(
'Error auto-cropping transparent borders for mediaId ${mediaFile.mediaId}: $e',
);
await twonlyDB.mediaFilesDao.updateMedia(

View file

@ -112,7 +112,7 @@ Future<bool> createThumbnailsForImage(
);
return true;
} else {
Log.warn('Compressed image thumbnail is empty or missing.');
Log.error('Compressed image thumbnail is empty or missing.');
try {
if (destinationFile.existsSync()) {
destinationFile.deleteSync();

View file

@ -158,21 +158,9 @@ Future<void> runMigrations() async {
}
await UserService.update((u) => u.appVersion = 116);
}
if (userService.currentUser.appVersion < 117) {
final contacts = await twonlyDB.contactsDao.getAllContacts();
final contactCount = contacts.where((c) => c.accepted).length;
await UserService.update((u) {
u.appVersion = 117;
if (contactCount > 5) {
u.askForFriendPromotions = false;
}
});
}
if (kDebugMode) {
assert(
AppState.latestAppVersionId == 117,
AppState.latestAppVersionId == 116,
'Forgot to update the target version in runMigrations() after incrementing AppState.latestAppVersionId.',
);
assert(

View file

@ -1,11 +1,11 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
import 'package:cryptography_plus/cryptography_plus.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/constants/routes.keys.dart';
import 'package:twonly/src/constants/secure_storage.keys.dart';
import 'package:twonly/src/localization/generated/app_localizations.dart';
@ -13,7 +13,6 @@ import 'package:twonly/src/localization/generated/app_localizations_de.dart';
import 'package:twonly/src/localization/generated/app_localizations_en.dart';
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.dart';
import 'package:twonly/src/providers/routing.provider.dart';
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
@ -71,6 +70,69 @@ Future<void> showPushNotificationFromServerMessages(
}
}
Future<void> handlePushData(String pushDataB64) async {
try {
final pushData = EncryptedPushNotification.fromBuffer(
base64.decode(pushDataB64),
);
PushNotification? pushNotification;
PushUser? foundPushUser;
if (pushData.keyId == 0) {
final key = 'InsecureOnlyUsedForAddingContact'.codeUnits;
pushNotification = await tryDecryptMessage(key, pushData);
} else {
final pushUsers = await getPushKeys(SecureStorageKeys.receivingPushKeys);
for (final pushUser in pushUsers) {
for (final key in pushUser.pushKeys) {
if (key.id == pushData.keyId) {
pushNotification = await tryDecryptMessage(key.key, pushData);
if (pushNotification != null) {
foundPushUser = pushUser;
break;
}
}
}
// found correct key and user
if (foundPushUser != null) break;
}
}
if (pushNotification != null) {
if (pushNotification.kind == PushKind.TEST_NOTIFICATION) {
await customLocalPushNotification(
'Test notification',
'This is a test notification.',
);
} else if (foundPushUser != null) {
if (pushNotification.hasMessageId()) {
if (isUUIDNewer(
foundPushUser.lastMessageId,
pushNotification.messageId,
)) {
Log.info(
'Got a push notification for a message which was already opened.',
);
return;
}
}
await showLocalPushNotification(foundPushUser, pushNotification);
} else {
await showLocalPushNotificationWithoutUserId(pushNotification);
}
}
} catch (e) {
Log.error(e);
final lang = getLocalizations();
await customLocalPushNotification(
lang.notificationTitleUnknown,
lang.notificationBodyUnknown,
);
}
}
Future<PushNotification?> tryDecryptMessage(
List<int> key,
EncryptedPushNotification push,
@ -110,32 +172,6 @@ Future<void> showLocalPushNotification(
return;
}
var targetGroupId = groupId;
if (targetGroupId == null) {
try {
if (userService.isUserCreated) {
targetGroupId = getUUIDforDirectChat(
userService.currentUser.userId,
pushUser.userId.toInt(),
);
}
} catch (_) {}
}
if (targetGroupId != null) {
try {
final currentUri = routerProvider.routerDelegate.currentConfiguration.uri;
if (currentUri.path.contains(targetGroupId)) {
Log.info(
'Suppressing local push notification because chat with group $targetGroupId is currently open.',
);
return;
}
} catch (e) {
Log.error('Error checking current route: $e');
}
}
title = pushUser.displayName;
body = getPushNotificationText(pushNotification);
if (body == '') {
@ -159,7 +195,6 @@ Future<void> showLocalPushNotification(
ticker: 'You got a new message.',
largeIcon: styleInformation,
icon: 'ic_launcher_foreground',
groupKey: 'com.twonly.messages',
);
const darwinNotificationDetails = DarwinNotificationDetails();
@ -191,29 +226,43 @@ Future<void> showLocalPushNotification(
notificationDetails,
payload: payload,
);
}
if (Platform.isAndroid) {
final summaryAndroidDetails = AndroidNotificationDetails(
'0',
lang.notificationCategoryMessageTitle,
channelDescription: lang.notificationCategoryMessageDesc,
importance: Importance.max,
priority: Priority.max,
groupKey: 'com.twonly.messages',
setAsGroupSummary: true,
icon: 'ic_launcher_foreground',
);
final summaryNotificationDetails = NotificationDetails(
android: summaryAndroidDetails,
);
await flutterLocalNotificationsPlugin.show(
0,
lang.notificationCategoryMessageTitle,
'',
summaryNotificationDetails,
payload: Routes.chats,
);
Future<void> showLocalPushNotificationWithoutUserId(
PushNotification pushNotification,
) async {
final lang = getLocalizations();
var title = lang.notificationTitleUnknown;
var body = lang.notificationBodyUnknown;
if (pushNotification.kind == PushKind.CONTACT_REQUEST) {
title = lang.you;
body = lang.notificationContactRequestUnknownUser;
}
final androidNotificationDetails = AndroidNotificationDetails(
'0',
lang.notificationCategoryMessageTitle,
channelDescription: lang.notificationCategoryMessageDesc,
importance: Importance.max,
priority: Priority.max,
ticker: 'You got a new message.',
);
const darwinNotificationDetails = DarwinNotificationDetails();
final notificationDetails = NotificationDetails(
android: androidNotificationDetails,
iOS: darwinNotificationDetails,
);
await flutterLocalNotificationsPlugin.show(
2,
title,
body,
notificationDetails,
payload: pushNotification.kind.name,
);
}
Future<String?> getAvatarIcon(int contactId) async {

View file

@ -1,29 +0,0 @@
import 'dart:async';
import 'dart:io' show Platform;
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/services/background/callback_dispatcher.background.dart';
import 'package:twonly/src/services/notifications/fcm.notifications.dart';
import 'package:twonly/src/services/notifications/setup.notifications.dart';
import 'package:twonly/src/utils/log.dart';
@pragma('vm:entry-point')
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
SentryWidgetsFlutterBinding.ensureInitialized();
await AppEnvironment.init();
final isInitialized = await initBackgroundExecution();
await setupPushNotification();
Log.info('Handling a background message: ${message.messageId}');
await FcmNotificationService.handleRemoteMessage(message);
if (Platform.isAndroid) {
if (isInitialized) {
await handlePeriodicTask(lastExecutionInSecondsLimit: 10);
}
} else {
// make sure every thing run...
await Future.delayed(const Duration(milliseconds: 2000));
}
}

View file

@ -1,3 +1,5 @@
// ignore_for_file: unreachable_from_main
import 'dart:async';
import 'dart:io' show Platform;
@ -5,11 +7,13 @@ import 'package:firebase_app_installations/firebase_app_installations.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/constants/secure_storage.keys.dart';
import 'package:twonly/src/services/background/callback_dispatcher.background.dart';
import 'package:twonly/src/services/notifications/background.notifications.dart';
import 'package:twonly/src/services/notifications/fcm.background.dart';
import 'package:twonly/src/services/notifications/setup.notifications.dart';
import 'package:twonly/src/services/user.service.dart';
import 'package:twonly/src/utils/log.dart';
@ -17,253 +21,240 @@ import '../../../firebase_options.dart';
// see more here: https://firebase.google.com/docs/cloud-messaging/flutter/receive?hl=de
class FcmNotificationService {
static Future<void> initStartup() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
FirebaseMessaging.onMessage.listen(handleRemoteMessage);
}
static Future<void> initAfterUserLoaded() async {
unawaited(_checkForTokenUpdates());
unawaited(_checkFcmHealthAndResetIfNeeded());
}
static Future<void> initFCMAfterAuthenticated({bool force = false}) async {
final fcmToken = userService.currentUser.fcmToken;
if (userService.currentUser.updateFCMToken || force) {
if (fcmToken == null) {
Log.error('FCM token could not be updated as it is empty');
await _checkForTokenUpdates();
return;
Future<void> checkForTokenUpdates() async {
try {
if (!userService.isUserCreated) return;
if (Platform.isIOS) {
var apnsToken = await FirebaseMessaging.instance.getAPNSToken();
for (var i = 0; i < 20; i++) {
if (apnsToken != null) break;
await Future<void>.delayed(const Duration(seconds: 1));
apnsToken = await FirebaseMessaging.instance.getAPNSToken();
}
final res = await apiService.updateFCMToken(
fcmToken,
);
if (res.isSuccess) {
Log.info('Uploaded new FCM token!');
await UserService.update((u) {
u.updateFCMToken = false;
});
} else {
Log.error('Could not update FCM token!');
if (apnsToken == null) {
Log.error('Could not get APNS token even after 20s...');
return;
}
}
}
static Future<void> resetFCMTokens() async {
await FirebaseInstallations.instance.delete();
Log.info('Firebase Installation successfully deleted.');
await FirebaseMessaging.instance.deleteToken();
Log.info('Old FCM deleted.');
await UserService.update((u) => u.fcmToken = null);
await _checkForTokenUpdates();
await initFCMAfterAuthenticated(force: true);
}
final fcmToken = await FirebaseMessaging.instance.getToken();
if (fcmToken == null) {
Log.error('Could not get fcm token');
return;
}
static Future<void> _checkForTokenUpdates() async {
try {
if (!userService.isUserCreated) {
Log.info(
'Checking for FCM token updates skipped: user is not yet created.',
);
return;
}
if (Platform.isIOS) {
var apnsToken = await FirebaseMessaging.instance.getAPNSToken();
for (var i = 0; i < 20; i++) {
if (apnsToken != null) break;
await Future<void>.delayed(const Duration(seconds: 1));
apnsToken = await FirebaseMessaging.instance.getAPNSToken();
}
if (apnsToken == null) {
Log.error('Could not get APNS token even after 20s...');
return;
}
}
Log.info('Loaded FCM token.');
final fcmToken = await FirebaseMessaging.instance.getToken();
if (fcmToken == null) {
Log.error('Could not get fcm token');
return;
}
if (userService.currentUser.fcmToken == null ||
fcmToken != userService.currentUser.fcmToken) {
Log.info('Got new FCM token.');
await UserService.update((u) {
u
..updateFCMToken = true
..fcmToken = fcmToken;
});
}
Log.info('Loaded FCM token.');
if (userService.currentUser.fcmToken == null ||
fcmToken != userService.currentUser.fcmToken) {
Log.info('Got new FCM token.');
await UserService.update((u) {
u
..updateFCMToken = true
..fcmToken = fcmToken;
});
if (apiService.isAuthenticated) {
final res = await apiService.updateFCMToken(fcmToken);
if (res.isSuccess) {
Log.info('Uploaded new FCM token!');
await UserService.update((u) {
u.updateFCMToken = false;
});
} else {
Log.error('Could not update FCM token!');
}
}
}
FirebaseMessaging.instance.onTokenRefresh
// ignore: avoid_types_on_closure_parameters
.listen((String fcmToken) async {
await UserService.update((u) {
u
..updateFCMToken = true
..fcmToken = fcmToken;
});
if (apiService.isAuthenticated) {
final res = await apiService.updateFCMToken(fcmToken);
if (res.isSuccess) {
Log.info('Uploaded new FCM token!');
await UserService.update((u) {
u.updateFCMToken = false;
});
} else {
Log.error('Could not update FCM token!');
}
}
})
.onError((err) {
Log.error('could not listen on token refresh');
FirebaseMessaging.instance.onTokenRefresh
.listen((fcmToken) async {
await UserService.update((u) {
u
..updateFCMToken = true
..fcmToken = fcmToken;
});
} catch (e) {
Log.error('could not load fcm token: $e');
}
})
.onError((err) {
Log.error('could not listen on token refresh');
});
} catch (e) {
Log.error('could not load fcm token: $e');
}
}
static Future<void> handleRemoteMessage(RemoteMessage message) async {
await _updateLastFcmMessageTimestamp();
if (!Platform.isAndroid) {
Log.error('Got message in Dart while on iOS');
}
if (message.notification != null && AppState.isAppInBackground) {
Log.error(
'Got notification but app is in background, so the SDK already have shown the message.',
);
Future<void> initFCMAfterAuthenticated({bool force = false}) async {
final fcmToken = userService.currentUser.fcmToken;
if (userService.currentUser.updateFCMToken || force) {
if (fcmToken == null) {
Log.error('FCM token could not be updated as it is empty');
await checkForTokenUpdates();
return;
}
if (message.notification != null || message.data['title'] != null) {
final title =
message.notification?.title ?? message.data['title'] as String? ?? '';
final body =
message.notification?.body ?? message.data['body'] as String? ?? '';
await customLocalPushNotification(title, body);
}
}
static Future<void> _updateLastFcmMessageTimestamp() async {
const storage = FlutterSecureStorage();
final nowMs = DateTime.now().millisecondsSinceEpoch.toString();
try {
await storage.write(
key: SecureStorageKeys.lastFcmMessageTimestamp,
value: nowMs,
iOptions: const IOSOptions(
groupId: 'CN332ZUGRP.eu.twonly.shared',
accessibility: KeychainAccessibility.first_unlock,
),
);
Log.info('Updated last FCM message timestamp to $nowMs');
} catch (e) {
Log.error('Could not write last FCM message timestamp: $e');
}
}
static Future<void> updateLastServerMessageTimestamp() async {
const storage = FlutterSecureStorage();
final nowMs = DateTime.now().millisecondsSinceEpoch.toString();
try {
await storage.write(
key: SecureStorageKeys.lastServerMessageTimestamp,
value: nowMs,
iOptions: const IOSOptions(
groupId: 'CN332ZUGRP.eu.twonly.shared',
accessibility: KeychainAccessibility.first_unlock,
),
);
Log.info('Updated last server message timestamp to $nowMs');
} catch (e) {
Log.error('Could not write last server message timestamp: $e');
}
}
static Future<void> _checkFcmHealthAndResetIfNeeded() async {
if (!userService.isUserCreated) {
Log.info('FCM health check skipped: user is not yet created.');
return;
}
const storage = FlutterSecureStorage();
try {
final lastFcmStr = await storage.read(
key: SecureStorageKeys.lastFcmMessageTimestamp,
iOptions: const IOSOptions(
groupId: 'CN332ZUGRP.eu.twonly.shared',
accessibility: KeychainAccessibility.first_unlock,
),
);
final lastServerStr = await storage.read(
key: SecureStorageKeys.lastServerMessageTimestamp,
iOptions: const IOSOptions(
groupId: 'CN332ZUGRP.eu.twonly.shared',
accessibility: KeychainAccessibility.first_unlock,
),
);
final now = DateTime.now();
final threeDaysAgo = now.subtract(const Duration(days: 3));
DateTime? lastFcmTime;
if (lastFcmStr != null) {
final ms = int.tryParse(lastFcmStr);
if (ms != null) {
lastFcmTime = DateTime.fromMillisecondsSinceEpoch(ms);
}
}
if (lastFcmTime != null) {
Log.info(
'Last message received via FCM messaging system: $lastFcmTime',
);
} else {
Log.info('No record of a message received via FCM messaging system.');
}
DateTime? lastServerTime;
if (lastServerStr != null) {
final ms = int.tryParse(lastServerStr);
if (ms != null) {
lastServerTime = DateTime.fromMillisecondsSinceEpoch(ms);
}
}
final fcmInactive =
lastFcmTime == null || lastFcmTime.isBefore(threeDaysAgo);
final serverActive =
lastServerTime != null && lastServerTime.isAfter(threeDaysAgo);
if (fcmInactive && serverActive) {
Log.warn(
'FCM has been inactive for >3 days, but server messages have been active. Resetting FCM tokens...',
);
await resetFCMTokens();
} else {
Log.info('FCM check passed. No reset needed.');
}
} catch (e) {
Log.error('Error during FCM health check: $e');
final res = await apiService.updateFCMToken(
fcmToken,
);
if (res.isSuccess) {
Log.info('Uploaded new FCM token!');
await UserService.update((u) {
u.updateFCMToken = false;
});
} else {
Log.error('Could not update FCM token!');
}
}
}
Future<void> resetFCMTokens() async {
await FirebaseInstallations.instance.delete();
Log.info('Firebase Installation successfully deleted.');
await FirebaseMessaging.instance.deleteToken();
Log.info('Old FCM deleted.');
await UserService.update((u) => u.fcmToken = null);
await checkForTokenUpdates();
await initFCMAfterAuthenticated(force: true);
}
Future<void> initFCMService() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
unawaited(checkForTokenUpdates());
unawaited(checkFcmHealthAndResetIfNeeded());
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
FirebaseMessaging.onMessage.listen(handleRemoteMessage);
}
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
SentryWidgetsFlutterBinding.ensureInitialized();
await AppEnvironment.init();
final isInitialized = await initBackgroundExecution();
await setupPushNotification();
Log.info('Handling a background message: ${message.messageId}');
await handleRemoteMessage(message);
if (Platform.isAndroid) {
if (isInitialized) {
await handlePeriodicTask(lastExecutionInSecondsLimit: 10);
}
} else {
// make sure every thing run...
await Future.delayed(const Duration(milliseconds: 2000));
}
}
Future<void> handleRemoteMessage(RemoteMessage message) async {
await updateLastFcmMessageTimestamp();
if (!Platform.isAndroid) {
Log.error('Got message in Dart while on iOS');
}
if (message.notification != null && AppState.isAppInBackground) {
Log.error(
'Got notification but app is in background, so the SDK already have shown the message.',
);
return;
}
if (message.notification != null || message.data['title'] != null) {
final title =
message.notification?.title ?? message.data['title'] as String? ?? '';
final body =
message.notification?.body ?? message.data['body'] as String? ?? '';
await customLocalPushNotification(title, body);
}
// On Android the push notification is now shown in the server_message.dart. This ensures
// that the messages was successfully decrypted before showing the push notification
// else if (message.data['push_data'] != null) {
// await handlePushData(message.data['push_data'] as String);
// }
}
Future<void> updateLastFcmMessageTimestamp() async {
const storage = FlutterSecureStorage();
final nowMs = DateTime.now().millisecondsSinceEpoch.toString();
try {
await storage.write(
key: SecureStorageKeys.lastFcmMessageTimestamp,
value: nowMs,
iOptions: const IOSOptions(
groupId: 'CN332ZUGRP.eu.twonly.shared',
accessibility: KeychainAccessibility.first_unlock,
),
);
Log.info('Updated last FCM message timestamp to $nowMs');
} catch (e) {
Log.error('Could not write last FCM message timestamp: $e');
}
}
Future<void> updateLastServerMessageTimestamp() async {
const storage = FlutterSecureStorage();
final nowMs = DateTime.now().millisecondsSinceEpoch.toString();
try {
await storage.write(
key: SecureStorageKeys.lastServerMessageTimestamp,
value: nowMs,
iOptions: const IOSOptions(
groupId: 'CN332ZUGRP.eu.twonly.shared',
accessibility: KeychainAccessibility.first_unlock,
),
);
Log.info('Updated last server message timestamp to $nowMs');
} catch (e) {
Log.error('Could not write last server message timestamp: $e');
}
}
Future<void> checkFcmHealthAndResetIfNeeded() async {
if (!userService.isUserCreated) return;
const storage = FlutterSecureStorage();
try {
final lastFcmStr = await storage.read(
key: SecureStorageKeys.lastFcmMessageTimestamp,
iOptions: const IOSOptions(
groupId: 'CN332ZUGRP.eu.twonly.shared',
accessibility: KeychainAccessibility.first_unlock,
),
);
final lastServerStr = await storage.read(
key: SecureStorageKeys.lastServerMessageTimestamp,
iOptions: const IOSOptions(
groupId: 'CN332ZUGRP.eu.twonly.shared',
accessibility: KeychainAccessibility.first_unlock,
),
);
final now = DateTime.now();
final threeDaysAgo = now.subtract(const Duration(days: 3));
DateTime? lastFcmTime;
if (lastFcmStr != null) {
final ms = int.tryParse(lastFcmStr);
if (ms != null) {
lastFcmTime = DateTime.fromMillisecondsSinceEpoch(ms);
}
}
if (lastFcmTime != null) {
Log.info('Last message received via FCM messaging system: $lastFcmTime');
} else {
Log.info('No record of a message received via FCM messaging system.');
}
DateTime? lastServerTime;
if (lastServerStr != null) {
final ms = int.tryParse(lastServerStr);
if (ms != null) {
lastServerTime = DateTime.fromMillisecondsSinceEpoch(ms);
}
}
// Check conditions:
// 1. No messages received via FCM in the last 3 days (either null or older than 3 days)
final fcmInactive = lastFcmTime == null || lastFcmTime.isBefore(threeDaysAgo);
// 2. Server message received within the last 3 days
final serverActive = lastServerTime != null && lastServerTime.isAfter(threeDaysAgo);
if (fcmInactive && serverActive) {
Log.warn('FCM has been inactive for >3 days, but server messages have been active. Resetting FCM tokens...');
await resetFCMTokens();
} else {
Log.info('FCM check passed. No reset needed.');
}
} catch (e) {
Log.error('Error during FCM health check: $e');
}
}

View file

@ -1,640 +0,0 @@
import 'dart:async';
import 'dart:convert' show base64Url, utf8;
import 'package:clock/clock.dart';
import 'package:collection/collection.dart';
import 'package:crypto/crypto.dart' hide Hmac;
import 'package:cryptography_plus/cryptography_plus.dart'
show Hmac, Mac, SecretBox, SecretKey, Xchacha20;
import 'package:drift/drift.dart';
import 'package:fixnum/fixnum.dart';
import 'package:go_router/go_router.dart';
import 'package:twonly/core/bridge/wrapper.dart';
import 'package:twonly/core/bridge/wrapper/key_manager.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/constants/keyvalue.keys.dart';
import 'package:twonly/src/constants/routes.keys.dart';
import 'package:twonly/src/database/daos/contacts.dao.dart'
show getContactDisplayName;
import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/json/onboarding_state.model.dart';
import 'package:twonly/src/model/json/userdata.model.dart'
show PasswordLessRecovery;
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'
as pb;
import 'package:twonly/src/model/protobuf/client/generated/passwordless_recovery.pb.dart';
import 'package:twonly/src/providers/routing.provider.dart';
import 'package:twonly/src/services/api/messages.api.dart';
import 'package:twonly/src/services/user.service.dart';
import 'package:twonly/src/utils/avatars.dart' show getAvatarSvg;
import 'package:twonly/src/utils/keyvalue.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/views/settings/backup/passwordless_recovery/help_a_friend.passwordless_recovery.view.dart';
enum SecondFactorType { email, pin, none }
class PasswordlessRecoveryService {
static final StreamController<String> onEmailTokenReceived =
StreamController<String>.broadcast();
static String linkPrefix = 'https://me.twonly.eu/r/#';
static final Set<String> _handledNotificationIds = {};
static Future<void> handleRecoveryLink(String link) async {
final hashIndex = link.indexOf('#');
if (hashIndex == -1) return;
final fragment = link.substring(hashIndex + 1);
final parts = fragment.split('/');
if (parts.length < 2) {
if (fragment.isNotEmpty) {
onEmailTokenReceived.add(fragment);
final context = rootNavigatorKey.currentContext;
if (context != null && context.mounted) {
unawaited(context.push(Routes.recoverPasswordless, extra: fragment));
}
}
return;
}
final notificationId = parts[0];
final base64Key = parts[1];
if (_handledNotificationIds.contains(notificationId)) {
Log.info(
'Notification ID $notificationId was already handled, skipping.',
);
return;
}
_handledNotificationIds.add(notificationId);
final encryptionKey = base64Url.decode(base64Url.normalize(base64Key));
final context = rootNavigatorKey.currentContext;
if (context != null && context.mounted) {
unawaited(
context.navPush(
HelpAFriendPasswordlessRecoveryView(
notificationId: notificationId,
encryptionKey: encryptionKey,
),
),
);
}
}
static Future<bool> submitRecoveryShare(
String notificationId,
List<int> encryptionKey,
Contact contact,
) async {
try {
final share = contact.recoveryContactsSecretShare;
if (share == null) {
Log.warn('Contact does not have a recovery share stored.');
return false;
}
final trustedFriend = TrustedFriendShare_User(
userId: Int64(userService.currentUser.userId),
displayName: userService.currentUser.displayName,
avatar: userService.currentUser.avatarSvg != null
? utf8.encode(userService.currentUser.avatarSvg!)
: null,
);
final shareUser = TrustedFriendShare_User(
userId: Int64(contact.userId),
displayName: getContactDisplayName(contact),
avatar: contact.avatarSvgCompressed != null
? utf8.encode(getAvatarSvg(contact.avatarSvgCompressed!))
: null,
);
final trustedFriendShare = TrustedFriendShare(
trustedFriend: trustedFriend,
shareUser: shareUser,
threshold: contact.recoveryContactsThreshold,
sharedSecretData: share,
);
final xchacha20 = Xchacha20.poly1305Aead();
final secretBox = await xchacha20.encrypt(
trustedFriendShare.writeToBuffer(),
secretKey: SecretKey(encryptionKey),
nonce: xchacha20.newNonce(),
);
final envelope = EncryptedEnvelope(
encryptedData: secretBox.cipherText,
iv: secretBox.nonce,
mac: secretBox.mac.bytes,
);
final res = await apiService.submitRecoveryShare(
notificationId: notificationId,
encryptedMessage: envelope.writeToBuffer(),
);
return res.isSuccess;
} catch (e) {
Log.error('Failed to submit recovery share', error: e);
return false;
}
}
static Future<bool> enablePasswordlessRecovery({
required List<int> trustedFriendIds,
required SecondFactorType secondFactorType,
required String secondFactorValue,
required int threshold,
}) async {
// 1. Get all currently trusted friends contacts and send them delete messages
final oldTrustedFriends = await (twonlyDB.select(
twonlyDB.contacts,
)..where((t) => t.recoveryIsTrustedFriend.equals(true))).get();
for (final contact in oldTrustedFriends) {
try {
await sendCipherText(
contact.userId,
pb.EncryptedContent(
passwordlessRecovery: pb.EncryptedContent_PasswordLessRecovery(
delete: true,
),
),
);
} catch (e) {
Log.error(
'Failed to send delete PasswordLessRecovery message to contact ${contact.userId}: $e',
);
}
}
// 1. Reset current recovery data to ensure a clean state
await twonlyDB.contactsDao.resetRecoveryDataForAllContacts();
await UserService.update((u) => u.passwordLessRecovery = null);
final config = PasswordLessRecovery(threshold);
final xchacha20 = Xchacha20.poly1305Aead();
// 2. If enabled, handle the second factor and create serverKey
Uint8List? serverKey;
Uint8List? encryptedServerKey;
SecretKey? secondFactorEncryptedServerKeyKey;
String? emailHint;
switch (secondFactorType) {
case SecondFactorType.email:
// The serverKey is encrypted with the email; this protects the server from seeing the user's email address, while
// also ensuring that the server can only send the real secret to the user's configured email, as a different
// email will result in a different secret key.
// This is only stored so the user can see there email, and verify that he has set the valid mail...
config.email = secondFactorValue;
emailHint = createEmailHint(secondFactorValue);
// E-Mail Protection:
// - Server can only learn the email during recovery. Ensured as the server gets the NONCE and MAC to decrpyt during recovery.
// - Trusted-friends: Server key is send to the mail, they whould need access to the user's mail account.
secondFactorEncryptedServerKeyKey = SecretKey(
Uint8List.fromList(sha256.convert(utf8.encode(config.email!)).bytes),
);
case SecondFactorType.pin:
// The pin seed - never shared with the server - ensures that the server is unable to brute-force real user's pin.
config.pinSeed = getRandomUint8List(32);
// As the pin is heavily protected against brute-forcing e.g. will be deleted by the server after X tries, the
// unlock token is required to prevent a malicous user (except the trusted friends) to triger this deletion.
config.pinUnlockToken = getRandomUint8List(32);
// Brute-force protection for the user's pin:
// - Server: Does not know the seed.
// - Trusted friends:
// Can only check the result X times before the server deletes the key. As they do not have
// the mac and the cypher text they are unable to brute-force the pin localy. And the server only allows 10
// tries.
final pinProtectionKey = await Hmac.sha256().calculateMac(
Uint8List.fromList(utf8.encode(secondFactorValue)),
secretKey: SecretKey(config.pinSeed!),
);
// To restore the user has to provide the server with this encryption key. The server then can verify the
// correct pin was entered, when decrypting the server key as he also receives the mac and nonce from the user
// during recovery. Only when the mac is correct the server provides the user with the serverKey.
secondFactorEncryptedServerKeyKey = SecretKey(pinProtectionKey.bytes);
case SecondFactorType.none:
}
if (secondFactorEncryptedServerKeyKey != null) {
// The server key is used to encrypt the RecoveryData of the users. This ensures that when the trusted friends
// colaberate, they additional need the serverKey to decrypt the user's key.
serverKey = getRandomUint8List(32);
final secretBox = await xchacha20.encrypt(
serverKey,
secretKey: secondFactorEncryptedServerKeyKey,
nonce: xchacha20.newNonce(),
);
// The server only gets the encrypted server key and the mac. Because the server does not know the nonce (192-bit
// because of XChaCha), he is unable to decrypt the server key without the help of the trusted friends. This
// ensures that the server never learns the users orginal pin, as he is missing the pin_seed and also unable to
// brute-force the email of the user as he does not have the nonce.
encryptedServerKey = Uint8List.fromList([
...secretBox.cipherText,
...secretBox.mac.bytes,
]);
config
..encryptedServerKeyNonce = secretBox.nonce
..encryptedServerKey = encryptedServerKey;
}
// 3. Using shamir's secret to generate the shares for the users.
// 3.1. Create the SharedSecretData
var recoveryData = RecoveryData(
userId: Int64(userService.currentUser.userId),
keyManager: await RustKeyManager.serialize(),
).writeToBuffer();
if (serverKey != null) {
// Second factor was enabled, so encrypt the recoveryData using the serverKey.
final secretBox = await xchacha20.encrypt(
recoveryData,
secretKey: SecretKey(serverKey),
nonce: xchacha20.newNonce(),
);
recoveryData = EncryptedEnvelope(
encryptedData: secretBox.cipherText,
iv: secretBox.nonce,
mac: secretBox.mac.bytes,
).writeToBuffer();
}
final sharedSecretData = SharedSecretData(
recoveryData: recoveryData,
pinSeed: config.pinSeed,
pinUnlockToken: config.pinUnlockToken,
emailHint: emailHint,
encryptedServerKeyNonce: config.encryptedServerKeyNonce,
).writeToBuffer();
// 3.2. Use the amount of trusted friends to generate the shares
final List<Uint8List> shares;
try {
shares = await RustUtils.generateShares(
secret: sharedSecretData,
total: trustedFriendIds.length,
threshold: threshold,
);
if (shares.length != trustedFriendIds.length) {
Log.error('shares.length != trustedFriendIds.length');
return false;
}
} catch (e) {
Log.error('Failed to generate secret shares: $e');
return false;
}
await UserService.update((u) => u.passwordLessRecovery = config);
// 3.4. Store the shares in the contact's rows
for (final contactId in trustedFriendIds) {
await twonlyDB.contactsDao.updateContact(
contactId,
ContactsCompanion(
recoveryIsTrustedFriend: const Value(true),
recoveryLastHeartbeat: const Value(null),
recoverySecretShare: Value(shares.removeLast()),
),
);
}
unawaited(performHeartbeat());
// The passwordless is configured sucessfully.
return true;
}
static Future<bool> testPin(String pin) async {
final config = userService.currentUser.passwordLessRecovery;
if (config?.pinSeed == null || config?.encryptedServerKey == null) {
return false;
}
try {
final pinProtectionKey = await Hmac.sha256().calculateMac(
Uint8List.fromList(utf8.encode(pin)),
secretKey: SecretKey(config!.pinSeed!),
);
final secondFactorEncryptedServerKeyKey = SecretKey(
pinProtectionKey.bytes,
);
final xchacha20 = Xchacha20.poly1305Aead();
final combined = config.encryptedServerKey!;
final cipherText = combined.sublist(0, combined.length - 16);
final macBytes = combined.sublist(combined.length - 16);
final secretBox = SecretBox(
cipherText,
nonce: config.encryptedServerKeyNonce!,
mac: Mac(macBytes),
);
await xchacha20.decrypt(
secretBox,
secretKey: secondFactorEncryptedServerKeyKey,
);
return true;
} catch (e) {
Log.error('Failed to test pin: $e');
return false;
}
}
static Future<void> performHeartbeat() async {
final config = userService.currentUser.passwordLessRecovery;
if (config != null) {
final lastHeartbeat = config.lastServerHeartbeat;
final isOlderThanAMonth =
lastHeartbeat != null &&
clock.now().difference(lastHeartbeat).inDays > 20;
if ((lastHeartbeat == null || isOlderThanAMonth) &&
config.encryptedServerKey != null) {
final res = await apiService.registerPasswordLessRecovery(
config.encryptedServerKey!,
config.pinUnlockToken,
);
if (res.isSuccess) {
await UserService.update((u) {
u.passwordLessRecovery?.lastServerHeartbeat = clock.now();
});
}
}
final lastContactHeartbeat = config.lastContactHeartbeat;
final isContactHeartbeatOlderThan24h =
lastContactHeartbeat == null ||
clock.now().difference(lastContactHeartbeat).inHours >= 24;
if (isContactHeartbeatOlderThan24h) {
// Get all contacts where recoveryLastHeartbeat is NULL. Then for each contacts send.
// recoveryLastHeartbeat is ONLY updated in case the contact has responded.
final pendingShares =
await (twonlyDB.select(twonlyDB.contacts)..where(
(t) =>
t.recoveryIsTrustedFriend.equals(true) &
t.recoveryLastHeartbeat.isNull() &
t.recoverySecretShare.isNotNull(),
))
.get();
for (final contact in pendingShares) {
try {
await sendCipherText(
contact.userId,
pb.EncryptedContent(
passwordlessRecovery: pb.EncryptedContent_PasswordLessRecovery(
recoverySecretShare: contact.recoverySecretShare,
delete: false,
threshold: Int64(config.threshold),
),
),
);
} catch (e) {
Log.error(
'Failed to send PasswordLessRecovery share to contact ${contact.userId}: $e',
);
}
}
await UserService.update((u) {
u.passwordLessRecovery?.lastContactHeartbeat = clock.now();
});
}
}
// Send heartbeat to the friends I am a trusted friend.
final oneWeekAgo = clock.now().subtract(const Duration(days: 7));
final trustedFriendsToNotify =
await (twonlyDB.select(twonlyDB.contacts)..where(
(t) =>
t.recoveryContactsSecretShare.isNotNull() &
(t.recoveryContactsLastHeartbeat.isNull() |
t.recoveryContactsLastHeartbeat.isSmallerThanValue(
oneWeekAgo,
)),
))
.get();
for (final contact in trustedFriendsToNotify) {
try {
final share = contact.recoveryContactsSecretShare!;
final hash = sha256.convert(share).bytes;
await sendCipherText(
contact.userId,
pb.EncryptedContent(
passwordlessRecoveryHeartbeat:
pb.EncryptedContent_PasswordLessRecoveryHeartbeat(
hash: hash,
),
),
);
await twonlyDB.contactsDao.updateContact(
contact.userId,
ContactsCompanion(
recoveryContactsLastHeartbeat: Value(clock.now()),
),
);
} catch (e) {
Log.error(
'Failed to send PasswordLessRecoveryHeartbeat to contact ${contact.userId}: $e',
);
}
}
}
static Future<void> handlePasswordlessRecovery(
int fromUserId,
pb.EncryptedContent_PasswordLessRecovery msg,
String receiptId,
) async {
if (msg.delete) {
Log.info(
'[$receiptId] Received request to delete passwordless recovery share from contact $fromUserId',
);
await twonlyDB.contactsDao.updateContact(
fromUserId,
const ContactsCompanion(
recoveryContactsSecretShare: Value(null),
recoveryContactsLastHeartbeat: Value(null),
),
);
} else if (msg.hasRecoverySecretShare() && msg.hasThreshold()) {
Log.info(
'[$receiptId] Received new passwordless recovery share from contact $fromUserId',
);
await twonlyDB.contactsDao.updateContact(
fromUserId,
ContactsCompanion(
recoveryContactsSecretShare: Value(
Uint8List.fromList(msg.recoverySecretShare),
),
recoveryContactsThreshold: Value(msg.threshold.toInt()),
recoveryContactsLastHeartbeat: const Value(
null, // this will trigger that a heartbeat will be send...
),
),
);
}
unawaited(performHeartbeat());
}
static Future<void> handlePasswordlessRecoveryHeartbeat(
int fromUserId,
pb.EncryptedContent_PasswordLessRecoveryHeartbeat msg,
String receiptId,
) async {
Log.info(
'[$receiptId] Received passwordless recovery heartbeat from contact $fromUserId',
);
final contact = await twonlyDB.contactsDao.getContactById(fromUserId);
final storedShare = contact?.recoverySecretShare;
if (storedShare == null) {
unawaited(
sendCipherText(
fromUserId,
pb.EncryptedContent(
passwordlessRecovery: pb.EncryptedContent_PasswordLessRecovery(
delete: true,
),
),
),
);
Log.warn(
'[$receiptId] Received passwordless recovery heartbeat from $fromUserId but we did not send him a secret share.',
);
return;
}
final computedHash = sha256.convert(storedShare).bytes;
final recoveryLastHeartbeat =
const ListEquality().equals(computedHash, msg.hash)
? clock.now()
: null; // The stored share not valid (maybe a old backup was restored). This will cause the performHeartbeat to resend him his share
Log.info(
'[$receiptId] Got heartbeat: ($recoveryLastHeartbeat)',
);
await twonlyDB.contactsDao.updateContact(
fromUserId,
ContactsCompanion(
recoveryLastHeartbeat: Value(recoveryLastHeartbeat),
),
);
}
static Future<bool> checkAndStorePasswordlessMessages(
OnboardingState state,
) async {
if (!state.serverRegistered ||
state.notificationId == null ||
state.downloadAuthToken == null ||
state.encryptionKey == null) {
return false;
}
final alreadyReceivedIds = state.receivedShares
.map((s) => Int64(s.messageId))
.toList();
final response = await apiService.checkForPasswordlessNotification(
notificationId: state.notificationId!,
downloadAuthToken: state.downloadAuthToken!,
alreadyReceivedIds: alreadyReceivedIds,
);
if (response == null || response.messages.isEmpty) {
return false;
}
final xchacha20 = Xchacha20.poly1305Aead();
final secretKey = SecretKey(state.encryptionKey!);
var didUpdate = false;
for (final msg in response.messages) {
final msgId = msg.id.toInt();
try {
final envelope = EncryptedEnvelope.fromBuffer(msg.encryptedMessage);
final secretBox = SecretBox(
envelope.encryptedData,
nonce: envelope.iv,
mac: Mac(envelope.mac),
);
final plaintext = await xchacha20.decrypt(
secretBox,
secretKey: secretKey,
);
final share = TrustedFriendShare.fromBuffer(plaintext);
final receivedShare = ReceivedRecoveryShare(
messageId: msgId,
trustedFriendDisplayName: share.trustedFriend.displayName,
myDisplayName: share.shareUser.displayName,
myUserId: share.shareUser.userId.toInt(),
myAvatarSvg: share.shareUser.hasAvatar()
? share.shareUser.avatar
: null,
threshold: share.threshold,
sharedSecretDataBytes: share.sharedSecretData,
);
state.receivedShares.add(receivedShare);
didUpdate = true;
Log.info(
'Received recovery share from ${share.trustedFriend.displayName} '
'for user ${share.shareUser.displayName}',
);
} catch (e) {
Log.error(
'Failed to decrypt/parse passwordless notification message $msgId: $e',
);
}
}
if (didUpdate) {
await KeyValueStore.update<OnboardingState>(
key: KeyValueKeys.onboardingState,
update: (s) => s.receivedShares = state.receivedShares,
);
}
return didUpdate;
}
}

View file

@ -1 +1,8 @@
enum SetupProfile { standard, customized }
enum SetupProfile { standard, customized, maximum }
enum SecurityProfile { normal, strict }
extension SecurityProfileExtension on SecurityProfile {
bool get showWarningForNonVerifiedContacts => this == SecurityProfile.strict;
bool get showOnlyVerifiedInChatViewList => this == SecurityProfile.normal;
}

View file

@ -29,7 +29,7 @@ Future<CiphertextMessage?> _signalEncryptMessage(
final session = SessionCipher.fromStore(signalStore, address);
return await session.encrypt(plaintextContent);
} catch (e) {
Log.error('Could not encrypt message for target $target: $e');
Log.error(e.toString());
return null;
}
}
@ -38,9 +38,8 @@ Future<(EncryptedContent?, PlaintextContent_DecryptionErrorMessage_Type?)>
signalDecryptMessage(
int fromUserId,
Uint8List encryptedContentRaw,
int type, {
Set<int>? brokenSessionsInCurrentBatch,
}) async {
int type,
) async {
// Hold the lock only for the cryptographic operation, not for network I/O
Log.info('Acquiring lockingSignalProtocol for $fromUserId');
final (
@ -75,7 +74,6 @@ signalDecryptMessage(
);
}
recordResyncAttempt(fromUserId, success: true);
return (EncryptedContent.fromBuffer(plaintext), null, false);
} on InvalidKeyIdException catch (e) {
Log.warn(e);
@ -115,25 +113,22 @@ signalDecryptMessage(
// Handle session resync OUTSIDE the lock to avoid holding it during
// network round-trips (which can block for up to 60 seconds)
if (needsResync) {
brokenSessionsInCurrentBatch?.add(fromUserId);
if (shouldAttemptResync(fromUserId)) {
if (await handleSessionResync(fromUserId)) {
// This flag prevents from resyncing the session the client received
// multiple new messages from the server he could not decrypt
recordResyncAttempt(fromUserId, success: false);
if (needsResync && !resyncedUsers.contains(fromUserId)) {
if (await handleSessionResync(fromUserId)) {
// This flag prevents from resyncing the session the client received
// multiple new messages from the server he could not decrypt
resyncedUsers.add(fromUserId);
// This message contains a new PreKeyBundle establishing a new signal
// session
await sendCipherText(
fromUserId,
EncryptedContent(
errorMessages: EncryptedContent_ErrorMessages(
type: EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC,
),
// This message contains a new PreKeyBundle establishing a new signal
// session
await sendCipherText(
fromUserId,
EncryptedContent(
errorMessages: EncryptedContent_ErrorMessages(
type: EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC,
),
);
}
),
);
}
}

View file

@ -1,36 +1,12 @@
import 'dart:math';
import 'package:mutex/mutex.dart';
/// Unified lock for all Signal protocol operations (encryption, decryption, session management).
final lockingSignalProtocol = Mutex();
/// Tracking users who have already been resynced in the current session.
final Map<int, ({int failureCount, DateTime lastAttempt})> _resyncAttempts = {};
final resyncedUsers = <int>{};
const int maxResyncAttempts = 3;
bool shouldAttemptResync(int userId) {
final attempt = _resyncAttempts[userId];
if (attempt == null) return true;
if (attempt.failureCount >= maxResyncAttempts) return false;
final cooldown = Duration(minutes: 5 * pow(5, attempt.failureCount - 1).toInt());
return DateTime.now().difference(attempt.lastAttempt) > cooldown;
}
void recordResyncAttempt(int userId, {required bool success}) {
if (success) {
_resyncAttempts.remove(userId);
} else {
final current = _resyncAttempts[userId];
_resyncAttempts[userId] = (
failureCount: (current?.failureCount ?? 0) + 1,
lastAttempt: DateTime.now(),
);
}
}
/// Reset the resync tracking set (currently unused, backoff handles expiry naturally).
/// Reset the resync tracking set.
void resetResyncedUsers() {
// No-op. We want the backoff state to persist across reconnects.
resyncedUsers.clear();
}

View file

@ -1,6 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:twonly/locator.dart';
@ -130,12 +128,6 @@ Future<void> handleUserStudyUpload() async {
});
}
} catch (e) {
if (e is http.ClientException ||
e is SocketException ||
e is TimeoutException) {
Log.warn('Error uploading user study data: $e');
} else {
Log.error(e);
}
Log.error(e);
}
}

View file

@ -7,7 +7,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/locator.dart';
import 'package:twonly/src/utils/log.dart';
String getAvatarSvg(Uint8List avatarSvgCompressed) {
return utf8.decode(gzip.decode(avatarSvgCompressed));
@ -17,35 +16,28 @@ Future<void> createPushAvatars({int? forceForUserId}) async {
final contacts = await twonlyDB.contactsDao.getAllContacts();
for (final contact in contacts) {
try {
if (contact.avatarSvgCompressed == null) continue;
if (contact.avatarSvgCompressed == null) continue;
if (forceForUserId == null) {
if (avatarPNGFile(contact.userId).existsSync()) {
continue; // only create the avatar in case no avatar exists yet fot this user
}
} else if (contact.userId != forceForUserId) {
// only update the avatar for this specified contact
continue;
if (forceForUserId == null) {
if (avatarPNGFile(contact.userId).existsSync()) {
continue; // only create the avatar in case no avatar exists yet fot this user
}
final avatarSvg = getAvatarSvg(contact.avatarSvgCompressed!);
final pictureInfo = await vg.loadPicture(
SvgStringLoader(avatarSvg),
null,
);
final image = await pictureInfo.picture.toImage(270, 300);
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
final pngBytes = byteData!.buffer.asUint8List();
await avatarPNGFile(contact.userId).writeAsBytes(pngBytes);
pictureInfo.picture.dispose();
} catch (e) {
Log.error(e);
} else if (contact.userId != forceForUserId) {
// only update the avatar for this specified contact
continue;
}
final avatarSvg = getAvatarSvg(contact.avatarSvgCompressed!);
final pictureInfo = await vg.loadPicture(SvgStringLoader(avatarSvg), null);
final image = await pictureInfo.picture.toImage(270, 300);
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
final pngBytes = byteData!.buffer.asUint8List();
await avatarPNGFile(contact.userId).writeAsBytes(pngBytes);
pictureInfo.picture.dispose();
}
}
@ -60,26 +52,10 @@ File avatarPNGFile(int contactId) {
return File('${avatarsDirectory.path}/$contactId.png');
}
File currentUserAvatarFile(int avatarCounter) {
final avatarsDirectory = Directory(
'${AppEnvironment.cacheDir}/avatars',
);
if (!avatarsDirectory.existsSync()) {
avatarsDirectory.createSync(recursive: true);
}
return File('${avatarsDirectory.path}/user_$avatarCounter.png');
}
Future<String?> getUserAvatar() async {
Future<Uint8List> getUserAvatar() async {
if (userService.currentUser.avatarSvg == null) {
return null;
}
final avatarCounter = userService.currentUser.avatarCounter;
final file = currentUserAvatarFile(avatarCounter);
if (file.existsSync()) {
return file.path;
final data = await rootBundle.load('assets/images/default_avatar.png');
return data.buffer.asUint8List();
}
final pictureInfo = await vg.loadPicture(
@ -92,8 +68,9 @@ Future<String?> getUserAvatar() async {
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
final pngBytes = byteData!.buffer.asUint8List();
await file.writeAsBytes(pngBytes);
final file = avatarPNGFile(userService.currentUser.userId)
..writeAsBytesSync(pngBytes);
pictureInfo.picture.dispose();
return file.path;
return file.readAsBytesSync();
}

View file

@ -1,39 +1,12 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:mutex/mutex.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/model/json/backup.model.dart';
import 'package:twonly/src/model/json/onboarding_state.model.dart';
import 'package:twonly/src/utils/exclusive_access.utils.dart';
import 'package:twonly/src/utils/log.dart';
typedef ModelFactory = ({
Object Function(Map<String, dynamic> json) fromJson,
Map<String, dynamic> Function(Object value) toJson,
Object Function() defaultValue,
});
class KeyValueStore {
static final Map<Type, ModelFactory> _registry = {
OnboardingState: (
fromJson: OnboardingState.fromJson,
toJson: (val) => (val as OnboardingState).toJson(),
defaultValue: OnboardingState.new,
),
CurrentBackupStatus: (
fromJson: CurrentBackupStatus.fromJson,
toJson: (val) => (val as CurrentBackupStatus).toJson(),
defaultValue: CurrentBackupStatus.new,
),
BackupRecovery: (
fromJson: BackupRecovery.fromJson,
toJson: (val) => (val as BackupRecovery).toJson(),
defaultValue: () => BackupRecovery(username: '', password: '', userId: 0),
),
};
static final Map<String, Mutex> _mutexes = {};
static Mutex _getMutex(String key) {
@ -92,77 +65,4 @@ class KeyValueStore {
}
});
}
static Future<T> update<T>({
required String key,
required FutureOr<void> Function(T value) update,
}) async {
final factory = _registry[T];
if (factory == null) {
throw ArgumentError('Type $T is not registered in KeyValueStore.');
}
return _exclusive(key, () async {
T val;
final file = await _getFilePath(key);
try {
if (file.existsSync()) {
final contents = await file.readAsString();
val = factory.fromJson(jsonDecode(contents) as Map<String, dynamic>) as T;
} else {
val = factory.defaultValue() as T;
}
} catch (e) {
Log.warn('Error reading file. Resetting to default.: $e');
val = factory.defaultValue() as T;
}
await update(val);
try {
await file.parent.create(recursive: true);
await file.writeAsString(jsonEncode(factory.toJson(val as Object)));
} catch (e) {
Log.error('Error writing file: $e');
}
return val;
});
}
static Future<T> getModel<T>(String key) async {
final factory = _registry[T];
if (factory == null) {
throw ArgumentError('Type $T is not registered in KeyValueStore.');
}
return _exclusive(key, () async {
final file = await _getFilePath(key);
try {
if (file.existsSync()) {
final contents = await file.readAsString();
return factory.fromJson(jsonDecode(contents) as Map<String, dynamic>) as T;
}
} catch (e) {
Log.warn('Error reading file. Returning default.: $e');
}
return factory.defaultValue() as T;
});
}
static Future<T?> getModelOrNull<T>(String key) async {
final factory = _registry[T];
if (factory == null) {
throw ArgumentError('Type $T is not registered in KeyValueStore.');
}
return _exclusive(key, () async {
final file = await _getFilePath(key);
try {
if (file.existsSync()) {
final contents = await file.readAsString();
return factory.fromJson(jsonDecode(contents) as Map<String, dynamic>) as T;
}
} catch (e) {
Log.warn('Error reading file.: $e');
}
return null;
});
}
}

View file

@ -41,14 +41,10 @@ class Log {
}
static void error(
Object? messageInput, {
Object? messageInput, [
Object? error,
StackTrace? stackTrace,
bool onlyIfSentryEnabled = false,
}) {
if (!AppState.allowErrorTrackingViaSentry && onlyIfSentryEnabled) {
return;
}
]) {
final message = filterLogMessage('$messageInput');
if (AppState.allowErrorTrackingViaSentry) {
try {

View file

@ -11,12 +11,10 @@ import 'package:gal/gal.dart';
import 'package:image/image.dart' as img;
import 'package:intl/intl.dart';
import 'package:local_auth/local_auth.dart';
import 'package:path/path.dart';
import 'package:provider/provider.dart';
import 'package:twonly/src/localization/generated/app_localizations.dart';
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
import 'package:twonly/src/providers/settings.provider.dart';
import 'package:twonly/src/services/backup.service.dart';
import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart';
@ -38,7 +36,6 @@ extension ShortCutsExtension on BuildContext {
Future<String?> saveImageToGallery(
Uint8List imageBytes, {
DateTime? createdAt,
String? name,
}) async {
var bytesToProcess = imageBytes;
@ -78,7 +75,7 @@ Future<String?> saveImageToGallery(
await Gal.requestAccess(toAlbum: true);
}
try {
await Gal.putImageBytes(jpgImages, album: 'twonly', name: name ?? 'image');
await Gal.putImageBytes(jpgImages, album: 'twonly');
return null;
} on GalException catch (e) {
Log.error(e);
@ -86,45 +83,17 @@ Future<String?> saveImageToGallery(
}
}
Future<String?> saveVideoToGallery(
String videoPath, {
String? name,
}) async {
Future<String?> saveVideoToGallery(String videoPath) async {
final hasAccess = await Gal.hasAccess(toAlbum: true);
if (!hasAccess) {
await Gal.requestAccess(toAlbum: true);
}
var pathToSave = videoPath;
File? tempFile;
try {
if (name != null) {
final file = File(videoPath);
final extension = file.path.split('.').last;
final tempDir = Directory.systemTemp;
tempFile = File(join(tempDir.path, '$name.$extension'));
if (tempFile.existsSync()) {
try {
tempFile.deleteSync();
} catch (_) {}
}
file.copySync(tempFile.path);
pathToSave = tempFile.path;
}
await Gal.putVideo(pathToSave, album: 'twonly');
await Gal.putVideo(videoPath, album: 'twonly');
return null;
} on GalException catch (e) {
Log.error(e);
return e.type.message;
} finally {
if (tempFile != null && tempFile.existsSync()) {
try {
tempFile.deleteSync();
} catch (e) {
Log.error('Failed to delete temp video file: $e');
}
}
}
}
@ -406,43 +375,3 @@ String joinWithAnd(List<String> items, String andWord) {
if (items.length == 1) return items.first;
return '${items.sublist(0, items.length - 1).join(', ')} $andWord ${items.last}';
}
String createEmailHint(String email) {
final parts = email.split('@');
if (parts.length != 2) return email;
final local = parts[0];
final domain = parts[1];
final localMasked = local.length > 2
? '${local[0]}${'*' * (local.length - 2)}${local[local.length - 1]}'
: local;
final domainParts = domain.split('.');
if (domainParts.isEmpty) return '$localMasked@$domain';
final domainName = domainParts[0];
final domainNameMasked = domainName.length > 1
? '${domainName[0]}${'*' * (domainName.length - 1)}'
: domainName;
final restDomain = domainParts.skip(1).join('.');
return '$localMasked@$domainNameMasked${restDomain.isNotEmpty ? '.$restDomain' : ''}';
}
extension RecoveryErrorLocalization on RecoveryError {
String toLocalizedString(BuildContext context) {
switch (this) {
case RecoveryError.noInternet:
return context.lang.recoverErrorNoInternet;
case RecoveryError.usernameNotValid:
return context.lang.recoverErrorUsernameNotValid;
case RecoveryError.passwordInvalid:
return context.lang.recoverErrorPasswordInvalid;
case RecoveryError.tryAgainLater:
return context.lang.recoverErrorTryAgainLater;
case RecoveryError.unkownError:
return context.lang.recoverErrorUnknown;
}
}
}

View file

@ -1,87 +0,0 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
import 'package:twonly/src/visual/elements/my_button.element.dart';
class AddContactDialog extends StatelessWidget {
const AddContactDialog({
required this.username,
super.key,
});
final String username;
static Future<bool?> show(BuildContext context, String username) {
return showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (context) => AddContactDialog(username: username),
);
}
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
backgroundColor: Theme.of(context).colorScheme.surface,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const AvatarIcon(
fontSize: 16,
),
const SizedBox(width: 12),
Flexible(
child: Text(
username,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
],
),
const SizedBox(height: 24),
Text(
context.lang.userFoundBody(username),
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 16,
),
),
const SizedBox(height: 28),
Row(
children: [
Expanded(
child: MyButton(
variant: MyButtonVariant.secondary,
onPressed: () => Navigator.pop(context, false),
child: Text(context.lang.cancel),
),
),
const SizedBox(width: 12),
Expanded(
child: MyButton(
onPressed: () => Navigator.pop(context, true),
child: Text(context.lang.friendSuggestionsRequest),
),
),
],
),
],
),
),
);
}
}

Some files were not shown because too many files have changed in this diff Show more