mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-10-09 02:54:14 +00:00
commit
09bc880401
33 changed files with 4008 additions and 290 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -57,3 +57,4 @@ rust/target
|
|||
rust_dependencies/target
|
||||
fastlane/repo/status/running.json
|
||||
.cache/
|
||||
APKS/*.apk
|
||||
|
|
|
|||
4
.gitmodules
vendored
4
.gitmodules
vendored
|
|
@ -3,7 +3,3 @@
|
|||
path = dependencies
|
||||
# url = ssh://git@git.twonly.eu:22222/twonly/twonly-app-dependencies.git
|
||||
url = https://git.twonly.eu/twonly/twonly-app-dependencies.git
|
||||
[submodule "lib/src/localization/translations"]
|
||||
path = lib/src/localization/translations
|
||||
# url = ssh://git@git.twonly.eu:22222/twonly/twonly-translations.git
|
||||
url = https://git.twonly.eu/twonly/twonly-translations
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
# Changelog
|
||||
|
||||
## 0.5.3
|
||||
|
||||
- Hotfix: Fixed stuck Signal sessions
|
||||
- Improve: Gracefully upgrade all sessions to PQXDH
|
||||
|
||||
## 0.5.2
|
||||
|
||||
- Hotfix: Fixed an infinite loop involving delivery receipts
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/app-check.git",
|
||||
"state" : {
|
||||
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
|
||||
"version" : "11.2.0"
|
||||
"revision" : "97f7d74dd0e8f3d0fe5fc75cc8957aa73bdc948a",
|
||||
"version" : "11.3.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -23,8 +23,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/firebase/firebase-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "8d5b4189f1f482df8d5c58c9985ea70491ef5382",
|
||||
"version" : "12.14.0"
|
||||
"revision" : "346daa9f46316aa372b35b317e18224acc2e9063",
|
||||
"version" : "12.18.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -50,8 +50,8 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/google/GoogleAppMeasurement.git",
|
||||
"state" : {
|
||||
"revision" : "219e564a8510e983e675c94f77f7f7c50049f22d",
|
||||
"version" : "12.14.0"
|
||||
"revision" : "f04760d460296cc0fa430935a7be212e5bd67fc5",
|
||||
"version" : "12.18.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
ce49e7d90cd902197f9a9cbc84219d23
|
||||
|
|
@ -47,6 +47,14 @@ class RustSignal {
|
|||
bundle: bundle,
|
||||
);
|
||||
|
||||
/// Removes the current v2 session for a contact. The Dart hotfix layer
|
||||
/// immediately establishes its replacement from a freshly fetched bundle.
|
||||
static Future<void> resetContactSession({required PlatformInt64 contactId}) =>
|
||||
RustLib.instance.api
|
||||
.crateBridgeWrapperSignalRustSignalResetContactSession(
|
||||
contactId: contactId,
|
||||
);
|
||||
|
||||
@override
|
||||
int get hashCode => 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
|||
String get codegenVersion => '2.12.0';
|
||||
|
||||
@override
|
||||
int get rustContentHash => 1109927244;
|
||||
int get rustContentHash => -2042422055;
|
||||
|
||||
static const kDefaultExternalLibraryLoaderConfig =
|
||||
ExternalLibraryLoaderConfig(
|
||||
|
|
@ -284,6 +284,10 @@ abstract class RustLibApi extends BaseApi {
|
|||
required FrbPreKeyBundle bundle,
|
||||
});
|
||||
|
||||
Future<void> crateBridgeWrapperSignalRustSignalResetContactSession({
|
||||
required PlatformInt64 contactId,
|
||||
});
|
||||
|
||||
Future<List<Uint8List>> crateBridgeWrapperRustUtilsGenerateShares({
|
||||
required List<int> secret,
|
||||
required int total,
|
||||
|
|
@ -1853,6 +1857,41 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
argNames: ["name", "deviceId", "bundle"],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> crateBridgeWrapperSignalRustSignalResetContactSession({
|
||||
required PlatformInt64 contactId,
|
||||
}) {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
sse_encode_i_64(contactId, serializer);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 37,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_unit,
|
||||
decodeErrorData: sse_decode_AnyhowException,
|
||||
),
|
||||
constMeta:
|
||||
kCrateBridgeWrapperSignalRustSignalResetContactSessionConstMeta,
|
||||
argValues: [contactId],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta
|
||||
get kCrateBridgeWrapperSignalRustSignalResetContactSessionConstMeta =>
|
||||
const TaskConstMeta(
|
||||
debugName: "rust_signal_reset_contact_session",
|
||||
argNames: ["contactId"],
|
||||
);
|
||||
|
||||
@override
|
||||
Future<List<Uint8List>> crateBridgeWrapperRustUtilsGenerateShares({
|
||||
required List<int> secret,
|
||||
|
|
@ -1869,7 +1908,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 37,
|
||||
funcId: 38,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1904,7 +1943,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 38,
|
||||
funcId: 39,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1940,7 +1979,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 39,
|
||||
funcId: 40,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -1977,7 +2016,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 40,
|
||||
funcId: 41,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2015,7 +2054,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 41,
|
||||
funcId: 42,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2053,7 +2092,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 42,
|
||||
funcId: 43,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2091,7 +2130,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 43,
|
||||
funcId: 44,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2130,7 +2169,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 44,
|
||||
funcId: 45,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2169,7 +2208,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 45,
|
||||
funcId: 46,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2214,7 +2253,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 46,
|
||||
funcId: 47,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2266,7 +2305,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 47,
|
||||
funcId: 48,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2307,7 +2346,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 48,
|
||||
funcId: 49,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2345,7 +2384,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 49,
|
||||
funcId: 50,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2383,7 +2422,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 50,
|
||||
funcId: 51,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2421,7 +2460,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 51,
|
||||
funcId: 52,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2459,7 +2498,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 52,
|
||||
funcId: 53,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2501,7 +2540,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 53,
|
||||
funcId: 54,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
@ -2541,7 +2580,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
|||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 54,
|
||||
funcId: 55,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -150,6 +150,43 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
|||
.get();
|
||||
}
|
||||
|
||||
/// Finds contacts for which repeated, unacknowledged message delivery is a
|
||||
/// strong indication that only the outgoing Signal ratchet is broken.
|
||||
Future<List<int>> getContactsWithStuckSignalMessages({
|
||||
required int retryThreshold,
|
||||
int minimumMessages = 2,
|
||||
}) async {
|
||||
final messageCount = countAll();
|
||||
final query = selectOnly(receipts)
|
||||
..addColumns([receipts.contactId, messageCount])
|
||||
..where(
|
||||
receipts.retryCount.isBiggerThanValue(retryThreshold) &
|
||||
receipts.willBeRetriedByMediaUpload.equals(false),
|
||||
)
|
||||
..groupBy([receipts.contactId]);
|
||||
|
||||
final rows = await query.get();
|
||||
return rows
|
||||
.where((row) => (row.read(messageCount) ?? 0) >= minimumMessages)
|
||||
.map((row) => row.read(receipts.contactId)!)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Starts retry accounting again after establishing a fresh Signal session.
|
||||
Future<void> resetMessageRetryCountsForContact(int contactId) async {
|
||||
await (update(receipts)..where(
|
||||
(receipt) =>
|
||||
receipt.contactId.equals(contactId) &
|
||||
receipt.willBeRetriedByMediaUpload.equals(false),
|
||||
))
|
||||
.write(
|
||||
const ReceiptsCompanion(
|
||||
retryCount: Value(0),
|
||||
lastRetry: Value(null),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<Receipt>> getReceiptsForMediaRetransmissions() async {
|
||||
final markedRetriesTime = clock.now().subtract(
|
||||
const Duration(
|
||||
|
|
|
|||
|
|
@ -635,13 +635,13 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @settingsNotifyResetTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Didn\'t receive a test notification?'**
|
||||
/// **'Reset notification tokens'**
|
||||
String get settingsNotifyResetTitle;
|
||||
|
||||
/// No description provided for @settingsNotifyResetTitleSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'If you haven\'t received any test notifications, click here to reset your notification tokens.'**
|
||||
/// **'Reset your notification tokens if you have problems receiving push notifications.'**
|
||||
String get settingsNotifyResetTitleSubtitle;
|
||||
|
||||
/// No description provided for @settingsNotifyResetTitleReset.
|
||||
|
|
@ -956,6 +956,42 @@ abstract class AppLocalizations {
|
|||
/// **'Block'**
|
||||
String get contactBlock;
|
||||
|
||||
/// No description provided for @contactTroubleshoot.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Troubleshoot connection'**
|
||||
String get contactTroubleshoot;
|
||||
|
||||
/// No description provided for @contactResetSessionTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Reset connection with {username}?'**
|
||||
String contactResetSessionTitle(String username);
|
||||
|
||||
/// No description provided for @contactResetSessionBody.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'twonly will remove the current encrypted connection and establish a new one. Messages currently in transit may be lost.'**
|
||||
String get contactResetSessionBody;
|
||||
|
||||
/// No description provided for @contactResetSessionConfirm.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Reset connection'**
|
||||
String get contactResetSessionConfirm;
|
||||
|
||||
/// No description provided for @contactResetSessionSuccess.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The encrypted connection with {username} was re-established.'**
|
||||
String contactResetSessionSuccess(String username);
|
||||
|
||||
/// No description provided for @contactResetSessionFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The connection could not be reset. Check your internet connection and try again.'**
|
||||
String get contactResetSessionFailed;
|
||||
|
||||
/// No description provided for @contactBlockTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -1226,6 +1262,18 @@ abstract class AppLocalizations {
|
|||
/// **'You are part of the paid plan of {username}!'**
|
||||
String partOfPaidPlanOf(Object username);
|
||||
|
||||
/// No description provided for @partOfPaidPlanFrom.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Part of the paid plan of'**
|
||||
String get partOfPaidPlanFrom;
|
||||
|
||||
/// No description provided for @subscriptionCurrentPlanBadge.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Current'**
|
||||
String get subscriptionCurrentPlanBadge;
|
||||
|
||||
/// No description provided for @year.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -1253,67 +1301,67 @@ abstract class AppLocalizations {
|
|||
/// No description provided for @proFeature1.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'✓ Unlimited media file uploads'**
|
||||
/// **'Unlimited media file uploads'**
|
||||
String get proFeature1;
|
||||
|
||||
/// No description provided for @proFeature2.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'✓ 1 additional Plus user'**
|
||||
/// **'1 additional Plus user'**
|
||||
String get proFeature2;
|
||||
|
||||
/// No description provided for @proFeature3.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'✓ 25GB Memories storage'**
|
||||
/// **'25GB Memories storage'**
|
||||
String get proFeature3;
|
||||
|
||||
/// No description provided for @proFeature4.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'✓ Restore flames'**
|
||||
/// **'Restore flames'**
|
||||
String get proFeature4;
|
||||
|
||||
/// No description provided for @familyFeature1.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'✓ Unlimited media file uploads'**
|
||||
/// **'Unlimited media file uploads'**
|
||||
String get familyFeature1;
|
||||
|
||||
/// No description provided for @familyFeature2.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'✓ 4 additional Plus user'**
|
||||
/// **'4 additional Plus user'**
|
||||
String get familyFeature2;
|
||||
|
||||
/// No description provided for @familyFeature3.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'✓ 50GB Memories storage'**
|
||||
/// **'50GB Memories storage'**
|
||||
String get familyFeature3;
|
||||
|
||||
/// No description provided for @familyFeature4.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'✓ Support twonly'**
|
||||
/// **'Restore flames'**
|
||||
String get familyFeature4;
|
||||
|
||||
/// No description provided for @freeFeature1.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'✓ 10 Media file uploads per day'**
|
||||
/// **'10 Media file uploads per day'**
|
||||
String get freeFeature1;
|
||||
|
||||
/// No description provided for @plusFeature1.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'✓ Unlimited media file uploads'**
|
||||
/// **'Unlimited media file uploads'**
|
||||
String get plusFeature1;
|
||||
|
||||
/// No description provided for @plusFeature2.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'✓ Additional features (coming-soon)'**
|
||||
/// **'Additional features (coming-soon)'**
|
||||
String get plusFeature2;
|
||||
|
||||
/// No description provided for @manageAdditionalUsers.
|
||||
|
|
@ -1838,6 +1886,12 @@ abstract class AppLocalizations {
|
|||
/// **'Edit history'**
|
||||
String get editHistory;
|
||||
|
||||
/// No description provided for @fileSize.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'File size'**
|
||||
String get fileSize;
|
||||
|
||||
/// No description provided for @archivedChats.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -2228,6 +2282,12 @@ abstract class AppLocalizations {
|
|||
/// **'Messages from other users.'**
|
||||
String get notificationCategoryMessageDesc;
|
||||
|
||||
/// No description provided for @notificationConnectionFallback.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You may have new messages.'**
|
||||
String get notificationConnectionFallback;
|
||||
|
||||
/// No description provided for @groupContextMenuDeleteGroup.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -4537,6 +4597,42 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'Exporting memories...'**
|
||||
String get memoriesExportingProgress;
|
||||
|
||||
/// No description provided for @fileLimitReachedTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'File too large to send'**
|
||||
String get fileLimitReachedTitle;
|
||||
|
||||
/// No description provided for @fileLimitReachedDetail.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'This file is {size}, but your plan allows at most {limit} per send.'**
|
||||
String fileLimitReachedDetail(String size, String limit);
|
||||
|
||||
/// No description provided for @fileLimitReachedDetailNoSize.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Your plan allows at most {limit} per send.'**
|
||||
String fileLimitReachedDetailNoSize(String limit);
|
||||
|
||||
/// No description provided for @fileLimitReachedHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Record a shorter video and send it again.'**
|
||||
String get fileLimitReachedHint;
|
||||
|
||||
/// No description provided for @fileLimitReachedHintFree.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Record a shorter video, or upgrade your plan to send larger files.'**
|
||||
String get fileLimitReachedHintFree;
|
||||
|
||||
/// No description provided for @fileLimitReachedUpgrade.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Upgrade plan'**
|
||||
String get fileLimitReachedUpgrade;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -294,11 +294,11 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
'Um eine Testbenachrichtigung zu erhalten, klicke auf OK. Falls du die Testbenachrichtigung nicht erhältst, klicke bitte auf den neuen Menüpunkt, der nach dem Klicken auf „OK“ angezeigt wird.';
|
||||
|
||||
@override
|
||||
String get settingsNotifyResetTitle => 'Keine Testbenachrichtigung erhalten?';
|
||||
String get settingsNotifyResetTitle => 'Benachrichtigungstoken zurücksetzen';
|
||||
|
||||
@override
|
||||
String get settingsNotifyResetTitleSubtitle =>
|
||||
'Falls du keine Testbenachrichtigungen erhalten hast, klicke hier, um deine Benachrichtigungstoken zurückzusetzen.';
|
||||
'Setze deine Benachrichtigungstoken zurück, wenn du Probleme beim Empfang von Push-Benachrichtigungen hast.';
|
||||
|
||||
@override
|
||||
String get settingsNotifyResetTitleReset =>
|
||||
|
|
@ -475,6 +475,30 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get contactBlock => 'Blockieren';
|
||||
|
||||
@override
|
||||
String get contactTroubleshoot => 'Verbindungsprobleme beheben';
|
||||
|
||||
@override
|
||||
String contactResetSessionTitle(String username) {
|
||||
return 'Verbindung zu $username zurücksetzen?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactResetSessionBody =>
|
||||
'twonly löscht die aktuelle verschlüsselte Verbindung und baut sie anschließend neu auf. Nachrichten, die gerade übertragen werden, können dabei verloren gehen.';
|
||||
|
||||
@override
|
||||
String get contactResetSessionConfirm => 'Verbindung zurücksetzen';
|
||||
|
||||
@override
|
||||
String contactResetSessionSuccess(String username) {
|
||||
return 'Die verschlüsselte Verbindung zu $username wurde neu aufgebaut.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactResetSessionFailed =>
|
||||
'Die Verbindung konnte nicht zurückgesetzt werden. Prüfe deine Internetverbindung und versuche es erneut.';
|
||||
|
||||
@override
|
||||
String contactBlockTitle(Object username) {
|
||||
return 'Blockiere $username';
|
||||
|
|
@ -633,6 +657,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
return 'Du bist Teil des bezahlten Plans von $username!';
|
||||
}
|
||||
|
||||
@override
|
||||
String get partOfPaidPlanFrom => 'Teil des bezahlten Plans von';
|
||||
|
||||
@override
|
||||
String get subscriptionCurrentPlanBadge => 'Aktuell';
|
||||
|
||||
@override
|
||||
String get year => 'Jahr';
|
||||
|
||||
|
|
@ -646,37 +676,37 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get monthly => 'Monatlich';
|
||||
|
||||
@override
|
||||
String get proFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads';
|
||||
String get proFeature1 => 'Unbegrenzte Medien-Datei-Uploads';
|
||||
|
||||
@override
|
||||
String get proFeature2 => '✓ 1 zusätzlicher Plus Benutzer';
|
||||
String get proFeature2 => '1 zusätzlicher Plus Benutzer';
|
||||
|
||||
@override
|
||||
String get proFeature3 => '✓ 25GB Memories Speicher';
|
||||
String get proFeature3 => '25GB Memories Speicher';
|
||||
|
||||
@override
|
||||
String get proFeature4 => '✓ Flammen wiederherstellen';
|
||||
String get proFeature4 => 'Flammen wiederherstellen';
|
||||
|
||||
@override
|
||||
String get familyFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads';
|
||||
String get familyFeature1 => 'Unbegrenzte Medien-Datei-Uploads';
|
||||
|
||||
@override
|
||||
String get familyFeature2 => '✓ 4 zusätzliche Plus Benutzer';
|
||||
String get familyFeature2 => '4 zusätzliche Plus Benutzer';
|
||||
|
||||
@override
|
||||
String get familyFeature3 => '✓ 50GB Memories Speicher';
|
||||
String get familyFeature3 => '50GB Memories Speicher';
|
||||
|
||||
@override
|
||||
String get familyFeature4 => '✓ Flammen wiederherstellen';
|
||||
String get familyFeature4 => 'Flammen wiederherstellen';
|
||||
|
||||
@override
|
||||
String get freeFeature1 => '✓ 10 Medien-Datei-Uploads pro Tag';
|
||||
String get freeFeature1 => '10 Medien-Datei-Uploads pro Tag';
|
||||
|
||||
@override
|
||||
String get plusFeature1 => '✓ Unbegrenzte Medien-Datei-Uploads';
|
||||
String get plusFeature1 => 'Unbegrenzte Medien-Datei-Uploads';
|
||||
|
||||
@override
|
||||
String get plusFeature2 => '✓ Zusatzfunktionen (coming-soon)';
|
||||
String get plusFeature2 => 'Zusatzfunktionen (coming-soon)';
|
||||
|
||||
@override
|
||||
String get manageAdditionalUsers => 'Zusätzliche Benutzer verwalten';
|
||||
|
|
@ -983,6 +1013,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get editHistory => 'Bearbeitungshistorie';
|
||||
|
||||
@override
|
||||
String get fileSize => 'Dateigröße';
|
||||
|
||||
@override
|
||||
String get archivedChats => 'Archivierte Chats';
|
||||
|
||||
|
|
@ -1241,6 +1274,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get notificationCategoryMessageDesc =>
|
||||
'Nachrichten von anderen Benutzern.';
|
||||
|
||||
@override
|
||||
String get notificationConnectionFallback =>
|
||||
'Du könntest neue Nachrichten haben.';
|
||||
|
||||
@override
|
||||
String get groupContextMenuDeleteGroup =>
|
||||
'Dadurch werden alle Nachrichten in diesem Chat dauerhaft gelöscht.';
|
||||
|
|
@ -2622,4 +2659,28 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get memoriesExportingProgress => 'Erinnerungen werden exportiert...';
|
||||
|
||||
@override
|
||||
String get fileLimitReachedTitle => 'Datei zu groß zum Senden';
|
||||
|
||||
@override
|
||||
String fileLimitReachedDetail(String size, String limit) {
|
||||
return 'Diese Datei ist $size groß, dein Tarif erlaubt aber höchstens $limit pro Sendung.';
|
||||
}
|
||||
|
||||
@override
|
||||
String fileLimitReachedDetailNoSize(String limit) {
|
||||
return 'Dein Tarif erlaubt höchstens $limit pro Sendung.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get fileLimitReachedHint =>
|
||||
'Nimm ein kürzeres Video auf und sende es erneut.';
|
||||
|
||||
@override
|
||||
String get fileLimitReachedHintFree =>
|
||||
'Nimm ein kürzeres Video auf oder wechsle den Tarif, um größere Dateien zu senden.';
|
||||
|
||||
@override
|
||||
String get fileLimitReachedUpgrade => 'Tarif wechseln';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -290,11 +290,11 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
'Press OK to receive a test notification. If you do not receive the test notification, please click on the new menu item that appears after you click “OK”.';
|
||||
|
||||
@override
|
||||
String get settingsNotifyResetTitle => 'Didn\'t receive a test notification?';
|
||||
String get settingsNotifyResetTitle => 'Reset notification tokens';
|
||||
|
||||
@override
|
||||
String get settingsNotifyResetTitleSubtitle =>
|
||||
'If you haven\'t received any test notifications, click here to reset your notification tokens.';
|
||||
'Reset your notification tokens if you have problems receiving push notifications.';
|
||||
|
||||
@override
|
||||
String get settingsNotifyResetTitleReset =>
|
||||
|
|
@ -471,6 +471,30 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get contactBlock => 'Block';
|
||||
|
||||
@override
|
||||
String get contactTroubleshoot => 'Troubleshoot connection';
|
||||
|
||||
@override
|
||||
String contactResetSessionTitle(String username) {
|
||||
return 'Reset connection with $username?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactResetSessionBody =>
|
||||
'twonly will remove the current encrypted connection and establish a new one. Messages currently in transit may be lost.';
|
||||
|
||||
@override
|
||||
String get contactResetSessionConfirm => 'Reset connection';
|
||||
|
||||
@override
|
||||
String contactResetSessionSuccess(String username) {
|
||||
return 'The encrypted connection with $username was re-established.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactResetSessionFailed =>
|
||||
'The connection could not be reset. Check your internet connection and try again.';
|
||||
|
||||
@override
|
||||
String contactBlockTitle(Object username) {
|
||||
return 'Block $username';
|
||||
|
|
@ -628,6 +652,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
return 'You are part of the paid plan of $username!';
|
||||
}
|
||||
|
||||
@override
|
||||
String get partOfPaidPlanFrom => 'Part of the paid plan of';
|
||||
|
||||
@override
|
||||
String get subscriptionCurrentPlanBadge => 'Current';
|
||||
|
||||
@override
|
||||
String get year => 'year';
|
||||
|
||||
|
|
@ -641,37 +671,37 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get monthly => 'Monthly';
|
||||
|
||||
@override
|
||||
String get proFeature1 => '✓ Unlimited media file uploads';
|
||||
String get proFeature1 => 'Unlimited media file uploads';
|
||||
|
||||
@override
|
||||
String get proFeature2 => '✓ 1 additional Plus user';
|
||||
String get proFeature2 => '1 additional Plus user';
|
||||
|
||||
@override
|
||||
String get proFeature3 => '✓ 25GB Memories storage';
|
||||
String get proFeature3 => '25GB Memories storage';
|
||||
|
||||
@override
|
||||
String get proFeature4 => '✓ Restore flames';
|
||||
String get proFeature4 => 'Restore flames';
|
||||
|
||||
@override
|
||||
String get familyFeature1 => '✓ Unlimited media file uploads';
|
||||
String get familyFeature1 => 'Unlimited media file uploads';
|
||||
|
||||
@override
|
||||
String get familyFeature2 => '✓ 4 additional Plus user';
|
||||
String get familyFeature2 => '4 additional Plus user';
|
||||
|
||||
@override
|
||||
String get familyFeature3 => '✓ 50GB Memories storage';
|
||||
String get familyFeature3 => '50GB Memories storage';
|
||||
|
||||
@override
|
||||
String get familyFeature4 => '✓ Support twonly';
|
||||
String get familyFeature4 => 'Restore flames';
|
||||
|
||||
@override
|
||||
String get freeFeature1 => '✓ 10 Media file uploads per day';
|
||||
String get freeFeature1 => '10 Media file uploads per day';
|
||||
|
||||
@override
|
||||
String get plusFeature1 => '✓ Unlimited media file uploads';
|
||||
String get plusFeature1 => 'Unlimited media file uploads';
|
||||
|
||||
@override
|
||||
String get plusFeature2 => '✓ Additional features (coming-soon)';
|
||||
String get plusFeature2 => 'Additional features (coming-soon)';
|
||||
|
||||
@override
|
||||
String get manageAdditionalUsers => 'Manage additional users';
|
||||
|
|
@ -976,6 +1006,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get editHistory => 'Edit history';
|
||||
|
||||
@override
|
||||
String get fileSize => 'File size';
|
||||
|
||||
@override
|
||||
String get archivedChats => 'Archived chats';
|
||||
|
||||
|
|
@ -1233,6 +1266,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get notificationCategoryMessageDesc => 'Messages from other users.';
|
||||
|
||||
@override
|
||||
String get notificationConnectionFallback => 'You may have new messages.';
|
||||
|
||||
@override
|
||||
String get groupContextMenuDeleteGroup =>
|
||||
'This will permanently delete all messages in this chat.';
|
||||
|
|
@ -2597,4 +2633,28 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get memoriesExportingProgress => 'Exporting memories...';
|
||||
|
||||
@override
|
||||
String get fileLimitReachedTitle => 'File too large to send';
|
||||
|
||||
@override
|
||||
String fileLimitReachedDetail(String size, String limit) {
|
||||
return 'This file is $size, but your plan allows at most $limit per send.';
|
||||
}
|
||||
|
||||
@override
|
||||
String fileLimitReachedDetailNoSize(String limit) {
|
||||
return 'Your plan allows at most $limit per send.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get fileLimitReachedHint =>
|
||||
'Record a shorter video and send it again.';
|
||||
|
||||
@override
|
||||
String get fileLimitReachedHintFree =>
|
||||
'Record a shorter video, or upgrade your plan to send larger files.';
|
||||
|
||||
@override
|
||||
String get fileLimitReachedUpgrade => 'Upgrade plan';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 50cc5cb69dd91a5c626534fe4c015e50d2a0a486
|
||||
1017
lib/src/localization/translations/de.arb
Normal file
1017
lib/src/localization/translations/de.arb
Normal file
File diff suppressed because it is too large
Load diff
1027
lib/src/localization/translations/en.arb
Normal file
1027
lib/src/localization/translations/en.arb
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -259,6 +259,7 @@ class Handshake_Register extends $pb.GeneratedMessage {
|
|||
$core.String? langCode,
|
||||
$fixnum.Int64? proofOfWork,
|
||||
$core.List<$core.int>? loginToken,
|
||||
Handshake_InitialPqcKeys? initialPqcKeys,
|
||||
}) {
|
||||
final result = create();
|
||||
if (username != null) result.username = username;
|
||||
|
|
@ -273,6 +274,7 @@ class Handshake_Register extends $pb.GeneratedMessage {
|
|||
if (langCode != null) result.langCode = langCode;
|
||||
if (proofOfWork != null) result.proofOfWork = proofOfWork;
|
||||
if (loginToken != null) result.loginToken = loginToken;
|
||||
if (initialPqcKeys != null) result.initialPqcKeys = initialPqcKeys;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -305,6 +307,8 @@ class Handshake_Register extends $pb.GeneratedMessage {
|
|||
..aInt64(10, _omitFieldNames ? '' : 'proofOfWork')
|
||||
..a<$core.List<$core.int>>(
|
||||
11, _omitFieldNames ? '' : 'loginToken', $pb.PbFieldType.OY)
|
||||
..aOM<Handshake_InitialPqcKeys>(12, _omitFieldNames ? '' : 'initialPqcKeys',
|
||||
subBuilder: Handshake_InitialPqcKeys.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -344,6 +348,8 @@ class Handshake_Register extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(2)
|
||||
void clearInviteCode() => $_clearField(2);
|
||||
|
||||
/// Deprecated legacy Signal fields. The server ignores these fields; they
|
||||
/// remain allocated only to avoid reusing their wire tags.
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<$core.int> get publicIdentityKey => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
|
|
@ -425,6 +431,287 @@ class Handshake_Register extends $pb.GeneratedMessage {
|
|||
$core.bool hasLoginToken() => $_has(10);
|
||||
@$pb.TagNumber(11)
|
||||
void clearLoginToken() => $_clearField(11);
|
||||
|
||||
@$pb.TagNumber(12)
|
||||
Handshake_InitialPqcKeys get initialPqcKeys => $_getN(11);
|
||||
@$pb.TagNumber(12)
|
||||
set initialPqcKeys(Handshake_InitialPqcKeys value) => $_setField(12, value);
|
||||
@$pb.TagNumber(12)
|
||||
$core.bool hasInitialPqcKeys() => $_has(11);
|
||||
@$pb.TagNumber(12)
|
||||
void clearInitialPqcKeys() => $_clearField(12);
|
||||
@$pb.TagNumber(12)
|
||||
Handshake_InitialPqcKeys ensureInitialPqcKeys() => $_ensure(11);
|
||||
}
|
||||
|
||||
class Handshake_InitialPqcKeys_PqcPreKey extends $pb.GeneratedMessage {
|
||||
factory Handshake_InitialPqcKeys_PqcPreKey({
|
||||
$fixnum.Int64? eccPreKeyId,
|
||||
$core.List<$core.int>? eccPreKey,
|
||||
$fixnum.Int64? kyberPreKeyId,
|
||||
$core.List<$core.int>? kyberPreKey,
|
||||
$core.List<$core.int>? kyberPreKeySignature,
|
||||
}) {
|
||||
final result = create();
|
||||
if (eccPreKeyId != null) result.eccPreKeyId = eccPreKeyId;
|
||||
if (eccPreKey != null) result.eccPreKey = eccPreKey;
|
||||
if (kyberPreKeyId != null) result.kyberPreKeyId = kyberPreKeyId;
|
||||
if (kyberPreKey != null) result.kyberPreKey = kyberPreKey;
|
||||
if (kyberPreKeySignature != null)
|
||||
result.kyberPreKeySignature = kyberPreKeySignature;
|
||||
return result;
|
||||
}
|
||||
|
||||
Handshake_InitialPqcKeys_PqcPreKey._();
|
||||
|
||||
factory Handshake_InitialPqcKeys_PqcPreKey.fromBuffer(
|
||||
$core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory Handshake_InitialPqcKeys_PqcPreKey.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Handshake.InitialPqcKeys.PqcPreKey',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||
createEmptyInstance: create)
|
||||
..aInt64(1, _omitFieldNames ? '' : 'eccPreKeyId')
|
||||
..a<$core.List<$core.int>>(
|
||||
2, _omitFieldNames ? '' : 'eccPreKey', $pb.PbFieldType.OY)
|
||||
..aInt64(3, _omitFieldNames ? '' : 'kyberPreKeyId')
|
||||
..a<$core.List<$core.int>>(
|
||||
4, _omitFieldNames ? '' : 'kyberPreKey', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(
|
||||
5, _omitFieldNames ? '' : 'kyberPreKeySignature', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Handshake_InitialPqcKeys_PqcPreKey clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Handshake_InitialPqcKeys_PqcPreKey copyWith(
|
||||
void Function(Handshake_InitialPqcKeys_PqcPreKey) updates) =>
|
||||
super.copyWith((message) =>
|
||||
updates(message as Handshake_InitialPqcKeys_PqcPreKey))
|
||||
as Handshake_InitialPqcKeys_PqcPreKey;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Handshake_InitialPqcKeys_PqcPreKey create() =>
|
||||
Handshake_InitialPqcKeys_PqcPreKey._();
|
||||
@$core.override
|
||||
Handshake_InitialPqcKeys_PqcPreKey createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Handshake_InitialPqcKeys_PqcPreKey getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<Handshake_InitialPqcKeys_PqcPreKey>(
|
||||
create);
|
||||
static Handshake_InitialPqcKeys_PqcPreKey? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$fixnum.Int64 get eccPreKeyId => $_getI64(0);
|
||||
@$pb.TagNumber(1)
|
||||
set eccPreKeyId($fixnum.Int64 value) => $_setInt64(0, value);
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasEccPreKeyId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearEccPreKeyId() => $_clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get eccPreKey => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set eccPreKey($core.List<$core.int> value) => $_setBytes(1, value);
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasEccPreKey() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearEccPreKey() => $_clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$fixnum.Int64 get kyberPreKeyId => $_getI64(2);
|
||||
@$pb.TagNumber(3)
|
||||
set kyberPreKeyId($fixnum.Int64 value) => $_setInt64(2, value);
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasKyberPreKeyId() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearKyberPreKeyId() => $_clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.List<$core.int> get kyberPreKey => $_getN(3);
|
||||
@$pb.TagNumber(4)
|
||||
set kyberPreKey($core.List<$core.int> value) => $_setBytes(3, value);
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasKyberPreKey() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearKyberPreKey() => $_clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.List<$core.int> get kyberPreKeySignature => $_getN(4);
|
||||
@$pb.TagNumber(5)
|
||||
set kyberPreKeySignature($core.List<$core.int> value) => $_setBytes(4, value);
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasKyberPreKeySignature() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearKyberPreKeySignature() => $_clearField(5);
|
||||
}
|
||||
|
||||
class Handshake_InitialPqcKeys extends $pb.GeneratedMessage {
|
||||
factory Handshake_InitialPqcKeys({
|
||||
$core.List<$core.int>? publicIdentityKey,
|
||||
$fixnum.Int64? registrationId,
|
||||
$fixnum.Int64? eccSignedPrekeyId,
|
||||
$core.List<$core.int>? eccSignedPrekey,
|
||||
$core.List<$core.int>? eccSignedPrekeySignature,
|
||||
$fixnum.Int64? kyberSignedPrekeyId,
|
||||
$core.List<$core.int>? kyberSignedPrekey,
|
||||
$core.List<$core.int>? kyberSignedPrekeySignature,
|
||||
$core.Iterable<Handshake_InitialPqcKeys_PqcPreKey>? prekeys,
|
||||
}) {
|
||||
final result = create();
|
||||
if (publicIdentityKey != null) result.publicIdentityKey = publicIdentityKey;
|
||||
if (registrationId != null) result.registrationId = registrationId;
|
||||
if (eccSignedPrekeyId != null) result.eccSignedPrekeyId = eccSignedPrekeyId;
|
||||
if (eccSignedPrekey != null) result.eccSignedPrekey = eccSignedPrekey;
|
||||
if (eccSignedPrekeySignature != null)
|
||||
result.eccSignedPrekeySignature = eccSignedPrekeySignature;
|
||||
if (kyberSignedPrekeyId != null)
|
||||
result.kyberSignedPrekeyId = kyberSignedPrekeyId;
|
||||
if (kyberSignedPrekey != null) result.kyberSignedPrekey = kyberSignedPrekey;
|
||||
if (kyberSignedPrekeySignature != null)
|
||||
result.kyberSignedPrekeySignature = kyberSignedPrekeySignature;
|
||||
if (prekeys != null) result.prekeys.addAll(prekeys);
|
||||
return result;
|
||||
}
|
||||
|
||||
Handshake_InitialPqcKeys._();
|
||||
|
||||
factory Handshake_InitialPqcKeys.fromBuffer($core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory Handshake_InitialPqcKeys.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Handshake.InitialPqcKeys',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||
createEmptyInstance: create)
|
||||
..a<$core.List<$core.int>>(
|
||||
1, _omitFieldNames ? '' : 'publicIdentityKey', $pb.PbFieldType.OY)
|
||||
..aInt64(2, _omitFieldNames ? '' : 'registrationId')
|
||||
..aInt64(3, _omitFieldNames ? '' : 'eccSignedPrekeyId')
|
||||
..a<$core.List<$core.int>>(
|
||||
4, _omitFieldNames ? '' : 'eccSignedPrekey', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(5,
|
||||
_omitFieldNames ? '' : 'eccSignedPrekeySignature', $pb.PbFieldType.OY)
|
||||
..aInt64(6, _omitFieldNames ? '' : 'kyberSignedPrekeyId')
|
||||
..a<$core.List<$core.int>>(
|
||||
7, _omitFieldNames ? '' : 'kyberSignedPrekey', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(8,
|
||||
_omitFieldNames ? '' : 'kyberSignedPrekeySignature', $pb.PbFieldType.OY)
|
||||
..pPM<Handshake_InitialPqcKeys_PqcPreKey>(
|
||||
9, _omitFieldNames ? '' : 'prekeys',
|
||||
subBuilder: Handshake_InitialPqcKeys_PqcPreKey.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Handshake_InitialPqcKeys clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Handshake_InitialPqcKeys copyWith(
|
||||
void Function(Handshake_InitialPqcKeys) updates) =>
|
||||
super.copyWith((message) => updates(message as Handshake_InitialPqcKeys))
|
||||
as Handshake_InitialPqcKeys;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Handshake_InitialPqcKeys create() => Handshake_InitialPqcKeys._();
|
||||
@$core.override
|
||||
Handshake_InitialPqcKeys createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Handshake_InitialPqcKeys getDefault() => _defaultInstance ??=
|
||||
$pb.GeneratedMessage.$_defaultFor<Handshake_InitialPqcKeys>(create);
|
||||
static Handshake_InitialPqcKeys? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<$core.int> get publicIdentityKey => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set publicIdentityKey($core.List<$core.int> value) => $_setBytes(0, value);
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasPublicIdentityKey() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearPublicIdentityKey() => $_clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$fixnum.Int64 get registrationId => $_getI64(1);
|
||||
@$pb.TagNumber(2)
|
||||
set registrationId($fixnum.Int64 value) => $_setInt64(1, value);
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasRegistrationId() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearRegistrationId() => $_clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$fixnum.Int64 get eccSignedPrekeyId => $_getI64(2);
|
||||
@$pb.TagNumber(3)
|
||||
set eccSignedPrekeyId($fixnum.Int64 value) => $_setInt64(2, value);
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasEccSignedPrekeyId() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearEccSignedPrekeyId() => $_clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.List<$core.int> get eccSignedPrekey => $_getN(3);
|
||||
@$pb.TagNumber(4)
|
||||
set eccSignedPrekey($core.List<$core.int> value) => $_setBytes(3, value);
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasEccSignedPrekey() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearEccSignedPrekey() => $_clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.List<$core.int> get eccSignedPrekeySignature => $_getN(4);
|
||||
@$pb.TagNumber(5)
|
||||
set eccSignedPrekeySignature($core.List<$core.int> value) =>
|
||||
$_setBytes(4, value);
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasEccSignedPrekeySignature() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearEccSignedPrekeySignature() => $_clearField(5);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$fixnum.Int64 get kyberSignedPrekeyId => $_getI64(5);
|
||||
@$pb.TagNumber(6)
|
||||
set kyberSignedPrekeyId($fixnum.Int64 value) => $_setInt64(5, value);
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasKyberSignedPrekeyId() => $_has(5);
|
||||
@$pb.TagNumber(6)
|
||||
void clearKyberSignedPrekeyId() => $_clearField(6);
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
$core.List<$core.int> get kyberSignedPrekey => $_getN(6);
|
||||
@$pb.TagNumber(7)
|
||||
set kyberSignedPrekey($core.List<$core.int> value) => $_setBytes(6, value);
|
||||
@$pb.TagNumber(7)
|
||||
$core.bool hasKyberSignedPrekey() => $_has(6);
|
||||
@$pb.TagNumber(7)
|
||||
void clearKyberSignedPrekey() => $_clearField(7);
|
||||
|
||||
@$pb.TagNumber(8)
|
||||
$core.List<$core.int> get kyberSignedPrekeySignature => $_getN(7);
|
||||
@$pb.TagNumber(8)
|
||||
set kyberSignedPrekeySignature($core.List<$core.int> value) =>
|
||||
$_setBytes(7, value);
|
||||
@$pb.TagNumber(8)
|
||||
$core.bool hasKyberSignedPrekeySignature() => $_has(7);
|
||||
@$pb.TagNumber(8)
|
||||
void clearKyberSignedPrekeySignature() => $_clearField(8);
|
||||
|
||||
@$pb.TagNumber(9)
|
||||
$pb.PbList<Handshake_InitialPqcKeys_PqcPreKey> get prekeys => $_getList(8);
|
||||
}
|
||||
|
||||
class Handshake_GetAuthChallenge extends $pb.GeneratedMessage {
|
||||
|
|
@ -602,6 +889,7 @@ class Handshake_Authenticate extends $pb.GeneratedMessage {
|
|||
$core.String? appVersion,
|
||||
$fixnum.Int64? deviceId,
|
||||
$core.bool? inBackground,
|
||||
$core.bool? supportsMailboxV2,
|
||||
}) {
|
||||
final result = create();
|
||||
if (userId != null) result.userId = userId;
|
||||
|
|
@ -609,6 +897,7 @@ class Handshake_Authenticate extends $pb.GeneratedMessage {
|
|||
if (appVersion != null) result.appVersion = appVersion;
|
||||
if (deviceId != null) result.deviceId = deviceId;
|
||||
if (inBackground != null) result.inBackground = inBackground;
|
||||
if (supportsMailboxV2 != null) result.supportsMailboxV2 = supportsMailboxV2;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -632,6 +921,7 @@ class Handshake_Authenticate extends $pb.GeneratedMessage {
|
|||
..aOS(3, _omitFieldNames ? '' : 'appVersion')
|
||||
..aInt64(4, _omitFieldNames ? '' : 'deviceId')
|
||||
..aOB(5, _omitFieldNames ? '' : 'inBackground')
|
||||
..aOB(6, _omitFieldNames ? '' : 'supportsMailboxV2')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -698,6 +988,17 @@ class Handshake_Authenticate extends $pb.GeneratedMessage {
|
|||
$core.bool hasInBackground() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearInBackground() => $_clearField(5);
|
||||
|
||||
/// Set by clients that understand PendingMessagesV2 / RequestPendingMessages.
|
||||
/// Absent or false means the legacy NewMessage(s) mailbox protocol is used.
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool get supportsMailboxV2 => $_getBF(5);
|
||||
@$pb.TagNumber(6)
|
||||
set supportsMailboxV2($core.bool value) => $_setBool(5, value);
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasSupportsMailboxV2() => $_has(5);
|
||||
@$pb.TagNumber(6)
|
||||
void clearSupportsMailboxV2() => $_clearField(6);
|
||||
}
|
||||
|
||||
class Handshake_AuthenticateWithLoginToken extends $pb.GeneratedMessage {
|
||||
|
|
@ -707,6 +1008,7 @@ class Handshake_AuthenticateWithLoginToken extends $pb.GeneratedMessage {
|
|||
$core.String? appVersion,
|
||||
$fixnum.Int64? deviceId,
|
||||
$core.bool? inBackground,
|
||||
$core.bool? supportsMailboxV2,
|
||||
}) {
|
||||
final result = create();
|
||||
if (userId != null) result.userId = userId;
|
||||
|
|
@ -714,6 +1016,7 @@ class Handshake_AuthenticateWithLoginToken extends $pb.GeneratedMessage {
|
|||
if (appVersion != null) result.appVersion = appVersion;
|
||||
if (deviceId != null) result.deviceId = deviceId;
|
||||
if (inBackground != null) result.inBackground = inBackground;
|
||||
if (supportsMailboxV2 != null) result.supportsMailboxV2 = supportsMailboxV2;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -738,6 +1041,7 @@ class Handshake_AuthenticateWithLoginToken extends $pb.GeneratedMessage {
|
|||
..aOS(3, _omitFieldNames ? '' : 'appVersion')
|
||||
..aInt64(4, _omitFieldNames ? '' : 'deviceId')
|
||||
..aOB(5, _omitFieldNames ? '' : 'inBackground')
|
||||
..aOB(6, _omitFieldNames ? '' : 'supportsMailboxV2')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -807,6 +1111,16 @@ class Handshake_AuthenticateWithLoginToken extends $pb.GeneratedMessage {
|
|||
$core.bool hasInBackground() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearInBackground() => $_clearField(5);
|
||||
|
||||
/// See Authenticate.supports_mailbox_v2.
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool get supportsMailboxV2 => $_getBF(5);
|
||||
@$pb.TagNumber(6)
|
||||
set supportsMailboxV2($core.bool value) => $_setBool(5, value);
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasSupportsMailboxV2() => $_has(5);
|
||||
@$pb.TagNumber(6)
|
||||
void clearSupportsMailboxV2() => $_clearField(6);
|
||||
}
|
||||
|
||||
class Handshake_GetServerKeyForPasswordLessRecovery
|
||||
|
|
@ -1388,11 +1702,13 @@ class ApplicationData_TextMessage extends $pb.GeneratedMessage {
|
|||
$fixnum.Int64? userId,
|
||||
$core.List<$core.int>? body,
|
||||
$core.List<$core.int>? pushData,
|
||||
$core.bool? wakeReceiver,
|
||||
}) {
|
||||
final result = create();
|
||||
if (userId != null) result.userId = userId;
|
||||
if (body != null) result.body = body;
|
||||
if (pushData != null) result.pushData = pushData;
|
||||
if (wakeReceiver != null) result.wakeReceiver = wakeReceiver;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -1415,6 +1731,7 @@ class ApplicationData_TextMessage extends $pb.GeneratedMessage {
|
|||
3, _omitFieldNames ? '' : 'body', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(
|
||||
4, _omitFieldNames ? '' : 'pushData', $pb.PbFieldType.OY)
|
||||
..aOB(5, _omitFieldNames ? '' : 'wakeReceiver')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -1465,6 +1782,15 @@ class ApplicationData_TextMessage extends $pb.GeneratedMessage {
|
|||
$core.bool hasPushData() => $_has(2);
|
||||
@$pb.TagNumber(4)
|
||||
void clearPushData() => $_clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool get wakeReceiver => $_getBF(3);
|
||||
@$pb.TagNumber(5)
|
||||
set wakeReceiver($core.bool value) => $_setBool(3, value);
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasWakeReceiver() => $_has(3);
|
||||
@$pb.TagNumber(5)
|
||||
void clearWakeReceiver() => $_clearField(5);
|
||||
}
|
||||
|
||||
class ApplicationData_GetUserByUsername extends $pb.GeneratedMessage {
|
||||
|
|
@ -2176,6 +2502,8 @@ class ApplicationData_UploadPqcPreKeys extends $pb.GeneratedMessage {
|
|||
$core.List<$core.int>? kyberSignedPrekey,
|
||||
$core.List<$core.int>? kyberSignedPrekeySignature,
|
||||
$core.Iterable<ApplicationData_PqcPreKey>? prekeys,
|
||||
$core.List<$core.int>? publicIdentityKey,
|
||||
$fixnum.Int64? registrationId,
|
||||
}) {
|
||||
final result = create();
|
||||
if (eccSignedPrekeyId != null) result.eccSignedPrekeyId = eccSignedPrekeyId;
|
||||
|
|
@ -2188,6 +2516,8 @@ class ApplicationData_UploadPqcPreKeys extends $pb.GeneratedMessage {
|
|||
if (kyberSignedPrekeySignature != null)
|
||||
result.kyberSignedPrekeySignature = kyberSignedPrekeySignature;
|
||||
if (prekeys != null) result.prekeys.addAll(prekeys);
|
||||
if (publicIdentityKey != null) result.publicIdentityKey = publicIdentityKey;
|
||||
if (registrationId != null) result.registrationId = registrationId;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -2218,6 +2548,9 @@ class ApplicationData_UploadPqcPreKeys extends $pb.GeneratedMessage {
|
|||
_omitFieldNames ? '' : 'kyberSignedPrekeySignature', $pb.PbFieldType.OY)
|
||||
..pPM<ApplicationData_PqcPreKey>(7, _omitFieldNames ? '' : 'prekeys',
|
||||
subBuilder: ApplicationData_PqcPreKey.create)
|
||||
..a<$core.List<$core.int>>(
|
||||
8, _omitFieldNames ? '' : 'publicIdentityKey', $pb.PbFieldType.OY)
|
||||
..aInt64(9, _omitFieldNames ? '' : 'registrationId')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -2301,6 +2634,26 @@ class ApplicationData_UploadPqcPreKeys extends $pb.GeneratedMessage {
|
|||
|
||||
@$pb.TagNumber(7)
|
||||
$pb.PbList<ApplicationData_PqcPreKey> get prekeys => $_getList(6);
|
||||
|
||||
/// Required when uploading keys for the first time after a keyless
|
||||
/// registration. Optional for later key refreshes.
|
||||
@$pb.TagNumber(8)
|
||||
$core.List<$core.int> get publicIdentityKey => $_getN(7);
|
||||
@$pb.TagNumber(8)
|
||||
set publicIdentityKey($core.List<$core.int> value) => $_setBytes(7, value);
|
||||
@$pb.TagNumber(8)
|
||||
$core.bool hasPublicIdentityKey() => $_has(7);
|
||||
@$pb.TagNumber(8)
|
||||
void clearPublicIdentityKey() => $_clearField(8);
|
||||
|
||||
@$pb.TagNumber(9)
|
||||
$fixnum.Int64 get registrationId => $_getI64(8);
|
||||
@$pb.TagNumber(9)
|
||||
set registrationId($fixnum.Int64 value) => $_setInt64(8, value);
|
||||
@$pb.TagNumber(9)
|
||||
$core.bool hasRegistrationId() => $_has(8);
|
||||
@$pb.TagNumber(9)
|
||||
void clearRegistrationId() => $_clearField(9);
|
||||
}
|
||||
|
||||
class ApplicationData_DownloadDone extends $pb.GeneratedMessage {
|
||||
|
|
@ -3301,6 +3654,52 @@ class ApplicationData_DisableMemoriesBackup extends $pb.GeneratedMessage {
|
|||
static ApplicationData_DisableMemoriesBackup? _defaultInstance;
|
||||
}
|
||||
|
||||
/// Asks the server to (re)start a mailbox drain for the authenticated user.
|
||||
/// The server responds immediately; messages arrive as PendingMessagesV2.
|
||||
class ApplicationData_RequestPendingMessages extends $pb.GeneratedMessage {
|
||||
factory ApplicationData_RequestPendingMessages() => create();
|
||||
|
||||
ApplicationData_RequestPendingMessages._();
|
||||
|
||||
factory ApplicationData_RequestPendingMessages.fromBuffer(
|
||||
$core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory ApplicationData_RequestPendingMessages.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'ApplicationData.RequestPendingMessages',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||
createEmptyInstance: create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
ApplicationData_RequestPendingMessages clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
ApplicationData_RequestPendingMessages copyWith(
|
||||
void Function(ApplicationData_RequestPendingMessages) updates) =>
|
||||
super.copyWith((message) =>
|
||||
updates(message as ApplicationData_RequestPendingMessages))
|
||||
as ApplicationData_RequestPendingMessages;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ApplicationData_RequestPendingMessages create() =>
|
||||
ApplicationData_RequestPendingMessages._();
|
||||
@$core.override
|
||||
ApplicationData_RequestPendingMessages createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ApplicationData_RequestPendingMessages getDefault() =>
|
||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
|
||||
ApplicationData_RequestPendingMessages>(create);
|
||||
static ApplicationData_RequestPendingMessages? _defaultInstance;
|
||||
}
|
||||
|
||||
enum ApplicationData_ApplicationData {
|
||||
textMessage,
|
||||
getUserByUsername,
|
||||
|
|
@ -3330,6 +3729,7 @@ enum ApplicationData_ApplicationData {
|
|||
deleteMemory,
|
||||
disableMemoriesBackup,
|
||||
uploadPqcPrekeys,
|
||||
requestPendingMessages,
|
||||
notSet
|
||||
}
|
||||
|
||||
|
|
@ -3363,6 +3763,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
ApplicationData_DeleteMemory? deleteMemory,
|
||||
ApplicationData_DisableMemoriesBackup? disableMemoriesBackup,
|
||||
ApplicationData_UploadPqcPreKeys? uploadPqcPrekeys,
|
||||
ApplicationData_RequestPendingMessages? requestPendingMessages,
|
||||
}) {
|
||||
final result = create();
|
||||
if (textMessage != null) result.textMessage = textMessage;
|
||||
|
|
@ -3404,6 +3805,8 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
if (disableMemoriesBackup != null)
|
||||
result.disableMemoriesBackup = disableMemoriesBackup;
|
||||
if (uploadPqcPrekeys != null) result.uploadPqcPrekeys = uploadPqcPrekeys;
|
||||
if (requestPendingMessages != null)
|
||||
result.requestPendingMessages = requestPendingMessages;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -3446,6 +3849,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
38: ApplicationData_ApplicationData.deleteMemory,
|
||||
39: ApplicationData_ApplicationData.disableMemoriesBackup,
|
||||
40: ApplicationData_ApplicationData.uploadPqcPrekeys,
|
||||
43: ApplicationData_ApplicationData.requestPendingMessages,
|
||||
0: ApplicationData_ApplicationData.notSet
|
||||
};
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
|
|
@ -3481,7 +3885,8 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
37,
|
||||
38,
|
||||
39,
|
||||
40
|
||||
40,
|
||||
43
|
||||
])
|
||||
..aOM<ApplicationData_TextMessage>(1, _omitFieldNames ? '' : 'textMessage',
|
||||
protoName: 'textMessage',
|
||||
|
|
@ -3579,6 +3984,9 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
..aOM<ApplicationData_UploadPqcPreKeys>(
|
||||
40, _omitFieldNames ? '' : 'uploadPqcPrekeys',
|
||||
subBuilder: ApplicationData_UploadPqcPreKeys.create)
|
||||
..aOM<ApplicationData_RequestPendingMessages>(
|
||||
43, _omitFieldNames ? '' : 'requestPendingMessages',
|
||||
subBuilder: ApplicationData_RequestPendingMessages.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -3628,6 +4036,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(38)
|
||||
@$pb.TagNumber(39)
|
||||
@$pb.TagNumber(40)
|
||||
@$pb.TagNumber(43)
|
||||
ApplicationData_ApplicationData whichApplicationData() =>
|
||||
_ApplicationData_ApplicationDataByTag[$_whichOneof(0)]!;
|
||||
@$pb.TagNumber(1)
|
||||
|
|
@ -3658,6 +4067,7 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(38)
|
||||
@$pb.TagNumber(39)
|
||||
@$pb.TagNumber(40)
|
||||
@$pb.TagNumber(43)
|
||||
void clearApplicationData() => $_clearField($_whichOneof(0));
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
|
|
@ -4003,6 +4413,20 @@ class ApplicationData extends $pb.GeneratedMessage {
|
|||
void clearUploadPqcPrekeys() => $_clearField(40);
|
||||
@$pb.TagNumber(40)
|
||||
ApplicationData_UploadPqcPreKeys ensureUploadPqcPrekeys() => $_ensure(27);
|
||||
|
||||
@$pb.TagNumber(43)
|
||||
ApplicationData_RequestPendingMessages get requestPendingMessages =>
|
||||
$_getN(28);
|
||||
@$pb.TagNumber(43)
|
||||
set requestPendingMessages(ApplicationData_RequestPendingMessages value) =>
|
||||
$_setField(43, value);
|
||||
@$pb.TagNumber(43)
|
||||
$core.bool hasRequestPendingMessages() => $_has(28);
|
||||
@$pb.TagNumber(43)
|
||||
void clearRequestPendingMessages() => $_clearField(43);
|
||||
@$pb.TagNumber(43)
|
||||
ApplicationData_RequestPendingMessages ensureRequestPendingMessages() =>
|
||||
$_ensure(28);
|
||||
}
|
||||
|
||||
class Response_PreKey extends $pb.GeneratedMessage {
|
||||
|
|
@ -4173,18 +4597,84 @@ class Response_PqcPrekeys extends $pb.GeneratedMessage {
|
|||
$pb.PbList<ApplicationData_PqcPreKey> get prekeys => $_getList(0);
|
||||
}
|
||||
|
||||
enum Response_Ok_Ok { none, prekeys, prekeysPqc, notSet }
|
||||
/// Response to a PendingMessagesV2 batch. Contains only the delivery IDs the
|
||||
/// client has durably persisted; the server deletes exactly those rows.
|
||||
class Response_AcknowledgedPendingMessages extends $pb.GeneratedMessage {
|
||||
factory Response_AcknowledgedPendingMessages({
|
||||
$core.Iterable<$fixnum.Int64>? deliveryIds,
|
||||
}) {
|
||||
final result = create();
|
||||
if (deliveryIds != null) result.deliveryIds.addAll(deliveryIds);
|
||||
return result;
|
||||
}
|
||||
|
||||
Response_AcknowledgedPendingMessages._();
|
||||
|
||||
factory Response_AcknowledgedPendingMessages.fromBuffer(
|
||||
$core.List<$core.int> data,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(data, registry);
|
||||
factory Response_AcknowledgedPendingMessages.fromJson($core.String json,
|
||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(json, registry);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Response.AcknowledgedPendingMessages',
|
||||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||
createEmptyInstance: create)
|
||||
..p<$fixnum.Int64>(
|
||||
1, _omitFieldNames ? '' : 'deliveryIds', $pb.PbFieldType.K6)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_AcknowledgedPendingMessages clone() => deepCopy();
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
Response_AcknowledgedPendingMessages copyWith(
|
||||
void Function(Response_AcknowledgedPendingMessages) updates) =>
|
||||
super.copyWith((message) =>
|
||||
updates(message as Response_AcknowledgedPendingMessages))
|
||||
as Response_AcknowledgedPendingMessages;
|
||||
|
||||
@$core.override
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_AcknowledgedPendingMessages create() =>
|
||||
Response_AcknowledgedPendingMessages._();
|
||||
@$core.override
|
||||
Response_AcknowledgedPendingMessages createEmptyInstance() => create();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Response_AcknowledgedPendingMessages getDefault() =>
|
||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<
|
||||
Response_AcknowledgedPendingMessages>(create);
|
||||
static Response_AcknowledgedPendingMessages? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$pb.PbList<$fixnum.Int64> get deliveryIds => $_getList(0);
|
||||
}
|
||||
|
||||
enum Response_Ok_Ok {
|
||||
none,
|
||||
prekeys,
|
||||
prekeysPqc,
|
||||
acknowledgedPendingMessages,
|
||||
notSet
|
||||
}
|
||||
|
||||
class Response_Ok extends $pb.GeneratedMessage {
|
||||
factory Response_Ok({
|
||||
$core.bool? none,
|
||||
Response_Prekeys? prekeys,
|
||||
Response_PqcPrekeys? prekeysPqc,
|
||||
Response_AcknowledgedPendingMessages? acknowledgedPendingMessages,
|
||||
}) {
|
||||
final result = create();
|
||||
if (none != null) result.none = none;
|
||||
if (prekeys != null) result.prekeys = prekeys;
|
||||
if (prekeysPqc != null) result.prekeysPqc = prekeysPqc;
|
||||
if (acknowledgedPendingMessages != null)
|
||||
result.acknowledgedPendingMessages = acknowledgedPendingMessages;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -4201,6 +4691,7 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
1: Response_Ok_Ok.none,
|
||||
2: Response_Ok_Ok.prekeys,
|
||||
3: Response_Ok_Ok.prekeysPqc,
|
||||
4: Response_Ok_Ok.acknowledgedPendingMessages,
|
||||
0: Response_Ok_Ok.notSet
|
||||
};
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
|
|
@ -4208,12 +4699,15 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'client_to_server'),
|
||||
createEmptyInstance: create)
|
||||
..oo(0, [1, 2, 3])
|
||||
..oo(0, [1, 2, 3, 4])
|
||||
..aOB(1, _omitFieldNames ? '' : 'None', protoName: 'None')
|
||||
..aOM<Response_Prekeys>(2, _omitFieldNames ? '' : 'prekeys',
|
||||
subBuilder: Response_Prekeys.create)
|
||||
..aOM<Response_PqcPrekeys>(3, _omitFieldNames ? '' : 'prekeysPqc',
|
||||
subBuilder: Response_PqcPrekeys.create)
|
||||
..aOM<Response_AcknowledgedPendingMessages>(
|
||||
4, _omitFieldNames ? '' : 'acknowledgedPendingMessages',
|
||||
subBuilder: Response_AcknowledgedPendingMessages.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -4238,10 +4732,12 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(1)
|
||||
@$pb.TagNumber(2)
|
||||
@$pb.TagNumber(3)
|
||||
@$pb.TagNumber(4)
|
||||
Response_Ok_Ok whichOk() => _Response_Ok_OkByTag[$_whichOneof(0)]!;
|
||||
@$pb.TagNumber(1)
|
||||
@$pb.TagNumber(2)
|
||||
@$pb.TagNumber(3)
|
||||
@$pb.TagNumber(4)
|
||||
void clearOk() => $_clearField($_whichOneof(0));
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
|
|
@ -4274,6 +4770,20 @@ class Response_Ok extends $pb.GeneratedMessage {
|
|||
void clearPrekeysPqc() => $_clearField(3);
|
||||
@$pb.TagNumber(3)
|
||||
Response_PqcPrekeys ensurePrekeysPqc() => $_ensure(2);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
Response_AcknowledgedPendingMessages get acknowledgedPendingMessages =>
|
||||
$_getN(3);
|
||||
@$pb.TagNumber(4)
|
||||
set acknowledgedPendingMessages(Response_AcknowledgedPendingMessages value) =>
|
||||
$_setField(4, value);
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasAcknowledgedPendingMessages() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearAcknowledgedPendingMessages() => $_clearField(4);
|
||||
@$pb.TagNumber(4)
|
||||
Response_AcknowledgedPendingMessages ensureAcknowledgedPendingMessages() =>
|
||||
$_ensure(3);
|
||||
}
|
||||
|
||||
enum Response_Response { ok, error, notSet }
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ const Handshake$json = {
|
|||
'3': [
|
||||
Handshake_RequestPOW$json,
|
||||
Handshake_Register$json,
|
||||
Handshake_InitialPqcKeys$json,
|
||||
Handshake_GetAuthChallenge$json,
|
||||
Handshake_GetUserIdByUsername$json,
|
||||
Handshake_GetAuthToken$json,
|
||||
|
|
@ -221,18 +222,46 @@ const Handshake_Register$json = {
|
|||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'publicIdentityKey'
|
||||
'9': 1,
|
||||
'10': 'publicIdentityKey',
|
||||
'17': true
|
||||
},
|
||||
{
|
||||
'1': 'signed_prekey',
|
||||
'3': 4,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'9': 2,
|
||||
'10': 'signedPrekey',
|
||||
'17': true
|
||||
},
|
||||
{'1': 'signed_prekey', '3': 4, '4': 1, '5': 12, '10': 'signedPrekey'},
|
||||
{
|
||||
'1': 'signed_prekey_signature',
|
||||
'3': 5,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'signedPrekeySignature'
|
||||
'9': 3,
|
||||
'10': 'signedPrekeySignature',
|
||||
'17': true
|
||||
},
|
||||
{
|
||||
'1': 'signed_prekey_id',
|
||||
'3': 6,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'9': 4,
|
||||
'10': 'signedPrekeyId',
|
||||
'17': true
|
||||
},
|
||||
{
|
||||
'1': 'registration_id',
|
||||
'3': 7,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'9': 5,
|
||||
'10': 'registrationId',
|
||||
'17': true
|
||||
},
|
||||
{'1': 'signed_prekey_id', '3': 6, '4': 1, '5': 3, '10': 'signedPrekeyId'},
|
||||
{'1': 'registration_id', '3': 7, '4': 1, '5': 3, '10': 'registrationId'},
|
||||
{'1': 'is_ios', '3': 8, '4': 1, '5': 8, '10': 'isIos'},
|
||||
{'1': 'lang_code', '3': 9, '4': 1, '5': 9, '10': 'langCode'},
|
||||
{'1': 'proof_of_work', '3': 10, '4': 1, '5': 3, '10': 'proofOfWork'},
|
||||
|
|
@ -241,14 +270,114 @@ const Handshake_Register$json = {
|
|||
'3': 11,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'9': 1,
|
||||
'9': 6,
|
||||
'10': 'loginToken',
|
||||
'17': true
|
||||
},
|
||||
{
|
||||
'1': 'initial_pqc_keys',
|
||||
'3': 12,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.Handshake.InitialPqcKeys',
|
||||
'9': 7,
|
||||
'10': 'initialPqcKeys',
|
||||
'17': true
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_invite_code'},
|
||||
{'1': '_public_identity_key'},
|
||||
{'1': '_signed_prekey'},
|
||||
{'1': '_signed_prekey_signature'},
|
||||
{'1': '_signed_prekey_id'},
|
||||
{'1': '_registration_id'},
|
||||
{'1': '_login_token'},
|
||||
{'1': '_initial_pqc_keys'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use handshakeDescriptor instead')
|
||||
const Handshake_InitialPqcKeys$json = {
|
||||
'1': 'InitialPqcKeys',
|
||||
'2': [
|
||||
{
|
||||
'1': 'public_identity_key',
|
||||
'3': 1,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'publicIdentityKey'
|
||||
},
|
||||
{'1': 'registration_id', '3': 2, '4': 1, '5': 3, '10': 'registrationId'},
|
||||
{
|
||||
'1': 'ecc_signed_prekey_id',
|
||||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'10': 'eccSignedPrekeyId'
|
||||
},
|
||||
{
|
||||
'1': 'ecc_signed_prekey',
|
||||
'3': 4,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'eccSignedPrekey'
|
||||
},
|
||||
{
|
||||
'1': 'ecc_signed_prekey_signature',
|
||||
'3': 5,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'eccSignedPrekeySignature'
|
||||
},
|
||||
{
|
||||
'1': 'kyber_signed_prekey_id',
|
||||
'3': 6,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'10': 'kyberSignedPrekeyId'
|
||||
},
|
||||
{
|
||||
'1': 'kyber_signed_prekey',
|
||||
'3': 7,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'kyberSignedPrekey'
|
||||
},
|
||||
{
|
||||
'1': 'kyber_signed_prekey_signature',
|
||||
'3': 8,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'kyberSignedPrekeySignature'
|
||||
},
|
||||
{
|
||||
'1': 'prekeys',
|
||||
'3': 9,
|
||||
'4': 3,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.Handshake.InitialPqcKeys.PqcPreKey',
|
||||
'10': 'prekeys'
|
||||
},
|
||||
],
|
||||
'3': [Handshake_InitialPqcKeys_PqcPreKey$json],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use handshakeDescriptor instead')
|
||||
const Handshake_InitialPqcKeys_PqcPreKey$json = {
|
||||
'1': 'PqcPreKey',
|
||||
'2': [
|
||||
{'1': 'ecc_pre_key_id', '3': 1, '4': 1, '5': 3, '10': 'eccPreKeyId'},
|
||||
{'1': 'ecc_pre_key', '3': 2, '4': 1, '5': 12, '10': 'eccPreKey'},
|
||||
{'1': 'kyber_pre_key_id', '3': 3, '4': 1, '5': 3, '10': 'kyberPreKeyId'},
|
||||
{'1': 'kyber_pre_key', '3': 4, '4': 1, '5': 12, '10': 'kyberPreKey'},
|
||||
{
|
||||
'1': 'kyber_pre_key_signature',
|
||||
'3': 5,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'10': 'kyberPreKeySignature'
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -307,11 +436,21 @@ const Handshake_Authenticate$json = {
|
|||
'10': 'inBackground',
|
||||
'17': true
|
||||
},
|
||||
{
|
||||
'1': 'supports_mailbox_v2',
|
||||
'3': 6,
|
||||
'4': 1,
|
||||
'5': 8,
|
||||
'9': 3,
|
||||
'10': 'supportsMailboxV2',
|
||||
'17': true
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_app_version'},
|
||||
{'1': '_device_id'},
|
||||
{'1': '_in_background'},
|
||||
{'1': '_supports_mailbox_v2'},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -330,6 +469,18 @@ const Handshake_AuthenticateWithLoginToken$json = {
|
|||
{'1': 'app_version', '3': 3, '4': 1, '5': 9, '10': 'appVersion'},
|
||||
{'1': 'device_id', '3': 4, '4': 1, '5': 3, '10': 'deviceId'},
|
||||
{'1': 'in_background', '3': 5, '4': 1, '5': 8, '10': 'inBackground'},
|
||||
{
|
||||
'1': 'supports_mailbox_v2',
|
||||
'3': 6,
|
||||
'4': 1,
|
||||
'5': 8,
|
||||
'9': 0,
|
||||
'10': 'supportsMailboxV2',
|
||||
'17': true
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_supports_mailbox_v2'},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -443,39 +594,59 @@ final $typed_data.Uint8List handshakeDescriptor = $convert.base64Decode(
|
|||
'ZmljYXRpb25IAFIgcmVnaXN0ZXJQYXNzd29yZGxlc3NOb3RpZmljYXRpb24SjQEKI2NoZWNrX2'
|
||||
'Zvcl9wYXNzd29yZGxlc3Nfbm90aWZpY2F0aW9uGAogASgLMjwuY2xpZW50X3RvX3NlcnZlci5I'
|
||||
'YW5kc2hha2UuQ2hlY2tGb3JQYXNzd29yZGxlc3NOb3RpZmljYXRpb25IAFIgY2hlY2tGb3JQYX'
|
||||
'Nzd29yZGxlc3NOb3RpZmljYXRpb24aDAoKUmVxdWVzdFBPVxrKAwoIUmVnaXN0ZXISGgoIdXNl'
|
||||
'Nzd29yZGxlc3NOb3RpZmljYXRpb24aDAoKUmVxdWVzdFBPVxrCBQoIUmVnaXN0ZXISGgoIdXNl'
|
||||
'cm5hbWUYASABKAlSCHVzZXJuYW1lEiQKC2ludml0ZV9jb2RlGAIgASgJSABSCmludml0ZUNvZG'
|
||||
'WIAQESLgoTcHVibGljX2lkZW50aXR5X2tleRgDIAEoDFIRcHVibGljSWRlbnRpdHlLZXkSIwoN'
|
||||
'c2lnbmVkX3ByZWtleRgEIAEoDFIMc2lnbmVkUHJla2V5EjYKF3NpZ25lZF9wcmVrZXlfc2lnbm'
|
||||
'F0dXJlGAUgASgMUhVzaWduZWRQcmVrZXlTaWduYXR1cmUSKAoQc2lnbmVkX3ByZWtleV9pZBgG'
|
||||
'IAEoA1IOc2lnbmVkUHJla2V5SWQSJwoPcmVnaXN0cmF0aW9uX2lkGAcgASgDUg5yZWdpc3RyYX'
|
||||
'Rpb25JZBIVCgZpc19pb3MYCCABKAhSBWlzSW9zEhsKCWxhbmdfY29kZRgJIAEoCVIIbGFuZ0Nv'
|
||||
'ZGUSIgoNcHJvb2Zfb2Zfd29yaxgKIAEoA1ILcHJvb2ZPZldvcmsSJAoLbG9naW5fdG9rZW4YCy'
|
||||
'ABKAxIAVIKbG9naW5Ub2tlbogBAUIOCgxfaW52aXRlX2NvZGVCDgoMX2xvZ2luX3Rva2VuGhIK'
|
||||
'EEdldEF1dGhDaGFsbGVuZ2UaMQoTR2V0VXNlcklkQnlVc2VybmFtZRIaCgh1c2VybmFtZRgBIA'
|
||||
'EoCVIIdXNlcm5hbWUaQwoMR2V0QXV0aFRva2VuEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIa'
|
||||
'CghyZXNwb25zZRgCIAEoDFIIcmVzcG9uc2Ua6AEKDEF1dGhlbnRpY2F0ZRIXCgd1c2VyX2lkGA'
|
||||
'EgASgDUgZ1c2VySWQSHQoKYXV0aF90b2tlbhgCIAEoDFIJYXV0aFRva2VuEiQKC2FwcF92ZXJz'
|
||||
'aW9uGAMgASgJSABSCmFwcFZlcnNpb26IAQESIAoJZGV2aWNlX2lkGAQgASgDSAFSCGRldmljZU'
|
||||
'lkiAEBEigKDWluX2JhY2tncm91bmQYBSABKAhIAlIMaW5CYWNrZ3JvdW5kiAEBQg4KDF9hcHBf'
|
||||
'dmVyc2lvbkIMCgpfZGV2aWNlX2lkQhAKDl9pbl9iYWNrZ3JvdW5kGsYBChpBdXRoZW50aWNhdG'
|
||||
'VXaXRoTG9naW5Ub2tlbhIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSLAoSc2VjcmV0X2xvZ2lu'
|
||||
'X3Rva2VuGAIgASgMUhBzZWNyZXRMb2dpblRva2VuEh8KC2FwcF92ZXJzaW9uGAMgASgJUgphcH'
|
||||
'BWZXJzaW9uEhsKCWRldmljZV9pZBgEIAEoA1IIZGV2aWNlSWQSIwoNaW5fYmFja2dyb3VuZBgF'
|
||||
'IAEoCFIMaW5CYWNrZ3JvdW5kGqUCCiNHZXRTZXJ2ZXJLZXlGb3JQYXNzd29yZExlc3NSZWNvdm'
|
||||
'VyeRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSMgoVc2VydmVyX2tleV9wcm90ZWN0aW9uGAIg'
|
||||
'ASgMUhNzZXJ2ZXJLZXlQcm90ZWN0aW9uEi0KEHBpbl91bmxvY2tfdG9rZW4YAyABKAxIAFIOcG'
|
||||
'luVW5sb2NrVG9rZW6IAQESMQoScGluX3Byb3RlY3Rpb25fa2V5GAQgASgMSAFSEHBpblByb3Rl'
|
||||
'Y3Rpb25LZXmIAQESGQoFZW1haWwYBSABKAlIAlIFZW1haWyIAQFCEwoRX3Bpbl91bmxvY2tfdG'
|
||||
'9rZW5CFQoTX3Bpbl9wcm90ZWN0aW9uX2tleUIICgZfZW1haWwaywEKIFJlZ2lzdGVyUGFzc3dv'
|
||||
'cmRsZXNzTm90aWZpY2F0aW9uEicKD25vdGlmaWNhdGlvbl9pZBgBIAEoCVIObm90aWZpY2F0aW'
|
||||
'9uSWQSLgoTZG93bmxvYWRfYXV0aF90b2tlbhgCIAEoDFIRZG93bmxvYWRBdXRoVG9rZW4SGwoJ'
|
||||
'bGFuZ19jb2RlGAMgASgJUghsYW5nQ29kZRIiCgpnb29nbGVfZmNtGAQgASgJSABSCWdvb2dsZU'
|
||||
'ZjbYgBAUINCgtfZ29vZ2xlX2ZjbRq8AQogQ2hlY2tGb3JQYXNzd29yZGxlc3NOb3RpZmljYXRp'
|
||||
'b24SJwoPbm90aWZpY2F0aW9uX2lkGAEgASgJUg5ub3RpZmljYXRpb25JZBIuChNkb3dubG9hZF'
|
||||
'9hdXRoX3Rva2VuGAIgASgMUhFkb3dubG9hZEF1dGhUb2tlbhI/ChxhbHJlYWR5X3JlY2VpdmVk'
|
||||
'X21lc3NhZ2VfaWRzGAMgAygDUhlhbHJlYWR5UmVjZWl2ZWRNZXNzYWdlSWRzQgsKCUhhbmRzaG'
|
||||
'FrZQ==');
|
||||
'WIAQESMwoTcHVibGljX2lkZW50aXR5X2tleRgDIAEoDEgBUhFwdWJsaWNJZGVudGl0eUtleYgB'
|
||||
'ARIoCg1zaWduZWRfcHJla2V5GAQgASgMSAJSDHNpZ25lZFByZWtleYgBARI7ChdzaWduZWRfcH'
|
||||
'Jla2V5X3NpZ25hdHVyZRgFIAEoDEgDUhVzaWduZWRQcmVrZXlTaWduYXR1cmWIAQESLQoQc2ln'
|
||||
'bmVkX3ByZWtleV9pZBgGIAEoA0gEUg5zaWduZWRQcmVrZXlJZIgBARIsCg9yZWdpc3RyYXRpb2'
|
||||
'5faWQYByABKANIBVIOcmVnaXN0cmF0aW9uSWSIAQESFQoGaXNfaW9zGAggASgIUgVpc0lvcxIb'
|
||||
'CglsYW5nX2NvZGUYCSABKAlSCGxhbmdDb2RlEiIKDXByb29mX29mX3dvcmsYCiABKANSC3Byb2'
|
||||
'9mT2ZXb3JrEiQKC2xvZ2luX3Rva2VuGAsgASgMSAZSCmxvZ2luVG9rZW6IAQESWQoQaW5pdGlh'
|
||||
'bF9wcWNfa2V5cxgMIAEoCzIqLmNsaWVudF90b19zZXJ2ZXIuSGFuZHNoYWtlLkluaXRpYWxQcW'
|
||||
'NLZXlzSAdSDmluaXRpYWxQcWNLZXlziAEBQg4KDF9pbnZpdGVfY29kZUIWChRfcHVibGljX2lk'
|
||||
'ZW50aXR5X2tleUIQCg5fc2lnbmVkX3ByZWtleUIaChhfc2lnbmVkX3ByZWtleV9zaWduYXR1cm'
|
||||
'VCEwoRX3NpZ25lZF9wcmVrZXlfaWRCEgoQX3JlZ2lzdHJhdGlvbl9pZEIOCgxfbG9naW5fdG9r'
|
||||
'ZW5CEwoRX2luaXRpYWxfcHFjX2tleXMa1AUKDkluaXRpYWxQcWNLZXlzEi4KE3B1YmxpY19pZG'
|
||||
'VudGl0eV9rZXkYASABKAxSEXB1YmxpY0lkZW50aXR5S2V5EicKD3JlZ2lzdHJhdGlvbl9pZBgC'
|
||||
'IAEoA1IOcmVnaXN0cmF0aW9uSWQSLwoUZWNjX3NpZ25lZF9wcmVrZXlfaWQYAyABKANSEWVjY1'
|
||||
'NpZ25lZFByZWtleUlkEioKEWVjY19zaWduZWRfcHJla2V5GAQgASgMUg9lY2NTaWduZWRQcmVr'
|
||||
'ZXkSPQobZWNjX3NpZ25lZF9wcmVrZXlfc2lnbmF0dXJlGAUgASgMUhhlY2NTaWduZWRQcmVrZX'
|
||||
'lTaWduYXR1cmUSMwoWa3liZXJfc2lnbmVkX3ByZWtleV9pZBgGIAEoA1ITa3liZXJTaWduZWRQ'
|
||||
'cmVrZXlJZBIuChNreWJlcl9zaWduZWRfcHJla2V5GAcgASgMUhFreWJlclNpZ25lZFByZWtleR'
|
||||
'JBCh1reWJlcl9zaWduZWRfcHJla2V5X3NpZ25hdHVyZRgIIAEoDFIaa3liZXJTaWduZWRQcmVr'
|
||||
'ZXlTaWduYXR1cmUSTgoHcHJla2V5cxgJIAMoCzI0LmNsaWVudF90b19zZXJ2ZXIuSGFuZHNoYW'
|
||||
'tlLkluaXRpYWxQcWNLZXlzLlBxY1ByZUtleVIHcHJla2V5cxrUAQoJUHFjUHJlS2V5EiMKDmVj'
|
||||
'Y19wcmVfa2V5X2lkGAEgASgDUgtlY2NQcmVLZXlJZBIeCgtlY2NfcHJlX2tleRgCIAEoDFIJZW'
|
||||
'NjUHJlS2V5EicKEGt5YmVyX3ByZV9rZXlfaWQYAyABKANSDWt5YmVyUHJlS2V5SWQSIgoNa3li'
|
||||
'ZXJfcHJlX2tleRgEIAEoDFILa3liZXJQcmVLZXkSNQoXa3liZXJfcHJlX2tleV9zaWduYXR1cm'
|
||||
'UYBSABKAxSFGt5YmVyUHJlS2V5U2lnbmF0dXJlGhIKEEdldEF1dGhDaGFsbGVuZ2UaMQoTR2V0'
|
||||
'VXNlcklkQnlVc2VybmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaQwoMR2V0QXV0aF'
|
||||
'Rva2VuEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIaCghyZXNwb25zZRgCIAEoDFIIcmVzcG9u'
|
||||
'c2UatQIKDEF1dGhlbnRpY2F0ZRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSHQoKYXV0aF90b2'
|
||||
'tlbhgCIAEoDFIJYXV0aFRva2VuEiQKC2FwcF92ZXJzaW9uGAMgASgJSABSCmFwcFZlcnNpb26I'
|
||||
'AQESIAoJZGV2aWNlX2lkGAQgASgDSAFSCGRldmljZUlkiAEBEigKDWluX2JhY2tncm91bmQYBS'
|
||||
'ABKAhIAlIMaW5CYWNrZ3JvdW5kiAEBEjMKE3N1cHBvcnRzX21haWxib3hfdjIYBiABKAhIA1IR'
|
||||
'c3VwcG9ydHNNYWlsYm94VjKIAQFCDgoMX2FwcF92ZXJzaW9uQgwKCl9kZXZpY2VfaWRCEAoOX2'
|
||||
'luX2JhY2tncm91bmRCFgoUX3N1cHBvcnRzX21haWxib3hfdjIakwIKGkF1dGhlbnRpY2F0ZVdp'
|
||||
'dGhMb2dpblRva2VuEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIsChJzZWNyZXRfbG9naW5fdG'
|
||||
'9rZW4YAiABKAxSEHNlY3JldExvZ2luVG9rZW4SHwoLYXBwX3ZlcnNpb24YAyABKAlSCmFwcFZl'
|
||||
'cnNpb24SGwoJZGV2aWNlX2lkGAQgASgDUghkZXZpY2VJZBIjCg1pbl9iYWNrZ3JvdW5kGAUgAS'
|
||||
'gIUgxpbkJhY2tncm91bmQSMwoTc3VwcG9ydHNfbWFpbGJveF92MhgGIAEoCEgAUhFzdXBwb3J0'
|
||||
'c01haWxib3hWMogBAUIWChRfc3VwcG9ydHNfbWFpbGJveF92MhqlAgojR2V0U2VydmVyS2V5Rm'
|
||||
'9yUGFzc3dvcmRMZXNzUmVjb3ZlcnkSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEjIKFXNlcnZl'
|
||||
'cl9rZXlfcHJvdGVjdGlvbhgCIAEoDFITc2VydmVyS2V5UHJvdGVjdGlvbhItChBwaW5fdW5sb2'
|
||||
'NrX3Rva2VuGAMgASgMSABSDnBpblVubG9ja1Rva2VuiAEBEjEKEnBpbl9wcm90ZWN0aW9uX2tl'
|
||||
'eRgEIAEoDEgBUhBwaW5Qcm90ZWN0aW9uS2V5iAEBEhkKBWVtYWlsGAUgASgJSAJSBWVtYWlsiA'
|
||||
'EBQhMKEV9waW5fdW5sb2NrX3Rva2VuQhUKE19waW5fcHJvdGVjdGlvbl9rZXlCCAoGX2VtYWls'
|
||||
'GssBCiBSZWdpc3RlclBhc3N3b3JkbGVzc05vdGlmaWNhdGlvbhInCg9ub3RpZmljYXRpb25faW'
|
||||
'QYASABKAlSDm5vdGlmaWNhdGlvbklkEi4KE2Rvd25sb2FkX2F1dGhfdG9rZW4YAiABKAxSEWRv'
|
||||
'd25sb2FkQXV0aFRva2VuEhsKCWxhbmdfY29kZRgDIAEoCVIIbGFuZ0NvZGUSIgoKZ29vZ2xlX2'
|
||||
'ZjbRgEIAEoCUgAUglnb29nbGVGY22IAQFCDQoLX2dvb2dsZV9mY20avAEKIENoZWNrRm9yUGFz'
|
||||
'c3dvcmRsZXNzTm90aWZpY2F0aW9uEicKD25vdGlmaWNhdGlvbl9pZBgBIAEoCVIObm90aWZpY2'
|
||||
'F0aW9uSWQSLgoTZG93bmxvYWRfYXV0aF90b2tlbhgCIAEoDFIRZG93bmxvYWRBdXRoVG9rZW4S'
|
||||
'PwocYWxyZWFkeV9yZWNlaXZlZF9tZXNzYWdlX2lkcxgDIAMoA1IZYWxyZWFkeVJlY2VpdmVkTW'
|
||||
'Vzc2FnZUlkc0ILCglIYW5kc2hha2U=');
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData$json = {
|
||||
|
|
@ -733,6 +904,15 @@ const ApplicationData$json = {
|
|||
'9': 0,
|
||||
'10': 'uploadPqcPrekeys'
|
||||
},
|
||||
{
|
||||
'1': 'request_pending_messages',
|
||||
'3': 43,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.ApplicationData.RequestPendingMessages',
|
||||
'9': 0,
|
||||
'10': 'requestPendingMessages'
|
||||
},
|
||||
],
|
||||
'3': [
|
||||
ApplicationData_TextMessage$json,
|
||||
|
|
@ -763,7 +943,8 @@ const ApplicationData$json = {
|
|||
ApplicationData_GetMemoriesUrl$json,
|
||||
ApplicationData_GetMemoriesUsage$json,
|
||||
ApplicationData_DeleteMemory$json,
|
||||
ApplicationData_DisableMemoriesBackup$json
|
||||
ApplicationData_DisableMemoriesBackup$json,
|
||||
ApplicationData_RequestPendingMessages$json
|
||||
],
|
||||
'8': [
|
||||
{'1': 'ApplicationData'},
|
||||
|
|
@ -773,6 +954,8 @@ const ApplicationData$json = {
|
|||
{'1': 11, '2': 12},
|
||||
{'1': 13, '2': 18},
|
||||
{'1': 19, '2': 20},
|
||||
{'1': 41, '2': 42},
|
||||
{'1': 42, '2': 43},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -791,6 +974,7 @@ const ApplicationData_TextMessage$json = {
|
|||
'10': 'pushData',
|
||||
'17': true
|
||||
},
|
||||
{'1': 'wake_receiver', '3': 5, '4': 1, '5': 8, '10': 'wakeReceiver'},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_push_data'},
|
||||
|
|
@ -951,6 +1135,28 @@ const ApplicationData_UploadPqcPreKeys$json = {
|
|||
'6': '.client_to_server.ApplicationData.PqcPreKey',
|
||||
'10': 'prekeys'
|
||||
},
|
||||
{
|
||||
'1': 'public_identity_key',
|
||||
'3': 8,
|
||||
'4': 1,
|
||||
'5': 12,
|
||||
'9': 0,
|
||||
'10': 'publicIdentityKey',
|
||||
'17': true
|
||||
},
|
||||
{
|
||||
'1': 'registration_id',
|
||||
'3': 9,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'9': 1,
|
||||
'10': 'registrationId',
|
||||
'17': true
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_public_identity_key'},
|
||||
{'1': '_registration_id'},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -1108,6 +1314,11 @@ const ApplicationData_DisableMemoriesBackup$json = {
|
|||
'1': 'DisableMemoriesBackup',
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData_RequestPendingMessages$json = {
|
||||
'1': 'RequestPendingMessages',
|
||||
};
|
||||
|
||||
/// Descriptor for `ApplicationData`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
||||
'Cg9BcHBsaWNhdGlvbkRhdGESUQoLdGV4dE1lc3NhZ2UYASABKAsyLS5jbGllbnRfdG9fc2Vydm'
|
||||
|
|
@ -1161,49 +1372,55 @@ final $typed_data.Uint8List applicationDataDescriptor = $convert.base64Decode(
|
|||
'c2VydmVyLkFwcGxpY2F0aW9uRGF0YS5EaXNhYmxlTWVtb3JpZXNCYWNrdXBIAFIVZGlzYWJsZU'
|
||||
'1lbW9yaWVzQmFja3VwEmIKEnVwbG9hZF9wcWNfcHJla2V5cxgoIAEoCzIyLmNsaWVudF90b19z'
|
||||
'ZXJ2ZXIuQXBwbGljYXRpb25EYXRhLlVwbG9hZFBxY1ByZUtleXNIAFIQdXBsb2FkUHFjUHJla2'
|
||||
'V5cxpqCgtUZXh0TWVzc2FnZRIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQSEgoEYm9keRgDIAEo'
|
||||
'DFIEYm9keRIgCglwdXNoX2RhdGEYBCABKAxIAFIIcHVzaERhdGGIAQFCDAoKX3B1c2hfZGF0YR'
|
||||
'ovChFHZXRVc2VyQnlVc2VybmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaLAoOQ2hh'
|
||||
'bmdlVXNlcm5hbWUSGgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1lGjUKFFVwZGF0ZUdvb2dsZU'
|
||||
'ZjbVRva2VuEh0KCmdvb2dsZV9mY20YASABKAlSCWdvb2dsZUZjbRomCgtHZXRVc2VyQnlJZBIX'
|
||||
'Cgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaEwoRR2V0QXZhaWxhYmxlUGxhbnMaFQoTR2V0Q3Vycm'
|
||||
'VudFBsYW5JbmZvcxovChRSZW1vdmVBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1'
|
||||
'c2VySWQaLQoSR2V0UHJla2V5c0J5VXNlcklkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBoyCh'
|
||||
'dHZXRTaWduZWRQcmVLZXlCeVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQamwEKElVw'
|
||||
'ZGF0ZVNpZ25lZFByZUtleRIoChBzaWduZWRfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZX'
|
||||
'lJZBIjCg1zaWduZWRfcHJla2V5GAIgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtl'
|
||||
'eV9zaWduYXR1cmUYAyABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRrUAQoJUHFjUHJlS2V5Ei'
|
||||
'MKDmVjY19wcmVfa2V5X2lkGAEgASgDUgtlY2NQcmVLZXlJZBIeCgtlY2NfcHJlX2tleRgCIAEo'
|
||||
'DFIJZWNjUHJlS2V5EicKEGt5YmVyX3ByZV9rZXlfaWQYAyABKANSDWt5YmVyUHJlS2V5SWQSIg'
|
||||
'oNa3liZXJfcHJlX2tleRgEIAEoDFILa3liZXJQcmVLZXkSNQoXa3liZXJfcHJlX2tleV9zaWdu'
|
||||
'YXR1cmUYBSABKAxSFGt5YmVyUHJlS2V5U2lnbmF0dXJlGp0DChBVcGxvYWRQcWNQcmVLZXlzEi'
|
||||
'8KFGVjY19zaWduZWRfcHJla2V5X2lkGAEgASgDUhFlY2NTaWduZWRQcmVrZXlJZBIqChFlY2Nf'
|
||||
'c2lnbmVkX3ByZWtleRgCIAEoDFIPZWNjU2lnbmVkUHJla2V5Ej0KG2VjY19zaWduZWRfcHJla2'
|
||||
'V5X3NpZ25hdHVyZRgDIAEoDFIYZWNjU2lnbmVkUHJla2V5U2lnbmF0dXJlEjMKFmt5YmVyX3Np'
|
||||
'Z25lZF9wcmVrZXlfaWQYBCABKANSE2t5YmVyU2lnbmVkUHJla2V5SWQSLgoTa3liZXJfc2lnbm'
|
||||
'VkX3ByZWtleRgFIAEoDFIRa3liZXJTaWduZWRQcmVrZXkSQQoda3liZXJfc2lnbmVkX3ByZWtl'
|
||||
'eV9zaWduYXR1cmUYBiABKAxSGmt5YmVyU2lnbmVkUHJla2V5U2lnbmF0dXJlEkUKB3ByZWtleX'
|
||||
'MYByADKAsyKy5jbGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5QcWNQcmVLZXlSB3By'
|
||||
'ZWtleXMaNQoMRG93bmxvYWREb25lEiUKDmRvd25sb2FkX3Rva2VuGAEgASgMUg1kb3dubG9hZF'
|
||||
'Rva2VuGk4KClJlcG9ydFVzZXISKAoQcmVwb3J0ZWRfdXNlcl9pZBgBIAEoA1IOcmVwb3J0ZWRV'
|
||||
'c2VySWQSFgoGcmVhc29uGAIgASgJUgZyZWFzb24acQoLSVBBUHVyY2hhc2USHQoKcHJvZHVjdF'
|
||||
'9pZBgBIAEoCVIJcHJvZHVjdElkEhYKBnNvdXJjZRgCIAEoCVIGc291cmNlEisKEXZlcmlmaWNh'
|
||||
'dGlvbl9kYXRhGAMgASgJUhB2ZXJpZmljYXRpb25EYXRhGg8KDUlQQUZvcmNlQ2hlY2saDwoNRG'
|
||||
'VsZXRlQWNjb3VudBosChFBZGRBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2Vy'
|
||||
'SWQaMAoNU2V0TG9naW5Ub2tlbhIfCgtsb2dpbl90b2tlbhgBIAEoDFIKbG9naW5Ub2tlbhqOAQ'
|
||||
'ocUmVnaXN0ZXJQYXNzd29yZExlc3NSZWNvdmVyeRIuChJlbmNyeXB0ZWRTZXJ2ZXJLZXkYASAB'
|
||||
'KAxSEmVuY3J5cHRlZFNlcnZlcktleRIrCg5waW5VbmxvY2tUb2tlbhgCIAEoDEgAUg5waW5Vbm'
|
||||
'xvY2tUb2tlbogBAUIRCg9fcGluVW5sb2NrVG9rZW4acAoYUGFzc3dvcmRsZXNzTm90aWZpY2F0'
|
||||
'aW9uEicKD25vdGlmaWNhdGlvbl9pZBgBIAEoCVIObm90aWZpY2F0aW9uSWQSKwoRZW5jcnlwdG'
|
||||
'VkX21lc3NhZ2UYAiABKAxSEGVuY3J5cHRlZE1lc3NhZ2UaawoVUmVxdWVzdE1lbW9yaWVzVXBs'
|
||||
'b2FkEhIKBHNpemUYASABKANSBHNpemUSIwoNb3JpZ2luYWxfZGF0ZRgCIAEoA1IMb3JpZ2luYW'
|
||||
'xEYXRlEhkKCG1lZGlhX2lkGAMgASgJUgdtZWRpYUlkGjIKFUNvbmZpcm1NZW1vcmllc1VwbG9h'
|
||||
'ZBIZCghtZWRpYV9pZBgBIAEoCVIHbWVkaWFJZBpICg9HZXRNZW1vcmllc0xpc3QSHwoLb2Zmc2'
|
||||
'V0X2RhdGUYASABKANSCm9mZnNldERhdGUSFAoFbGltaXQYAiABKANSBWxpbWl0GkkKDkdldE1l'
|
||||
'bW9yaWVzVXJsEhkKCG1lZGlhX2lkGAEgASgJUgdtZWRpYUlkEhwKCXRodW1ibmFpbBgCIAEoCF'
|
||||
'IJdGh1bWJuYWlsGhIKEEdldE1lbW9yaWVzVXNhZ2UaKQoMRGVsZXRlTWVtb3J5EhkKCG1lZGlh'
|
||||
'X2lkGAEgASgJUgdtZWRpYUlkGhcKFURpc2FibGVNZW1vcmllc0JhY2t1cEIRCg9BcHBsaWNhdG'
|
||||
'lvbkRhdGFKBAgJEApKBAgLEAxKBAgNEBJKBAgTEBQ=');
|
||||
'V5cxJ0ChhyZXF1ZXN0X3BlbmRpbmdfbWVzc2FnZXMYKyABKAsyOC5jbGllbnRfdG9fc2VydmVy'
|
||||
'LkFwcGxpY2F0aW9uRGF0YS5SZXF1ZXN0UGVuZGluZ01lc3NhZ2VzSABSFnJlcXVlc3RQZW5kaW'
|
||||
'5nTWVzc2FnZXMajwEKC1RleHRNZXNzYWdlEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBISCgRi'
|
||||
'b2R5GAMgASgMUgRib2R5EiAKCXB1c2hfZGF0YRgEIAEoDEgAUghwdXNoRGF0YYgBARIjCg13YW'
|
||||
'tlX3JlY2VpdmVyGAUgASgIUgx3YWtlUmVjZWl2ZXJCDAoKX3B1c2hfZGF0YRovChFHZXRVc2Vy'
|
||||
'QnlVc2VybmFtZRIaCgh1c2VybmFtZRgBIAEoCVIIdXNlcm5hbWUaLAoOQ2hhbmdlVXNlcm5hbW'
|
||||
'USGgoIdXNlcm5hbWUYASABKAlSCHVzZXJuYW1lGjUKFFVwZGF0ZUdvb2dsZUZjbVRva2VuEh0K'
|
||||
'Cmdvb2dsZV9mY20YASABKAlSCWdvb2dsZUZjbRomCgtHZXRVc2VyQnlJZBIXCgd1c2VyX2lkGA'
|
||||
'EgASgDUgZ1c2VySWQaEwoRR2V0QXZhaWxhYmxlUGxhbnMaFQoTR2V0Q3VycmVudFBsYW5JbmZv'
|
||||
'cxovChRSZW1vdmVBZGRpdGlvbmFsVXNlchIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQaLQoSR2'
|
||||
'V0UHJla2V5c0J5VXNlcklkEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBoyChdHZXRTaWduZWRQ'
|
||||
'cmVLZXlCeVVzZXJJZBIXCgd1c2VyX2lkGAEgASgDUgZ1c2VySWQamwEKElVwZGF0ZVNpZ25lZF'
|
||||
'ByZUtleRIoChBzaWduZWRfcHJla2V5X2lkGAEgASgDUg5zaWduZWRQcmVrZXlJZBIjCg1zaWdu'
|
||||
'ZWRfcHJla2V5GAIgASgMUgxzaWduZWRQcmVrZXkSNgoXc2lnbmVkX3ByZWtleV9zaWduYXR1cm'
|
||||
'UYAyABKAxSFXNpZ25lZFByZWtleVNpZ25hdHVyZRrUAQoJUHFjUHJlS2V5EiMKDmVjY19wcmVf'
|
||||
'a2V5X2lkGAEgASgDUgtlY2NQcmVLZXlJZBIeCgtlY2NfcHJlX2tleRgCIAEoDFIJZWNjUHJlS2'
|
||||
'V5EicKEGt5YmVyX3ByZV9rZXlfaWQYAyABKANSDWt5YmVyUHJlS2V5SWQSIgoNa3liZXJfcHJl'
|
||||
'X2tleRgEIAEoDFILa3liZXJQcmVLZXkSNQoXa3liZXJfcHJlX2tleV9zaWduYXR1cmUYBSABKA'
|
||||
'xSFGt5YmVyUHJlS2V5U2lnbmF0dXJlGqwEChBVcGxvYWRQcWNQcmVLZXlzEi8KFGVjY19zaWdu'
|
||||
'ZWRfcHJla2V5X2lkGAEgASgDUhFlY2NTaWduZWRQcmVrZXlJZBIqChFlY2Nfc2lnbmVkX3ByZW'
|
||||
'tleRgCIAEoDFIPZWNjU2lnbmVkUHJla2V5Ej0KG2VjY19zaWduZWRfcHJla2V5X3NpZ25hdHVy'
|
||||
'ZRgDIAEoDFIYZWNjU2lnbmVkUHJla2V5U2lnbmF0dXJlEjMKFmt5YmVyX3NpZ25lZF9wcmVrZX'
|
||||
'lfaWQYBCABKANSE2t5YmVyU2lnbmVkUHJla2V5SWQSLgoTa3liZXJfc2lnbmVkX3ByZWtleRgF'
|
||||
'IAEoDFIRa3liZXJTaWduZWRQcmVrZXkSQQoda3liZXJfc2lnbmVkX3ByZWtleV9zaWduYXR1cm'
|
||||
'UYBiABKAxSGmt5YmVyU2lnbmVkUHJla2V5U2lnbmF0dXJlEkUKB3ByZWtleXMYByADKAsyKy5j'
|
||||
'bGllbnRfdG9fc2VydmVyLkFwcGxpY2F0aW9uRGF0YS5QcWNQcmVLZXlSB3ByZWtleXMSMwoTcH'
|
||||
'VibGljX2lkZW50aXR5X2tleRgIIAEoDEgAUhFwdWJsaWNJZGVudGl0eUtleYgBARIsCg9yZWdp'
|
||||
'c3RyYXRpb25faWQYCSABKANIAVIOcmVnaXN0cmF0aW9uSWSIAQFCFgoUX3B1YmxpY19pZGVudG'
|
||||
'l0eV9rZXlCEgoQX3JlZ2lzdHJhdGlvbl9pZBo1CgxEb3dubG9hZERvbmUSJQoOZG93bmxvYWRf'
|
||||
'dG9rZW4YASABKAxSDWRvd25sb2FkVG9rZW4aTgoKUmVwb3J0VXNlchIoChByZXBvcnRlZF91c2'
|
||||
'VyX2lkGAEgASgDUg5yZXBvcnRlZFVzZXJJZBIWCgZyZWFzb24YAiABKAlSBnJlYXNvbhpxCgtJ'
|
||||
'UEFQdXJjaGFzZRIdCgpwcm9kdWN0X2lkGAEgASgJUglwcm9kdWN0SWQSFgoGc291cmNlGAIgAS'
|
||||
'gJUgZzb3VyY2USKwoRdmVyaWZpY2F0aW9uX2RhdGEYAyABKAlSEHZlcmlmaWNhdGlvbkRhdGEa'
|
||||
'DwoNSVBBRm9yY2VDaGVjaxoPCg1EZWxldGVBY2NvdW50GiwKEUFkZEFkZGl0aW9uYWxVc2VyEh'
|
||||
'cKB3VzZXJfaWQYASABKANSBnVzZXJJZBowCg1TZXRMb2dpblRva2VuEh8KC2xvZ2luX3Rva2Vu'
|
||||
'GAEgASgMUgpsb2dpblRva2VuGo4BChxSZWdpc3RlclBhc3N3b3JkTGVzc1JlY292ZXJ5Ei4KEm'
|
||||
'VuY3J5cHRlZFNlcnZlcktleRgBIAEoDFISZW5jcnlwdGVkU2VydmVyS2V5EisKDnBpblVubG9j'
|
||||
'a1Rva2VuGAIgASgMSABSDnBpblVubG9ja1Rva2VuiAEBQhEKD19waW5VbmxvY2tUb2tlbhpwCh'
|
||||
'hQYXNzd29yZGxlc3NOb3RpZmljYXRpb24SJwoPbm90aWZpY2F0aW9uX2lkGAEgASgJUg5ub3Rp'
|
||||
'ZmljYXRpb25JZBIrChFlbmNyeXB0ZWRfbWVzc2FnZRgCIAEoDFIQZW5jcnlwdGVkTWVzc2FnZR'
|
||||
'prChVSZXF1ZXN0TWVtb3JpZXNVcGxvYWQSEgoEc2l6ZRgBIAEoA1IEc2l6ZRIjCg1vcmlnaW5h'
|
||||
'bF9kYXRlGAIgASgDUgxvcmlnaW5hbERhdGUSGQoIbWVkaWFfaWQYAyABKAlSB21lZGlhSWQaMg'
|
||||
'oVQ29uZmlybU1lbW9yaWVzVXBsb2FkEhkKCG1lZGlhX2lkGAEgASgJUgdtZWRpYUlkGkgKD0dl'
|
||||
'dE1lbW9yaWVzTGlzdBIfCgtvZmZzZXRfZGF0ZRgBIAEoA1IKb2Zmc2V0RGF0ZRIUCgVsaW1pdB'
|
||||
'gCIAEoA1IFbGltaXQaSQoOR2V0TWVtb3JpZXNVcmwSGQoIbWVkaWFfaWQYASABKAlSB21lZGlh'
|
||||
'SWQSHAoJdGh1bWJuYWlsGAIgASgIUgl0aHVtYm5haWwaEgoQR2V0TWVtb3JpZXNVc2FnZRopCg'
|
||||
'xEZWxldGVNZW1vcnkSGQoIbWVkaWFfaWQYASABKAlSB21lZGlhSWQaFwoVRGlzYWJsZU1lbW9y'
|
||||
'aWVzQmFja3VwGhgKFlJlcXVlc3RQZW5kaW5nTWVzc2FnZXNCEQoPQXBwbGljYXRpb25EYXRhSg'
|
||||
'QICRAKSgQICxAMSgQIDRASSgQIExAUSgQIKRAqSgQIKhAr');
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response$json = {
|
||||
|
|
@ -1232,6 +1449,7 @@ const Response$json = {
|
|||
Response_PreKey$json,
|
||||
Response_Prekeys$json,
|
||||
Response_PqcPrekeys$json,
|
||||
Response_AcknowledgedPendingMessages$json,
|
||||
Response_Ok$json
|
||||
],
|
||||
'8': [
|
||||
|
|
@ -1278,6 +1496,14 @@ const Response_PqcPrekeys$json = {
|
|||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_AcknowledgedPendingMessages$json = {
|
||||
'1': 'AcknowledgedPendingMessages',
|
||||
'2': [
|
||||
{'1': 'delivery_ids', '3': 1, '4': 3, '5': 3, '10': 'deliveryIds'},
|
||||
],
|
||||
};
|
||||
|
||||
@$core.Deprecated('Use responseDescriptor instead')
|
||||
const Response_Ok$json = {
|
||||
'1': 'Ok',
|
||||
|
|
@ -1301,6 +1527,15 @@ const Response_Ok$json = {
|
|||
'9': 0,
|
||||
'10': 'prekeysPqc'
|
||||
},
|
||||
{
|
||||
'1': 'acknowledged_pending_messages',
|
||||
'3': 4,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.client_to_server.Response.AcknowledgedPendingMessages',
|
||||
'9': 0,
|
||||
'10': 'acknowledgedPendingMessages'
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': 'Ok'},
|
||||
|
|
@ -1314,7 +1549,11 @@ final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
|
|||
'Eg4KAmlkGAEgASgDUgJpZBIWCgZwcmVrZXkYAiABKAxSBnByZWtleRpGCgdQcmVrZXlzEjsKB3'
|
||||
'ByZWtleXMYASADKAsyIS5jbGllbnRfdG9fc2VydmVyLlJlc3BvbnNlLlByZUtleVIHcHJla2V5'
|
||||
'cxpTCgpQcWNQcmVrZXlzEkUKB3ByZWtleXMYASADKAsyKy5jbGllbnRfdG9fc2VydmVyLkFwcG'
|
||||
'xpY2F0aW9uRGF0YS5QcWNQcmVLZXlSB3ByZWtleXMaqgEKAk9rEhQKBE5vbmUYASABKAhIAFIE'
|
||||
'Tm9uZRI+CgdwcmVrZXlzGAIgASgLMiIuY2xpZW50X3RvX3NlcnZlci5SZXNwb25zZS5QcmVrZX'
|
||||
'lzSABSB3ByZWtleXMSSAoLcHJla2V5c19wcWMYAyABKAsyJS5jbGllbnRfdG9fc2VydmVyLlJl'
|
||||
'c3BvbnNlLlBxY1ByZWtleXNIAFIKcHJla2V5c1BxY0IECgJPa0IKCghSZXNwb25zZQ==');
|
||||
'xpY2F0aW9uRGF0YS5QcWNQcmVLZXlSB3ByZWtleXMaQAobQWNrbm93bGVkZ2VkUGVuZGluZ01l'
|
||||
'c3NhZ2VzEiEKDGRlbGl2ZXJ5X2lkcxgBIAMoA1ILZGVsaXZlcnlJZHMaqAIKAk9rEhQKBE5vbm'
|
||||
'UYASABKAhIAFIETm9uZRI+CgdwcmVrZXlzGAIgASgLMiIuY2xpZW50X3RvX3NlcnZlci5SZXNw'
|
||||
'b25zZS5QcmVrZXlzSABSB3ByZWtleXMSSAoLcHJla2V5c19wcWMYAyABKAsyJS5jbGllbnRfdG'
|
||||
'9fc2VydmVyLlJlc3BvbnNlLlBxY1ByZWtleXNIAFIKcHJla2V5c1BxYxJ8Ch1hY2tub3dsZWRn'
|
||||
'ZWRfcGVuZGluZ19tZXNzYWdlcxgEIAEoCzI2LmNsaWVudF90b19zZXJ2ZXIuUmVzcG9uc2UuQW'
|
||||
'Nrbm93bGVkZ2VkUGVuZGluZ01lc3NhZ2VzSABSG2Fja25vd2xlZGdlZFBlbmRpbmdNZXNzYWdl'
|
||||
'c0IECgJPa0IKCghSZXNwb25zZQ==');
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ enum V0_Kind {
|
|||
error,
|
||||
newMessages,
|
||||
requestNewPqcPreKeys,
|
||||
mailboxDrained,
|
||||
notSet
|
||||
}
|
||||
|
||||
|
|
@ -108,6 +109,7 @@ class V0 extends $pb.GeneratedMessage {
|
|||
$0.ErrorCode? error,
|
||||
NewMessages? newMessages,
|
||||
$core.bool? requestNewPqcPreKeys,
|
||||
$core.bool? mailboxDrained,
|
||||
}) {
|
||||
final result = create();
|
||||
if (seq != null) result.seq = seq;
|
||||
|
|
@ -118,6 +120,7 @@ class V0 extends $pb.GeneratedMessage {
|
|||
if (newMessages != null) result.newMessages = newMessages;
|
||||
if (requestNewPqcPreKeys != null)
|
||||
result.requestNewPqcPreKeys = requestNewPqcPreKeys;
|
||||
if (mailboxDrained != null) result.mailboxDrained = mailboxDrained;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -137,6 +140,7 @@ class V0 extends $pb.GeneratedMessage {
|
|||
6: V0_Kind.error,
|
||||
7: V0_Kind.newMessages,
|
||||
8: V0_Kind.requestNewPqcPreKeys,
|
||||
11: V0_Kind.mailboxDrained,
|
||||
0: V0_Kind.notSet
|
||||
};
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
|
|
@ -144,7 +148,7 @@ class V0 extends $pb.GeneratedMessage {
|
|||
package:
|
||||
const $pb.PackageName(_omitMessageNames ? '' : 'server_to_client'),
|
||||
createEmptyInstance: create)
|
||||
..oo(0, [2, 3, 4, 6, 7, 8])
|
||||
..oo(0, [2, 3, 4, 6, 7, 8, 11])
|
||||
..a<$fixnum.Int64>(1, _omitFieldNames ? '' : 'seq', $pb.PbFieldType.OU6,
|
||||
defaultOrMaker: $fixnum.Int64.ZERO)
|
||||
..aOM<Response>(2, _omitFieldNames ? '' : 'response',
|
||||
|
|
@ -159,6 +163,8 @@ class V0 extends $pb.GeneratedMessage {
|
|||
protoName: 'newMessages', subBuilder: NewMessages.create)
|
||||
..aOB(8, _omitFieldNames ? '' : 'RequestNewPqcPreKeys',
|
||||
protoName: 'RequestNewPqcPreKeys')
|
||||
..aOB(11, _omitFieldNames ? '' : 'mailboxDrained',
|
||||
protoName: 'mailboxDrained')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -185,6 +191,7 @@ class V0 extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(6)
|
||||
@$pb.TagNumber(7)
|
||||
@$pb.TagNumber(8)
|
||||
@$pb.TagNumber(11)
|
||||
V0_Kind whichKind() => _V0_KindByTag[$_whichOneof(0)]!;
|
||||
@$pb.TagNumber(2)
|
||||
@$pb.TagNumber(3)
|
||||
|
|
@ -192,6 +199,7 @@ class V0 extends $pb.GeneratedMessage {
|
|||
@$pb.TagNumber(6)
|
||||
@$pb.TagNumber(7)
|
||||
@$pb.TagNumber(8)
|
||||
@$pb.TagNumber(11)
|
||||
void clearKind() => $_clearField($_whichOneof(0));
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
|
|
@ -262,6 +270,15 @@ class V0 extends $pb.GeneratedMessage {
|
|||
$core.bool hasRequestNewPqcPreKeys() => $_has(6);
|
||||
@$pb.TagNumber(8)
|
||||
void clearRequestNewPqcPreKeys() => $_clearField(8);
|
||||
|
||||
@$pb.TagNumber(11)
|
||||
$core.bool get mailboxDrained => $_getBF(7);
|
||||
@$pb.TagNumber(11)
|
||||
set mailboxDrained($core.bool value) => $_setBool(7, value);
|
||||
@$pb.TagNumber(11)
|
||||
$core.bool hasMailboxDrained() => $_has(7);
|
||||
@$pb.TagNumber(11)
|
||||
void clearMailboxDrained() => $_clearField(11);
|
||||
}
|
||||
|
||||
class NewMessage extends $pb.GeneratedMessage {
|
||||
|
|
|
|||
|
|
@ -96,6 +96,14 @@ const V0$json = {
|
|||
'9': 0,
|
||||
'10': 'error'
|
||||
},
|
||||
{
|
||||
'1': 'mailboxDrained',
|
||||
'3': 11,
|
||||
'4': 1,
|
||||
'5': 8,
|
||||
'9': 0,
|
||||
'10': 'mailboxDrained'
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': 'Kind'},
|
||||
|
|
@ -110,7 +118,8 @@ final $typed_data.Uint8List v0Descriptor = $convert.base64Decode(
|
|||
'0uc2VydmVyX3RvX2NsaWVudC5OZXdNZXNzYWdlc0gAUgtuZXdNZXNzYWdlcxIuChFSZXF1ZXN0'
|
||||
'TmV3UHJlS2V5cxgEIAEoCEgAUhFSZXF1ZXN0TmV3UHJlS2V5cxI0ChRSZXF1ZXN0TmV3UHFjUH'
|
||||
'JlS2V5cxgIIAEoCEgAUhRSZXF1ZXN0TmV3UHFjUHJlS2V5cxIoCgVlcnJvchgGIAEoDjIQLmVy'
|
||||
'cm9yLkVycm9yQ29kZUgAUgVlcnJvckIGCgRLaW5k');
|
||||
'cm9yLkVycm9yQ29kZUgAUgVlcnJvchIoCg5tYWlsYm94RHJhaW5lZBgLIAEoCEgAUg5tYWlsYm'
|
||||
'94RHJhaW5lZEIGCgRLaW5k');
|
||||
|
||||
@$core.Deprecated('Use newMessageDescriptor instead')
|
||||
const NewMessage$json = {
|
||||
|
|
|
|||
|
|
@ -1016,6 +1016,8 @@ class ApiService {
|
|||
Uint8List kyberSignedPreKey,
|
||||
Uint8List kyberSignedPreKeySignature,
|
||||
List<client.ApplicationData_PqcPreKey> prekeys,
|
||||
Uint8List publicIdentityKey,
|
||||
int registrationId,
|
||||
) async {
|
||||
final get = ApplicationData_UploadPqcPreKeys()
|
||||
..eccSignedPrekeyId = Int64(eccSignedPreKeyId)
|
||||
|
|
@ -1024,7 +1026,9 @@ class ApiService {
|
|||
..kyberSignedPrekeyId = Int64(kyberSignedPreKeyId)
|
||||
..kyberSignedPrekey = kyberSignedPreKey
|
||||
..kyberSignedPrekeySignature = kyberSignedPreKeySignature
|
||||
..prekeys.addAll(prekeys);
|
||||
..prekeys.addAll(prekeys)
|
||||
..publicIdentityKey = publicIdentityKey
|
||||
..registrationId = Int64(registrationId);
|
||||
final appData = ApplicationData()..uploadPqcPrekeys = get;
|
||||
final req = createClientToServerFromApplicationData(appData);
|
||||
return sendRequestSync(req);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ 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/services/notifications/pushkeys.notifications.dart';
|
||||
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
||||
import 'package:twonly/src/services/signal/protocol_state.signal.dart';
|
||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||
import 'package:twonly/src/services/user.service.dart' show UserService;
|
||||
import 'package:twonly/src/services/user_discovery.service.dart';
|
||||
|
|
@ -26,8 +27,47 @@ import 'package:twonly/src/utils/misc.dart';
|
|||
|
||||
final lockRetransmission = Mutex();
|
||||
|
||||
const int _outOfSyncRetryThreshold = 10;
|
||||
|
||||
Future<void> _resetStuckOutgoingSignalSessions() async {
|
||||
final contactIds = await twonlyDB.receiptsDao
|
||||
.getContactsWithStuckSignalMessages(
|
||||
retryThreshold: _outOfSyncRetryThreshold,
|
||||
);
|
||||
|
||||
for (final contactId in contactIds) {
|
||||
if (!shouldAttemptResync(contactId)) continue;
|
||||
|
||||
Log.warn(
|
||||
'Detected an out-of-sync outgoing Signal session with $contactId: '
|
||||
'multiple messages exceeded $_outOfSyncRetryThreshold retries.',
|
||||
);
|
||||
|
||||
var reset = false;
|
||||
try {
|
||||
reset = await resetSignalSession(contactId);
|
||||
} catch (error) {
|
||||
Log.error(
|
||||
'Could not automatically reset session with $contactId: $error',
|
||||
);
|
||||
}
|
||||
|
||||
if (!reset) {
|
||||
recordResyncAttempt(contactId, success: false);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Avoid resetting the same healthy replacement session on the next
|
||||
// retransmission pass. Outstanding messages will now be encrypted again
|
||||
// using the freshly established session.
|
||||
await twonlyDB.receiptsDao.resetMessageRetryCountsForContact(contactId);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> retransmitAllMessages() async {
|
||||
return lockRetransmission.protect(() async {
|
||||
await _resetStuckOutgoingSignalSessions();
|
||||
|
||||
final receipts = await twonlyDB.receiptsDao.getReceiptsForRetransmission();
|
||||
|
||||
if (receipts.isEmpty) return;
|
||||
|
|
@ -159,36 +199,24 @@ Future<(Uint8List, Uint8List?)?> _tryToSendCompleteMessageInternal({
|
|||
.writeToBuffer();
|
||||
}
|
||||
|
||||
if (message.type == pb.Message_Type.CIPHERTEXT) {
|
||||
final encryptResult = await signalEncryptMessage(
|
||||
receipt.contactId,
|
||||
Uint8List.fromList(message.encryptedContent),
|
||||
);
|
||||
if (message.type == pb.Message_Type.CIPHERTEXT ||
|
||||
message.type == pb.Message_Type.CIPHERTEXT_V2) {
|
||||
final useV2 = contact.signalVersion == SignalVersion.v2;
|
||||
final encryptResult = useV2
|
||||
? await signalEncryptMessageV2(
|
||||
receipt.contactId,
|
||||
Uint8List.fromList(message.encryptedContent),
|
||||
)
|
||||
: await signalEncryptMessage(
|
||||
receipt.contactId,
|
||||
Uint8List.fromList(message.encryptedContent),
|
||||
);
|
||||
|
||||
if (encryptResult == null) {
|
||||
Log.error(
|
||||
'[${receipt.receiptId}] Could not encrypt the message for user ${receipt.contactId}. Aborting and trying again.',
|
||||
);
|
||||
if (receipt.messageId != null) {
|
||||
await twonlyDB.messagesDao.handleMessageAckByServer(
|
||||
receipt.contactId,
|
||||
receipt.messageId!,
|
||||
clock.now(),
|
||||
);
|
||||
}
|
||||
await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId);
|
||||
return null;
|
||||
}
|
||||
message
|
||||
..encryptedContent = encryptResult.ciphertext
|
||||
..type = encryptResult.type;
|
||||
} else if (message.type == pb.Message_Type.CIPHERTEXT_V2) {
|
||||
final encryptResult = await signalEncryptMessageV2(
|
||||
receipt.contactId,
|
||||
Uint8List.fromList(message.encryptedContent),
|
||||
);
|
||||
if (encryptResult == null) {
|
||||
Log.error(
|
||||
'[${receipt.receiptId}] Could not encrypt the message (V2) for user ${receipt.contactId}. Aborting and trying again.',
|
||||
'[${receipt.receiptId}] Could not encrypt the message '
|
||||
'(${useV2 ? 'V2' : 'V1'}) for user ${receipt.contactId}. '
|
||||
'Aborting and trying again.',
|
||||
);
|
||||
if (receipt.messageId != null) {
|
||||
await twonlyDB.messagesDao.handleMessageAckByServer(
|
||||
|
|
@ -200,6 +228,7 @@ Future<(Uint8List, Uint8List?)?> _tryToSendCompleteMessageInternal({
|
|||
await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId);
|
||||
return null;
|
||||
}
|
||||
|
||||
message
|
||||
..encryptedContent = encryptResult.ciphertext
|
||||
..type = encryptResult.type;
|
||||
|
|
|
|||
|
|
@ -39,8 +39,27 @@ import 'package:twonly/src/services/signal/session.signal.dart';
|
|||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
const _signalUpgradeMailboxIdleDelay = Duration(milliseconds: 500);
|
||||
Timer? _signalUpgradeMailboxIdleTimer;
|
||||
|
||||
void _scheduleSignalUpgradeAfterMailboxIdle() {
|
||||
_signalUpgradeMailboxIdleTimer?.cancel();
|
||||
_signalUpgradeMailboxIdleTimer = Timer(_signalUpgradeMailboxIdleDelay, () {
|
||||
_signalUpgradeMailboxIdleTimer = null;
|
||||
unawaited(upgradeSignalSessionsToV2());
|
||||
});
|
||||
}
|
||||
|
||||
void _upgradeSignalSessionsAfterMailboxDrain() {
|
||||
_signalUpgradeMailboxIdleTimer?.cancel();
|
||||
_signalUpgradeMailboxIdleTimer = null;
|
||||
unawaited(upgradeSignalSessionsToV2());
|
||||
}
|
||||
|
||||
Future<void> handleServerMessage(server.ServerToClient msg) async {
|
||||
Log.info('Processing a message from the server.');
|
||||
var receivedIncomingMessages = false;
|
||||
var mailboxDrained = false;
|
||||
|
||||
/// Returns means, that the server can delete the message from the server.
|
||||
final ok = client.Response_Ok()..none = true;
|
||||
|
|
@ -54,6 +73,7 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
|
|||
} else if (msg.v0.hasNewMessage()) {
|
||||
Log.info('Got 1 message from the server.');
|
||||
await handleClient2ClientMessage(msg.v0.newMessage);
|
||||
receivedIncomingMessages = true;
|
||||
} else if (msg.v0.hasNewMessages()) {
|
||||
Log.info(
|
||||
'Got ${msg.v0.newMessages.newMessages.length} messages from the server.',
|
||||
|
|
@ -69,6 +89,10 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
|
|||
Log.error(e);
|
||||
}
|
||||
}
|
||||
receivedIncomingMessages = true;
|
||||
} else if (msg.v0.hasMailboxDrained()) {
|
||||
Log.info('All pending messages have been downloaded.');
|
||||
mailboxDrained = true;
|
||||
} else {
|
||||
Log.error('Unknown server message: $msg');
|
||||
}
|
||||
|
|
@ -90,6 +114,15 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
|
|||
}
|
||||
AppState.gotMessageFromServer = true;
|
||||
Log.info('All messages from the server processed.');
|
||||
|
||||
if (mailboxDrained) {
|
||||
_upgradeSignalSessionsAfterMailboxDrain();
|
||||
} else if (receivedIncomingMessages) {
|
||||
// Released servers do not send a mailbox-drained marker. They wait for
|
||||
// this ACK before sending the next batch, so a short, resettable idle
|
||||
// window approximates the queue boundary without blocking delivery.
|
||||
_scheduleSignalUpgradeAfterMailboxIdle();
|
||||
}
|
||||
}
|
||||
|
||||
DateTime lastPushKeyRequest = clock.now().subtract(const Duration(hours: 1));
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ class SignalIdentityService {
|
|||
bundle.kyberPreKeyPublic,
|
||||
bundle.kyberPreKeySignature,
|
||||
[],
|
||||
bundle.identityKey,
|
||||
bundle.registrationId,
|
||||
);
|
||||
|
||||
if (pqcRes.isError) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:typed_data';
|
|||
|
||||
import 'package:drift/drift.dart' as drift;
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:twonly/core/bridge/wrapper/signal.dart';
|
||||
import 'package:twonly/core/signal/engine.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
|
|
@ -13,12 +14,83 @@ import 'package:twonly/src/services/signal/protocol_state.signal.dart';
|
|||
import 'package:twonly/src/services/signal/utils.signal.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
final Mutex _upgradeSignalSessionsLock = Mutex();
|
||||
|
||||
Future<bool> processSignalUserData(Response_UserData userData) async {
|
||||
return lockingSignalProtocol.protect(() async {
|
||||
return _processSignalUserData(userData);
|
||||
});
|
||||
}
|
||||
|
||||
/// Upgrades every known v1 contact that currently publishes a PQXDH bundle.
|
||||
///
|
||||
/// The legacy session is deliberately retained: messages that were already in
|
||||
/// flight before the upgrade can still be decrypted, while newly queued
|
||||
/// messages use the contact's updated v2 session.
|
||||
Future<void> upgradeSignalSessionsToV2() async {
|
||||
if (_upgradeSignalSessionsLock.isLocked) return;
|
||||
|
||||
try {
|
||||
await _upgradeSignalSessionsLock.protect(() async {
|
||||
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
||||
final legacyContacts = contacts.where(
|
||||
(contact) =>
|
||||
contact.signalVersion == SignalVersion.v1 &&
|
||||
!contact.accountDeleted,
|
||||
);
|
||||
|
||||
for (final contact in legacyContacts) {
|
||||
try {
|
||||
final userData = await apiService.getUserById(contact.userId);
|
||||
if (userData == null || !userData.hasPqcBundle()) continue;
|
||||
|
||||
if (await processSignalUserData(userData)) {
|
||||
Log.info('Upgraded Signal session with ${contact.userId} to v2.');
|
||||
}
|
||||
} catch (error) {
|
||||
// Migration is best-effort. A contact that is temporarily
|
||||
// unavailable remains on v1 and is retried after a later message
|
||||
// queue drain.
|
||||
Log.warn(
|
||||
'Could not upgrade Signal session with ${contact.userId}: $error',
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// This function is started without awaiting it from the message loop.
|
||||
Log.warn('Could not start Signal session upgrades: $error');
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces the current session with a freshly downloaded pre-key bundle.
|
||||
Future<bool> resetSignalSession(int contactId) async {
|
||||
final userData = await apiService.getUserById(contactId);
|
||||
if (userData == null) return false;
|
||||
|
||||
return lockingSignalProtocol.protect(() async {
|
||||
if (userData.hasPqcBundle()) {
|
||||
await RustSignal.resetContactSession(contactId: contactId);
|
||||
} else {
|
||||
final signalStore = await getSignalStore();
|
||||
await signalStore?.deleteAllSessions(contactId.toString());
|
||||
}
|
||||
final reset = await _processSignalUserData(userData);
|
||||
if (reset) recordResyncAttempt(contactId, success: true);
|
||||
return reset;
|
||||
});
|
||||
}
|
||||
|
||||
ECPublicKey _decodePublicKey(List<int> bytes, String fieldName) {
|
||||
if (bytes.length != 33 || bytes.first != Curve.djbType) {
|
||||
throw InvalidKeyException(
|
||||
'$fieldName must be a serialized 33-byte Curve25519 public key '
|
||||
'(received ${bytes.length} bytes)',
|
||||
);
|
||||
}
|
||||
return Curve.decodePoint(Uint8List.fromList(bytes), 0);
|
||||
}
|
||||
|
||||
Future<bool> _processSignalUserData(Response_UserData userData) async {
|
||||
if (userData.hasPqcBundle()) {
|
||||
return _processSignalUserDataV2(userData);
|
||||
|
|
@ -29,17 +101,20 @@ Future<bool> _processSignalUserData(Response_UserData userData) async {
|
|||
Future<bool> _processSignalUserDataV2(Response_UserData userData) async {
|
||||
try {
|
||||
final tempIdentityKey = IdentityKey(
|
||||
Curve.decodePoint(
|
||||
DjbECPublicKey(
|
||||
Uint8List.fromList(userData.publicIdentityKey),
|
||||
).serialize(),
|
||||
1,
|
||||
),
|
||||
_decodePublicKey(userData.publicIdentityKey, 'identity key'),
|
||||
);
|
||||
|
||||
final signalStore = await getSignalStore();
|
||||
if (signalStore != null) {
|
||||
final existingIdentity = await signalStore.getIdentity(
|
||||
final contact = await twonlyDB.contactsDao.getContactById(
|
||||
userData.userId.toInt(),
|
||||
);
|
||||
|
||||
// Only the one-time V1 -> V2 migration needs to compare against the
|
||||
// legacy Dart identity store. Established V2 contacts are authenticated
|
||||
// by the identity retained in the Rust store; consulting the obsolete
|
||||
// Dart store here can make a valid V2 reset fail on legacy/corrupt data.
|
||||
if (contact?.signalVersion != SignalVersion.v2) {
|
||||
final signalStore = await getSignalStore();
|
||||
final existingIdentity = await signalStore?.getIdentity(
|
||||
SignalProtocolAddress(userData.userId.toString(), defaultDeviceId),
|
||||
);
|
||||
|
||||
|
|
@ -60,11 +135,9 @@ Future<bool> _processSignalUserDataV2(Response_UserData userData) async {
|
|||
);
|
||||
} else if (userData.prekeys.isNotEmpty) {
|
||||
tempEccPreKeyId = userData.prekeys.first.id.toInt();
|
||||
tempEccPreKeyPublic = Curve.decodePoint(
|
||||
DjbECPublicKey(
|
||||
Uint8List.fromList(userData.prekeys.first.prekey),
|
||||
).serialize(),
|
||||
1,
|
||||
tempEccPreKeyPublic = _decodePublicKey(
|
||||
userData.prekeys.first.prekey,
|
||||
'prekey',
|
||||
).serialize();
|
||||
}
|
||||
|
||||
|
|
@ -102,9 +175,6 @@ Future<bool> _processSignalUserDataV2(Response_UserData userData) async {
|
|||
bundle: rustBundle,
|
||||
);
|
||||
|
||||
final contact = await twonlyDB.contactsDao.getContactById(
|
||||
userData.userId.toInt(),
|
||||
);
|
||||
if (contact != null && contact.signalVersion != SignalVersion.v2) {
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
contact.userId,
|
||||
|
|
@ -120,64 +190,57 @@ Future<bool> _processSignalUserDataV2(Response_UserData userData) async {
|
|||
}
|
||||
|
||||
Future<bool> _processSignalUserDataV1(Response_UserData userData) async {
|
||||
final SignalProtocolStore? signalStore = await getSignalStore();
|
||||
|
||||
if (signalStore == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final targetAddress = getSignalAddress(userData.userId.toInt());
|
||||
|
||||
final sessionBuilder = SessionBuilder.fromSignalStore(
|
||||
signalStore,
|
||||
targetAddress,
|
||||
);
|
||||
|
||||
ECPublicKey? tempPrePublicKey;
|
||||
int? tempPreKeyId;
|
||||
|
||||
if (userData.prekeys.isNotEmpty) {
|
||||
tempPrePublicKey = Curve.decodePoint(
|
||||
DjbECPublicKey(
|
||||
Uint8List.fromList(userData.prekeys.first.prekey),
|
||||
).serialize(),
|
||||
1,
|
||||
);
|
||||
tempPreKeyId = userData.prekeys.first.id.toInt();
|
||||
}
|
||||
|
||||
final tempSignedPreKeyId = userData.signedPrekeyId.toInt();
|
||||
|
||||
final tempSignedPreKeyPublic = Curve.decodePoint(
|
||||
DjbECPublicKey(Uint8List.fromList(userData.signedPrekey)).serialize(),
|
||||
1,
|
||||
);
|
||||
|
||||
final tempSignedPreKeySignature = Uint8List.fromList(
|
||||
userData.signedPrekeySignature,
|
||||
);
|
||||
|
||||
final tempIdentityKey = IdentityKey(
|
||||
Curve.decodePoint(
|
||||
DjbECPublicKey(
|
||||
Uint8List.fromList(userData.publicIdentityKey),
|
||||
).serialize(),
|
||||
1,
|
||||
),
|
||||
);
|
||||
|
||||
final preKeyBundle = PreKeyBundle(
|
||||
userData.registrationId.toInt(),
|
||||
defaultDeviceId,
|
||||
tempPreKeyId,
|
||||
tempPrePublicKey,
|
||||
tempSignedPreKeyId,
|
||||
tempSignedPreKeyPublic,
|
||||
tempSignedPreKeySignature,
|
||||
tempIdentityKey,
|
||||
);
|
||||
|
||||
try {
|
||||
final SignalProtocolStore? signalStore = await getSignalStore();
|
||||
|
||||
if (signalStore == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final targetAddress = getSignalAddress(userData.userId.toInt());
|
||||
|
||||
final sessionBuilder = SessionBuilder.fromSignalStore(
|
||||
signalStore,
|
||||
targetAddress,
|
||||
);
|
||||
|
||||
ECPublicKey? tempPrePublicKey;
|
||||
int? tempPreKeyId;
|
||||
|
||||
if (userData.prekeys.isNotEmpty) {
|
||||
tempPrePublicKey = _decodePublicKey(
|
||||
userData.prekeys.first.prekey,
|
||||
'prekey',
|
||||
);
|
||||
tempPreKeyId = userData.prekeys.first.id.toInt();
|
||||
}
|
||||
|
||||
final tempSignedPreKeyId = userData.signedPrekeyId.toInt();
|
||||
|
||||
final tempSignedPreKeyPublic = _decodePublicKey(
|
||||
userData.signedPrekey,
|
||||
'signed prekey',
|
||||
);
|
||||
|
||||
final tempSignedPreKeySignature = Uint8List.fromList(
|
||||
userData.signedPrekeySignature,
|
||||
);
|
||||
|
||||
final tempIdentityKey = IdentityKey(
|
||||
_decodePublicKey(userData.publicIdentityKey, 'identity key'),
|
||||
);
|
||||
|
||||
final preKeyBundle = PreKeyBundle(
|
||||
userData.registrationId.toInt(),
|
||||
defaultDeviceId,
|
||||
tempPreKeyId,
|
||||
tempPrePublicKey,
|
||||
tempSignedPreKeyId,
|
||||
tempSignedPreKeyPublic,
|
||||
tempSignedPreKeySignature,
|
||||
tempIdentityKey,
|
||||
);
|
||||
|
||||
await sessionBuilder.processPreKeyBundle(preKeyBundle);
|
||||
return true;
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -127,8 +127,10 @@ class MainCameraController {
|
|||
try {
|
||||
await cameraControllerTemp?.stopImageStream();
|
||||
// ignore: empty_catches
|
||||
} catch (e) {}
|
||||
await cameraControllerTemp?.dispose();
|
||||
} catch (e) {
|
||||
} finally {
|
||||
await cameraControllerTemp?.dispose();
|
||||
}
|
||||
},
|
||||
);
|
||||
initCameraStarted = false;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import 'package:twonly/src/database/daos/contacts.dao.dart';
|
|||
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.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/alert.dialog.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
|
|
@ -38,6 +39,7 @@ class ContactView extends StatefulWidget {
|
|||
class _ContactViewState extends State<ContactView> {
|
||||
Contact? _contact;
|
||||
List<GroupMember> _memberOfGroups = [];
|
||||
bool _isResettingSignalSession = false;
|
||||
|
||||
late StreamSubscription<Contact?> _streamContact;
|
||||
late StreamSubscription<List<GroupMember>> _streamMemberOfGroups;
|
||||
|
|
@ -136,6 +138,37 @@ class _ContactViewState extends State<ContactView> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> handleResetSignalSession(Contact contact) async {
|
||||
if (_isResettingSignalSession) return;
|
||||
|
||||
final displayName = getContactDisplayName(contact);
|
||||
final confirmed = await showAlertDialog(
|
||||
context,
|
||||
context.lang.contactResetSessionTitle(displayName),
|
||||
context.lang.contactResetSessionBody,
|
||||
customOk: context.lang.contactResetSessionConfirm,
|
||||
);
|
||||
if (!confirmed || !mounted) return;
|
||||
|
||||
setState(() => _isResettingSignalSession = true);
|
||||
var success = false;
|
||||
try {
|
||||
success = await resetSignalSession(contact.userId);
|
||||
} catch (error) {
|
||||
Log.error('Could not reset Signal session: $error');
|
||||
}
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() => _isResettingSignalSession = false);
|
||||
showSnackbar(
|
||||
context,
|
||||
success
|
||||
? context.lang.contactResetSessionSuccess(displayName)
|
||||
: context.lang.contactResetSessionFailed,
|
||||
level: success ? SnackbarLevel.success : SnackbarLevel.error,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_contact == null) return Container();
|
||||
|
|
@ -268,6 +301,19 @@ class _ContactViewState extends State<ContactView> {
|
|||
text: context.lang.contactBlock,
|
||||
onTap: () => handleUserBlockRequest(contact),
|
||||
),
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.screwdriverWrench,
|
||||
text: context.lang.contactTroubleshoot,
|
||||
trailing: _isResettingSignalSession
|
||||
? const SizedBox.square(
|
||||
dimension: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: null,
|
||||
onTap: _isResettingSignalSession
|
||||
? null
|
||||
: () => handleResetSignalSession(contact),
|
||||
),
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.userMinus,
|
||||
iconSize: 16,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec
|
|||
|
||||
publish_to: 'none'
|
||||
|
||||
version: 0.5.2+172
|
||||
version: 0.5.3+173
|
||||
|
||||
environment:
|
||||
sdk: ^3.11.0
|
||||
|
|
|
|||
|
|
@ -13,6 +13,65 @@ use zip::{CompressionMethod, ZipArchive, ZipWriter};
|
|||
pub(crate) struct BackupArchive {}
|
||||
|
||||
impl BackupArchive {
|
||||
async fn reset_restored_signal_sessions(
|
||||
file_name: &str,
|
||||
database_path: &Path,
|
||||
encryption_key: Option<&str>,
|
||||
) -> Result<()> {
|
||||
let table = match file_name {
|
||||
"twonly.sqlite" => "signal_session_stores",
|
||||
"rust_db.sqlite" => "signal_sessions",
|
||||
_ => return Ok(()),
|
||||
};
|
||||
|
||||
let database =
|
||||
Database::new(&database_path.display().to_string(), encryption_key, false).await?;
|
||||
|
||||
let table_exists: bool = sqlx::query_scalar(
|
||||
"SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?)",
|
||||
)
|
||||
.bind(table)
|
||||
.fetch_one(&database.pool)
|
||||
.await?;
|
||||
|
||||
if table_exists {
|
||||
match file_name {
|
||||
"twonly.sqlite" => {
|
||||
sqlx::query("DELETE FROM signal_session_stores")
|
||||
.execute(&database.pool)
|
||||
.await?;
|
||||
}
|
||||
"rust_db.sqlite" => {
|
||||
sqlx::query("DELETE FROM signal_sessions")
|
||||
.execute(&database.pool)
|
||||
.await?;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// The hotfix chooses the encryption implementation from this column.
|
||||
// A restored v2 marker without its restored session would make the
|
||||
// first outgoing message fail before the normal migration can rebuild
|
||||
// it. Mark contacts as v1; the post-download migration upgrades every
|
||||
// peer that currently publishes a PQXDH bundle.
|
||||
if file_name == "twonly.sqlite" {
|
||||
let signal_version_exists: bool = sqlx::query_scalar(
|
||||
"SELECT EXISTS(SELECT 1 FROM pragma_table_info('contacts') WHERE name = 'signal_version')",
|
||||
)
|
||||
.fetch_one(&database.pool)
|
||||
.await?;
|
||||
if signal_version_exists {
|
||||
sqlx::query("UPDATE contacts SET signal_version = 'v1'")
|
||||
.execute(&database.pool)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
database.pool.close().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn get_backup_files(
|
||||
ctx: &Context,
|
||||
|
|
@ -147,9 +206,19 @@ impl BackupArchive {
|
|||
}
|
||||
}
|
||||
|
||||
for (file_name, target_dir, is_db, _) in Self::get_backup_files(ctx, &key_manager)? {
|
||||
for (file_name, target_dir, is_db, mut encryption_key) in
|
||||
Self::get_backup_files(ctx, &key_manager)?
|
||||
{
|
||||
let src = restore_temp_dir.join(file_name);
|
||||
if src.exists() {
|
||||
if is_db {
|
||||
Self::reset_restored_signal_sessions(
|
||||
file_name,
|
||||
&src,
|
||||
encryption_key.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
std::fs::create_dir_all(&target_dir)?;
|
||||
let dst = target_dir.join(file_name);
|
||||
if is_db {
|
||||
|
|
@ -161,6 +230,7 @@ impl BackupArchive {
|
|||
|
||||
std::fs::copy(src, dst)?;
|
||||
}
|
||||
encryption_key.zeroize();
|
||||
}
|
||||
|
||||
std::fs::remove_dir_all(&restore_temp_dir)?;
|
||||
|
|
@ -210,6 +280,18 @@ mod tests {
|
|||
ReceivedMessage::insert(&db.pool, 1, b"original message")
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query(
|
||||
"INSERT INTO signal_identities(name, identity_key, timestamp) VALUES('restored-peer', x'040506', 1)",
|
||||
)
|
||||
.execute(&db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query(
|
||||
"INSERT INTO signal_sessions(name, device_id, record_bytes) VALUES('restored-peer', 1, x'010203')",
|
||||
)
|
||||
.execute(&db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Add a file
|
||||
let config_file = PathBuf::from(&config.data_dir).join("user_discovery_config.json");
|
||||
|
|
@ -217,6 +299,39 @@ mod tests {
|
|||
key_manager.main_key.get_login_token()
|
||||
};
|
||||
|
||||
// The hotfix still carries the legacy Dart Signal store alongside the
|
||||
// Rust PQXDH store. Both session stores are part of an archive.
|
||||
{
|
||||
let config = ctx.get_config().unwrap();
|
||||
let app_db_path = PathBuf::from(&config.database_dir).join("twonly.sqlite");
|
||||
let app_db = Database::new(&app_db_path.display().to_string(), None, false)
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query(
|
||||
"CREATE TABLE contacts(user_id INTEGER PRIMARY KEY, signal_version TEXT NOT NULL)",
|
||||
)
|
||||
.execute(&app_db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query(
|
||||
"CREATE TABLE signal_session_stores(device_id INTEGER NOT NULL, name TEXT NOT NULL, session_record BLOB NOT NULL, PRIMARY KEY(device_id, name))",
|
||||
)
|
||||
.execute(&app_db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query("INSERT INTO contacts(user_id, signal_version) VALUES(7, 'v2')")
|
||||
.execute(&app_db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query(
|
||||
"INSERT INTO signal_session_stores(device_id, name, session_record) VALUES(1, '7', x'070809')",
|
||||
)
|
||||
.execute(&app_db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
app_db.pool.close().await;
|
||||
}
|
||||
|
||||
// 2. Create backup
|
||||
let backup_path = BackupArchive::create_backup(&ctx).await.unwrap();
|
||||
assert!(backup_path.exists());
|
||||
|
|
@ -266,6 +381,44 @@ mod tests {
|
|||
assert_eq!(messages[0].sender_id, 1);
|
||||
assert_eq!(messages[0].content, b"original message");
|
||||
|
||||
let session_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM signal_sessions")
|
||||
.fetch_one(&db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(session_count, 0, "restored PQXDH sessions must be reset");
|
||||
|
||||
let identity_key: Vec<u8> = sqlx::query_scalar(
|
||||
"SELECT identity_key FROM signal_identities WHERE name = 'restored-peer'",
|
||||
)
|
||||
.fetch_one(&db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
identity_key,
|
||||
vec![4, 5, 6],
|
||||
"trusted contact identities must survive session reset"
|
||||
);
|
||||
|
||||
let app_db_path = PathBuf::from(&config.database_dir).join("twonly.sqlite");
|
||||
let app_db = Database::new(&app_db_path.display().to_string(), None, false)
|
||||
.await
|
||||
.unwrap();
|
||||
let legacy_session_count: i64 =
|
||||
sqlx::query_scalar("SELECT COUNT(*) FROM signal_session_stores")
|
||||
.fetch_one(&app_db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
legacy_session_count, 0,
|
||||
"restored legacy Signal sessions must be reset"
|
||||
);
|
||||
let signal_version: String =
|
||||
sqlx::query_scalar("SELECT signal_version FROM contacts WHERE user_id = 7")
|
||||
.fetch_one(&app_db.pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(signal_version, "v1");
|
||||
|
||||
let config_file = PathBuf::from(&config.data_dir).join("user_discovery_config.json");
|
||||
let config_content = std::fs::read_to_string(config_file).unwrap();
|
||||
assert_eq!(config_content, "original config");
|
||||
|
|
|
|||
|
|
@ -29,6 +29,17 @@ impl RustSignal {
|
|||
engine.process_prekey_bundle(name, device_id, bundle).await
|
||||
}
|
||||
|
||||
/// Removes the current v2 session for a contact. The Dart hotfix layer
|
||||
/// immediately establishes its replacement from a freshly fetched bundle.
|
||||
pub async fn reset_contact_session(contact_id: i64) -> Result<()> {
|
||||
let guard = get_twonly_flutter()?.signal_engine.lock().await;
|
||||
let engine = guard.as_ref().ok_or(TwonlyError::Initialization)?;
|
||||
engine
|
||||
.reset_session(&contact_id.to_string(), 1, true)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn encrypt(name: String, device_id: u32, plaintext: Vec<u8>) -> Result<Vec<u8>> {
|
||||
let guard = get_twonly_flutter()?.signal_engine.lock().await;
|
||||
let engine = guard.as_ref().ok_or(TwonlyError::Initialization)?;
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ flutter_rust_bridge::frb_generated_boilerplate!(
|
|||
default_rust_auto_opaque = RustAutoOpaqueMoi,
|
||||
);
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1109927244;
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -2042422055;
|
||||
|
||||
// Section: executor
|
||||
|
||||
|
|
@ -915,6 +915,46 @@ fn wire__crate__bridge__wrapper__signal__rust_signal_process_prekey_bundle_impl(
|
|||
},
|
||||
)
|
||||
}
|
||||
fn wire__crate__bridge__wrapper__signal__rust_signal_reset_contact_session_impl(
|
||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||
rust_vec_len_: i32,
|
||||
data_len_: i32,
|
||||
) {
|
||||
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec, _, _, _>(
|
||||
flutter_rust_bridge::for_generated::TaskInfo {
|
||||
debug_name: "rust_signal_reset_contact_session",
|
||||
port: Some(port_),
|
||||
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
|
||||
},
|
||||
move || {
|
||||
let message = unsafe {
|
||||
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
|
||||
ptr_,
|
||||
rust_vec_len_,
|
||||
data_len_,
|
||||
)
|
||||
};
|
||||
let mut deserializer =
|
||||
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||
let api_contact_id = <i64>::sse_decode(&mut deserializer);
|
||||
deserializer.end();
|
||||
move |context| async move {
|
||||
transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>(
|
||||
(move || async move {
|
||||
let output_ok =
|
||||
crate::bridge::wrapper::signal::RustSignal::reset_contact_session(
|
||||
api_contact_id,
|
||||
)
|
||||
.await?;
|
||||
Ok(output_ok)
|
||||
})()
|
||||
.await,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
fn wire__crate__bridge__wrapper__rust_utils_generate_shares_impl(
|
||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||
|
|
@ -2201,24 +2241,25 @@ fn pde_ffi_dispatcher_primary_impl(
|
|||
34 => wire__crate__bridge__wrapper__signal__rust_signal_generate_bundle_impl(port, ptr, rust_vec_len, data_len),
|
||||
35 => wire__crate__bridge__wrapper__signal__rust_signal_generate_pqc_prekeys_impl(port, ptr, rust_vec_len, data_len),
|
||||
36 => wire__crate__bridge__wrapper__signal__rust_signal_process_prekey_bundle_impl(port, ptr, rust_vec_len, data_len),
|
||||
37 => wire__crate__bridge__wrapper__rust_utils_generate_shares_impl(port, ptr, rust_vec_len, data_len),
|
||||
38 => wire__crate__bridge__wrapper__rust_utils_recover_secret_impl(port, ptr, rust_vec_len, data_len),
|
||||
39 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_announced_user_by_public_id_impl(port, ptr, rust_vec_len, data_len),
|
||||
40 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_config_impl(port, ptr, rust_vec_len, data_len),
|
||||
41 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_contact_promotion_impl(port, ptr, rust_vec_len, data_len),
|
||||
42 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_contact_version_impl(port, ptr, rust_vec_len, data_len),
|
||||
43 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_other_promotions_by_public_id_impl(port, ptr, rust_vec_len, data_len),
|
||||
44 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_own_promotions_after_version_impl(port, ptr, rust_vec_len, data_len),
|
||||
45 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_share_for_contact_impl(port, ptr, rust_vec_len, data_len),
|
||||
46 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_push_new_user_relation_impl(port, ptr, rust_vec_len, data_len),
|
||||
47 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_push_own_promotion_and_clear_old_version_impl(port, ptr, rust_vec_len, data_len),
|
||||
48 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_set_contact_version_impl(port, ptr, rust_vec_len, data_len),
|
||||
49 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_set_shares_impl(port, ptr, rust_vec_len, data_len),
|
||||
50 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_store_other_promotion_impl(port, ptr, rust_vec_len, data_len),
|
||||
51 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_update_config_impl(port, ptr, rust_vec_len, data_len),
|
||||
52 => wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_sign_data_impl(port, ptr, rust_vec_len, data_len),
|
||||
53 => wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_verify_signature_impl(port, ptr, rust_vec_len, data_len),
|
||||
54 => wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_verify_stored_pubkey_impl(port, ptr, rust_vec_len, data_len),
|
||||
37 => wire__crate__bridge__wrapper__signal__rust_signal_reset_contact_session_impl(port, ptr, rust_vec_len, data_len),
|
||||
38 => wire__crate__bridge__wrapper__rust_utils_generate_shares_impl(port, ptr, rust_vec_len, data_len),
|
||||
39 => wire__crate__bridge__wrapper__rust_utils_recover_secret_impl(port, ptr, rust_vec_len, data_len),
|
||||
40 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_announced_user_by_public_id_impl(port, ptr, rust_vec_len, data_len),
|
||||
41 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_config_impl(port, ptr, rust_vec_len, data_len),
|
||||
42 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_contact_promotion_impl(port, ptr, rust_vec_len, data_len),
|
||||
43 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_contact_version_impl(port, ptr, rust_vec_len, data_len),
|
||||
44 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_other_promotions_by_public_id_impl(port, ptr, rust_vec_len, data_len),
|
||||
45 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_own_promotions_after_version_impl(port, ptr, rust_vec_len, data_len),
|
||||
46 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_get_share_for_contact_impl(port, ptr, rust_vec_len, data_len),
|
||||
47 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_push_new_user_relation_impl(port, ptr, rust_vec_len, data_len),
|
||||
48 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_push_own_promotion_and_clear_old_version_impl(port, ptr, rust_vec_len, data_len),
|
||||
49 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_set_contact_version_impl(port, ptr, rust_vec_len, data_len),
|
||||
50 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_set_shares_impl(port, ptr, rust_vec_len, data_len),
|
||||
51 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_store_other_promotion_impl(port, ptr, rust_vec_len, data_len),
|
||||
52 => wire__crate__bridge__callbacks__user_discovery__user_discovery_store_flutter_update_config_impl(port, ptr, rust_vec_len, data_len),
|
||||
53 => wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_sign_data_impl(port, ptr, rust_vec_len, data_len),
|
||||
54 => wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_verify_signature_impl(port, ptr, rust_vec_len, data_len),
|
||||
55 => wire__crate__bridge__callbacks__user_discovery__user_discovery_utils_flutter_verify_stored_pubkey_impl(port, ptr, rust_vec_len, data_len),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ use crate::error::{Result, TwonlyError};
|
|||
use libsignal_protocol::{
|
||||
message_encrypt, process_prekey_bundle, CiphertextMessageType, DeviceId, GenericSignedPreKey,
|
||||
IdentityKey, IdentityKeyPair, IdentityKeyStore, KyberPreKeyId, KyberPreKeyStore, PreKeyBundle,
|
||||
PreKeyId, PreKeySignalMessage, PreKeyStore, ProtocolAddress, PublicKey, SignalMessage,
|
||||
SignedPreKeyId, SignedPreKeyStore, Timestamp,
|
||||
PreKeyId, PreKeySignalMessage, PreKeyStore, ProtocolAddress, PublicKey, SessionStore,
|
||||
SignalMessage, SignedPreKeyId, SignedPreKeyStore, Timestamp,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
|
@ -414,6 +414,58 @@ impl RustSignalEngine {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Retires the session with a peer so a fresh one can replace it.
|
||||
///
|
||||
/// A hard reset deletes the complete record, including archived states.
|
||||
/// A soft reset archives only the current state so in-flight messages can
|
||||
/// still be decrypted while a replacement session is established.
|
||||
///
|
||||
/// Returns whether a session existed.
|
||||
pub async fn reset_session(&self, name: &str, device_id: u32, hard: bool) -> Result<bool> {
|
||||
let mut store_guard = self.store.lock().await;
|
||||
let store = &mut *store_guard;
|
||||
|
||||
let d_id = DeviceId::try_from(device_id)
|
||||
.map_err(|_| TwonlyError::Generic(format!("Invalid device id: {}", device_id)))?;
|
||||
let address = ProtocolAddress::new(name.to_owned(), d_id);
|
||||
|
||||
if hard {
|
||||
let deleted =
|
||||
sqlx::query("DELETE FROM signal_sessions WHERE name = ? AND device_id = ?")
|
||||
.bind(name)
|
||||
.bind(device_id)
|
||||
.execute(&store.pool)
|
||||
.await?
|
||||
.rows_affected();
|
||||
tracing::warn!(name, device_id, "deleted the signal session");
|
||||
return Ok(deleted != 0);
|
||||
}
|
||||
|
||||
let Some(mut record) = store
|
||||
.session_store
|
||||
.load_session(&address)
|
||||
.assert_send()
|
||||
.await
|
||||
.map_err(|e| TwonlyError::Signal(e.to_string()))?
|
||||
else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
record
|
||||
.archive_current_state()
|
||||
.map_err(|e| TwonlyError::Signal(e.to_string()))?;
|
||||
|
||||
store
|
||||
.session_store
|
||||
.store_session(&address, &record)
|
||||
.assert_send()
|
||||
.await
|
||||
.map_err(|e| TwonlyError::Signal(e.to_string()))?;
|
||||
|
||||
tracing::warn!(name, device_id, "archived the signal session for a reset");
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub async fn encrypt_message(
|
||||
&self,
|
||||
name: String,
|
||||
|
|
@ -618,4 +670,40 @@ mod tests {
|
|||
|
||||
assert_eq!(plaintext.to_vec(), decrypted);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn hard_reset_deletes_and_allows_rebuilding_a_contact_session() {
|
||||
let (alice_engine, _alice_dir) = create_test_engine("alice").await;
|
||||
let (bob_engine, _bob_dir) = create_test_engine("bob").await;
|
||||
|
||||
alice_engine
|
||||
.process_prekey_bundle(
|
||||
"bob".to_string(),
|
||||
1,
|
||||
bob_engine.generate_bundle().await.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(alice_engine.reset_session("bob", 1, true).await.unwrap());
|
||||
assert!(!alice_engine.reset_session("bob", 1, true).await.unwrap());
|
||||
assert!(alice_engine
|
||||
.encrypt_message("bob".to_string(), 1, b"missing".to_vec())
|
||||
.await
|
||||
.is_err());
|
||||
|
||||
alice_engine
|
||||
.process_prekey_bundle(
|
||||
"bob".to_string(),
|
||||
1,
|
||||
bob_engine.generate_bundle().await.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(alice_engine
|
||||
.encrypt_message("bob".to_string(), 1, b"rebuilt".to_vec())
|
||||
.await
|
||||
.is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
test/model/mailbox_drained_proto_test.dart
Normal file
13
test/model/mailbox_drained_proto_test.dart
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart';
|
||||
|
||||
void main() {
|
||||
test('decodes the server mailbox-drained signal', () {
|
||||
// ServerToClient.V0 (field 1) containing V0.mailboxDrained (field 11).
|
||||
final message = ServerToClient.fromBuffer([0x0a, 0x02, 0x58, 0x01]);
|
||||
|
||||
expect(message.v0.hasMailboxDrained(), isTrue);
|
||||
expect(message.v0.mailboxDrained, isTrue);
|
||||
expect(message.v0.whichKind(), V0_Kind.mailboxDrained);
|
||||
});
|
||||
}
|
||||
27
test/model/pqc_upload_proto_test.dart
Normal file
27
test/model/pqc_upload_proto_test.dart
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pb.dart';
|
||||
|
||||
void main() {
|
||||
test('PQC upload preserves migration identity fields on the wire', () {
|
||||
final request = ApplicationData_UploadPqcPreKeys(
|
||||
eccSignedPrekeyId: Int64(11),
|
||||
eccSignedPrekey: [5, 6],
|
||||
eccSignedPrekeySignature: [7, 8],
|
||||
kyberSignedPrekeyId: Int64(12),
|
||||
kyberSignedPrekey: [9, 10],
|
||||
kyberSignedPrekeySignature: [11, 12],
|
||||
publicIdentityKey: List<int>.filled(33, 1),
|
||||
registrationId: Int64(42),
|
||||
);
|
||||
|
||||
final decoded = ApplicationData_UploadPqcPreKeys.fromBuffer(
|
||||
request.writeToBuffer(),
|
||||
);
|
||||
|
||||
expect(decoded.publicIdentityKey, hasLength(33));
|
||||
expect(decoded.registrationId, Int64(42));
|
||||
expect(decoded.hasPublicIdentityKey(), isTrue);
|
||||
expect(decoded.hasRegistrationId(), isTrue);
|
||||
});
|
||||
}
|
||||
86
test/services/receipts_out_of_sync_detection_test.dart
Normal file
86
test/services/receipts_out_of_sync_detection_test.dart
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
|
||||
void main() {
|
||||
late TwonlyDB db;
|
||||
|
||||
setUp(() async {
|
||||
db = TwonlyDB.forTesting(
|
||||
DatabaseConnection(
|
||||
NativeDatabase.memory(),
|
||||
closeStreamsSynchronously: true,
|
||||
),
|
||||
);
|
||||
|
||||
for (final contactId in [10, 11, 12]) {
|
||||
await db.contactsDao.insertContact(
|
||||
ContactsCompanion.insert(
|
||||
userId: Value(contactId),
|
||||
username: 'contact-$contactId',
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
tearDown(() => db.close());
|
||||
|
||||
Future<void> addReceipt(
|
||||
int contactId,
|
||||
int retryCount, {
|
||||
bool mediaUpload = false,
|
||||
}) async {
|
||||
await db.receiptsDao.insertReceipt(
|
||||
ReceiptsCompanion(
|
||||
contactId: Value(contactId),
|
||||
message: Value(Uint8List.fromList([contactId])),
|
||||
retryCount: Value(retryCount),
|
||||
willBeRetriedByMediaUpload: Value(mediaUpload),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
test(
|
||||
'detects multiple message retries above the ratchet threshold',
|
||||
() async {
|
||||
await addReceipt(10, 21);
|
||||
await addReceipt(10, 30);
|
||||
await addReceipt(11, 21);
|
||||
await addReceipt(11, 20);
|
||||
await addReceipt(12, 25, mediaUpload: true);
|
||||
await addReceipt(12, 25, mediaUpload: true);
|
||||
|
||||
final contactIds = await db.receiptsDao
|
||||
.getContactsWithStuckSignalMessages(
|
||||
retryThreshold: 20,
|
||||
);
|
||||
|
||||
expect(contactIds, [10]);
|
||||
},
|
||||
);
|
||||
|
||||
test('clears message counters after a successful session reset', () async {
|
||||
await addReceipt(10, 21);
|
||||
await addReceipt(10, 30);
|
||||
await addReceipt(10, 25, mediaUpload: true);
|
||||
|
||||
await db.receiptsDao.resetMessageRetryCountsForContact(10);
|
||||
|
||||
final receipts = await (db.select(
|
||||
db.receipts,
|
||||
)..where((receipt) => receipt.contactId.equals(10))).get();
|
||||
final messageReceipts = receipts.where(
|
||||
(receipt) => !receipt.willBeRetriedByMediaUpload,
|
||||
);
|
||||
final mediaReceipt = receipts.singleWhere(
|
||||
(receipt) => receipt.willBeRetriedByMediaUpload,
|
||||
);
|
||||
|
||||
expect(
|
||||
messageReceipts.map((receipt) => receipt.retryCount),
|
||||
everyElement(0),
|
||||
);
|
||||
expect(mediaReceipt.retryCount, 25);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue