90a1390716
Fixes reported by testers and found in an app review: - First message right after install no longer fails. Before the app finished registering with the relay, the outgoing message could crash on a null delivery address. The profile field is now optional and the send path throws a clear "still connecting, try again shortly" error while registration completes in the background. - The QR code and share link now appear only once the delivery address is ready. Previously a link generated before the first connection lacked routing, so the other side could not send the first message. The screen now shows a connecting state and reactively swaps in the QR when ready. - Incomplete contact links are rejected on import instead of failing at first send, with a clear prompt to ask for a fresh link. - Auto-lock no longer silences the app: after locking and unlocking, messaging is restarted (it previously stayed dead until app restart). - Group messages are marked failed on a non-per-member send error instead of hanging in the "sending" state forever. - Hardened the relay client against a rare duplicate connection when toggling Tor or locking mid-connect, and fromShareString now rejects a delivery address of the wrong length. - Pin unifiedpush below 6.2.0 to drop the webcrypto dependency (its native framework lacks LC_ENCRYPTION_INFO and blocked the iOS upload). - Skip Android release lint (network dependency check hangs on the build host and adds no value for a Flutter app).
197 lines
5.4 KiB
Dart
197 lines
5.4 KiB
Dart
/// Typované chyby, které se zobrazují uživateli.
|
|
///
|
|
/// Service vrstva nemá přístup k lokalizaci (žádný BuildContext), proto
|
|
/// nesmí formulovat texty pro uživatele — vyhazuje tyhle typy a UI je
|
|
/// překládá přes `describeError` (core/l10n/error_messages.dart).
|
|
/// `toString()` je anglický technický popis pro logy, ne pro UI.
|
|
library;
|
|
|
|
sealed class SepotUserError implements Exception {
|
|
const SepotUserError();
|
|
}
|
|
|
|
/// Trust root relaye nesedí s pinovaným — server nelze ověřit.
|
|
class TrustRootChangedError extends SepotUserError {
|
|
const TrustRootChangedError();
|
|
|
|
@override
|
|
String toString() => 'relay trust root changed — refusing to communicate';
|
|
}
|
|
|
|
/// Relay nedostupný (typicky fetch sender certificate).
|
|
class RelayUnavailableError extends SepotUserError {
|
|
const RelayUnavailableError();
|
|
|
|
@override
|
|
String toString() => 'relay unavailable';
|
|
}
|
|
|
|
/// Vlastní registrace na relay ještě nedoběhla — nemáme sender adresu.
|
|
/// Přechodný stav po čerstvé instalaci; registrace běží na pozadí.
|
|
class NotYetRegisteredError extends SepotUserError {
|
|
const NotYetRegisteredError();
|
|
|
|
@override
|
|
String toString() => 'not registered on relay yet';
|
|
}
|
|
|
|
/// Odkaz/QR kontaktu je neúplný (chybí routing aid+da) — vygenerovaný
|
|
/// dřív, než měla protistrana dokončenou registraci na relay.
|
|
class ContactLinkIncompleteError extends SepotUserError {
|
|
const ContactLinkIncompleteError();
|
|
|
|
@override
|
|
String toString() => 'contact link is missing routing (incomplete)';
|
|
}
|
|
|
|
/// Příloha přesahuje serverový limit.
|
|
class AttachmentTooLargeError extends SepotUserError {
|
|
const AttachmentTooLargeError(this.maxBytes);
|
|
|
|
final int maxBytes;
|
|
|
|
int get maxMb => (maxBytes / (1024 * 1024)).round();
|
|
|
|
@override
|
|
String toString() => 'attachment too large (max $maxBytes bytes)';
|
|
}
|
|
|
|
/// Skupina bez jediného dalšího člena nedává smysl.
|
|
class GroupNeedsMemberError extends SepotUserError {
|
|
const GroupNeedsMemberError();
|
|
|
|
@override
|
|
String toString() => 'group needs at least one member';
|
|
}
|
|
|
|
/// Překročen strop počtu členů skupiny.
|
|
class GroupTooLargeError extends SepotUserError {
|
|
const GroupTooLargeError(this.maxMembers);
|
|
|
|
final int maxMembers;
|
|
|
|
@override
|
|
String toString() => 'group exceeds $maxMembers members';
|
|
}
|
|
|
|
/// Člen skupiny nemá doručovací adresu (starý odkaz bez routingu).
|
|
class MemberNoDeliveryAddressError extends SepotUserError {
|
|
const MemberNoDeliveryAddressError(this.displayName);
|
|
|
|
final String displayName;
|
|
|
|
@override
|
|
String toString() => 'member "$displayName" has no delivery address';
|
|
}
|
|
|
|
/// Kontakt nemá doručovací adresu (starý odkaz bez routingu).
|
|
class ContactNoDeliveryAddressError extends SepotUserError {
|
|
const ContactNoDeliveryAddressError();
|
|
|
|
@override
|
|
String toString() => 'contact has no delivery address';
|
|
}
|
|
|
|
/// Identity klíč z relaye nesedí s uloženým kontaktem — možný MITM.
|
|
class IdentityKeyMismatchError extends SepotUserError {
|
|
const IdentityKeyMismatchError();
|
|
|
|
@override
|
|
String toString() => 'identity key mismatch — possible MITM, message not sent';
|
|
}
|
|
|
|
// === Obnova účtu (mnemonic) ===
|
|
|
|
/// Fráze má špatný počet slov.
|
|
class MnemonicWordCountError extends SepotUserError {
|
|
const MnemonicWordCountError(this.count, this.expected);
|
|
|
|
final int count;
|
|
final int expected;
|
|
|
|
@override
|
|
String toString() => 'mnemonic has $count words, expected $expected';
|
|
}
|
|
|
|
/// Fráze neprošla validací BIP39 (neznámé slovo, checksum…).
|
|
class MnemonicInvalidError extends SepotUserError {
|
|
const MnemonicInvalidError(this.detail);
|
|
|
|
final String detail;
|
|
|
|
@override
|
|
String toString() => 'invalid mnemonic: $detail';
|
|
}
|
|
|
|
// === Zálohy historie (backup_service) ===
|
|
|
|
/// Soubor zálohy je poškozený (nejde přečíst/dešifrovat strukturu).
|
|
class BackupCorruptedError extends SepotUserError {
|
|
const BackupCorruptedError();
|
|
|
|
@override
|
|
String toString() => 'corrupted backup file';
|
|
}
|
|
|
|
/// Soubor není záloha Šepotu.
|
|
class BackupUnknownFormatError extends SepotUserError {
|
|
const BackupUnknownFormatError();
|
|
|
|
@override
|
|
String toString() => 'unknown backup format';
|
|
}
|
|
|
|
/// Záloha je šifrovaná klíčem jiné identity.
|
|
class BackupWrongIdentityError extends SepotUserError {
|
|
const BackupWrongIdentityError();
|
|
|
|
@override
|
|
String toString() => 'backup belongs to a different identity';
|
|
}
|
|
|
|
// === Kontakty ===
|
|
|
|
/// Kontakt s tímto fingerprintem už existuje.
|
|
class ContactAlreadyExistsError extends SepotUserError {
|
|
const ContactAlreadyExistsError();
|
|
|
|
@override
|
|
String toString() => 'contact already exists';
|
|
}
|
|
|
|
// === Nahlášení zneužití (report_service) ===
|
|
|
|
/// Hlášení bez popisu nemá co řešit.
|
|
class ReportEmptyDescriptionError extends SepotUserError {
|
|
const ReportEmptyDescriptionError();
|
|
|
|
@override
|
|
String toString() => 'report has empty description';
|
|
}
|
|
|
|
/// Server hlášení je nedosažitelný.
|
|
class ReportConnectionError extends SepotUserError {
|
|
const ReportConnectionError();
|
|
|
|
@override
|
|
String toString() => 'report endpoint unreachable';
|
|
}
|
|
|
|
/// Rate limit hlášení (429).
|
|
class ReportRateLimitedError extends SepotUserError {
|
|
const ReportRateLimitedError();
|
|
|
|
@override
|
|
String toString() => 'report rate limited';
|
|
}
|
|
|
|
/// Server hlášení vrátil chybový kód.
|
|
class ReportServerError extends SepotUserError {
|
|
const ReportServerError(this.statusCode);
|
|
|
|
final int statusCode;
|
|
|
|
@override
|
|
String toString() => 'report failed with status $statusCode';
|
|
}
|