feat(links): поддержка max.ru-ссылок и komet:// диплинков

- резолв max.ru/{join,joincall,u,c,:auth,<channel>}
- открытие чатов/профилей/звонков и подтверждение веб-входа в приложении
- внешние диплинки: intent-filter (Android), URL-схемы (iOS/macOS), komet://-регистрация в рантайме (Windows/Linux)
- fix(calls): не слать opcode 79 до login (жижа я тебя выебу)
This commit is contained in:
klockky
2026-06-23 14:18:51 +03:00
parent 8985887cea
commit ddb60b1d71
16 changed files with 646 additions and 112 deletions
+11 -1
View File
@@ -428,11 +428,20 @@ class LoginResult {
class AccountModule {
final Api _api;
final _loginStatusController = StreamController<LoginStatus>.broadcast();
bool _loggedIn = false;
AccountModule(this._api);
AccountModule(this._api) {
_api.stateStream.listen((state) {
if (state != SessionState.online) _loggedIn = false;
});
}
Stream<LoginStatus> get loginStatusStream => _loginStatusController.stream;
/// `true`, только когда сервер считает сессию ONLINE — после успешного
/// login (opcode 19), а не просто после хэндшейка (opcode 6).
bool get isLoggedIn => _loggedIn;
Future<PrivacyConfig> getPrivacyConfig() async {
final accountId = await TokenStorage.getActiveAccountId();
if (accountId != null) {
@@ -999,6 +1008,7 @@ class AccountModule {
}
final result = await _processLoginResponse(dataMap, resolvedAccountId);
_loggedIn = true;
_loginStatusController.add(LoginStatus.success);
return result;
} catch (e) {
+72
View File
@@ -0,0 +1,72 @@
import 'dart:async';
import '../../core/protocol/opcode_map.dart';
import '../../core/protocol/packet.dart';
import '../api.dart';
sealed class ResolvedLink {
const ResolvedLink();
}
class ResolvedChat extends ResolvedLink {
final Map<dynamic, dynamic> chat;
final Map<dynamic, dynamic>? message;
const ResolvedChat(this.chat, this.message);
}
class ResolvedUser extends ResolvedLink {
final Map<dynamic, dynamic> contact;
const ResolvedUser(this.contact);
}
class ResolvedLinkError extends ResolvedLink {
final String message;
const ResolvedLinkError(this.message);
}
abstract class LinkModule {
static Future<ResolvedLink?> resolve(Api api, String url) async {
final Packet response;
try {
response = await api.sendRequest(Opcode.linkInfo, {'link': url});
} on TimeoutException {
return const ResolvedLinkError('Превышено время ожидания');
} on PacketError catch (e) {
return ResolvedLinkError(e.message);
}
final payload = response.payload;
if (payload is! Map) return null;
if (!response.isOk) {
return ResolvedLinkError(messageFromErrorPayload(payload));
}
final chat = payload['chat'];
if (chat is Map) {
final message = payload['message'];
return ResolvedChat(chat, message is Map ? message : null);
}
final user = payload['user'];
if (user is Map && user['contact'] is Map) {
return ResolvedUser(user['contact'] as Map);
}
return null;
}
static Future<String?> join(Api api, String url) async {
try {
final response = await api.sendRequest(Opcode.chatJoin, {'link': url});
if (response.isOk) return null;
return messageFromErrorPayload(response.payload);
} on TimeoutException {
return 'Превышено время ожидания';
} on PacketError catch (e) {
return e.message;
}
}
}