Files
sepot/app/lib/core/errors.dart
T
Julius Joska 90a1390716 fix: reliable first-message send, QR routing, auto-lock recovery
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).
2026-07-18 19:53:41 +02:00

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';
}