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
description: >
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:
targets:
@@ -19,4 +21,4 @@ runs:
- name: Cache Rust build
uses: Swatinem/rust-cache@v2
with:
workspaces: third_party/kolibri/kolibri-dart/rust
workspaces: native/komet_crypto/rust
+5
View File
@@ -148,3 +148,8 @@ maxmint/
maxtun/
turnprobe/
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"]
path = third_party/rlottie
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;
({String host, int port}) endpoint;
({String host, int port, bool trustMincifryCa}) endpoint;
try {
endpoint = await ServerConfig.loadEndpoint().timeout(_endpointTimeout);
} catch (e) {
@@ -129,10 +129,13 @@ class Api {
endpoint = (
host: ServerConfig.defaultHost,
port: ServerConfig.defaultPort,
trustMincifryCa: ServerConfig.defaultTrustMincifryCa,
);
}
if (gen != _connectGen) return;
setTrustMincifryCa(enabled: endpoint.trustMincifryCa);
final (session, wireLog) = await _buildSessionOptions(endpoint);
if (gen != _connectGen) return;
@@ -366,7 +369,7 @@ class Api {
/// Строит устройство-поля и создаёт сессию ядра. Заодно заполняет
/// [_userAgent] и [_deviceId] для геттеров.
Future<(KolibriSession, Stream<WireLogEvent>)> _buildSessionOptions(
({String host, int port}) endpoint,
({String host, int port, bool trustMincifryCa}) endpoint,
) async {
final deviceInfo = DeviceInfoPlugin();
+11 -3
View File
@@ -1,15 +1,18 @@
import 'package:shared_preferences/shared_preferences.dart';
abstract class ServerConfig {
static const String defaultHost = 'api.oneme.ru';
static const String defaultHost = 'api2.oneme.ru';
static const int defaultPort = 443;
static const bool defaultTrustMincifryCa = true;
static const String prefHostKey = 'server_host_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 requestTimeout = Duration(seconds: 30);
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 rawHost = prefs.getString(prefHostKey);
final rawPort = prefs.getInt(prefPortKey);
@@ -20,6 +23,11 @@ abstract class ServerConfig {
if (rawPort != null && rawPort >= 1 && rawPort <= 65535) {
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 '../storage/app_instance.dart';
import '../storage/token_storage.dart';
import '../transport/tls_config.dart';
import '../utils/logger.dart';
const _channelId = 'komet_messages';
@@ -56,6 +57,7 @@ Future<void> _handleCallDecline(String payloadJson) async {
// Фоновый изолят: инициализируем ядро перед vcp-декодом/сигналингом.
await initKolibri();
await TlsConfig.applyMincifryTrust();
final params = ConversationParams.decode(vcp);
if (params == null) return;
@@ -93,6 +95,7 @@ Future<void> _handleReply(String payloadJson, String text) async {
SharedPreferences.setPrefix('flutter.${AppInstance.id}.');
} catch (_) {}
}
await TlsConfig.applyMincifryTrust();
final plugin = FlutterLocalNotificationsPlugin();
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 '../config/config.dart';
abstract class TlsConfig {
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 {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool(prefKey) ?? false;
@@ -27,6 +27,7 @@ class _ServerSettingsSheetState extends State<ServerSettingsSheet> {
text: '${ServerConfig.defaultPort}',
);
bool _busy = false;
bool _trustMincifryCa = ServerConfig.defaultTrustMincifryCa;
@override
void initState() {
@@ -40,6 +41,7 @@ class _ServerSettingsSheetState extends State<ServerSettingsSheet> {
setState(() {
_hostController.text = endpoint.host;
_portController.text = '${endpoint.port}';
_trustMincifryCa = endpoint.trustMincifryCa;
});
}
@@ -55,6 +57,7 @@ class _ServerSettingsSheetState extends State<ServerSettingsSheet> {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(ServerConfig.prefHostKey, host);
await prefs.setInt(ServerConfig.prefPortKey, port);
await prefs.setBool(ServerConfig.prefTrustMincifryKey, _trustMincifryCa);
await api.disconnect();
unawaited(api.connect());
final online = await api.stateStream
@@ -82,8 +85,10 @@ class _ServerSettingsSheetState extends State<ServerSettingsSheet> {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(ServerConfig.prefHostKey);
await prefs.remove(ServerConfig.prefPortKey);
await prefs.remove(ServerConfig.prefTrustMincifryKey);
_hostController.text = ServerConfig.defaultHost;
_portController.text = '${ServerConfig.defaultPort}';
_trustMincifryCa = ServerConfig.defaultTrustMincifryCa;
await api.disconnect();
api.connect();
final online = await api.stateStream
@@ -154,6 +159,49 @@ class _ServerSettingsSheetState extends State<ServerSettingsSheet> {
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
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),
FilledButton(
onPressed: _busy ? null : () => _apply(l10n),
+2
View File
@@ -18,6 +18,8 @@
"serverSettingsTitle": "Server",
"serverHostLabel": "Host",
"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",
"serverUseDefault": "Reset to default",
"serverInvalidHostOrPort": "Enter a valid host and port (165535)",
+12
View File
@@ -206,6 +206,18 @@ abstract class AppLocalizations {
/// **'Port'**
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.
///
/// In en, this message translates to:
+7
View File
@@ -63,6 +63,13 @@ class AppLocalizationsEn extends AppLocalizations {
@override
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
String get serverApply => 'Apply and reconnect';
+7
View File
@@ -64,6 +64,13 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get serverPortLabel => 'Порт';
@override
String get serverTrustMincifryTitle => 'Доверять сертификату Минцифры';
@override
String get serverTrustMincifrySubtitle =>
'Нужно для api2.oneme.ru: его сертификат выпущен под корнем Russian Trusted Root CA, которого нет в обычном хранилище. Корень зашит в приложение, остальные хосты проверяются как раньше.';
@override
String get serverApply => 'Применить и переподключиться';
+2
View File
@@ -18,6 +18,8 @@
"serverSettingsTitle": "Сервер",
"serverHostLabel": "Хост",
"serverPortLabel": "Порт",
"serverTrustMincifryTitle": "Доверять сертификату Минцифры",
"serverTrustMincifrySubtitle": "Нужно для api2.oneme.ru: его сертификат выпущен под корнем Russian Trusted Root CA, которого нет в обычном хранилище. Корень зашит в приложение, остальные хосты проверяются как раньше.",
"serverApply": "Применить и переподключиться",
"serverUseDefault": "Сбросить к умолчанию",
"serverInvalidHostOrPort": "Укажите корректный хост и порт (1–65535)",
+1
View File
@@ -174,6 +174,7 @@ void _installLogCapture() {
void main(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized();
await initKolibri();
await TlsConfig.applyMincifryTrust();
DebugTest.parse(args);
_installLogCapture();
VideoPlayerMediaKit.ensureInitialized(
+6
View File
@@ -86,6 +86,8 @@ PODS:
- GoogleUtilities/Privacy
- kolibri (0.0.1):
- FlutterMacOS
- komet_crypto (0.0.1):
- FlutterMacOS
- media_kit_libs_macos_video (1.0.4):
- FlutterMacOS
- media_kit_video (0.0.1):
@@ -143,6 +145,7 @@ DEPENDENCIES:
- FlutterMacOS (from `Flutter/ephemeral`)
- geolocator_apple (from `Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin`)
- 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_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`)
- mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin`)
@@ -201,6 +204,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin
kolibri:
:path: Flutter/ephemeral/.symlinks/plugins/kolibri/macos
komet_crypto:
:path: Flutter/ephemeral/.symlinks/plugins/komet_crypto/macos
media_kit_libs_macos_video:
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos
media_kit_video:
@@ -252,6 +257,7 @@ SPEC CHECKSUMS:
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 4f2618a4a1e762a1ee134a1e2323bba9843e06da
kolibri: 93062ece67f68ec0b909876b527aa15e198b4a73
komet_crypto: 856fa27dc180350f88a7cf6de6b512d2d221b737
media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
+7 -6
View File
@@ -601,10 +601,10 @@ packages:
dependency: transitive
description:
name: freezed_annotation
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8"
url: "https://pub.dev"
source: hosted
version: "2.4.4"
version: "3.1.0"
geolocator:
dependency: "direct main"
description:
@@ -760,10 +760,11 @@ packages:
kolibri:
dependency: "direct main"
description:
path: "third_party/kolibri/kolibri-dart"
relative: true
source: path
version: "0.1.0"
name: kolibri
sha256: e8ed4eab5687204a77d449743ceb73b0797f5702afb0a7841610a127fc102710
url: "https://pub.dev"
source: hosted
version: "0.1.2"
komet_crypto:
dependency: "direct main"
description:
+3 -3
View File
@@ -35,9 +35,9 @@ dependencies:
intl: any
# Rust networking core (kolibri) — FFI plugin, replaces the Dart transport.
# Pinned to the third_party/kolibri submodule.
kolibri:
path: third_party/kolibri/kolibri-dart
# Published from the KometTeam/kolibri repo; the native core is compiled at
# app build time and pulled from that repo by git tag.
kolibri: ^0.1.2
# Rust message-encryption core — Argon2id + ChaCha20-Poly1305, output encoded
# as lowercase Cyrillic base32. Separate from kolibri: that is vendored
Submodule third_party/kolibri deleted from 1aa610f55b