Files
sevenhill f74057ce9b
Build Android (FCM) / build-android-fcm (push) Canceled after 0s
Build Android / build-android (push) Canceled after 0s
Build iOS / build-ios (push) Canceled after 0s
Build Linux / build-linux (push) Canceled after 0s
Build macOS / build-macos (push) Canceled after 0s
Build Windows / build-windows (push) Canceled after 0s
Release (main) / android (oneme) (push) Canceled after 0s
Release (main) / android (qlyra) (push) Canceled after 0s
Release (main) / windows (push) Canceled after 0s
Release (main) / linux (push) Canceled after 0s
Release (main) / macos (push) Canceled after 0s
Release (main) / ios (push) Canceled after 0s
Release (main) / release (push) Canceled after 0s
Rebrand application as Qlyra
2026-08-30 13:16:17 +03:00

105 lines
3.4 KiB
Dart

/// Типы команд в протоколе
abstract class CmdType {
static const int request =
0; // запрос клиента / пуш от сервера (направление определяет смысл)
static const int push = 0; // пуш от сервера (имеет смысл только для incoming)
static const int ok = 1; // ответ: ок
static const int notFound = 2; // ответ: не найдено
static const int error = 3; // ответ: ошибка
}
/// Распакованный пакет.
///
/// Провод (фрейминг, MsgPack, сжатие) живёт в Rust-ядре kusoft; здесь пакет —
/// это уже декодированный [payload] (Map/List/скаляр, бинарь — Uint8List) плюс
/// метаданные заголовка.
class Packet {
int api;
int cmd;
int seq;
int opcode;
dynamic payload;
Packet({
this.api = 10,
this.cmd = 0,
this.seq = 0,
this.opcode = 0,
this.payload,
});
bool get isOk => cmd == CmdType.ok;
bool get isError => cmd == CmdType.error;
bool get isPush => cmd == CmdType.push;
@override
String toString() =>
'Packet(ver=$api cmd=$cmd seq=$seq opcode=$opcode payload=$payload)';
}
class PacketError implements Exception {
final String message;
final String? errorKey;
const PacketError(this.message, {this.errorKey});
@override
String toString() => message;
}
class SessionExpiredException extends PacketError {
const SessionExpiredException(super.message);
}
bool isPermanentSendFailure(Object error) {
if (error is! PacketError) return false;
if (error is SessionExpiredException) return false;
return !(error.errorKey?.contains('not.ready') ?? false);
}
String messageFromErrorPayload(dynamic payload) {
if (payload is Map) {
final msg = payload['message'];
if (msg == 'FAIL_WRONG_PASSWORD' || msg == 'FAIL_LOGIN_TOKEN') {
return 'Ваш токен был отклонён сервером, хм... Попробуйте войти ещё раз.';
}
for (final key in ['localizedMessage', 'title', 'message']) {
final v = payload[key];
if (v is! String) continue;
final text = v.trim();
if (text.isEmpty || _isRawServerTemplate(text)) continue;
return text;
}
return 'Неизвестная ошибка';
}
if (payload == null) return 'Неизвестная ошибка';
final s = payload.toString();
return s.isNotEmpty ? s : 'Неизвестная ошибка';
}
bool _isRawServerTemplate(String text) =>
text.startsWith('Key: ') || text.startsWith('key: ');
bool isSessionExpiredPayload(dynamic payload) {
return payload is Map &&
(payload['message'] == 'FAIL_LOGIN_TOKEN' ||
payload['message'] == 'FAIL_WRONG_PASSWORD');
}
void throwIfPacketError(Packet packet) {
if (!packet.isError) return;
final payload = packet.payload;
if (isSessionExpiredPayload(payload)) {
throw SessionExpiredException(messageFromErrorPayload(payload));
}
throw PacketError(messageFromErrorPayload(payload));
}
bool isSessionStateError(Object error) {
if (error is SessionExpiredException) return true;
final text = error.toString().toLowerCase();
return text.contains('состояние сессии') ||
text.contains('сессия не найдена') ||
text.contains('авторизационная сессия') ||
text.contains('сессия не онлайн');
}