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
+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(