Rebrand application as Qlyra
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

This commit is contained in:
sevenhill
2026-08-30 13:16:17 +03:00
parent 0245f792de
commit f74057ce9b
592 changed files with 38233 additions and 1899 deletions
+54 -24
View File
@@ -28,6 +28,8 @@ abstract class MaxWebCmd {
abstract class MaxWebFraming {
static const int protocolVersion = 10;
static const int headerSize = 10;
static const int maxPayloadSize = 0x00ffffff;
static const int maxDecompressedPayloadSize = 64 * 1024 * 1024;
static Uint8List encode({
required int cmd,
@@ -35,9 +37,14 @@ abstract class MaxWebFraming {
required int opcode,
Object? payload,
}) {
final body = payload == null
? Uint8List(0)
: MaxMsgpack.encode(payload);
final body = payload == null ? Uint8List(0) : MaxMsgpack.encode(payload);
if (body.length > maxPayloadSize) {
throw ArgumentError.value(
body.length,
'payload',
'превышает допустимый размер кадра',
);
}
final frame = Uint8List(headerSize + body.length);
final view = ByteData.view(frame.buffer);
@@ -66,6 +73,12 @@ abstract class MaxWebFraming {
final compressionRatio = view.getUint8(6);
final length =
(view.getUint8(7) << 16) | (view.getUint8(8) << 8) | view.getUint8(9);
final expectedLength = headerSize + length;
if (frame.length != expectedLength) {
throw FormatException(
'MaxWebFraming: длина кадра ${frame.length} не совпадает с $expectedLength',
);
}
if (length <= 0) {
return MaxWebFrame(cmd: cmd, seq: seq, opcode: opcode);
@@ -73,7 +86,7 @@ abstract class MaxWebFraming {
var body = Uint8List.sublistView(frame, headerSize, headerSize + length);
if (compressionRatio > 0) {
body = Lz4Block.decompress(body, length * compressionRatio * 16);
body = Lz4Block.decompress(body, maxDecompressedPayloadSize);
}
return MaxWebFrame(
@@ -87,51 +100,67 @@ abstract class MaxWebFraming {
abstract class Lz4Block {
static Uint8List decompress(Uint8List source, int maxOutputSize) {
final output = Uint8List(maxOutputSize);
if (maxOutputSize < 0) {
throw ArgumentError.value(maxOutputSize, 'maxOutputSize');
}
final output = <int>[];
var input = 0;
var written = 0;
while (input < source.length) {
final token = source[input++];
var literalLength = token >> 4;
if (literalLength == 15) {
literalLength += _readLengthExtension(source, () => input, (v) => input = v);
literalLength += _readLengthExtension(
source,
() => input,
(v) => input = v,
);
}
if (written + literalLength > output.length) {
if (literalLength > source.length - input) {
throw const FormatException('Lz4Block: обрыв литералов');
}
if (literalLength > maxOutputSize - output.length) {
throw const FormatException('Lz4Block: литералы не помещаются');
}
output.setRange(written, written + literalLength,
Uint8List.sublistView(source, input, input + literalLength));
written += literalLength;
output.addAll(
Uint8List.sublistView(source, input, input + literalLength),
);
input += literalLength;
if (input >= source.length) break;
if (source.length - input < 2) {
throw const FormatException('Lz4Block: обрыв смещения совпадения');
}
final offset = source[input] | (source[input + 1] << 8);
input += 2;
if (offset == 0 || offset > written) {
if (offset == 0 || offset > output.length) {
throw const FormatException('Lz4Block: неверное смещение совпадения');
}
var matchLength = token & 0x0F;
if (matchLength == 15) {
matchLength += _readLengthExtension(source, () => input, (v) => input = v);
matchLength += _readLengthExtension(
source,
() => input,
(v) => input = v,
);
}
matchLength += 4;
if (written + matchLength > output.length) {
if (matchLength > maxOutputSize - output.length) {
throw const FormatException('Lz4Block: совпадение не помещается');
}
var from = written - offset;
for (var i = 0; i < matchLength; i++) {
output[written++] = output[from++];
output.add(output[output.length - offset]);
}
}
return Uint8List.sublistView(output, 0, written);
return Uint8List.fromList(output);
}
static int _readLengthExtension(
@@ -191,7 +220,9 @@ abstract class MaxMsgpack {
_write(sink, item);
});
} else {
throw ArgumentError('MaxMsgpack: неподдерживаемый тип ${value.runtimeType}');
throw ArgumentError(
'MaxMsgpack: неподдерживаемый тип ${value.runtimeType}',
);
}
}
@@ -306,11 +337,8 @@ abstract class MaxMsgpack {
}
class _Reader {
_Reader(this._bytes) : _view = ByteData.view(
_bytes.buffer,
_bytes.offsetInBytes,
_bytes.length,
);
_Reader(this._bytes)
: _view = ByteData.view(_bytes.buffer, _bytes.offsetInBytes, _bytes.length);
final Uint8List _bytes;
final ByteData _view;
@@ -393,7 +421,9 @@ class _Reader {
if (byte == 0xC8) return _ext(_u16());
if (byte == 0xC9) return _ext(_u32());
throw FormatException('MaxMsgpack: неизвестный маркер 0x${byte.toRadixString(16)}');
throw FormatException(
'MaxMsgpack: неизвестный маркер 0x${byte.toRadixString(16)}',
);
}
static const int _numberExtType = 1;
+5 -2
View File
@@ -81,7 +81,8 @@ class MaxWebSocketSession {
_subscription = socket.listen(
_onFrame,
onError: (Object error) => _failAll(MaxWebException('сокет: $error')),
onDone: () => _failAll(const MaxWebException('соединение закрыто сервером')),
onDone: () =>
_failAll(const MaxWebException('соединение закрыто сервером')),
cancelOnError: true,
);
@@ -168,7 +169,9 @@ class MaxWebSocketSession {
return MaxWebException(code, code: code);
}
}
return MaxWebException('опкод ${frame.opcode}: ошибка сервера (cmd=${frame.cmd})');
return MaxWebException(
'опкод ${frame.opcode}: ошибка сервера (cmd=${frame.cmd})',
);
}
void _failAll(MaxWebException error) {
+7 -2
View File
@@ -194,7 +194,10 @@ class WebPushService {
throw const MaxWebException('время подтверждения истекло');
}
Future<WebPushAuthStep> submitPassword(String trackId, String password) async {
Future<WebPushAuthStep> submitPassword(
String trackId,
String password,
) async {
final socket = _requireSocket();
final payload = _asMap(
await socket.request(Opcode.authLoginCheckPassword, <String, Object?>{
@@ -222,7 +225,9 @@ class WebPushService {
Future<void> registerSubscription(WebPushSubscription subscription) async {
final token = await TokenStorage.readSecure(_tokenKey);
if (token == null || token.isEmpty) {
throw const MaxWebException('сначала подключите уведомления в настройках');
throw const MaxWebException(
'сначала подключите уведомления в настройках',
);
}
final socket = MaxWebSocketSession(device: await device());