feat: api2.oneme.ru с корнем Минцифры; kolibri с pub.dev вместо сабмодуля

This commit is contained in:
klockky
2026-08-06 11:03:50 +03:00
parent a46ee7234c
commit ed33f53c6a
18 changed files with 135 additions and 20 deletions
+4 -2
View File
@@ -1,7 +1,9 @@
name: Setup Rust name: Setup Rust
description: > description: >
Install the Rust toolchain with platform targets and a build cache, so Install the Rust toolchain with platform targets and a build cache, so
cargokit can compile the kolibri native library during the Flutter build. cargokit can compile the native libraries during the Flutter build. kolibri
comes from pub.dev and builds inside the pub cache, so only the in-repo crate
is listed here; the shared cargo registry and git checkouts are cached anyway.
inputs: inputs:
targets: targets:
@@ -19,4 +21,4 @@ runs:
- name: Cache Rust build - name: Cache Rust build
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
with: with:
workspaces: third_party/kolibri/kolibri-dart/rust workspaces: native/komet_crypto/rust
+5
View File
@@ -148,3 +148,8 @@ maxmint/
maxtun/ maxtun/
turnprobe/ turnprobe/
test/live_server_probe_test.dart test/live_server_probe_test.dart
# local kolibri development: point the plugin at third_party/kolibri and the
# Rust core at third_party/kolibri/kolibri-net instead of the published ones
pubspec_overrides.yaml
.cargo/
-3
View File
@@ -1,6 +1,3 @@
[submodule "third_party/rlottie"] [submodule "third_party/rlottie"]
path = third_party/rlottie path = third_party/rlottie
url = https://github.com/Samsung/rlottie.git url = https://github.com/Samsung/rlottie.git
[submodule "third_party/kolibri"]
path = third_party/kolibri
url = https://github.com/KometTeam/kolibri.git
+5 -2
View File
@@ -121,7 +121,7 @@ class Api {
} }
if (gen != _connectGen) return; if (gen != _connectGen) return;
({String host, int port}) endpoint; ({String host, int port, bool trustMincifryCa}) endpoint;
try { try {
endpoint = await ServerConfig.loadEndpoint().timeout(_endpointTimeout); endpoint = await ServerConfig.loadEndpoint().timeout(_endpointTimeout);
} catch (e) { } catch (e) {
@@ -129,10 +129,13 @@ class Api {
endpoint = ( endpoint = (
host: ServerConfig.defaultHost, host: ServerConfig.defaultHost,
port: ServerConfig.defaultPort, port: ServerConfig.defaultPort,
trustMincifryCa: ServerConfig.defaultTrustMincifryCa,
); );
} }
if (gen != _connectGen) return; if (gen != _connectGen) return;
setTrustMincifryCa(enabled: endpoint.trustMincifryCa);
final (session, wireLog) = await _buildSessionOptions(endpoint); final (session, wireLog) = await _buildSessionOptions(endpoint);
if (gen != _connectGen) return; if (gen != _connectGen) return;
@@ -366,7 +369,7 @@ class Api {
/// Строит устройство-поля и создаёт сессию ядра. Заодно заполняет /// Строит устройство-поля и создаёт сессию ядра. Заодно заполняет
/// [_userAgent] и [_deviceId] для геттеров. /// [_userAgent] и [_deviceId] для геттеров.
Future<(KolibriSession, Stream<WireLogEvent>)> _buildSessionOptions( Future<(KolibriSession, Stream<WireLogEvent>)> _buildSessionOptions(
({String host, int port}) endpoint, ({String host, int port, bool trustMincifryCa}) endpoint,
) async { ) async {
final deviceInfo = DeviceInfoPlugin(); final deviceInfo = DeviceInfoPlugin();
+11 -3
View File
@@ -1,15 +1,18 @@
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
abstract class ServerConfig { abstract class ServerConfig {
static const String defaultHost = 'api.oneme.ru'; static const String defaultHost = 'api2.oneme.ru';
static const int defaultPort = 443; static const int defaultPort = 443;
static const bool defaultTrustMincifryCa = true;
static const String prefHostKey = 'server_host_override'; static const String prefHostKey = 'server_host_override';
static const String prefPortKey = 'server_port_override'; static const String prefPortKey = 'server_port_override';
static const String prefTrustMincifryKey = 'server_trust_mincifry_ca';
static const Duration pingInterval = Duration(seconds: 10); static const Duration pingInterval = Duration(seconds: 10);
static const Duration requestTimeout = Duration(seconds: 30); static const Duration requestTimeout = Duration(seconds: 30);
static const int maxReconnectAttempts = 50; static const int maxReconnectAttempts = 50;
static Future<({String host, int port})> loadEndpoint() async { static Future<({String host, int port, bool trustMincifryCa})>
loadEndpoint() async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final rawHost = prefs.getString(prefHostKey); final rawHost = prefs.getString(prefHostKey);
final rawPort = prefs.getInt(prefPortKey); final rawPort = prefs.getInt(prefPortKey);
@@ -20,6 +23,11 @@ abstract class ServerConfig {
if (rawPort != null && rawPort >= 1 && rawPort <= 65535) { if (rawPort != null && rawPort >= 1 && rawPort <= 65535) {
port = rawPort; port = rawPort;
} }
return (host: host, port: port); return (
host: host,
port: port,
trustMincifryCa:
prefs.getBool(prefTrustMincifryKey) ?? defaultTrustMincifryCa,
);
} }
} }
+3
View File
@@ -16,6 +16,7 @@ import '../calls/ws2_signaling.dart';
import '../protocol/opcode_map.dart'; import '../protocol/opcode_map.dart';
import '../storage/app_instance.dart'; import '../storage/app_instance.dart';
import '../storage/token_storage.dart'; import '../storage/token_storage.dart';
import '../transport/tls_config.dart';
import '../utils/logger.dart'; import '../utils/logger.dart';
const _channelId = 'komet_messages'; const _channelId = 'komet_messages';
@@ -56,6 +57,7 @@ Future<void> _handleCallDecline(String payloadJson) async {
// Фоновый изолят: инициализируем ядро перед vcp-декодом/сигналингом. // Фоновый изолят: инициализируем ядро перед vcp-декодом/сигналингом.
await initKolibri(); await initKolibri();
await TlsConfig.applyMincifryTrust();
final params = ConversationParams.decode(vcp); final params = ConversationParams.decode(vcp);
if (params == null) return; if (params == null) return;
@@ -93,6 +95,7 @@ Future<void> _handleReply(String payloadJson, String text) async {
SharedPreferences.setPrefix('flutter.${AppInstance.id}.'); SharedPreferences.setPrefix('flutter.${AppInstance.id}.');
} catch (_) {} } catch (_) {}
} }
await TlsConfig.applyMincifryTrust();
final plugin = FlutterLocalNotificationsPlugin(); final plugin = FlutterLocalNotificationsPlugin();
final notifId = chatId & 0x7fffffff; final notifId = chatId & 0x7fffffff;
+12
View File
@@ -1,8 +1,20 @@
import 'package:kolibri/kolibri.dart' show setTrustMincifryCa;
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../config/config.dart';
abstract class TlsConfig { abstract class TlsConfig {
static const String prefKey = 'dev_tls_insecure'; static const String prefKey = 'dev_tls_insecure';
static Future<void> applyMincifryTrust() async {
final prefs = await SharedPreferences.getInstance();
setTrustMincifryCa(
enabled:
prefs.getBool(ServerConfig.prefTrustMincifryKey) ??
ServerConfig.defaultTrustMincifryCa,
);
}
static Future<bool> isInsecureAllowed() async { static Future<bool> isInsecureAllowed() async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
return prefs.getBool(prefKey) ?? false; return prefs.getBool(prefKey) ?? false;
@@ -27,6 +27,7 @@ class _ServerSettingsSheetState extends State<ServerSettingsSheet> {
text: '${ServerConfig.defaultPort}', text: '${ServerConfig.defaultPort}',
); );
bool _busy = false; bool _busy = false;
bool _trustMincifryCa = ServerConfig.defaultTrustMincifryCa;
@override @override
void initState() { void initState() {
@@ -40,6 +41,7 @@ class _ServerSettingsSheetState extends State<ServerSettingsSheet> {
setState(() { setState(() {
_hostController.text = endpoint.host; _hostController.text = endpoint.host;
_portController.text = '${endpoint.port}'; _portController.text = '${endpoint.port}';
_trustMincifryCa = endpoint.trustMincifryCa;
}); });
} }
@@ -55,6 +57,7 @@ class _ServerSettingsSheetState extends State<ServerSettingsSheet> {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.setString(ServerConfig.prefHostKey, host); await prefs.setString(ServerConfig.prefHostKey, host);
await prefs.setInt(ServerConfig.prefPortKey, port); await prefs.setInt(ServerConfig.prefPortKey, port);
await prefs.setBool(ServerConfig.prefTrustMincifryKey, _trustMincifryCa);
await api.disconnect(); await api.disconnect();
unawaited(api.connect()); unawaited(api.connect());
final online = await api.stateStream final online = await api.stateStream
@@ -82,8 +85,10 @@ class _ServerSettingsSheetState extends State<ServerSettingsSheet> {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.remove(ServerConfig.prefHostKey); await prefs.remove(ServerConfig.prefHostKey);
await prefs.remove(ServerConfig.prefPortKey); await prefs.remove(ServerConfig.prefPortKey);
await prefs.remove(ServerConfig.prefTrustMincifryKey);
_hostController.text = ServerConfig.defaultHost; _hostController.text = ServerConfig.defaultHost;
_portController.text = '${ServerConfig.defaultPort}'; _portController.text = '${ServerConfig.defaultPort}';
_trustMincifryCa = ServerConfig.defaultTrustMincifryCa;
await api.disconnect(); await api.disconnect();
api.connect(); api.connect();
final online = await api.stateStream final online = await api.stateStream
@@ -154,6 +159,49 @@ class _ServerSettingsSheetState extends State<ServerSettingsSheet> {
inputFormatters: [FilteringTextInputFormatter.digitsOnly], inputFormatters: [FilteringTextInputFormatter.digitsOnly],
enabled: !_busy, enabled: !_busy,
), ),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.fromLTRB(14, 12, 10, 12),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.serverTrustMincifryTitle,
style: TextStyle(
color: cs.onSurface,
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 2),
Text(
l10n.serverTrustMincifrySubtitle,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 12.5,
height: 1.3,
),
),
],
),
),
const SizedBox(width: 12),
Switch(
value: _trustMincifryCa,
onChanged: _busy
? null
: (v) => setState(() => _trustMincifryCa = v),
),
],
),
),
const SizedBox(height: 24), const SizedBox(height: 24),
FilledButton( FilledButton(
onPressed: _busy ? null : () => _apply(l10n), onPressed: _busy ? null : () => _apply(l10n),
+2
View File
@@ -18,6 +18,8 @@
"serverSettingsTitle": "Server", "serverSettingsTitle": "Server",
"serverHostLabel": "Host", "serverHostLabel": "Host",
"serverPortLabel": "Port", "serverPortLabel": "Port",
"serverTrustMincifryTitle": "Trust the Минцифры CA",
"serverTrustMincifrySubtitle": "Required for api2.oneme.ru: its certificate chains to the Russian Trusted Root CA, which is absent from the standard trust store. The root is bundled with the app; other hosts keep using the usual roots.",
"serverApply": "Apply and reconnect", "serverApply": "Apply and reconnect",
"serverUseDefault": "Reset to default", "serverUseDefault": "Reset to default",
"serverInvalidHostOrPort": "Enter a valid host and port (165535)", "serverInvalidHostOrPort": "Enter a valid host and port (165535)",
+12
View File
@@ -206,6 +206,18 @@ abstract class AppLocalizations {
/// **'Port'** /// **'Port'**
String get serverPortLabel; String get serverPortLabel;
/// No description provided for @serverTrustMincifryTitle.
///
/// In en, this message translates to:
/// **'Trust the Минцифры CA'**
String get serverTrustMincifryTitle;
/// No description provided for @serverTrustMincifrySubtitle.
///
/// In en, this message translates to:
/// **'Required for api2.oneme.ru: its certificate chains to the Russian Trusted Root CA, which is absent from the standard trust store. The root is bundled with the app; other hosts keep using the usual roots.'**
String get serverTrustMincifrySubtitle;
/// No description provided for @serverApply. /// No description provided for @serverApply.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
+7
View File
@@ -63,6 +63,13 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get serverPortLabel => 'Port'; String get serverPortLabel => 'Port';
@override
String get serverTrustMincifryTitle => 'Trust the Минцифры CA';
@override
String get serverTrustMincifrySubtitle =>
'Required for api2.oneme.ru: its certificate chains to the Russian Trusted Root CA, which is absent from the standard trust store. The root is bundled with the app; other hosts keep using the usual roots.';
@override @override
String get serverApply => 'Apply and reconnect'; String get serverApply => 'Apply and reconnect';
+7
View File
@@ -64,6 +64,13 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get serverPortLabel => 'Порт'; String get serverPortLabel => 'Порт';
@override
String get serverTrustMincifryTitle => 'Доверять сертификату Минцифры';
@override
String get serverTrustMincifrySubtitle =>
'Нужно для api2.oneme.ru: его сертификат выпущен под корнем Russian Trusted Root CA, которого нет в обычном хранилище. Корень зашит в приложение, остальные хосты проверяются как раньше.';
@override @override
String get serverApply => 'Применить и переподключиться'; String get serverApply => 'Применить и переподключиться';
+2
View File
@@ -18,6 +18,8 @@
"serverSettingsTitle": "Сервер", "serverSettingsTitle": "Сервер",
"serverHostLabel": "Хост", "serverHostLabel": "Хост",
"serverPortLabel": "Порт", "serverPortLabel": "Порт",
"serverTrustMincifryTitle": "Доверять сертификату Минцифры",
"serverTrustMincifrySubtitle": "Нужно для api2.oneme.ru: его сертификат выпущен под корнем Russian Trusted Root CA, которого нет в обычном хранилище. Корень зашит в приложение, остальные хосты проверяются как раньше.",
"serverApply": "Применить и переподключиться", "serverApply": "Применить и переподключиться",
"serverUseDefault": "Сбросить к умолчанию", "serverUseDefault": "Сбросить к умолчанию",
"serverInvalidHostOrPort": "Укажите корректный хост и порт (1–65535)", "serverInvalidHostOrPort": "Укажите корректный хост и порт (1–65535)",
+1
View File
@@ -174,6 +174,7 @@ void _installLogCapture() {
void main(List<String> args) async { void main(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await initKolibri(); await initKolibri();
await TlsConfig.applyMincifryTrust();
DebugTest.parse(args); DebugTest.parse(args);
_installLogCapture(); _installLogCapture();
VideoPlayerMediaKit.ensureInitialized( VideoPlayerMediaKit.ensureInitialized(
+6
View File
@@ -86,6 +86,8 @@ PODS:
- GoogleUtilities/Privacy - GoogleUtilities/Privacy
- kolibri (0.0.1): - kolibri (0.0.1):
- FlutterMacOS - FlutterMacOS
- komet_crypto (0.0.1):
- FlutterMacOS
- media_kit_libs_macos_video (1.0.4): - media_kit_libs_macos_video (1.0.4):
- FlutterMacOS - FlutterMacOS
- media_kit_video (0.0.1): - media_kit_video (0.0.1):
@@ -143,6 +145,7 @@ DEPENDENCIES:
- FlutterMacOS (from `Flutter/ephemeral`) - FlutterMacOS (from `Flutter/ephemeral`)
- geolocator_apple (from `Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin`) - geolocator_apple (from `Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin`)
- kolibri (from `Flutter/ephemeral/.symlinks/plugins/kolibri/macos`) - kolibri (from `Flutter/ephemeral/.symlinks/plugins/kolibri/macos`)
- komet_crypto (from `Flutter/ephemeral/.symlinks/plugins/komet_crypto/macos`)
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`) - media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
- media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`) - media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`)
- mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin`) - mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin`)
@@ -201,6 +204,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin :path: Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin
kolibri: kolibri:
:path: Flutter/ephemeral/.symlinks/plugins/kolibri/macos :path: Flutter/ephemeral/.symlinks/plugins/kolibri/macos
komet_crypto:
:path: Flutter/ephemeral/.symlinks/plugins/komet_crypto/macos
media_kit_libs_macos_video: media_kit_libs_macos_video:
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos :path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos
media_kit_video: media_kit_video:
@@ -252,6 +257,7 @@ SPEC CHECKSUMS:
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 4f2618a4a1e762a1ee134a1e2323bba9843e06da GoogleUtilities: 4f2618a4a1e762a1ee134a1e2323bba9843e06da
kolibri: 93062ece67f68ec0b909876b527aa15e198b4a73 kolibri: 93062ece67f68ec0b909876b527aa15e198b4a73
komet_crypto: 856fa27dc180350f88a7cf6de6b512d2d221b737
media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65 media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758 media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
+7 -6
View File
@@ -601,10 +601,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: freezed_annotation name: freezed_annotation
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.4" version: "3.1.0"
geolocator: geolocator:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -760,10 +760,11 @@ packages:
kolibri: kolibri:
dependency: "direct main" dependency: "direct main"
description: description:
path: "third_party/kolibri/kolibri-dart" name: kolibri
relative: true sha256: e8ed4eab5687204a77d449743ceb73b0797f5702afb0a7841610a127fc102710
source: path url: "https://pub.dev"
version: "0.1.0" source: hosted
version: "0.1.2"
komet_crypto: komet_crypto:
dependency: "direct main" dependency: "direct main"
description: description:
+3 -3
View File
@@ -35,9 +35,9 @@ dependencies:
intl: any intl: any
# Rust networking core (kolibri) — FFI plugin, replaces the Dart transport. # Rust networking core (kolibri) — FFI plugin, replaces the Dart transport.
# Pinned to the third_party/kolibri submodule. # Published from the KometTeam/kolibri repo; the native core is compiled at
kolibri: # app build time and pulled from that repo by git tag.
path: third_party/kolibri/kolibri-dart kolibri: ^0.1.2
# Rust message-encryption core — Argon2id + ChaCha20-Poly1305, output encoded # Rust message-encryption core — Argon2id + ChaCha20-Poly1305, output encoded
# as lowercase Cyrillic base32. Separate from kolibri: that is vendored # as lowercase Cyrillic base32. Separate from kolibri: that is vendored
Submodule third_party/kolibri deleted from 1aa610f55b