mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-07-18 02:24:07 +00:00
Compare commits
74 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d31dad7179 | |||
| 9a579e818c | |||
| 722466cc34 | |||
| 8fc8907a87 | |||
| 3ed6c1b334 | |||
| f6f865a6e6 | |||
| 93f1031453 | |||
| 21ca0a9c37 | |||
| 48be46cf47 | |||
| dfa6ac2cfe | |||
| cda7df36f3 | |||
| cda52a5da8 | |||
| 2cee00aff6 | |||
| 599882700c | |||
| d70ad8fb85 | |||
| 43390871c2 | |||
| 511490b649 | |||
| 07037537e1 | |||
| 3cdbd69d88 | |||
| 1126d4172b | |||
| 04680e0855 | |||
| cec25675c3 | |||
| 7a8901cd2d | |||
| 9faa4fc49a | |||
| 991376e1d9 | |||
| 7ea311236d | |||
| ca77cc4902 | |||
| f8a2240992 | |||
| 00b2b25252 | |||
| 4d943eca1c | |||
| 6db45a6da8 | |||
| e000b7c41d | |||
| e7dab1102d | |||
| 7066e8c799 | |||
| e8829ed2c9 | |||
| 6a106a7fa2 | |||
| 4c42d55afd | |||
| f14a94d639 | |||
| c4fc14a909 | |||
| 19ffed3386 | |||
| f278a1dd38 | |||
| e4bc21ea10 | |||
| 26c9dc0066 | |||
| 0007ba62a0 | |||
| 40b645e803 | |||
| 4e2b90de33 | |||
| 9b82310d49 | |||
| c7db5b7bdc | |||
| 690f4cae2f | |||
| c28808537b | |||
| 98fa90a46b | |||
| 2687545e33 | |||
| 8070f66142 | |||
| be74a94f50 | |||
| d528913e84 | |||
| f22e9086ed | |||
| e7301020f6 | |||
| e23ac5932e | |||
| 96eddd7480 | |||
| 9daf275310 | |||
| 83fd99ea70 | |||
| 0ab6eba47d | |||
| d5eae605c3 | |||
| a7414c6fcc | |||
| 52489a3a98 | |||
| f8c5ba17c6 | |||
| f436137c9b | |||
| 5dc071a737 | |||
| bff567ee3d | |||
| 878aca0f12 | |||
| 7dbd190434 | |||
| 2f0e2a9e24 | |||
| 75079a790c | |||
| 0e377d0ebc |
216 changed files with 90211 additions and 3374 deletions
34
CHANGELOG.md
34
CHANGELOG.md
|
|
@ -1,5 +1,39 @@
|
|||
# Changelog
|
||||
|
||||
## 0.3.7
|
||||
|
||||
- Fix: Multiple UI issues
|
||||
|
||||
## 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
|
||||
|
||||
- Improved: Design of some UI components
|
||||
|
|
|
|||
33
README.md
33
README.md
|
|
@ -35,7 +35,6 @@ 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
|
||||
|
|
@ -43,7 +42,6 @@ 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
|
||||
|
|
@ -63,36 +61,9 @@ 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
|
||||
|
||||
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>
|
||||
## Contribution
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ analyzer:
|
|||
- "lib/core/**"
|
||||
- "lib/src/localization/**"
|
||||
- "rust_builder/"
|
||||
- "scripts/"
|
||||
- "build/"
|
||||
- "dependencies/**"
|
||||
- "pubspec.yaml"
|
||||
|
|
|
|||
BIN
assets/icons/verification_badge_numeric/verified_badge_1.svg.vec
Normal file
BIN
assets/icons/verification_badge_numeric/verified_badge_1.svg.vec
Normal file
Binary file not shown.
BIN
assets/icons/verification_badge_numeric/verified_badge_2.svg.vec
Normal file
BIN
assets/icons/verification_badge_numeric/verified_badge_2.svg.vec
Normal file
Binary file not shown.
BIN
assets/icons/verification_badge_numeric/verified_badge_3.svg.vec
Normal file
BIN
assets/icons/verification_badge_numeric/verified_badge_3.svg.vec
Normal file
Binary file not shown.
BIN
assets/icons/verified_badge_green.svg.vec
Normal file
BIN
assets/icons/verified_badge_green.svg.vec
Normal file
Binary file not shown.
BIN
assets/icons/verified_badge_red.svg.vec
Normal file
BIN
assets/icons/verified_badge_red.svg.vec
Normal file
Binary file not shown.
|
|
@ -18,33 +18,6 @@
|
|||
"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",
|
||||
|
|
@ -153,15 +126,6 @@
|
|||
"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",
|
||||
|
|
@ -170,24 +134,6 @@
|
|||
"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
|
||||
|
|
|
|||
|
|
@ -18,33 +18,6 @@
|
|||
"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",
|
||||
|
|
@ -153,15 +126,6 @@
|
|||
"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",
|
||||
|
|
@ -170,24 +134,6 @@
|
|||
"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
|
||||
|
|
|
|||
31
lib/app.dart
31
lib/app.dart
|
|
@ -5,9 +5,13 @@ 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';
|
||||
|
|
@ -18,7 +22,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.view.dart';
|
||||
import 'package:twonly/src/visual/views/recovery_from_secure_storage.view.dart';
|
||||
import 'package:twonly/src/visual/views/unlock_twonly.view.dart';
|
||||
|
||||
class App extends StatefulWidget {
|
||||
|
|
@ -151,6 +155,10 @@ 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();
|
||||
|
|
@ -180,7 +188,18 @@ 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;
|
||||
|
|
@ -223,9 +242,13 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
|||
}
|
||||
} else if (_showOnboarding) {
|
||||
child = OnboardingView(
|
||||
callbackOnSuccess: () => setState(() {
|
||||
_showOnboarding = false;
|
||||
}),
|
||||
callbackOnSuccess: () async {
|
||||
await KeyValueStore.update<OnboardingState>(
|
||||
key: KeyValueKeys.onboardingState,
|
||||
update: (state) => state.hasOnboardingFinished = true,
|
||||
);
|
||||
if (mounted) setState(() => _showOnboarding = false);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
child = RegisterView(
|
||||
|
|
|
|||
173
lib/core/bridge/callbacks/user_discovery.dart
Normal file
173
lib/core/bridge/callbacks/user_discovery.dart
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
// 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;
|
||||
}
|
||||
37
lib/core/bridge/wrapper.dart
Normal file
37
lib/core/bridge/wrapper.dart
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// 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;
|
||||
}
|
||||
|
|
@ -20,6 +20,12 @@ 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,
|
||||
|
|
@ -53,6 +59,10 @@ 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
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
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';
|
||||
|
|
@ -134,6 +136,19 @@ 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);
|
||||
|
||||
|
|
@ -205,6 +220,9 @@ 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);
|
||||
|
||||
|
|
@ -217,6 +235,16 @@ 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);
|
||||
|
||||
|
|
@ -266,6 +294,21 @@ 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,
|
||||
|
|
@ -357,6 +400,9 @@ 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);
|
||||
|
||||
|
|
@ -369,6 +415,16 @@ 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);
|
||||
|
||||
|
|
@ -517,6 +573,24 @@ 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,
|
||||
|
|
@ -634,6 +708,9 @@ 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);
|
||||
|
||||
|
|
@ -646,6 +723,18 @@ 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
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,6 +138,19 @@ 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);
|
||||
|
||||
|
|
@ -207,6 +222,9 @@ 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);
|
||||
|
||||
|
|
@ -219,6 +237,16 @@ 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);
|
||||
|
||||
|
|
@ -268,6 +296,21 @@ 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,
|
||||
|
|
@ -359,6 +402,9 @@ 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);
|
||||
|
||||
|
|
@ -371,6 +417,16 @@ 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);
|
||||
|
||||
|
|
@ -519,6 +575,24 @@ 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,
|
||||
|
|
@ -636,6 +710,9 @@ 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);
|
||||
|
||||
|
|
@ -648,6 +725,18 @@ 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,6 @@ class AppState {
|
|||
static bool isInBackgroundTask = false;
|
||||
static bool allowErrorTrackingViaSentry = false;
|
||||
static bool gotMessageFromServer = false;
|
||||
static int latestAppVersionId = 116;
|
||||
static int latestAppVersionId = 117;
|
||||
static bool hasCameraPermissions = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
|
|
@ -32,7 +33,7 @@ import 'package:twonly/src/utils/startup_guard.dart';
|
|||
|
||||
final _initMutex = Mutex();
|
||||
|
||||
/// This function is used to initialized the absolute minimum so it
|
||||
/// This function is used to initialize 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');
|
||||
|
|
@ -40,16 +41,13 @@ 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(
|
||||
|
|
@ -71,12 +69,21 @@ 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();
|
||||
|
||||
var userExists = false;
|
||||
|
||||
|
|
@ -166,7 +173,6 @@ Future<void> postStartupTasks() async {
|
|||
unawaited(MediaFileService.purgeTempFolder());
|
||||
|
||||
// 2. Service initializations
|
||||
unawaited(setupPushNotification());
|
||||
unawaited(finishStartedPreprocessing());
|
||||
unawaited(createPushAvatars());
|
||||
|
||||
|
|
|
|||
|
|
@ -314,9 +314,11 @@ class UserDiscoveryCallbacks {
|
|||
|
||||
static Future<Uint8List?> getContactPromotion(int contactId) async {
|
||||
try {
|
||||
final row = await (twonlyDB.select(
|
||||
twonlyDB.userDiscoveryOwnPromotions,
|
||||
)..where((tbl) => tbl.contactId.equals(contactId))).getSingleOrNull();
|
||||
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();
|
||||
return row?.promotion;
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ abstract class VideoCompressionChannel {
|
|||
});
|
||||
return outputPath;
|
||||
} on PlatformException catch (e) {
|
||||
Log.error('Failed to compress video: $e');
|
||||
Log.warn('Failed to compress video: $e');
|
||||
return null;
|
||||
} finally {
|
||||
_currentProgressCallback = null;
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@ 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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ 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';
|
||||
|
|
@ -35,8 +36,6 @@ 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';
|
||||
|
|
|
|||
|
|
@ -56,6 +56,18 @@ 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,
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
|||
|
||||
final result = await _insertGroup(insertGroup);
|
||||
if (result != null) {
|
||||
await into(groupMembers).insert(
|
||||
await into(groupMembers).insertOnConflictUpdate(
|
||||
GroupMembersCompanion(
|
||||
groupId: Value(result.groupId),
|
||||
contactId: Value(
|
||||
|
|
|
|||
|
|
@ -57,18 +57,70 @@ class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
|
|||
}
|
||||
|
||||
Future<bool> isContactVerified(int contactId) async {
|
||||
final row =
|
||||
await (select(keyVerifications)
|
||||
..where((kv) => kv.contactId.equals(contactId))
|
||||
..limit(1))
|
||||
.getSingleOrNull();
|
||||
return row != null;
|
||||
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;
|
||||
}
|
||||
|
||||
Stream<List<KeyVerification>> watchContactVerification(int contactId) {
|
||||
return (select(
|
||||
keyVerifications,
|
||||
)..where((kv) => kv.contactId.equals(contactId))).watch();
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<KeyVerification>> getContactVerification(int contactId) async {
|
||||
|
|
@ -207,12 +259,65 @@ class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> addKeyVerification(int contactId, VerificationType type) async {
|
||||
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 {
|
||||
try {
|
||||
await into(keyVerifications).insertOnConflictUpdate(
|
||||
KeyVerificationsCompanion(
|
||||
contactId: Value(contactId),
|
||||
type: Value(type),
|
||||
verifiedBy: Value(verifiedBy),
|
||||
),
|
||||
);
|
||||
if (userService.currentUser.isUserDiscoveryEnabled) {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
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])
|
||||
@DriftAccessor(tables: [MediaFiles, Messages])
|
||||
class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||
with _$MediaFilesDaoMixin {
|
||||
// this constructor is required so that the main database can create an instance
|
||||
|
|
@ -142,7 +143,9 @@ 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();
|
||||
}
|
||||
|
|
@ -154,6 +157,17 @@ 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) =>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ 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);
|
||||
}
|
||||
|
||||
|
|
@ -13,4 +16,10 @@ 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,15 +32,25 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
MessagesDao(super.db);
|
||||
|
||||
Stream<List<Message>> watchMessageNotOpened(String groupId) {
|
||||
return (select(messages)
|
||||
final query =
|
||||
select(messages).join([
|
||||
leftOuterJoin(
|
||||
mediaFiles,
|
||||
mediaFiles.mediaId.equalsExp(messages.mediaId),
|
||||
),
|
||||
])
|
||||
..where(
|
||||
(t) =>
|
||||
t.openedAt.isNull() &
|
||||
t.groupId.equals(groupId) &
|
||||
t.isDeletedFromSender.equals(false),
|
||||
messages.openedAt.isNull() &
|
||||
messages.groupId.equals(groupId) &
|
||||
messages.isDeletedFromSender.equals(false) &
|
||||
(messages.mediaId.isNull() |
|
||||
mediaFiles.downloadState.isNull() |
|
||||
mediaFiles.downloadState
|
||||
.equals(DownloadState.reuploadRequested.name)
|
||||
.not()),
|
||||
)
|
||||
..orderBy([(t) => OrderingTerm.desc(t.createdAt)]))
|
||||
.watch();
|
||||
..orderBy([OrderingTerm.desc(messages.createdAt)]);
|
||||
return query.map((row) => row.readTable(messages)).watch();
|
||||
}
|
||||
|
||||
Stream<List<Message>> watchMediaNotOpened(String groupId) {
|
||||
|
|
@ -52,9 +62,10 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
),
|
||||
])
|
||||
..where(
|
||||
(mediaFiles.downloadState.isNull() |
|
||||
mediaFiles.downloadState
|
||||
.equals(DownloadState.reuploadRequested.name)
|
||||
.not() &
|
||||
.not()) &
|
||||
mediaFiles.type.equals(MediaType.audio.name).not() &
|
||||
messages.openedAt.isNull() &
|
||||
messages.groupId.equals(groupId) &
|
||||
|
|
@ -66,6 +77,24 @@ 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(
|
||||
|
|
@ -73,19 +102,29 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
milliseconds: group!.deleteMessagesAfterMilliseconds,
|
||||
),
|
||||
);
|
||||
return (select(messages)
|
||||
final query =
|
||||
select(messages).join([
|
||||
leftOuterJoin(
|
||||
mediaFiles,
|
||||
mediaFiles.mediaId.equalsExp(messages.mediaId),
|
||||
),
|
||||
])
|
||||
..where(
|
||||
(t) =>
|
||||
t.groupId.equals(groupId) &
|
||||
messages.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
|
||||
(t.openedAt.isBiggerThanValue(deletionTime) |
|
||||
t.openedAt.isNull() |
|
||||
t.mediaStored.equals(true)),
|
||||
(messages.openedAt.isBiggerThanValue(deletionTime) |
|
||||
messages.openedAt.isNull() |
|
||||
messages.mediaStored.equals(true)) &
|
||||
(mediaFiles.downloadState
|
||||
.equals(DownloadState.reuploadRequested.name)
|
||||
.not() |
|
||||
mediaFiles.downloadState.isNull()),
|
||||
)
|
||||
..orderBy([(t) => OrderingTerm.desc(t.createdAt)])
|
||||
..limit(1))
|
||||
.watchSingleOrNull();
|
||||
..orderBy([OrderingTerm.desc(messages.createdAt)])
|
||||
..limit(1);
|
||||
|
||||
return query.map((row) => row.readTable(messages)).watchSingleOrNull();
|
||||
}
|
||||
|
||||
Future<Stream<List<Message>>> watchByGroupId(String groupId) async {
|
||||
|
|
@ -245,6 +284,7 @@ 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;
|
||||
|
|
@ -265,6 +305,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
modifiedAt: Value(timestamp),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> handleMessagesOpened(
|
||||
|
|
@ -272,21 +313,37 @@ 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 {
|
||||
var actionTimestamp = timestamp;
|
||||
await transaction(() async {
|
||||
final msg = await getMessageById(messageId).getSingleOrNull();
|
||||
if (msg != null && actionTimestamp.isBefore(msg.createdAt)) {
|
||||
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 $actionTimestamp is before message creation ${msg.createdAt}. '
|
||||
'Action timestamp $ts is before message creation ${msg.createdAt}. '
|
||||
'Clamping to creation time.',
|
||||
);
|
||||
actionTimestamp = msg.createdAt;
|
||||
ts = msg.createdAt;
|
||||
}
|
||||
|
||||
final ts = actionTimestamp;
|
||||
await transaction(() async {
|
||||
await into(messageActions).insertOnConflictUpdate(
|
||||
MessageActionsCompanion(
|
||||
messageId: Value(messageId),
|
||||
|
|
@ -320,7 +377,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
messages,
|
||||
)..where((tbl) => tbl.messageId.equals(messageId))).write(
|
||||
MessagesCompanion(
|
||||
openedAt: Value(actionTimestamp),
|
||||
openedAt: Value(timestamp),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -329,7 +386,11 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
'handleMessagesOpened completed for message $messageId',
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error('handleMessagesOpened failed for $messageId: $e');
|
||||
Log.warn('handleMessagesOpened failed for $messageId: $e');
|
||||
Log.error(
|
||||
'handleMessagesOpened failed for: $e',
|
||||
onlyIfSentryEnabled: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -339,7 +400,21 @@ 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),
|
||||
|
|
@ -453,6 +528,15 @@ 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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,11 +30,15 @@ class ReactionsDao extends DatabaseAccessor<TwonlyDB> with _$ReactionsDaoMixin {
|
|||
.getMessageById(messageId)
|
||||
.getSingleOrNull();
|
||||
if (msg == null) {
|
||||
Log.error('updateReaction: Message $messageId not found!');
|
||||
Log.warn('updateReaction: Message $messageId not found!');
|
||||
return;
|
||||
}
|
||||
if (msg.groupId != groupId) {
|
||||
Log.error('updateReaction: Message groupId ${msg.groupId} != $groupId');
|
||||
Log.warn('updateReaction: Message groupId ${msg.groupId} != $groupId');
|
||||
Log.error(
|
||||
'updateReaction: Message groupId mismatch',
|
||||
onlyIfSentryEnabled: true,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -201,8 +201,8 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
|||
);
|
||||
final updatedReceipt = await getReceiptById(newReceiptId);
|
||||
if (updatedReceipt == null) {
|
||||
Log.error(
|
||||
'Tried to change the receipt ID, but could not get the updated receipt...',
|
||||
Log.warn(
|
||||
'[$oldReceiptId] Tried to change the receipt ID to $newReceiptId, but could not get the updated receipt...',
|
||||
);
|
||||
}
|
||||
return updatedReceipt;
|
||||
|
|
@ -253,6 +253,9 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
|||
Future<void> gotReceipt(String receiptId) async {
|
||||
await into(
|
||||
receivedReceipts,
|
||||
).insert(ReceivedReceiptsCompanion(receiptId: Value(receiptId)));
|
||||
).insert(
|
||||
ReceivedReceiptsCompanion(receiptId: Value(receiptId)),
|
||||
mode: InsertMode.insertOrIgnore,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
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';
|
||||
|
|
@ -184,23 +185,51 @@ 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 countExp = userDiscoveryAnnouncedUsers.announcedUserId.count();
|
||||
|
||||
final query = selectOnly(userDiscoveryAnnouncedUsers)
|
||||
..addColumns([countExp])
|
||||
..where(
|
||||
// Filters: Has a username AND has not been shown to the user yet
|
||||
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),
|
||||
userDiscoveryAnnouncedUsers.isHidden.equals(false) &
|
||||
(announcedContact.userId.isNull() |
|
||||
announcedContact.deletedByUser.equals(true)),
|
||||
);
|
||||
|
||||
return query.watchSingle().map((row) => row.read(countExp) ?? 0);
|
||||
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 threshold = userService.currentUser.userDiscoveryThreshold;
|
||||
return relationCounts.values.where((count) => count >= threshold).length;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> markAllValidAnnouncedUsersAsShown() async {
|
||||
|
|
@ -234,6 +263,12 @@ 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();
|
||||
|
||||
|
|
|
|||
3047
lib/src/database/schemas/twonly_db/drift_schema_v18.json
Normal file
3047
lib/src/database/schemas/twonly_db/drift_schema_v18.json
Normal file
File diff suppressed because it is too large
Load diff
3073
lib/src/database/schemas/twonly_db/drift_schema_v19.json
Normal file
3073
lib/src/database/schemas/twonly_db/drift_schema_v19.json
Normal file
File diff suppressed because it is too large
Load diff
3107
lib/src/database/schemas/twonly_db/drift_schema_v20.json
Normal file
3107
lib/src/database/schemas/twonly_db/drift_schema_v20.json
Normal file
File diff suppressed because it is too large
Load diff
3127
lib/src/database/schemas/twonly_db/drift_schema_v21.json
Normal file
3127
lib/src/database/schemas/twonly_db/drift_schema_v21.json
Normal file
File diff suppressed because it is too large
Load diff
3137
lib/src/database/schemas/twonly_db/drift_schema_v22.json
Normal file
3137
lib/src/database/schemas/twonly_db/drift_schema_v22.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -42,7 +42,9 @@ class SignalPreKeyStore extends PreKeyStore {
|
|||
);
|
||||
|
||||
try {
|
||||
await twonlyDB.into(twonlyDB.signalPreKeyStores).insert(preKeyCompanion);
|
||||
await twonlyDB
|
||||
.into(twonlyDB.signalPreKeyStores)
|
||||
.insert(preKeyCompanion, mode: InsertMode.insertOrReplace);
|
||||
} catch (e) {
|
||||
Log.error('$e');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,15 +23,26 @@ class Contacts extends Table {
|
|||
|
||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||
|
||||
// contact_versions: HashMap<UserID, Vec<u8>>,
|
||||
// User Discovery
|
||||
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))();
|
||||
|
|
@ -57,6 +68,11 @@ 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)();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class TwonlyDB extends _$TwonlyDB {
|
|||
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 17;
|
||||
int get schemaVersion => 22;
|
||||
|
||||
static QueryExecutor _openConnection() {
|
||||
final connection = driftDatabase(
|
||||
|
|
@ -233,6 +233,48 @@ 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
|
|
@ -149,7 +149,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @onboardingNotProductBody.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'twonly is financed by donations and an optional subscription. Your data will never be sold.'**
|
||||
/// **'twonly is financed by an optional subscription. Your data will never be sold.'**
|
||||
String get onboardingNotProductBody;
|
||||
|
||||
/// No description provided for @registerUsernameSlogan.
|
||||
|
|
@ -590,48 +590,6 @@ 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 @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:
|
||||
|
|
@ -896,24 +854,60 @@ abstract class AppLocalizations {
|
|||
/// **'Verify contacts'**
|
||||
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'**
|
||||
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:
|
||||
|
|
@ -1598,6 +1592,18 @@ 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:
|
||||
|
|
@ -1922,6 +1928,12 @@ 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:
|
||||
|
|
@ -2360,6 +2372,12 @@ 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:
|
||||
|
|
@ -2567,7 +2585,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @verifiedPublicKey.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The public key of {username} has been verified and is valid.'**
|
||||
/// **'The identity of {username} has been successfully verified.'**
|
||||
String verifiedPublicKey(Object username);
|
||||
|
||||
/// No description provided for @memoriesAYearAgo.
|
||||
|
|
@ -2717,7 +2735,7 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @verificationBadgeGeneralDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'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.'**
|
||||
/// **'The badge gives you the peace of mind that you are messaging the *right person* and your *messages remain confidential*.'**
|
||||
String get verificationBadgeGeneralDesc;
|
||||
|
||||
/// No description provided for @verificationBadgeGreenDesc.
|
||||
|
|
@ -2744,12 +2762,30 @@ abstract class AppLocalizations {
|
|||
/// **'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:
|
||||
|
|
@ -2768,6 +2804,12 @@ 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:
|
||||
|
|
@ -3641,6 +3683,456 @@ 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
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get onboardingNotProductBody =>
|
||||
'twonly wird durch Spenden und ein optionales Abonnement finanziert. Deine Daten werden niemals verkauft.';
|
||||
'twonly wird durch ein optionales Abonnement finanziert. Deine Daten werden niemals verkauft.';
|
||||
|
||||
@override
|
||||
String get registerUsernameSlogan => 'Konto erstellen';
|
||||
|
|
@ -268,30 +268,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
return '$len Kontakt(e)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsPrivacyProfileSelectionTitle => 'Sicherheitsprofil';
|
||||
|
||||
@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';
|
||||
|
||||
|
|
@ -404,7 +380,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get contactUsLastWarning =>
|
||||
'Dies sind die Informationen, die an uns gesendet werden. Bitte prüfen Sie sie und klicke dann auf „Abschicken“.';
|
||||
'Dies sind die Informationen, die an uns gesendet werden. Bitte prüfe sie und klicke dann auf „Abschicken“.';
|
||||
|
||||
@override
|
||||
String get contactUsSuccess => 'Feedback erfolgreich übermittelt!';
|
||||
|
|
@ -435,18 +411,43 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get contactVerifyNumberTitle => 'Kontakte 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';
|
||||
|
||||
@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.';
|
||||
|
||||
|
|
@ -837,7 +838,14 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get backupTwonlySaveNow => 'Jetzt speichern';
|
||||
|
||||
@override
|
||||
String get backupChangePassword => 'Password ändern';
|
||||
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!';
|
||||
|
||||
@override
|
||||
String get twonlySafeRecoverTitle => 'Backup wiederherstellen';
|
||||
|
|
@ -1022,6 +1030,9 @@ 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?';
|
||||
|
|
@ -1303,6 +1314,11 @@ 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';
|
||||
|
||||
|
|
@ -1429,7 +1445,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String verifiedPublicKey(Object username) {
|
||||
return 'Der öffentliche Schlüssel von $username wurde überprüft und ist gültig.';
|
||||
return 'Die Identität von $username wurde erfolgreich überprüft.';
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -1528,7 +1544,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get verificationBadgeGeneralDesc =>
|
||||
'Der Haken gibt dir die Sicherheit, dass du mit der richtigen Person schreibst. Du kannst Kontakte jederzeit verifizieren, indem du deren QR-Code scannst.';
|
||||
'Der Haken gibt dir die Sicherheit, dass du mit der *richtigen Person* schreibst und deine *Nachrichten vertraulich* bleiben.';
|
||||
|
||||
@override
|
||||
String get verificationBadgeGreenDesc =>
|
||||
|
|
@ -1545,9 +1561,22 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@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?';
|
||||
|
||||
|
|
@ -1560,6 +1589,11 @@ 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(
|
||||
|
|
@ -2090,4 +2124,274 @@ 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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get onboardingNotProductBody =>
|
||||
'twonly is financed by donations and an optional subscription. Your data will never be sold.';
|
||||
'twonly is financed by an optional subscription. Your data will never be sold.';
|
||||
|
||||
@override
|
||||
String get registerUsernameSlogan => 'Create your account';
|
||||
|
|
@ -265,30 +265,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
return '$len contact(s)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsPrivacyProfileSelectionTitle => 'Security Profile';
|
||||
|
||||
@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';
|
||||
|
||||
|
|
@ -431,18 +407,43 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get contactVerifyNumberTitle => 'Verify contacts';
|
||||
|
||||
@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';
|
||||
|
||||
@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.';
|
||||
|
||||
|
|
@ -834,6 +835,13 @@ 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';
|
||||
|
||||
|
|
@ -1016,6 +1024,9 @@ 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?';
|
||||
|
|
@ -1295,6 +1306,11 @@ 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';
|
||||
|
||||
|
|
@ -1418,7 +1434,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String verifiedPublicKey(Object username) {
|
||||
return 'The public key of $username has been verified and is valid.';
|
||||
return 'The identity of $username has been successfully verified.';
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -1515,7 +1531,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get verificationBadgeGeneralDesc =>
|
||||
'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.';
|
||||
'The badge gives you the peace of mind that you are messaging the *right person* and your *messages remain confidential*.';
|
||||
|
||||
@override
|
||||
String get verificationBadgeGreenDesc =>
|
||||
|
|
@ -1532,9 +1548,22 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@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?';
|
||||
|
||||
|
|
@ -1547,6 +1576,11 @@ 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(
|
||||
|
|
@ -2075,4 +2109,270 @@ 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 673f6d8c3036d64060b1114912bd5bf5515d5420
|
||||
Subproject commit b7d703c82a5ac0b6776847cb4eb6d637620f6585
|
||||
65
lib/src/model/json/onboarding_state.model.dart
Normal file
65
lib/src/model/json/onboarding_state.model.dart
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
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);
|
||||
}
|
||||
67
lib/src/model/json/onboarding_state.model.g.dart
Normal file
67
lib/src/model/json/onboarding_state.model.g.dart
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// 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,
|
||||
};
|
||||
|
|
@ -40,9 +40,6 @@ class UserData {
|
|||
@JsonKey(defaultValue: SetupProfile.standard)
|
||||
SetupProfile setupProfile = SetupProfile.standard;
|
||||
|
||||
@JsonKey(defaultValue: SecurityProfile.normal)
|
||||
SecurityProfile securityProfile = SecurityProfile.normal;
|
||||
|
||||
// --- SUBSCRIPTION DTA ---
|
||||
|
||||
@JsonKey(defaultValue: 'Free')
|
||||
|
|
@ -128,6 +125,9 @@ class UserData {
|
|||
|
||||
// -- Custom DATA --
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
bool askForFriendPromotions = true;
|
||||
|
||||
@JsonKey(defaultValue: 100_000)
|
||||
int currentPreKeyIndexStart = 100_000;
|
||||
|
||||
|
|
@ -153,6 +153,8 @@ class UserData {
|
|||
@JsonKey(defaultValue: false)
|
||||
bool isBackupEnabled = false;
|
||||
|
||||
PasswordLessRecovery? passwordLessRecovery;
|
||||
|
||||
// Used for push notifcation via FCM.
|
||||
String? fcmToken;
|
||||
|
||||
|
|
@ -200,3 +202,34 @@ 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,12 +23,6 @@ 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
|
||||
|
|
@ -85,6 +79,7 @@ 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 =
|
||||
|
|
@ -102,6 +97,11 @@ 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,7 +126,6 @@ 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(),
|
||||
|
|
@ -161,6 +160,7 @@ 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,6 +169,7 @@ 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,
|
||||
|
|
@ -184,12 +185,6 @@ 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 = {
|
||||
|
|
@ -232,3 +227,40 @@ 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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -809,6 +809,301 @@ 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,
|
||||
|
|
@ -817,6 +1112,9 @@ enum Handshake_Handshake {
|
|||
requestPOW,
|
||||
authenticateWithLoginToken,
|
||||
getUseridByUsername,
|
||||
getServerKeyForPasswordlessRecovery,
|
||||
registerPasswordlessNotification,
|
||||
checkForPasswordlessNotification,
|
||||
notSet
|
||||
}
|
||||
|
||||
|
|
@ -829,6 +1127,12 @@ 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;
|
||||
|
|
@ -840,6 +1144,15 @@ 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;
|
||||
}
|
||||
|
||||
|
|
@ -861,6 +1174,9 @@ 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(
|
||||
|
|
@ -868,7 +1184,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])
|
||||
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
|
||||
..aOM<Handshake_Register>(1, _omitFieldNames ? '' : 'register',
|
||||
subBuilder: Handshake_Register.create)
|
||||
..aOM<Handshake_GetAuthChallenge>(
|
||||
|
|
@ -887,6 +1203,15 @@ 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.')
|
||||
|
|
@ -914,6 +1239,9 @@ 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)
|
||||
|
|
@ -923,6 +1251,9 @@ 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)
|
||||
|
|
@ -1006,6 +1337,51 @@ 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 {
|
||||
|
|
@ -2182,6 +2558,160 @@ 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,
|
||||
|
|
@ -2209,6 +2739,8 @@ enum ApplicationData_ApplicationData {
|
|||
ipaForceCheck,
|
||||
addAdditionalUser,
|
||||
setLoginToken,
|
||||
registerPasswordlessRecovery,
|
||||
passwordlessNotification,
|
||||
notSet
|
||||
}
|
||||
|
||||
|
|
@ -2240,6 +2772,8 @@ 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;
|
||||
|
|
@ -2274,6 +2808,10 @@ 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;
|
||||
}
|
||||
|
||||
|
|
@ -2314,6 +2852,8 @@ 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(
|
||||
|
|
@ -2347,7 +2887,9 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
27,
|
||||
28,
|
||||
29,
|
||||
30
|
||||
30,
|
||||
31,
|
||||
32
|
||||
])
|
||||
..aOM<ApplicationData_TextMessage>(1, _omitFieldNames ? '' : 'textMessage',
|
||||
protoName: 'textMessage',
|
||||
|
|
@ -2439,6 +2981,12 @@ 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.')
|
||||
|
|
@ -2486,6 +3034,8 @@ 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)
|
||||
|
|
@ -2514,6 +3064,8 @@ 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)
|
||||
|
|
@ -2819,6 +3371,36 @@ 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 {
|
||||
|
|
|
|||
|
|
@ -152,6 +152,33 @@ 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,
|
||||
|
|
@ -160,7 +187,10 @@ const Handshake$json = {
|
|||
Handshake_GetUserIdByUsername$json,
|
||||
Handshake_GetAuthToken$json,
|
||||
Handshake_Authenticate$json,
|
||||
Handshake_AuthenticateWithLoginToken$json
|
||||
Handshake_AuthenticateWithLoginToken$json,
|
||||
Handshake_GetServerKeyForPasswordLessRecovery$json,
|
||||
Handshake_RegisterPasswordlessNotification$json,
|
||||
Handshake_CheckForPasswordlessNotification$json
|
||||
],
|
||||
'8': [
|
||||
{'1': 'Handshake'},
|
||||
|
|
@ -303,6 +333,95 @@ 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'
|
||||
|
|
@ -316,26 +435,47 @@ final $typed_data.Uint8List handshakeDescriptor = $convert.base64Decode(
|
|||
'MjYuY2xpZW50X3RvX3NlcnZlci5IYW5kc2hha2UuQXV0aGVudGljYXRlV2l0aExvZ2luVG9rZW'
|
||||
'5IAFIaYXV0aGVudGljYXRlV2l0aExvZ2luVG9rZW4SZgoWZ2V0X3VzZXJpZF9ieV91c2VybmFt'
|
||||
'ZRgHIAEoCzIvLmNsaWVudF90b19zZXJ2ZXIuSGFuZHNoYWtlLkdldFVzZXJJZEJ5VXNlcm5hbW'
|
||||
'VIAFITZ2V0VXNlcmlkQnlVc2VybmFtZRoMCgpSZXF1ZXN0UE9XGsoDCghSZWdpc3RlchIaCgh1'
|
||||
'c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUSJAoLaW52aXRlX2NvZGUYAiABKAlIAFIKaW52aXRlQ2'
|
||||
'9kZYgBARIuChNwdWJsaWNfaWRlbnRpdHlfa2V5GAMgASgMUhFwdWJsaWNJZGVudGl0eUtleRIj'
|
||||
'Cg1zaWduZWRfcHJla2V5GAQgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaW'
|
||||
'duYXR1cmUYBSABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRIoChBzaWduZWRfcHJla2V5X2lk'
|
||||
'GAYgASgDUg5zaWduZWRQcmVrZXlJZBInCg9yZWdpc3RyYXRpb25faWQYByABKANSDnJlZ2lzdH'
|
||||
'JhdGlvbklkEhUKBmlzX2lvcxgIIAEoCFIFaXNJb3MSGwoJbGFuZ19jb2RlGAkgASgJUghsYW5n'
|
||||
'Q29kZRIiCg1wcm9vZl9vZl93b3JrGAogASgDUgtwcm9vZk9mV29yaxIkCgtsb2dpbl90b2tlbh'
|
||||
'gLIAEoDEgBUgpsb2dpblRva2VuiAEBQg4KDF9pbnZpdGVfY29kZUIOCgxfbG9naW5fdG9rZW4a'
|
||||
'EgoQR2V0QXV0aENoYWxsZW5nZRoxChNHZXRVc2VySWRCeVVzZXJuYW1lEhoKCHVzZXJuYW1lGA'
|
||||
'EgASgJUgh1c2VybmFtZRpDCgxHZXRBdXRoVG9rZW4SFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklk'
|
||||
'EhoKCHJlc3BvbnNlGAIgASgMUghyZXNwb25zZRroAQoMQXV0aGVudGljYXRlEhcKB3VzZXJfaW'
|
||||
'QYASABKANSBnVzZXJJZBIdCgphdXRoX3Rva2VuGAIgASgMUglhdXRoVG9rZW4SJAoLYXBwX3Zl'
|
||||
'cnNpb24YAyABKAlIAFIKYXBwVmVyc2lvbogBARIgCglkZXZpY2VfaWQYBCABKANIAVIIZGV2aW'
|
||||
'NlSWSIAQESKAoNaW5fYmFja2dyb3VuZBgFIAEoCEgCUgxpbkJhY2tncm91bmSIAQFCDgoMX2Fw'
|
||||
'cF92ZXJzaW9uQgwKCl9kZXZpY2VfaWRCEAoOX2luX2JhY2tncm91bmQaxgEKGkF1dGhlbnRpY2'
|
||||
'F0ZVdpdGhMb2dpblRva2VuEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIsChJzZWNyZXRfbG9n'
|
||||
'aW5fdG9rZW4YAiABKAxSEHNlY3JldExvZ2luVG9rZW4SHwoLYXBwX3ZlcnNpb24YAyABKAlSCm'
|
||||
'FwcFZlcnNpb24SGwoJZGV2aWNlX2lkGAQgASgDUghkZXZpY2VJZBIjCg1pbl9iYWNrZ3JvdW5k'
|
||||
'GAUgASgIUgxpbkJhY2tncm91bmRCCwoJSGFuZHNoYWtl');
|
||||
'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=');
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData$json = {
|
||||
|
|
@ -575,6 +715,24 @@ 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,
|
||||
|
|
@ -596,7 +754,9 @@ const ApplicationData$json = {
|
|||
ApplicationData_DeleteAccount$json,
|
||||
ApplicationData_AddAdditionalUser$json,
|
||||
ApplicationData_SetLoginToken$json,
|
||||
ApplicationData_Deprecated$json
|
||||
ApplicationData_Deprecated$json,
|
||||
ApplicationData_RegisterPasswordLessRecovery$json,
|
||||
ApplicationData_PasswordlessNotification$json
|
||||
],
|
||||
'8': [
|
||||
{'1': 'ApplicationData'},
|
||||
|
|
@ -775,6 +935,47 @@ 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'
|
||||
|
|
@ -820,27 +1021,36 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
|||
'dmVBZGRpdGlvbmFsVXNlchJjChFhZGRBZGRpdGlvbmFsVXNlchgdIAEoCzIzLmNsaWVudF90b1'
|
||||
'9zZXJ2ZXIuQXBwbGljYXRpb25EYXRhLkFkZEFkZGl0aW9uYWxVc2VySABSEWFkZEFkZGl0aW9u'
|
||||
'YWxVc2VyElkKD3NldF9sb2dpbl90b2tlbhgeIAEoCzIvLmNsaWVudF90b19zZXJ2ZXIuQXBwbG'
|
||||
'ljYXRpb25EYXRhLlNldExvZ2luVG9rZW5IAFINc2V0TG9naW5Ub2tlbhpqCgtUZXh0TWVzc2Fn'
|
||||
'ZRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSEgoEYm9keRgDIAEoDFIEYm9keRIgCglwdXNoX2'
|
||||
'RhdGEYBCABKAxIAFIIcHVzaERhdGGIAQFCDAoKX3B1c2hfZGF0YRovChFHZXRVc2VyQnlVc2Vy'
|
||||
'bmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaLAoOQ2hhbmdlVXNlcm5hbWUSGgoIdX'
|
||||
'Nlcm5hbWUYASABKAlSCHVzZXJuYW1lGjUKFFVwZGF0ZUdvb2dsZUZjbVRva2VuEh0KCmdvb2ds'
|
||||
'ZV9mY20YASABKAlSCWdvb2dsZUZjbRomCgtHZXRVc2VyQnlJZBIXCgd1c2VyX2lkGAEgASgDUg'
|
||||
'Z1c2VySWQaEwoRR2V0QXZhaWxhYmxlUGxhbnMaFwoVR2V0QWRkQWNjb3VudHNJbnZpdGVzGhUK'
|
||||
'E0dldEN1cnJlbnRQbGFuSW5mb3MaLwoUUmVtb3ZlQWRkaXRpb25hbFVzZXISFwoHdXNlcl9pZB'
|
||||
'gBIAEoA1IGdXNlcklkGi0KEkdldFByZWtleXNCeVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1'
|
||||
'c2VySWQaMgoXR2V0U2lnbmVkUHJlS2V5QnlVc2VySWQSFwoHdXNlcl9pZBgBIAEoA1IGdXNlck'
|
||||
'lkGpsBChJVcGRhdGVTaWduZWRQcmVLZXkSKAoQc2lnbmVkX3ByZWtleV9pZBgBIAEoA1IOc2ln'
|
||||
'bmVkUHJla2V5SWQSIwoNc2lnbmVkX3ByZWtleRgCIAEoDFIMc2lnbmVkUHJla2V5EjYKF3NpZ2'
|
||||
'5lZF9wcmVrZXlfc2lnbmF0dXJlGAMgASgMUhVzaWduZWRQcmVrZXlTaWduYXR1cmUaNQoMRG93'
|
||||
'bmxvYWREb25lEiUKDmRvd25sb2FkX3Rva2VuGAEgASgMUg1kb3dubG9hZFRva2VuGk4KClJlcG'
|
||||
'9ydFVzZXISKAoQcmVwb3J0ZWRfdXNlcl9pZBgBIAEoA1IOcmVwb3J0ZWRVc2VySWQSFgoGcmVh'
|
||||
'c29uGAIgASgJUgZyZWFzb24acQoLSVBBUHVyY2hhc2USHQoKcHJvZHVjdF9pZBgBIAEoCVIJcH'
|
||||
'JvZHVjdElkEhYKBnNvdXJjZRgCIAEoCVIGc291cmNlEisKEXZlcmlmaWNhdGlvbl9kYXRhGAMg'
|
||||
'ASgJUhB2ZXJpZmljYXRpb25EYXRhGg8KDUlQQUZvcmNlQ2hlY2saDwoNRGVsZXRlQWNjb3VudB'
|
||||
'osChFBZGRBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaMAoNU2V0TG9n'
|
||||
'aW5Ub2tlbhIfCgtsb2dpbl90b2tlbhgBIAEoDFIKbG9naW5Ub2tlbhoMCgpEZXByZWNhdGVkQh'
|
||||
'EKD0FwcGxpY2F0aW9uRGF0YQ==');
|
||||
'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==');
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response$json = {
|
||||
|
|
|
|||
|
|
@ -93,6 +93,10 @@ 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,
|
||||
|
|
@ -134,6 +138,8 @@ class ErrorCode extends $pb.ProtobufEnum {
|
|||
IPAPaymentExpired,
|
||||
UserIsNotInFreePlan,
|
||||
ForegroundSessionConnected,
|
||||
NoRecoveryData,
|
||||
InvalidRecoveryData,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, ErrorCode> _byValue =
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ const ErrorCode$json = {
|
|||
{'1': 'IPAPaymentExpired', '2': 1034},
|
||||
{'1': 'UserIsNotInFreePlan', '2': 1035},
|
||||
{'1': 'ForegroundSessionConnected', '2': 1036},
|
||||
{'1': 'NoRecoveryData', '2': 1037},
|
||||
{'1': 'InvalidRecoveryData', '2': 1038},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -80,4 +82,5 @@ final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode(
|
|||
'EIUIEhcKEkFwcFZlcnNpb25PdXRkYXRlZBCGCBIYChNOZXdEZXZpY2VSZWdpc3RlcmVkEIcIEh'
|
||||
'cKEkludmFsaWRQcm9vZk9mV29yaxCICBIZChRSZWdpc3RyYXRpb25EaXNhYmxlZBCJCBIWChFJ'
|
||||
'UEFQYXltZW50RXhwaXJlZBCKCBIYChNVc2VySXNOb3RJbkZyZWVQbGFuEIsIEh8KGkZvcmVncm'
|
||||
'91bmRTZXNzaW9uQ29ubmVjdGVkEIwI');
|
||||
'91bmRTZXNzaW9uQ29ubmVjdGVkEIwIEhMKDk5vUmVjb3ZlcnlEYXRhEI0IEhgKE0ludmFsaWRS'
|
||||
'ZWNvdmVyeURhdGEQjgg=');
|
||||
|
|
|
|||
|
|
@ -1505,6 +1505,136 @@ 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,
|
||||
|
|
@ -1521,6 +1651,8 @@ enum Response_Ok_Ok {
|
|||
downloadtokens,
|
||||
signedprekey,
|
||||
proofOfWork,
|
||||
passwordlessRecoveryServerKey,
|
||||
passwordlessNotificationMessages,
|
||||
notSet
|
||||
}
|
||||
|
||||
|
|
@ -1541,6 +1673,8 @@ 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;
|
||||
|
|
@ -1559,6 +1693,11 @@ 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;
|
||||
}
|
||||
|
||||
|
|
@ -1587,6 +1726,8 @@ 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(
|
||||
|
|
@ -1594,7 +1735,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])
|
||||
..oo(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17])
|
||||
..aOB(1, _omitFieldNames ? '' : 'None', protoName: 'None')
|
||||
..aInt64(2, _omitFieldNames ? '' : 'userid')
|
||||
..a<$core.List<$core.int>>(
|
||||
|
|
@ -1624,6 +1765,13 @@ 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.')
|
||||
|
|
@ -1660,6 +1808,8 @@ 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)
|
||||
|
|
@ -1676,6 +1826,8 @@ 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)
|
||||
|
|
@ -1835,6 +1987,31 @@ 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 }
|
||||
|
|
|
|||
|
|
@ -176,6 +176,8 @@ const Response$json = {
|
|||
Response_UploadToken$json,
|
||||
Response_DownloadTokens$json,
|
||||
Response_ProofOfWork$json,
|
||||
Response_PasswordlessNotificationMessage$json,
|
||||
Response_PasswordlessNotificationMessages$json,
|
||||
Response_Ok$json
|
||||
],
|
||||
'8': [
|
||||
|
|
@ -509,6 +511,36 @@ 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',
|
||||
|
|
@ -623,6 +655,23 @@ 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'},
|
||||
|
|
@ -677,22 +726,29 @@ final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
|
|||
'cKD2Rvd25sb2FkX3Rva2VucxgCIAMoDFIOZG93bmxvYWRUb2tlbnMaOQoORG93bmxvYWRUb2tl'
|
||||
'bnMSJwoPZG93bmxvYWRfdG9rZW5zGAEgAygMUg5kb3dubG9hZFRva2VucxpFCgtQcm9vZk9mV2'
|
||||
'9yaxIWCgZwcmVmaXgYASABKAlSBnByZWZpeBIeCgpkaWZmaWN1bHR5GAIgASgDUgpkaWZmaWN1'
|
||||
'bHR5GtcHCgJPaxIUCgROb25lGAEgASgISABSBE5vbmUSGAoGdXNlcmlkGAIgASgDSABSBnVzZX'
|
||||
'JpZBImCg1hdXRoY2hhbGxlbmdlGAMgASgMSABSDWF1dGhjaGFsbGVuZ2USSgoLdXBsb2FkdG9r'
|
||||
'ZW4YBCABKAsyJi5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlVwbG9hZFRva2VuSABSC3VwbG'
|
||||
'9hZHRva2VuEkEKCHVzZXJkYXRhGAUgASgLMiMuc2VydmVyX3RvX2NsaWVudC5SZXNwb25zZS5V'
|
||||
'c2VyRGF0YUgAUgh1c2VyZGF0YRIeCglhdXRodG9rZW4YBiABKAxIAFIJYXV0aHRva2VuEkoKDG'
|
||||
'RlcHJlY2F0ZWRfNxgHIAEoCzIlLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuRGVwcmVjYXRl'
|
||||
'ZEgAUgtkZXByZWNhdGVkNxJQCg1hdXRoZW50aWNhdGVkGAggASgLMiguc2VydmVyX3RvX2NsaW'
|
||||
'VudC5SZXNwb25zZS5BdXRoZW50aWNhdGVkSABSDWF1dGhlbnRpY2F0ZWQSOAoFcGxhbnMYCSAB'
|
||||
'KAsyIC5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLlBsYW5zSABSBXBsYW5zEk0KDHBsYW5iYW'
|
||||
'xsYW5jZRgKIAEoCzInLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuUGxhbkJhbGxhbmNlSABS'
|
||||
'DHBsYW5iYWxsYW5jZRJMCg1kZXByZWNhdGVkXzExGAsgASgLMiUuc2VydmVyX3RvX2NsaWVudC'
|
||||
'5SZXNwb25zZS5EZXByZWNhdGVkSABSDGRlcHJlY2F0ZWQxMRJfChJhZGRhY2NvdW50c2ludml0'
|
||||
'ZXMYDCABKAsyLS5zZXJ2ZXJfdG9fY2xpZW50LlJlc3BvbnNlLkFkZEFjY291bnRzSW52aXRlc0'
|
||||
'gAUhJhZGRhY2NvdW50c2ludml0ZXMSUwoOZG93bmxvYWR0b2tlbnMYDSABKAsyKS5zZXJ2ZXJf'
|
||||
'dG9fY2xpZW50LlJlc3BvbnNlLkRvd25sb2FkVG9rZW5zSABSDmRvd25sb2FkdG9rZW5zEk0KDH'
|
||||
'NpZ25lZHByZWtleRgOIAEoCzInLnNlcnZlcl90b19jbGllbnQuUmVzcG9uc2UuU2lnbmVkUHJl'
|
||||
'S2V5SABSDHNpZ25lZHByZWtleRJKCgtwcm9vZk9mV29yaxgPIAEoCzImLnNlcnZlcl90b19jbG'
|
||||
'llbnQuUmVzcG9uc2UuUHJvb2ZPZldvcmtIAFILcHJvb2ZPZldvcmtCBAoCT2tCCgoIUmVzcG9u'
|
||||
'c2U=');
|
||||
'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==');
|
||||
|
|
|
|||
|
|
@ -368,10 +368,12 @@ 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;
|
||||
}
|
||||
|
||||
|
|
@ -391,6 +393,7 @@ 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.')
|
||||
|
|
@ -433,6 +436,15 @@ 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 {
|
||||
|
|
@ -1827,6 +1839,154 @@ 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,
|
||||
|
|
@ -1852,6 +2012,10 @@ 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;
|
||||
|
|
@ -1884,6 +2048,12 @@ 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;
|
||||
}
|
||||
|
||||
|
|
@ -1955,6 +2125,13 @@ 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.')
|
||||
|
|
@ -2238,6 +2415,43 @@ 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 =
|
||||
|
|
|
|||
|
|
@ -79,16 +79,20 @@ 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, 3);
|
||||
$pb.ProtobufEnum.$_initByValueList(values, 4);
|
||||
static EncryptedContent_ErrorMessages_Type? valueOf($core.int value) =>
|
||||
value < 0 || value >= _byValue.length ? null : _byValue[value];
|
||||
|
||||
|
|
|
|||
|
|
@ -186,13 +186,22 @@ 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': 4,
|
||||
'9': 5,
|
||||
'10': 'messageUpdate',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -202,7 +211,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.Media',
|
||||
'9': 5,
|
||||
'9': 6,
|
||||
'10': 'media',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -212,7 +221,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.MediaUpdate',
|
||||
'9': 6,
|
||||
'9': 7,
|
||||
'10': 'mediaUpdate',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -222,7 +231,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.ContactUpdate',
|
||||
'9': 7,
|
||||
'9': 8,
|
||||
'10': 'contactUpdate',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -232,7 +241,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.ContactRequest',
|
||||
'9': 8,
|
||||
'9': 9,
|
||||
'10': 'contactRequest',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -242,7 +251,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.FlameSync',
|
||||
'9': 9,
|
||||
'9': 10,
|
||||
'10': 'flameSync',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -252,7 +261,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.PushKeys',
|
||||
'9': 10,
|
||||
'9': 11,
|
||||
'10': 'pushKeys',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -262,7 +271,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.Reaction',
|
||||
'9': 11,
|
||||
'9': 12,
|
||||
'10': 'reaction',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -272,7 +281,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.TextMessage',
|
||||
'9': 12,
|
||||
'9': 13,
|
||||
'10': 'textMessage',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -282,7 +291,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.GroupCreate',
|
||||
'9': 13,
|
||||
'9': 14,
|
||||
'10': 'groupCreate',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -292,7 +301,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.GroupJoin',
|
||||
'9': 14,
|
||||
'9': 15,
|
||||
'10': 'groupJoin',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -302,7 +311,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.GroupUpdate',
|
||||
'9': 15,
|
||||
'9': 16,
|
||||
'10': 'groupUpdate',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -312,7 +321,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.ResendGroupPublicKey',
|
||||
'9': 16,
|
||||
'9': 17,
|
||||
'10': 'resendGroupPublicKey',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -322,7 +331,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.ErrorMessages',
|
||||
'9': 17,
|
||||
'9': 18,
|
||||
'10': 'errorMessages',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -332,7 +341,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.AdditionalDataMessage',
|
||||
'9': 18,
|
||||
'9': 19,
|
||||
'10': 'additionalDataMessage',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -342,7 +351,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.TypingIndicator',
|
||||
'9': 19,
|
||||
'9': 20,
|
||||
'10': 'typingIndicator',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -352,7 +361,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.UserDiscoveryRequest',
|
||||
'9': 20,
|
||||
'9': 21,
|
||||
'10': 'userDiscoveryRequest',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -362,7 +371,7 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.UserDiscoveryUpdate',
|
||||
'9': 21,
|
||||
'9': 22,
|
||||
'10': 'userDiscoveryUpdate',
|
||||
'17': true
|
||||
},
|
||||
|
|
@ -372,10 +381,30 @@ const EncryptedContent$json = {
|
|||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.EncryptedContent.KeyVerificationProof',
|
||||
'9': 22,
|
||||
'9': 23,
|
||||
'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,
|
||||
|
|
@ -396,13 +425,16 @@ const EncryptedContent$json = {
|
|||
EncryptedContent_TypingIndicator$json,
|
||||
EncryptedContent_UserDiscoveryRequest$json,
|
||||
EncryptedContent_UserDiscoveryUpdate$json,
|
||||
EncryptedContent_KeyVerificationProof$json
|
||||
EncryptedContent_KeyVerificationProof$json,
|
||||
EncryptedContent_PasswordLessRecovery$json,
|
||||
EncryptedContent_PasswordLessRecoveryHeartbeat$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'},
|
||||
|
|
@ -422,6 +454,8 @@ const EncryptedContent$json = {
|
|||
{'1': '_user_discovery_request'},
|
||||
{'1': '_user_discovery_update'},
|
||||
{'1': '_key_verification_proof'},
|
||||
{'1': '_passwordless_recovery'},
|
||||
{'1': '_passwordless_recovery_heartbeat'},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -455,6 +489,7 @@ 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},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -464,6 +499,18 @@ 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'},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -932,109 +979,150 @@ 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'
|
||||
'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=');
|
||||
'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=');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,544 @@
|
|||
// 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');
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// 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
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
// 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==');
|
||||
|
|
@ -0,0 +1,670 @@
|
|||
// 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');
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// 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
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
// 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');
|
||||
|
|
@ -37,6 +37,7 @@ 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;
|
||||
|
|
@ -57,12 +58,15 @@ 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;
|
||||
|
|
@ -72,6 +76,7 @@ message EncryptedContent {
|
|||
// key for the state stored on the server
|
||||
bytes state_key = 3;
|
||||
bytes group_public_key = 4;
|
||||
optional string group_name = 5;
|
||||
}
|
||||
|
||||
message GroupJoin {
|
||||
|
|
@ -79,9 +84,7 @@ message EncryptedContent {
|
|||
bytes group_public_key = 1;
|
||||
}
|
||||
|
||||
message ResendGroupPublicKey {
|
||||
|
||||
}
|
||||
message ResendGroupPublicKey {}
|
||||
|
||||
message GroupUpdate {
|
||||
string group_action_type = 1; // GroupActionType.name
|
||||
|
|
@ -214,4 +217,14 @@ message EncryptedContent {
|
|||
bytes calculated_mac = 1;
|
||||
}
|
||||
|
||||
message PasswordLessRecovery {
|
||||
optional bytes recoverySecretShare = 1;
|
||||
bool delete = 2;
|
||||
int64 threshold = 3;
|
||||
}
|
||||
|
||||
message PasswordLessRecoveryHeartbeat {
|
||||
bytes hash = 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ package passwordless_recovery;
|
|||
// -> 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
|
||||
message RecoveryRequest {
|
||||
int64 temp_id = 1;
|
||||
string notification_id = 1;
|
||||
bytes public_key = 2;
|
||||
}
|
||||
|
||||
|
|
@ -36,8 +36,8 @@ message TrustedFriendShare {
|
|||
// The minimum threshold required to decrypte the shares.
|
||||
int32 threshold = 3;
|
||||
|
||||
// The actual share which will become: SecretSharedDate
|
||||
bytes share = 4;
|
||||
// The actual share which will become: SharedSecretData
|
||||
bytes shared_secret_data = 4;
|
||||
|
||||
message User {
|
||||
int64 user_id = 1;
|
||||
|
|
@ -46,41 +46,24 @@ message TrustedFriendShare {
|
|||
}
|
||||
}
|
||||
|
||||
// After received all shares this is decrypted by the user restoring its own
|
||||
message SecretSharedDate {
|
||||
|
||||
// No second factor was selected
|
||||
optional RecoveryData recovery_data = 1;
|
||||
|
||||
|
||||
// Server has
|
||||
optional SecondFactorMail second_factor_mail = 2;
|
||||
optional SecondFactorPin second_factor_pin = 3;
|
||||
|
||||
// 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
|
||||
optional bytes recovery_data_encrypted = 4;
|
||||
|
||||
|
||||
message SecondFactorPin {
|
||||
// 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.
|
||||
bytes unlock_token = 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.
|
||||
bytes pin_seed = 2;
|
||||
}
|
||||
|
||||
message SecondFactorMail {}
|
||||
|
||||
}
|
||||
|
||||
// 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.
|
||||
// The user identity keys. Can be used to restore the data backup.
|
||||
message RecoveryData {
|
||||
int64 user_id = 1;
|
||||
bytes private_key = 2;
|
||||
bytes backup_master_key = 3;
|
||||
bytes key_manager = 3;
|
||||
}
|
||||
|
||||
// After received all shares this is decrypted by the user restoring its own
|
||||
message SharedSecretData {
|
||||
|
||||
// The recovery data is encrypted in case a second factor was chosen.
|
||||
bytes recovery_data = 1;
|
||||
|
||||
optional bytes encrypted_server_key_nonce = 3;
|
||||
|
||||
optional bytes pin_seed = 4;
|
||||
optional bytes pin_unlock_token = 5;
|
||||
|
||||
optional string email_hint = 6;
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -13,7 +13,8 @@ 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.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/public_profile.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/account.view.dart';
|
||||
import 'package:twonly/src/visual/views/settings/appearance.view.dart';
|
||||
|
|
@ -39,7 +40,6 @@ 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,6 +58,13 @@ 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(
|
||||
|
|
@ -135,7 +142,13 @@ final routerProvider = GoRouter(
|
|||
GoRoute(
|
||||
path: Routes.cameraQRScanner,
|
||||
builder: (context, state) {
|
||||
return const QrCodeScannerView();
|
||||
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,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
|
|
@ -206,10 +219,6 @@ final routerProvider = GoRouter(
|
|||
path: 'user_discovery',
|
||||
builder: (context, state) => const UserDiscoverySettingsView(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'profile_selection',
|
||||
builder: (context, state) => const ProfileSelectionSettingsView(),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
|
|
@ -240,7 +249,9 @@ final routerProvider = GoRouter(
|
|||
routes: [
|
||||
GoRoute(
|
||||
path: 'verifybadge',
|
||||
builder: (context, state) => const VerificationBadeFaqView(),
|
||||
builder: (context, state) => VerificationBadeFaqView(
|
||||
contact: state.extra as Contact?,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ 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';
|
||||
|
|
@ -139,6 +140,7 @@ class ApiService {
|
|||
unawaited(fetchGroupStatesForUnjoinedGroups());
|
||||
unawaited(fetchMissingGroupPublicKey());
|
||||
unawaited(checkForDeletedUsernames());
|
||||
unawaited(PasswordlessRecoveryService.performHeartbeat());
|
||||
|
||||
unawaited(UserDiscoveryService.checkForNewAnnouncedUsers());
|
||||
|
||||
|
|
@ -684,11 +686,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', result);
|
||||
Log.error('could not request proof of work params', error: result);
|
||||
if (result.error == ErrorCode.RegistrationDisabled) {
|
||||
return (null, true);
|
||||
}
|
||||
Log.error('could not request proof of work params', result);
|
||||
Log.error('could not request proof of work params', error: result);
|
||||
return (null, false);
|
||||
}
|
||||
return (result.value.proofOfWork as Response_ProofOfWork, false);
|
||||
|
|
@ -758,6 +760,85 @@ 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;
|
||||
|
|
@ -870,4 +951,30 @@ 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@ 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(
|
||||
|
|
@ -28,6 +31,25 @@ 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),
|
||||
|
|
@ -46,6 +68,8 @@ 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}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
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,
|
||||
) async {
|
||||
Log.error('[$receiptId] Got error from $fromUserId: $error');
|
||||
String receiptId, {
|
||||
String? groupId,
|
||||
}) async {
|
||||
Log.warn('[$receiptId] Got error from $fromUserId: $error');
|
||||
|
||||
switch (error.type) {
|
||||
case EncryptedContent_ErrorMessages_Type
|
||||
|
|
@ -29,6 +36,59 @@ 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;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ 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(
|
||||
|
|
@ -43,11 +45,26 @@ Future<void> handleGroupCreate(
|
|||
stateVersionId: const Value(0),
|
||||
stateEncryptionKey: Value(Uint8List.fromList(newGroup.stateKey)),
|
||||
myGroupPrivateKey: Value(myGroupKey.serialize()),
|
||||
groupName: const Value(''),
|
||||
groupName: Value(newGroup.hasGroupName() ? newGroup.groupName : ''),
|
||||
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,
|
||||
|
|
@ -55,7 +72,6 @@ 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),
|
||||
|
|
@ -79,18 +95,8 @@ Future<void> handleGroupCreate(
|
|||
),
|
||||
);
|
||||
|
||||
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
|
||||
// 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.
|
||||
unawaited(fetchGroupStatesForUnjoinedGroups());
|
||||
|
||||
await sendCipherTextToGroup(
|
||||
|
|
@ -113,7 +119,9 @@ 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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<void> handleMedia(
|
||||
Future<bool> handleMedia(
|
||||
int fromUserId,
|
||||
String groupId,
|
||||
EncryptedContent_Media media,
|
||||
|
|
@ -36,7 +36,7 @@ Future<void> handleMedia(
|
|||
Log.warn(
|
||||
'[$receiptId] Got reupload for a message that either does not exists (${message == null}) or senderId = ${message?.senderId}',
|
||||
);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// in case there was already a downloaded file delete it...
|
||||
|
|
@ -64,7 +64,7 @@ Future<void> handleMedia(
|
|||
unawaited(startDownloadMedia(mediaFile, false));
|
||||
}
|
||||
|
||||
return;
|
||||
return true;
|
||||
case EncryptedContent_Media_Type.IMAGE:
|
||||
mediaType = MediaType.image;
|
||||
case EncryptedContent_Media_Type.VIDEO:
|
||||
|
|
@ -85,13 +85,13 @@ Future<void> handleMedia(
|
|||
Log.warn(
|
||||
'[$receiptId] $fromUserId tried to modify the message from ${messageTmp.senderId}.',
|
||||
);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (messageTmp.mediaId == null) {
|
||||
Log.warn(
|
||||
'[$receiptId] This message already exit without a mediaId. Message is dropped.',
|
||||
);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
|
||||
messageTmp.mediaId!,
|
||||
|
|
@ -100,7 +100,7 @@ Future<void> handleMedia(
|
|||
Log.warn(
|
||||
'[$receiptId] This message and media file already exit and was not requested again. Dropping it.',
|
||||
);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mediaFile != null) {
|
||||
|
|
@ -145,9 +145,13 @@ Future<void> handleMedia(
|
|||
|
||||
if (mediaFile == null) {
|
||||
Log.error('[$receiptId] Could not insert media file into database');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.info(
|
||||
'[$receiptId] Inserting media message: messageId=${media.senderMessageId}, mediaId=${mediaFile!.mediaId}',
|
||||
);
|
||||
|
||||
message = await twonlyDB.messagesDao.insertMessage(
|
||||
MessagesCompanion(
|
||||
messageId: Value(media.senderMessageId),
|
||||
|
|
@ -186,6 +190,7 @@ Future<void> handleMedia(
|
|||
);
|
||||
|
||||
unawaited(startDownloadMedia(mediaFile!, false));
|
||||
return true;
|
||||
} else {
|
||||
if (mediaFile == null && message == null) {
|
||||
Log.error(
|
||||
|
|
@ -200,6 +205,7 @@ Future<void> handleMedia(
|
|||
'[$receiptId] Could not insert new message as the message is empty.',
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<void> handleTextMessage(
|
||||
Future<bool> handleTextMessage(
|
||||
int fromUserId,
|
||||
String groupId,
|
||||
EncryptedContent_TextMessage textMessage,
|
||||
|
|
@ -26,7 +26,7 @@ Future<void> handleTextMessage(
|
|||
Log.warn(
|
||||
'[$receiptId] $fromUserId tried to overwrite message from ${existing.senderId}. Dropping.',
|
||||
);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
final message = await twonlyDB.messagesDao.insertMessage(
|
||||
|
|
@ -50,4 +50,5 @@ Future<void> handleTextMessage(
|
|||
if (message != null) {
|
||||
Log.info('[$receiptId] Inserted a new text message with ID: ${message.messageId}');
|
||||
}
|
||||
return message != null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,8 +143,12 @@ 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 {
|
||||
|
|
@ -227,7 +231,7 @@ Future<void> startDownloadMedia(MediaFile media, bool force) async {
|
|||
try {
|
||||
await downloadFileFast(media, apiUrl, mediaService.encryptedPath);
|
||||
} catch (e) {
|
||||
Log.error('Fast download failed: $e');
|
||||
Log.warn('Fast download failed: $e');
|
||||
await FileDownloader().enqueue(task);
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
@ -251,9 +255,13 @@ Future<void> downloadFileFast(
|
|||
return;
|
||||
} else {
|
||||
if (response.statusCode == 404 || response.statusCode == 403) {
|
||||
Log.error(
|
||||
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,
|
||||
);
|
||||
// Message was deleted from the server. Requesting it again from the sender to upload it again...
|
||||
await requestMediaReupload(media.mediaId);
|
||||
return;
|
||||
|
|
@ -264,6 +272,13 @@ 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) {
|
||||
|
|
@ -277,12 +292,6 @@ Future<void> requestMediaReupload(String mediaId) async {
|
|||
),
|
||||
),
|
||||
);
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
mediaId,
|
||||
const MediaFilesCompanion(
|
||||
downloadState: Value(DownloadState.reuploadRequested),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -293,7 +302,7 @@ Future<void> handleEncryptedFile(String mediaId) async {
|
|||
action: () async {
|
||||
final mediaService = await MediaFileService.fromMediaId(mediaId);
|
||||
if (mediaService == null) {
|
||||
Log.error('Media file not found in database.');
|
||||
Log.warn('[$mediaId] Media file not found in database.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -80,9 +80,13 @@ Future<void> handleUploadStatusUpdate(TaskStatusUpdate update) async {
|
|||
await markUploadAsSuccessful(media);
|
||||
return;
|
||||
}
|
||||
Log.error(
|
||||
Log.warn(
|
||||
'Got HTTP error ${update.responseStatusCode} for $mediaId',
|
||||
);
|
||||
Log.error(
|
||||
'Got HTTP error ${update.responseStatusCode} for media.',
|
||||
onlyIfSentryEnabled: true,
|
||||
);
|
||||
}
|
||||
|
||||
if (update.status == TaskStatus.notFound) {
|
||||
|
|
@ -118,9 +122,13 @@ Future<void> handleUploadStatusUpdate(TaskStatusUpdate update) async {
|
|||
|
||||
if (update.status == TaskStatus.failed ||
|
||||
update.status == TaskStatus.canceled) {
|
||||
Log.error(
|
||||
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,
|
||||
);
|
||||
final mediaService = MediaFileService(media);
|
||||
|
||||
// in case the media file is already uploaded to not reqtry
|
||||
|
|
|
|||
|
|
@ -227,9 +227,16 @@ Future<void> finishStartedPreprocessing() async {
|
|||
if (!service.originalPath.existsSync() &&
|
||||
!service.uploadRequestPath.existsSync()) {
|
||||
if (service.storedPath.existsSync()) {
|
||||
// media files was just stored..
|
||||
// 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.',
|
||||
|
|
@ -268,6 +275,7 @@ Future<void> finishStartedPreprocessing() async {
|
|||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Log.info(
|
||||
'Finishing started preprocessing of ${mediaFile.mediaId} in state ${mediaFile.uploadState}.',
|
||||
);
|
||||
|
|
@ -431,17 +439,45 @@ 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(
|
||||
'Hanlding media file ${mediaService.mediaFile.mediaId} in ${mediaService.mediaFile.uploadState}',
|
||||
'Handling 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();
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -640,7 +676,31 @@ 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,
|
||||
);
|
||||
|
|
@ -690,6 +750,13 @@ Future<void> uploadFileFastOrEnqueue(
|
|||
UploadTask task,
|
||||
MediaFileService media,
|
||||
) async {
|
||||
if (await _checkAndRecoverMissingUploadRequest(
|
||||
media,
|
||||
triggerBackgroundUpload: true,
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final requestMultipart = http.MultipartRequest(
|
||||
'POST',
|
||||
Uri.parse(task.url),
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ 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';
|
||||
|
|
@ -100,14 +101,18 @@ Future<(Uint8List, Uint8List?)?> _tryToSendCompleteMessageInternal({
|
|||
if (receiptId == null && receipt == null) return null;
|
||||
|
||||
try {
|
||||
if (receipt == null) {
|
||||
// ignore: parameter_assignments
|
||||
receipt = await twonlyDB.receiptsDao.getReceiptById(receiptId!);
|
||||
if (receipt == null) {
|
||||
Log.warn('[$receiptId] Receipt not found.');
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 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,
|
||||
|
|
@ -137,7 +142,7 @@ Future<(Uint8List, Uint8List?)?> _tryToSendCompleteMessageInternal({
|
|||
if (!onlyReturnEncryptedData &&
|
||||
receipt.ackByServerAt != null &&
|
||||
receipt.markForRetry == null) {
|
||||
Log.error('Message already uploaded and mark for retry is not set!');
|
||||
Log.info('Message already uploaded and mark for retry is not set.');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -176,7 +181,17 @@ Future<(Uint8List, Uint8List?)?> _tryToSendCompleteMessageInternal({
|
|||
Uint8List.fromList(message.encryptedContent),
|
||||
);
|
||||
if (cipherText == null) {
|
||||
Log.error('Could not encrypt the message. Aborting and trying again.');
|
||||
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);
|
||||
return null;
|
||||
}
|
||||
message.encryptedContent = cipherText.serialize();
|
||||
|
|
@ -434,7 +449,7 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
|
|||
final openReceipts = await twonlyDB.receiptsDao.getReceiptCountForContact(
|
||||
contactId,
|
||||
);
|
||||
if (openReceipts > 6) {
|
||||
if (openReceipts > 10) {
|
||||
// this prevents that these types of messages are send in case the receiver is offline
|
||||
return null;
|
||||
}
|
||||
|
|
@ -443,6 +458,19 @@ 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)) {
|
||||
|
|
@ -452,6 +480,7 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final response = pb.Message()
|
||||
..type = pb.Message_Type.CIPHERTEXT
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ 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';
|
||||
|
|
@ -55,9 +56,13 @@ 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);
|
||||
await handleClient2ClientMessage(
|
||||
newMessage,
|
||||
brokenSessionsInCurrentBatch: brokenSessionsInCurrentBatch,
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
}
|
||||
|
|
@ -82,7 +87,10 @@ DateTime lastPushKeyRequest = clock.now().subtract(const Duration(hours: 1));
|
|||
|
||||
final Map<String, Mutex> _messageLocks = {};
|
||||
|
||||
Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
|
||||
Future<void> handleClient2ClientMessage(
|
||||
NewMessage newMessage, {
|
||||
Set<int>? brokenSessionsInCurrentBatch,
|
||||
}) async {
|
||||
final body = Uint8List.fromList(newMessage.body);
|
||||
final message = Message.fromBuffer(body);
|
||||
final receiptId = message.receiptId;
|
||||
|
|
@ -96,7 +104,11 @@ Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
|
|||
}
|
||||
await mutex.protect(() async {
|
||||
try {
|
||||
await _handleClient2ClientMessage(newMessage, message);
|
||||
await _handleClient2ClientMessage(
|
||||
newMessage,
|
||||
message,
|
||||
brokenSessionsInCurrentBatch: brokenSessionsInCurrentBatch,
|
||||
);
|
||||
} finally {
|
||||
_messageLocks.remove(receiptId);
|
||||
}
|
||||
|
|
@ -105,11 +117,27 @@ Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
|
|||
|
||||
Future<void> _handleClient2ClientMessage(
|
||||
NewMessage newMessage,
|
||||
Message message,
|
||||
) async {
|
||||
Message message, {
|
||||
Set<int>? brokenSessionsInCurrentBatch,
|
||||
}) 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;
|
||||
}
|
||||
|
|
@ -199,6 +227,7 @@ Future<void> _handleClient2ClientMessage(
|
|||
encryptedContentRaw,
|
||||
message.type,
|
||||
receiptId,
|
||||
brokenSessionsInCurrentBatch: brokenSessionsInCurrentBatch,
|
||||
);
|
||||
if (plainTextContent != null) {
|
||||
response = Message(
|
||||
|
|
@ -211,6 +240,8 @@ 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
|
||||
|
|
@ -219,8 +250,9 @@ Future<void> _handleClient2ClientMessage(
|
|||
|
||||
response ??= Message(type: Message_Type.SENDER_DELIVERY_RECEIPT);
|
||||
|
||||
String? targetReceiptId;
|
||||
try {
|
||||
await twonlyDB.receiptsDao.insertReceipt(
|
||||
final inserted = await twonlyDB.receiptsDao.insertReceipt(
|
||||
ReceiptsCompanion(
|
||||
receiptId: receiptIdDB ?? Value(receiptId),
|
||||
contactId: Value(fromUserId),
|
||||
|
|
@ -228,10 +260,18 @@ 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');
|
||||
}
|
||||
await tryToSendCompleteMessage(receiptId: receiptId, blocking: false);
|
||||
if (targetReceiptId != null) {
|
||||
await tryToSendCompleteMessage(
|
||||
receiptId: targetReceiptId,
|
||||
blocking: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
case Message_Type.TEST_NOTIFICATION:
|
||||
break;
|
||||
|
|
@ -241,7 +281,11 @@ Future<void> _handleClient2ClientMessage(
|
|||
await twonlyDB.receiptsDao.gotReceipt(receiptId);
|
||||
Log.info('[$receiptId] Finished processing');
|
||||
} catch (e) {
|
||||
Log.error('[$receiptId] Error marking message as received: $e');
|
||||
Log.warn('[$receiptId] Error marking message as received: $e');
|
||||
Log.error(
|
||||
'Error marking message as received: $e',
|
||||
onlyIfSentryEnabled: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -249,13 +293,15 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessageRaw(
|
|||
int fromUserId,
|
||||
Uint8List encryptedContentRaw,
|
||||
Message_Type messageType,
|
||||
String receiptId,
|
||||
) async {
|
||||
String receiptId, {
|
||||
Set<int>? brokenSessionsInCurrentBatch,
|
||||
}) async {
|
||||
Log.info('[$receiptId] calling signalDecryptMessage');
|
||||
var (encryptedContent, decryptionErrorType) = await signalDecryptMessage(
|
||||
fromUserId,
|
||||
encryptedContentRaw,
|
||||
messageType.value,
|
||||
brokenSessionsInCurrentBatch: brokenSessionsInCurrentBatch,
|
||||
);
|
||||
|
||||
if (encryptedContent == null) {
|
||||
|
|
@ -272,7 +318,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessageRaw(
|
|||
|
||||
Log.info('[$receiptId] Calling handleEncryptedMessage');
|
||||
|
||||
final (a, b) = await handleEncryptedMessage(
|
||||
final result = await handleEncryptedMessage(
|
||||
fromUserId,
|
||||
encryptedContent,
|
||||
messageType,
|
||||
|
|
@ -281,9 +327,9 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessageRaw(
|
|||
|
||||
Log.info('[$receiptId] Finished handleEncryptedMessage');
|
||||
|
||||
if (a == null && b == null) {
|
||||
if (result.responseCipherText == null && result.responsePlaintext == null) {
|
||||
unawaited(FcmNotificationService.updateLastServerMessageTimestamp());
|
||||
if (Platform.isAndroid) {
|
||||
if (Platform.isAndroid && result.showPushNotification) {
|
||||
// Message was handled without any error. Show push notification to the user for Android.
|
||||
await showPushNotificationFromServerMessages(
|
||||
fromUserId,
|
||||
|
|
@ -292,10 +338,10 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessageRaw(
|
|||
}
|
||||
}
|
||||
|
||||
return (a, b);
|
||||
return (result.responseCipherText, result.responsePlaintext);
|
||||
}
|
||||
|
||||
Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
||||
Future<DecryptedMessageResult> handleEncryptedMessage(
|
||||
int fromUserId,
|
||||
EncryptedContent content,
|
||||
Message_Type messageType,
|
||||
|
|
@ -320,19 +366,28 @@ Future<(EncryptedContent?, PlaintextContent?)> 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 (
|
||||
null,
|
||||
PlaintextContent()
|
||||
return DecryptedMessageResult(
|
||||
responsePlaintext: PlaintextContent()
|
||||
..retryControlError = PlaintextContent_RetryErrorMessage(),
|
||||
);
|
||||
}
|
||||
return (null, null);
|
||||
return const DecryptedMessageResult();
|
||||
}
|
||||
|
||||
if (content.hasErrorMessages()) {
|
||||
|
|
@ -340,8 +395,27 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|||
fromUserId,
|
||||
content.errorMessages,
|
||||
receiptId,
|
||||
groupId: content.hasGroupId() ? content.groupId : null,
|
||||
);
|
||||
return (null, 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();
|
||||
}
|
||||
|
||||
if (content.hasContactUpdate()) {
|
||||
|
|
@ -351,7 +425,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|||
senderProfileCounter,
|
||||
receiptId,
|
||||
);
|
||||
return (null, null);
|
||||
return const DecryptedMessageResult(showPushNotification: false);
|
||||
}
|
||||
|
||||
if (content.hasUserDiscoveryRequest()) {
|
||||
|
|
@ -360,7 +434,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|||
content.userDiscoveryRequest,
|
||||
receiptId,
|
||||
);
|
||||
return (null, null);
|
||||
return const DecryptedMessageResult(showPushNotification: false);
|
||||
}
|
||||
|
||||
if (content.hasUserDiscoveryUpdate()) {
|
||||
|
|
@ -369,12 +443,12 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|||
content.userDiscoveryUpdate,
|
||||
receiptId,
|
||||
);
|
||||
return (null, null);
|
||||
return const DecryptedMessageResult();
|
||||
}
|
||||
|
||||
if (content.hasPushKeys()) {
|
||||
await handlePushKey(fromUserId, content.pushKeys, receiptId);
|
||||
return (null, null);
|
||||
return const DecryptedMessageResult();
|
||||
}
|
||||
|
||||
if (content.hasMessageUpdate()) {
|
||||
|
|
@ -383,7 +457,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|||
content.messageUpdate,
|
||||
receiptId,
|
||||
);
|
||||
return (null, null);
|
||||
return const DecryptedMessageResult();
|
||||
}
|
||||
|
||||
if (content.hasKeyVerificationProof()) {
|
||||
|
|
@ -391,7 +465,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|||
fromUserId,
|
||||
content.keyVerificationProof.calculatedMac,
|
||||
);
|
||||
return (null, null);
|
||||
return const DecryptedMessageResult();
|
||||
}
|
||||
|
||||
if (content.hasMediaUpdate()) {
|
||||
|
|
@ -400,12 +474,19 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|||
content.mediaUpdate,
|
||||
receiptId,
|
||||
);
|
||||
return (null, null);
|
||||
return const DecryptedMessageResult();
|
||||
}
|
||||
|
||||
if (!content.hasGroupId()) {
|
||||
Log.error('[$receiptId] Messages should have a groupId $fromUserId.');
|
||||
return (null, null);
|
||||
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();
|
||||
}
|
||||
|
||||
if (content.hasGroupCreate()) {
|
||||
|
|
@ -415,11 +496,12 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|||
content.groupCreate,
|
||||
receiptId,
|
||||
);
|
||||
return (null, null);
|
||||
return const DecryptedMessageResult();
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
|
@ -430,18 +512,17 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|||
);
|
||||
if (contact == null || !contact.accepted || contact.deletedByUser) {
|
||||
await handleNewContactRequest(fromUserId);
|
||||
Log.error(
|
||||
Log.warn(
|
||||
'[$receiptId] User tries to send message to direct chat while the user does not exist!',
|
||||
);
|
||||
return (
|
||||
EncryptedContent(
|
||||
return DecryptedMessageResult(
|
||||
responseCipherText: EncryptedContent(
|
||||
errorMessages: EncryptedContent_ErrorMessages(
|
||||
type: EncryptedContent_ErrorMessages_Type
|
||||
.ERROR_PROCESSING_MESSAGE_CREATED_ACCOUNT_REQUEST_INSTEAD,
|
||||
relatedReceiptId: receiptId,
|
||||
),
|
||||
),
|
||||
null,
|
||||
);
|
||||
}
|
||||
Log.info(
|
||||
|
|
@ -455,27 +536,35 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|||
);
|
||||
} else {
|
||||
if (content.hasGroupJoin()) {
|
||||
Log.error(
|
||||
Log.warn(
|
||||
'[$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 (
|
||||
null,
|
||||
PlaintextContent()
|
||||
return DecryptedMessageResult(
|
||||
responsePlaintext: PlaintextContent()
|
||||
..retryControlError = PlaintextContent_RetryErrorMessage(),
|
||||
);
|
||||
}
|
||||
|
||||
Log.error(
|
||||
'[$receiptId] User $fromUserId tried to access group ${content.groupId}.',
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
return (null, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (content.hasFlameSync()) {
|
||||
await handleFlameSync(content.groupId, content.flameSync, receiptId);
|
||||
return (null, null);
|
||||
return const DecryptedMessageResult();
|
||||
}
|
||||
|
||||
if (content.hasGroupUpdate()) {
|
||||
|
|
@ -485,7 +574,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|||
content.groupUpdate,
|
||||
receiptId,
|
||||
);
|
||||
return (null, null);
|
||||
return const DecryptedMessageResult();
|
||||
}
|
||||
|
||||
if (content.hasGroupJoin()) {
|
||||
|
|
@ -495,13 +584,12 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|||
content.groupJoin,
|
||||
receiptId,
|
||||
)) {
|
||||
return (
|
||||
null,
|
||||
PlaintextContent()
|
||||
return DecryptedMessageResult(
|
||||
responsePlaintext: PlaintextContent()
|
||||
..retryControlError = PlaintextContent_RetryErrorMessage(),
|
||||
);
|
||||
}
|
||||
return (null, null);
|
||||
return const DecryptedMessageResult();
|
||||
}
|
||||
|
||||
if (content.hasResendGroupPublicKey()) {
|
||||
|
|
@ -511,7 +599,7 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|||
content.groupJoin,
|
||||
receiptId,
|
||||
);
|
||||
return (null, null);
|
||||
return const DecryptedMessageResult();
|
||||
}
|
||||
|
||||
if (content.hasAdditionalDataMessage()) {
|
||||
|
|
@ -521,17 +609,17 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|||
content.additionalDataMessage,
|
||||
receiptId,
|
||||
);
|
||||
return (null, null);
|
||||
return const DecryptedMessageResult();
|
||||
}
|
||||
|
||||
if (content.hasTextMessage()) {
|
||||
await handleTextMessage(
|
||||
final isNewText = await handleTextMessage(
|
||||
fromUserId,
|
||||
content.groupId,
|
||||
content.textMessage,
|
||||
receiptId,
|
||||
);
|
||||
return (null, null);
|
||||
return DecryptedMessageResult(showPushNotification: isNewText);
|
||||
}
|
||||
|
||||
if (content.hasReaction()) {
|
||||
|
|
@ -541,17 +629,17 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|||
content.reaction,
|
||||
receiptId,
|
||||
);
|
||||
return (null, null);
|
||||
return const DecryptedMessageResult();
|
||||
}
|
||||
|
||||
if (content.hasMedia()) {
|
||||
await handleMedia(
|
||||
final isNewMedia = await handleMedia(
|
||||
fromUserId,
|
||||
content.groupId,
|
||||
content.media,
|
||||
receiptId,
|
||||
);
|
||||
return (null, null);
|
||||
return DecryptedMessageResult(showPushNotification: isNewMedia);
|
||||
}
|
||||
|
||||
if (content.hasTypingIndicator()) {
|
||||
|
|
@ -563,5 +651,39 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage(
|
|||
);
|
||||
}
|
||||
|
||||
return (null, null);
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,8 +105,9 @@ class BackupService {
|
|||
))) {
|
||||
final backupId = await RustBackupIdentity.getBackupId();
|
||||
if (backupId == null) {
|
||||
Log.error('No backup password was set by the user.');
|
||||
Log.warn('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 =
|
||||
|
|
@ -162,7 +163,7 @@ class BackupService {
|
|||
(backupDownloadToken, backupArchive) =
|
||||
await RustBackupArchive.createBackupArchive();
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
Log.warn('Creating archive backup failed: $e');
|
||||
return;
|
||||
}
|
||||
Log.info(
|
||||
|
|
@ -324,6 +325,26 @@ 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 {
|
||||
|
|
@ -337,7 +358,7 @@ class BackupService {
|
|||
},
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error('Error fetching backup: $e');
|
||||
Log.warn('Error fetching backup: $e');
|
||||
return (null, RecoveryError.noInternet);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ Future<bool> createNewGroup(String groupName, List<Contact> members) async {
|
|||
groupCreate: EncryptedContent_GroupCreate(
|
||||
stateKey: stateEncryptionKey,
|
||||
groupPublicKey: myGroupKey.getPublicKey().serialize(),
|
||||
groupName: group.groupName,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -770,6 +771,7 @@ Future<bool> addNewGroupMembers(
|
|||
groupCreate: EncryptedContent_GroupCreate(
|
||||
stateKey: group.stateEncryptionKey,
|
||||
groupPublicKey: keyPair.getPublicKey().serialize(),
|
||||
groupName: group.groupName,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,6 +13,8 @@ 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';
|
||||
|
|
@ -30,6 +32,12 @@ 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());
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
|
@ -13,7 +14,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/snackbar.dart';
|
||||
import 'package:twonly/src/visual/components/verification_success_dialog.comp.dart';
|
||||
|
||||
class KeyVerificationService {
|
||||
static Future<List<int>> getNewSecretVerificationToken() async {
|
||||
|
|
@ -77,12 +78,14 @@ class KeyVerificationService {
|
|||
final contact = await twonlyDB.contactsDao.getContactById(fromUserId);
|
||||
final context = rootNavigatorKey.currentContext;
|
||||
if (context != null && context.mounted && contact != null) {
|
||||
showSnackbar(
|
||||
unawaited(
|
||||
VerificationSuccessDialog.show(
|
||||
context,
|
||||
context.lang.secretQrTokenVerifiedSnackbar(
|
||||
contact,
|
||||
message: context.lang.secretQrTokenVerifiedSnackbar(
|
||||
getContactDisplayName(contact),
|
||||
),
|
||||
level: SnackbarLevel.success,
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
|
|
@ -91,6 +94,29 @@ 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(
|
||||
|
|
|
|||
|
|
@ -33,17 +33,18 @@ Future<void> compressImage(
|
|||
|
||||
Log.info('Compressed images size in bytes: ${compressedBytes.length}');
|
||||
|
||||
if (compressedBytes.length >= 1 * 1000 * 1000) {
|
||||
if (compressedBytes.length >= 2 * 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -106,11 +107,11 @@ Future<void> compressAndOverlayVideo(MediaFileService media) async {
|
|||
},
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error('during video compression: $e');
|
||||
Log.warn('during video compression: $e');
|
||||
}
|
||||
|
||||
if (compressedPath == null) {
|
||||
Log.error('Could not compress video using original video.');
|
||||
Log.warn('Could not compress video using original video.');
|
||||
// as a fall back use the non compressed version
|
||||
media.ffmpegOutputPath.copySync(media.tempPath.path);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ class MediaFileService {
|
|||
|
||||
Future<void> compressMedia() async {
|
||||
if (!originalPath.existsSync()) {
|
||||
Log.error('Could not compress as original media does not exists.');
|
||||
Log.warn('Could not compress as original media does not exists.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -296,7 +296,8 @@ 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}');
|
||||
|
|
@ -314,17 +315,21 @@ class MediaFileService {
|
|||
await tempPath.copy(storedPath.path);
|
||||
if (userService.currentUser.storeMediaFilesInGallery) {
|
||||
if (mediaFile.type == MediaType.video) {
|
||||
await saveVideoToGallery(storedPath.path);
|
||||
await saveVideoToGallery(
|
||||
storedPath.path,
|
||||
name: mediaFile.mediaId,
|
||||
);
|
||||
} else {
|
||||
await saveImageToGallery(
|
||||
storedPath.readAsBytesSync(),
|
||||
createdAt: mediaFile.createdAt,
|
||||
name: mediaFile.mediaId,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.error(
|
||||
'Could not store image neither as ${tempPath.path} does not exists.',
|
||||
Log.warn(
|
||||
'Could not store image locally as ${tempPath.path} does not exist.',
|
||||
);
|
||||
}
|
||||
unawaited(createThumbnail());
|
||||
|
|
@ -499,7 +504,7 @@ class MediaFileService {
|
|||
);
|
||||
await updateFromDB();
|
||||
} catch (e) {
|
||||
Log.error(
|
||||
Log.warn(
|
||||
'Error auto-cropping transparent borders for mediaId ${mediaFile.mediaId}: $e',
|
||||
);
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ Future<bool> createThumbnailsForImage(
|
|||
);
|
||||
return true;
|
||||
} else {
|
||||
Log.error('Compressed image thumbnail is empty or missing.');
|
||||
Log.warn('Compressed image thumbnail is empty or missing.');
|
||||
try {
|
||||
if (destinationFile.existsSync()) {
|
||||
destinationFile.deleteSync();
|
||||
|
|
|
|||
|
|
@ -158,9 +158,21 @@ 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 == 116,
|
||||
AppState.latestAppVersionId == 117,
|
||||
'Forgot to update the target version in runMigrations() after incrementing AppState.latestAppVersionId.',
|
||||
);
|
||||
assert(
|
||||
|
|
|
|||
|
|
@ -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,6 +13,7 @@ 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';
|
||||
|
|
@ -70,69 +71,6 @@ 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,
|
||||
|
|
@ -172,6 +110,32 @@ 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 == '') {
|
||||
|
|
@ -195,6 +159,7 @@ Future<void> showLocalPushNotification(
|
|||
ticker: 'You got a new message.',
|
||||
largeIcon: styleInformation,
|
||||
icon: 'ic_launcher_foreground',
|
||||
groupKey: 'com.twonly.messages',
|
||||
);
|
||||
|
||||
const darwinNotificationDetails = DarwinNotificationDetails();
|
||||
|
|
@ -226,43 +191,29 @@ Future<void> showLocalPushNotification(
|
|||
notificationDetails,
|
||||
payload: payload,
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
if (Platform.isAndroid) {
|
||||
final summaryAndroidDetails = AndroidNotificationDetails(
|
||||
'0',
|
||||
lang.notificationCategoryMessageTitle,
|
||||
channelDescription: lang.notificationCategoryMessageDesc,
|
||||
importance: Importance.max,
|
||||
priority: Priority.max,
|
||||
ticker: 'You got a new message.',
|
||||
groupKey: 'com.twonly.messages',
|
||||
setAsGroupSummary: true,
|
||||
icon: 'ic_launcher_foreground',
|
||||
);
|
||||
|
||||
const darwinNotificationDetails = DarwinNotificationDetails();
|
||||
final notificationDetails = NotificationDetails(
|
||||
android: androidNotificationDetails,
|
||||
iOS: darwinNotificationDetails,
|
||||
final summaryNotificationDetails = NotificationDetails(
|
||||
android: summaryAndroidDetails,
|
||||
);
|
||||
|
||||
await flutterLocalNotificationsPlugin.show(
|
||||
2,
|
||||
title,
|
||||
body,
|
||||
notificationDetails,
|
||||
payload: pushNotification.kind.name,
|
||||
0,
|
||||
lang.notificationCategoryMessageTitle,
|
||||
'',
|
||||
summaryNotificationDetails,
|
||||
payload: Routes.chats,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> getAvatarIcon(int contactId) async {
|
||||
|
|
|
|||
640
lib/src/services/passwordless_recovery.service.dart
Normal file
640
lib/src/services/passwordless_recovery.service.dart
Normal file
|
|
@ -0,0 +1,640 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1 @@
|
|||
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;
|
||||
}
|
||||
enum SetupProfile { standard, customized }
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ Future<CiphertextMessage?> _signalEncryptMessage(
|
|||
final session = SessionCipher.fromStore(signalStore, address);
|
||||
return await session.encrypt(plaintextContent);
|
||||
} catch (e) {
|
||||
Log.error(e.toString());
|
||||
Log.error('Could not encrypt message for target $target: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -38,8 +38,9 @@ Future<(EncryptedContent?, PlaintextContent_DecryptionErrorMessage_Type?)>
|
|||
signalDecryptMessage(
|
||||
int fromUserId,
|
||||
Uint8List encryptedContentRaw,
|
||||
int type,
|
||||
) async {
|
||||
int type, {
|
||||
Set<int>? brokenSessionsInCurrentBatch,
|
||||
}) async {
|
||||
// Hold the lock only for the cryptographic operation, not for network I/O
|
||||
Log.info('Acquiring lockingSignalProtocol for $fromUserId');
|
||||
final (
|
||||
|
|
@ -74,6 +75,7 @@ signalDecryptMessage(
|
|||
);
|
||||
}
|
||||
|
||||
recordResyncAttempt(fromUserId, success: true);
|
||||
return (EncryptedContent.fromBuffer(plaintext), null, false);
|
||||
} on InvalidKeyIdException catch (e) {
|
||||
Log.warn(e);
|
||||
|
|
@ -113,11 +115,13 @@ 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 && !resyncedUsers.contains(fromUserId)) {
|
||||
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
|
||||
resyncedUsers.add(fromUserId);
|
||||
recordResyncAttempt(fromUserId, success: false);
|
||||
|
||||
// This message contains a new PreKeyBundle establishing a new signal
|
||||
// session
|
||||
|
|
@ -131,6 +135,7 @@ signalDecryptMessage(
|
|||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (decryptedContent, errorType);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,36 @@
|
|||
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 resyncedUsers = <int>{};
|
||||
final Map<int, ({int failureCount, DateTime lastAttempt})> _resyncAttempts = {};
|
||||
|
||||
/// Reset the resync tracking set.
|
||||
void resetResyncedUsers() {
|
||||
resyncedUsers.clear();
|
||||
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).
|
||||
void resetResyncedUsers() {
|
||||
// No-op. We want the backoff state to persist across reconnects.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:twonly/locator.dart';
|
||||
|
|
@ -128,6 +130,12 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ 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));
|
||||
|
|
@ -16,6 +17,7 @@ Future<void> createPushAvatars({int? forceForUserId}) async {
|
|||
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
||||
|
||||
for (final contact in contacts) {
|
||||
try {
|
||||
if (contact.avatarSvgCompressed == null) continue;
|
||||
|
||||
if (forceForUserId == null) {
|
||||
|
|
@ -29,7 +31,10 @@ Future<void> createPushAvatars({int? forceForUserId}) async {
|
|||
|
||||
final avatarSvg = getAvatarSvg(contact.avatarSvgCompressed!);
|
||||
|
||||
final pictureInfo = await vg.loadPicture(SvgStringLoader(avatarSvg), null);
|
||||
final pictureInfo = await vg.loadPicture(
|
||||
SvgStringLoader(avatarSvg),
|
||||
null,
|
||||
);
|
||||
|
||||
final image = await pictureInfo.picture.toImage(270, 300);
|
||||
|
||||
|
|
@ -38,6 +43,9 @@ Future<void> createPushAvatars({int? forceForUserId}) async {
|
|||
|
||||
await avatarPNGFile(contact.userId).writeAsBytes(pngBytes);
|
||||
pictureInfo.picture.dispose();
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,10 +60,26 @@ File avatarPNGFile(int contactId) {
|
|||
return File('${avatarsDirectory.path}/$contactId.png');
|
||||
}
|
||||
|
||||
Future<Uint8List> getUserAvatar() async {
|
||||
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 {
|
||||
if (userService.currentUser.avatarSvg == null) {
|
||||
final data = await rootBundle.load('assets/images/default_avatar.png');
|
||||
return data.buffer.asUint8List();
|
||||
return null;
|
||||
}
|
||||
|
||||
final avatarCounter = userService.currentUser.avatarCounter;
|
||||
final file = currentUserAvatarFile(avatarCounter);
|
||||
if (file.existsSync()) {
|
||||
return file.path;
|
||||
}
|
||||
|
||||
final pictureInfo = await vg.loadPicture(
|
||||
|
|
@ -68,9 +92,8 @@ Future<Uint8List> getUserAvatar() async {
|
|||
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
final pngBytes = byteData!.buffer.asUint8List();
|
||||
|
||||
final file = avatarPNGFile(userService.currentUser.userId)
|
||||
..writeAsBytesSync(pngBytes);
|
||||
await file.writeAsBytes(pngBytes);
|
||||
pictureInfo.picture.dispose();
|
||||
|
||||
return file.readAsBytesSync();
|
||||
return file.path;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,39 @@
|
|||
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) {
|
||||
|
|
@ -65,4 +92,77 @@ 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,10 +41,14 @@ 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 {
|
||||
|
|
|
|||
|
|
@ -11,10 +11,12 @@ 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';
|
||||
|
||||
|
|
@ -36,6 +38,7 @@ extension ShortCutsExtension on BuildContext {
|
|||
Future<String?> saveImageToGallery(
|
||||
Uint8List imageBytes, {
|
||||
DateTime? createdAt,
|
||||
String? name,
|
||||
}) async {
|
||||
var bytesToProcess = imageBytes;
|
||||
|
||||
|
|
@ -75,7 +78,7 @@ Future<String?> saveImageToGallery(
|
|||
await Gal.requestAccess(toAlbum: true);
|
||||
}
|
||||
try {
|
||||
await Gal.putImageBytes(jpgImages, album: 'twonly');
|
||||
await Gal.putImageBytes(jpgImages, album: 'twonly', name: name ?? 'image');
|
||||
return null;
|
||||
} on GalException catch (e) {
|
||||
Log.error(e);
|
||||
|
|
@ -83,17 +86,45 @@ Future<String?> saveImageToGallery(
|
|||
}
|
||||
}
|
||||
|
||||
Future<String?> saveVideoToGallery(String videoPath) async {
|
||||
Future<String?> saveVideoToGallery(
|
||||
String videoPath, {
|
||||
String? name,
|
||||
}) async {
|
||||
final hasAccess = await Gal.hasAccess(toAlbum: true);
|
||||
if (!hasAccess) {
|
||||
await Gal.requestAccess(toAlbum: true);
|
||||
}
|
||||
|
||||
var pathToSave = videoPath;
|
||||
File? tempFile;
|
||||
|
||||
try {
|
||||
await Gal.putVideo(videoPath, album: 'twonly');
|
||||
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');
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -375,3 +406,43 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
87
lib/src/visual/components/add_contact_dialog.comp.dart
Normal file
87
lib/src/visual/components/add_contact_dialog.comp.dart
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -244,6 +244,7 @@ class EmojiAnimationComp extends StatelessWidget {
|
|||
return Lottie.asset(
|
||||
'assets/animated_icons/${animatedIcons[emoji]}',
|
||||
repeat: repeat,
|
||||
renderCache: RenderCache.raster,
|
||||
);
|
||||
} else if (isOneEmoji(emoji)) {
|
||||
return Text(
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/avatars.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:vector_graphics/vector_graphics.dart';
|
||||
|
||||
class AvatarIcon extends StatefulWidget {
|
||||
|
|
@ -14,12 +16,14 @@ class AvatarIcon extends StatefulWidget {
|
|||
this.contactId,
|
||||
this.myAvatar = false,
|
||||
this.fontSize = 20,
|
||||
this.svg,
|
||||
this.color,
|
||||
});
|
||||
final Group? group;
|
||||
final int? contactId;
|
||||
final bool myAvatar;
|
||||
final double? fontSize;
|
||||
final String? svg;
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
|
|
@ -28,7 +32,7 @@ class AvatarIcon extends StatefulWidget {
|
|||
|
||||
class _AvatarIconState extends State<AvatarIcon> {
|
||||
List<Contact> _avatarContacts = [];
|
||||
String? _avatarSvg;
|
||||
String? _myAvatarPath;
|
||||
|
||||
StreamSubscription<List<Contact>>? groupStream;
|
||||
StreamSubscription<List<Contact>>? contactsStream;
|
||||
|
|
@ -64,6 +68,12 @@ class _AvatarIconState extends State<AvatarIcon> {
|
|||
errorBuilder: errorBuilder,
|
||||
);
|
||||
}
|
||||
|
||||
Log.warn(
|
||||
'PNG avatar file for contact ${contact.userId} does not exist. Generating in background.',
|
||||
);
|
||||
unawaited(createPushAvatars(forceForUserId: contact.userId));
|
||||
|
||||
if (contact.avatarSvgCompressed != null) {
|
||||
return SvgPicture.string(
|
||||
getAvatarSvg(contact.avatarSvgCompressed!),
|
||||
|
|
@ -94,19 +104,9 @@ class _AvatarIconState extends State<AvatarIcon> {
|
|||
});
|
||||
} else if (widget.myAvatar) {
|
||||
_userSub = userService.onUserUpdated.listen((_) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
if (userService.currentUser.avatarSvg != null) {
|
||||
_avatarSvg = userService.currentUser.avatarSvg;
|
||||
} else {
|
||||
_avatarContacts = [];
|
||||
}
|
||||
unawaited(_updateMyAvatar());
|
||||
});
|
||||
}
|
||||
});
|
||||
if (userService.currentUser.avatarSvg != null) {
|
||||
_avatarSvg = userService.currentUser.avatarSvg;
|
||||
}
|
||||
unawaited(_updateMyAvatar());
|
||||
} else if (widget.contactId != null) {
|
||||
contactStream = twonlyDB.contactsDao
|
||||
.watchContact(widget.contactId!)
|
||||
|
|
@ -120,17 +120,49 @@ class _AvatarIconState extends State<AvatarIcon> {
|
|||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<void> _updateMyAvatar() async {
|
||||
final avatarSvg = userService.currentUser.avatarSvg;
|
||||
if (avatarSvg == null) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_myAvatarPath = null;
|
||||
_avatarContacts = [];
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final path = await getUserAvatar();
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_myAvatarPath = path;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final proSize = (widget.fontSize == null) ? 40 : (widget.fontSize! * 2);
|
||||
|
||||
Widget avatars = Container();
|
||||
|
||||
if (_avatarSvg != null) {
|
||||
if (widget.svg != null) {
|
||||
avatars = SvgPicture.string(
|
||||
_avatarSvg!,
|
||||
widget.svg!,
|
||||
errorBuilder: errorBuilder,
|
||||
);
|
||||
} else if (widget.myAvatar) {
|
||||
if (_myAvatarPath != null) {
|
||||
avatars = Image.file(
|
||||
File(_myAvatarPath!),
|
||||
errorBuilder: errorBuilder,
|
||||
);
|
||||
} else {
|
||||
avatars = const SvgPicture(
|
||||
AssetBytesLoader('assets/images/default_avatar.svg.vec'),
|
||||
);
|
||||
}
|
||||
} else if (_avatarContacts.length == 1) {
|
||||
avatars = getAvatarForContact(_avatarContacts.first);
|
||||
} else if (_avatarContacts.length >= 2) {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue