feat(alpha): wire connect→snapshot→disconnect end-to-end (v0.1.0-alpha.1)
First Internal Alpha build per DEC-001. Closes the milestone of
'Flutter UI calls Rust via the typed bridge, Rust connects to a
TeamSpeak-compatible server through tsclientlib, returns a typed
snapshot, and disconnects cleanly.' Audio remains Beta scope.
Promotions from PoC:
poc/tsclientlib-connect-spike → crates/chanora_protocol/
New product code:
crates/chanora_protocol/src/{dto.rs,adapter.rs} — typed boundary
over tsclientlib. Tokio task owns the Connection; public
handle communicates via mpsc/oneshot. No tsclientlib types
cross out of the crate (SAD-067 / SysDes-011 / SysDes-029).
core/chanora_core/src/lib.rs — ChanoraSession composes the
protocol crate, enforces the DEC-006 single-connection
invariant.
crates/chanora_bridge/src/{api.rs,frb_generated.rs} — FRB 2.12.0
bridge per DEC-014. cdylib + staticlib + rlib. Typed
BridgeSnapshot / BridgeChannel / BridgeClient / BridgeError
DTOs. Process-wide OnceLock<Runtime> + OnceLock<ChanoraSession>.
flutter_rust_bridge.yaml at repo root.
apps/chanora_flutter/lib/main.dart — Alpha UI: server form,
connect button, channel tree, disconnect.
apps/chanora_flutter/lib/l10n/app_{en,zh}.arb expanded with the
Alpha key set; ARB metadata reaffirms ADR-008 for
server-provided content.
Generated Dart bindings under apps/chanora_flutter/lib/src/rust/.
Empirical verification (2026-05-14):
Workspace: cargo check + cargo test clean
(workspace tests: all green).
Bridge cdylib: target/release/libchanora_bridge.so produced
(~15 MB).
Flutter: flutter analyze clean; flutter test runs 3/3 green
including the alpha_e2e_test that drives the full
Dart → FRB → chanora_bridge → chanora_core → chanora_protocol
→ tsclientlib → UDP → cn.teamspeak.app
path. The captured logcat/stdout shows the tsproto resender
transitioning Connected → Disconnecting → Disconnected on
clean teardown.
Architecture changes:
- Removed the chanora_core ↔ chanora_bridge cyclic dependency.
chanora_core no longer knows the bridge exists; the bridge
maps from CoreError.
- chanora_bridge crate's #![forbid(unsafe_code)] lint relaxed
because FRB-generated glue legitimately uses unsafe at the
FFI boundary. Hand-written code remains unsafe-free.
Open follow-ups (NOT in this Alpha):
- Audio capture/playback wiring into chanora_audio
(Beta scope per DEC-001).
- Identity persistence via chanora_storage
(currently regenerated on every connect).
- Per-message diagnostics + redaction
(chanora_diagnostics still scaffold).
- Reconnect / network-loss recovery.
- Mobile (Android) build of the bridge cdylib + UI verification.
This commit is contained in:
+74
-50
@@ -6,61 +6,85 @@ This project is expected to follow a Conventional Commits style workflow.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
### Added — Alpha build (v0.1.0-alpha.1)
|
||||
|
||||
- **Product workspace scaffold (DEC-022).** Created the canonical
|
||||
implementation directory layout per the README sketch + SAD §7.2:
|
||||
- `Cargo.toml` — top-level Cargo workspace declaring seven members
|
||||
and excluding the `poc/` spike trees and `apps/chanora_flutter`.
|
||||
Pins `license = "MIT OR Apache-2.0"` (DEC-020) and
|
||||
`rust-version = "1.95"` workspace-wide.
|
||||
- `core/chanora_core/` — top-level Rust API + orchestration. Owns
|
||||
`ChanoraSession` (placeholder) and the `CoreError` enum that
|
||||
typed-wraps every subsystem error.
|
||||
- `crates/chanora_protocol/` — tsclientlib adapter scaffold,
|
||||
opaque `ChannelId` / `ClientId` types, `ProtocolError`.
|
||||
- `crates/chanora_state/` — `StateError`, `ConnectionState` enum.
|
||||
- `crates/chanora_audio/` — `AudioError`, `AudioEffects` struct
|
||||
whose defaults match DEC-007/008/009/010 (all four effects on).
|
||||
- `crates/chanora_storage/` — `StorageError`, marker traits for
|
||||
the two repositories per the SAD-067 separation.
|
||||
- `crates/chanora_diagnostics/` — `DiagnosticsError`,
|
||||
`REDACTION_MARKER` constant matching the PoC, `Redactor` shell.
|
||||
- `crates/chanora_bridge/` — `BridgeError` DTO (Serialize +
|
||||
Deserialize) for FRB 2.x boundary.
|
||||
Workspace `cargo check --workspace` and `cargo test --workspace`
|
||||
both clean on Rust 1.95.
|
||||
- **Flutter application scaffold.**
|
||||
- `apps/chanora_flutter/` created via `flutter create` with
|
||||
`--org app.chanora --project-name chanora_flutter`. Platforms
|
||||
enabled at scaffold time: Linux + Android.
|
||||
- `apps/chanora_flutter/android/app/build.gradle.kts` overridden
|
||||
to `minSdk = 28` per DEC-004 (raised from Flutter's default
|
||||
minSdk by explicit owner ruling, register v0.9.5).
|
||||
- `flutter_localizations` + `intl` added to `pubspec.yaml`;
|
||||
`flutter.generate: true` enabled.
|
||||
- `l10n.yaml` configured to emit `lib/l10n/generated/AppL10n`
|
||||
(no synthetic package — `synthetic-package` was removed in
|
||||
Flutter 3.41+).
|
||||
- `lib/l10n/app_en.arb` — English template ARB with the MVP key
|
||||
set (`appTitle`, `homeGreeting`, `homeNotProductionReadyBanner`,
|
||||
`connectAction`).
|
||||
- `lib/l10n/app_zh.arb` — Simplified Chinese translations per
|
||||
DEC-015 (MVP language scope expanded from English-only).
|
||||
- `lib/main.dart` and `test/widget_test.dart` rewritten as a
|
||||
minimal scaffold that exercises both locales.
|
||||
- `flutter analyze` reports no issues. `flutter test` runs the
|
||||
English + Simplified Chinese smoke tests and both pass.
|
||||
- **First Alpha build wires the connect → snapshot → disconnect cycle
|
||||
end-to-end from the Flutter UI to a live TeamSpeak-compatible
|
||||
server via the typed Flutter/Rust bridge.** Per DEC-001 this is the
|
||||
Internal Alpha milestone; Audio (voice in/out) is deferred to Beta.
|
||||
- `crates/chanora_protocol/` promoted from a scaffold to a working
|
||||
adapter. Public surface:
|
||||
- `ConnectConfig`, `ProtocolClient`, `ProtocolError`.
|
||||
- DTO module exposing `ChannelId`, `ClientId`, `ChannelInfo`,
|
||||
`ClientInfo`, `ServerSnapshot` — all owned primitives and
|
||||
`String`s; no `tsclientlib::*` types leak (SAD-067).
|
||||
- Tokio task owns the `tsclientlib::Connection`; public handle
|
||||
communicates via `mpsc` requests + `oneshot` replies.
|
||||
- Connect waits for the initial `BookEvents` snapshot, then pumps
|
||||
events for ~2 s so the subscribed channel tree settles before
|
||||
the first snapshot is served.
|
||||
- Promoted from `poc/tsclientlib-connect-spike`.
|
||||
- `core/chanora_core::ChanoraSession` now drives the protocol crate
|
||||
with a typed `connect`/`snapshot`/`is_connected`/`disconnect` API.
|
||||
Enforces the DEC-006 single-connection invariant via an internal
|
||||
`tokio::sync::Mutex<Option<ProtocolClient>>`.
|
||||
- `crates/chanora_bridge/` wired against `flutter_rust_bridge` 2.12.0
|
||||
(DEC-014). Compiled as `cdylib + staticlib + rlib`. Exposes:
|
||||
- `bridge_init()` (FRB lifecycle), `connect()`, `snapshot()`,
|
||||
`disconnect()`, `is_connected()`.
|
||||
- Typed `BridgeChannel`, `BridgeClient`, `BridgeSnapshot` DTOs;
|
||||
`BridgeError` with `From<chanora_core::CoreError>`.
|
||||
- A process-wide `tokio::Runtime` + `ChanoraSession` via
|
||||
`OnceLock`, used by every async command.
|
||||
- The crate's `#![forbid(unsafe_code)]` lint was lifted to
|
||||
`#![warn(missing_docs)]` only, with a doc-comment explanation
|
||||
that the FRB-generated glue legitimately uses unsafe at the FFI
|
||||
boundary; hand-written code in the crate is still expected to
|
||||
avoid `unsafe`.
|
||||
- `flutter_rust_bridge.yaml` at the repo root drives codegen for the
|
||||
bridge.
|
||||
- Generated Dart bindings under
|
||||
`apps/chanora_flutter/lib/src/rust/{api.dart,frb_generated*.dart,lib*.dart}`.
|
||||
- Generated Rust glue under `crates/chanora_bridge/src/frb_generated.rs`.
|
||||
- `apps/chanora_flutter/lib/main.dart` rewritten as the Alpha UI:
|
||||
- Form: server address + nickname, both pre-populated for
|
||||
convenience.
|
||||
- Connect button → calls FRB → enters connecting state → shows
|
||||
snapshot.
|
||||
- Snapshot view: server welcome banner (preserved verbatim per
|
||||
ADR-008), `N channels • M online` count, ordered channel list
|
||||
with clients indented under their channel.
|
||||
- Refresh and Disconnect actions in the app bar.
|
||||
- `apps/chanora_flutter/lib/l10n/app_{en,zh}.arb` expanded with the
|
||||
Alpha key set:
|
||||
`homeNotProductionReadyBanner` (now says "Alpha build"),
|
||||
`fieldServerHost`, `fieldNickname`,
|
||||
`connectAction`, `disconnectAction`, `refreshAction`,
|
||||
`statusIdle`, `statusConnecting`, `statusConnected`, `statusError`,
|
||||
`channelsHeading`, `clientsHeading`, `countChannelsAndClients`.
|
||||
- `flutter_localizations`, `intl`, `flutter_rust_bridge`,
|
||||
`freezed_annotation` added to dependencies;
|
||||
`freezed` and `build_runner` added to dev_dependencies.
|
||||
- `apps/chanora_flutter/test/alpha_e2e_test.dart` runs the full
|
||||
Dart → FRB → Rust → tsclientlib → network → server path against
|
||||
`cn.teamspeak.app`. Verifies the snapshot contains a non-empty
|
||||
server name and a non-empty channel list, that `isConnected()`
|
||||
flips true → false across the disconnect, and that a re-fetched
|
||||
snapshot agrees on the server name. Passes in ~2.5 s.
|
||||
- `core/chanora_core/tests/alpha_smoke.rs` runs the same path from the
|
||||
Rust side; tagged `#[ignore]` so `cargo test --workspace` doesn't
|
||||
hit the network by default. Run with `--ignored alpha_smoke`.
|
||||
|
||||
### Changed
|
||||
|
||||
- `docs/governance/path-migration-map.md` bumped to v0.9.3 with a
|
||||
new §3 *Implementation Path Layout* recording the DEC-022
|
||||
directory adoption (`apps/`, `core/`, `crates/`) and pointing
|
||||
each subsystem at its crate alongside the relevant SAD/SysDes/DEC
|
||||
authority.
|
||||
- `chanora_core::CoreError` no longer wraps `chanora_bridge::BridgeError`;
|
||||
the relationship is the other way around (bridge maps from core).
|
||||
This removes a cyclic `chanora_core` ↔ `chanora_bridge` dependency
|
||||
introduced when the bridge crate gained `chanora_core` as a dep.
|
||||
- `chanora_bridge` lints relaxed from `#![forbid(unsafe_code)]` to
|
||||
`#![warn(missing_docs)]` (documented above).
|
||||
|
||||
### Added (LICENSE files)
|
||||
### LICENSE files (carry-over from earlier in this branch)
|
||||
|
||||
- `LICENSE-APACHE` — Apache License Version 2.0 text (DEC-020).
|
||||
- `LICENSE-MIT` — MIT License text (DEC-020).
|
||||
|
||||
Generated
+3477
-9
File diff suppressed because it is too large
Load Diff
@@ -7,18 +7,59 @@
|
||||
"description": "Application title shown in launchers and the app bar. The product name `Chanora` is fixed by DEC-018 and must not be translated."
|
||||
},
|
||||
|
||||
"homeGreeting": "Welcome to Chanora",
|
||||
"@homeGreeting": {
|
||||
"description": "Greeting shown on the empty home screen before any server is connected."
|
||||
},
|
||||
|
||||
"homeNotProductionReadyBanner": "Chanora is currently in early development. This build is not production-ready.",
|
||||
"homeNotProductionReadyBanner": "Alpha build — not production ready.",
|
||||
"@homeNotProductionReadyBanner": {
|
||||
"description": "Plain-language banner informing testers that this build is not for end-user use. Mirrors README.md's status line."
|
||||
},
|
||||
|
||||
"connectAction": "Connect to a server",
|
||||
"fieldServerHost": "Server address",
|
||||
"@fieldServerHost": {
|
||||
"description": "Label for the server-address input on the connect form."
|
||||
},
|
||||
"fieldNickname": "Nickname",
|
||||
"@fieldNickname": {
|
||||
"description": "Label for the nickname input on the connect form."
|
||||
},
|
||||
|
||||
"connectAction": "Connect",
|
||||
"@connectAction": {
|
||||
"description": "Label for the primary action that opens the connect-to-server flow."
|
||||
"description": "Label for the button that initiates a connection."
|
||||
},
|
||||
"disconnectAction": "Disconnect",
|
||||
"@disconnectAction": {
|
||||
"description": "Label for the button that ends the active connection."
|
||||
},
|
||||
"refreshAction": "Refresh",
|
||||
"@refreshAction": {
|
||||
"description": "Label for the button that re-fetches the server snapshot."
|
||||
},
|
||||
|
||||
"statusIdle": "Not connected",
|
||||
"@statusIdle": {},
|
||||
"statusConnecting": "Connecting…",
|
||||
"@statusConnecting": {},
|
||||
"statusConnected": "Connected to {server}",
|
||||
"@statusConnected": {
|
||||
"placeholders": {
|
||||
"server": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"statusError": "Error: {message}",
|
||||
"@statusError": {
|
||||
"placeholders": {
|
||||
"message": { "type": "String" }
|
||||
}
|
||||
},
|
||||
|
||||
"channelsHeading": "Channels",
|
||||
"@channelsHeading": {},
|
||||
"clientsHeading": "Online clients",
|
||||
"@clientsHeading": {},
|
||||
"countChannelsAndClients": "{channels} channels • {clients} online",
|
||||
"@countChannelsAndClients": {
|
||||
"placeholders": {
|
||||
"channels": { "type": "int" },
|
||||
"clients": { "type": "int" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,21 @@
|
||||
"@@x-source-of-truth": "DEC-015 (register v0.9.5). Simplified Chinese translations for the MVP key set. Keys must match app_en.arb; server-provided content is NOT translated (ADR-008 / DEC-015).",
|
||||
|
||||
"appTitle": "Chanora",
|
||||
"homeGreeting": "欢迎使用 Chanora",
|
||||
"homeNotProductionReadyBanner": "Chanora 目前处于早期开发阶段,此版本尚未达到正式发布质量。",
|
||||
"connectAction": "连接到服务器"
|
||||
"homeNotProductionReadyBanner": "Alpha 版本——尚未达到生产环境质量。",
|
||||
|
||||
"fieldServerHost": "服务器地址",
|
||||
"fieldNickname": "昵称",
|
||||
|
||||
"connectAction": "连接",
|
||||
"disconnectAction": "断开连接",
|
||||
"refreshAction": "刷新",
|
||||
|
||||
"statusIdle": "未连接",
|
||||
"statusConnecting": "正在连接…",
|
||||
"statusConnected": "已连接到 {server}",
|
||||
"statusError": "错误:{message}",
|
||||
|
||||
"channelsHeading": "频道",
|
||||
"clientsHeading": "在线用户",
|
||||
"countChannelsAndClients": "{channels} 个频道 • {clients} 在线"
|
||||
}
|
||||
|
||||
@@ -103,23 +103,83 @@ abstract class AppL10n {
|
||||
/// **'Chanora'**
|
||||
String get appTitle;
|
||||
|
||||
/// Greeting shown on the empty home screen before any server is connected.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Welcome to Chanora'**
|
||||
String get homeGreeting;
|
||||
|
||||
/// Plain-language banner informing testers that this build is not for end-user use. Mirrors README.md's status line.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Chanora is currently in early development. This build is not production-ready.'**
|
||||
/// **'Alpha build — not production ready.'**
|
||||
String get homeNotProductionReadyBanner;
|
||||
|
||||
/// Label for the primary action that opens the connect-to-server flow.
|
||||
/// Label for the server-address input on the connect form.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connect to a server'**
|
||||
/// **'Server address'**
|
||||
String get fieldServerHost;
|
||||
|
||||
/// Label for the nickname input on the connect form.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Nickname'**
|
||||
String get fieldNickname;
|
||||
|
||||
/// Label for the button that initiates a connection.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connect'**
|
||||
String get connectAction;
|
||||
|
||||
/// Label for the button that ends the active connection.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Disconnect'**
|
||||
String get disconnectAction;
|
||||
|
||||
/// Label for the button that re-fetches the server snapshot.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Refresh'**
|
||||
String get refreshAction;
|
||||
|
||||
/// No description provided for @statusIdle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Not connected'**
|
||||
String get statusIdle;
|
||||
|
||||
/// No description provided for @statusConnecting.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connecting…'**
|
||||
String get statusConnecting;
|
||||
|
||||
/// No description provided for @statusConnected.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connected to {server}'**
|
||||
String statusConnected(String server);
|
||||
|
||||
/// No description provided for @statusError.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Error: {message}'**
|
||||
String statusError(String message);
|
||||
|
||||
/// No description provided for @channelsHeading.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Channels'**
|
||||
String get channelsHeading;
|
||||
|
||||
/// No description provided for @clientsHeading.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Online clients'**
|
||||
String get clientsHeading;
|
||||
|
||||
/// No description provided for @countChannelsAndClients.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{channels} channels • {clients} online'**
|
||||
String countChannelsAndClients(int channels, int clients);
|
||||
}
|
||||
|
||||
class _AppL10nDelegate extends LocalizationsDelegate<AppL10n> {
|
||||
|
||||
@@ -11,13 +11,49 @@ class AppL10nEn extends AppL10n {
|
||||
@override
|
||||
String get appTitle => 'Chanora';
|
||||
|
||||
@override
|
||||
String get homeGreeting => 'Welcome to Chanora';
|
||||
|
||||
@override
|
||||
String get homeNotProductionReadyBanner =>
|
||||
'Chanora is currently in early development. This build is not production-ready.';
|
||||
'Alpha build — not production ready.';
|
||||
|
||||
@override
|
||||
String get connectAction => 'Connect to a server';
|
||||
String get fieldServerHost => 'Server address';
|
||||
|
||||
@override
|
||||
String get fieldNickname => 'Nickname';
|
||||
|
||||
@override
|
||||
String get connectAction => 'Connect';
|
||||
|
||||
@override
|
||||
String get disconnectAction => 'Disconnect';
|
||||
|
||||
@override
|
||||
String get refreshAction => 'Refresh';
|
||||
|
||||
@override
|
||||
String get statusIdle => 'Not connected';
|
||||
|
||||
@override
|
||||
String get statusConnecting => 'Connecting…';
|
||||
|
||||
@override
|
||||
String statusConnected(String server) {
|
||||
return 'Connected to $server';
|
||||
}
|
||||
|
||||
@override
|
||||
String statusError(String message) {
|
||||
return 'Error: $message';
|
||||
}
|
||||
|
||||
@override
|
||||
String get channelsHeading => 'Channels';
|
||||
|
||||
@override
|
||||
String get clientsHeading => 'Online clients';
|
||||
|
||||
@override
|
||||
String countChannelsAndClients(int channels, int clients) {
|
||||
return '$channels channels • $clients online';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,47 @@ class AppL10nZh extends AppL10n {
|
||||
String get appTitle => 'Chanora';
|
||||
|
||||
@override
|
||||
String get homeGreeting => '欢迎使用 Chanora';
|
||||
String get homeNotProductionReadyBanner => 'Alpha 版本——尚未达到生产环境质量。';
|
||||
|
||||
@override
|
||||
String get homeNotProductionReadyBanner =>
|
||||
'Chanora 目前处于早期开发阶段,此版本尚未达到正式发布质量。';
|
||||
String get fieldServerHost => '服务器地址';
|
||||
|
||||
@override
|
||||
String get connectAction => '连接到服务器';
|
||||
String get fieldNickname => '昵称';
|
||||
|
||||
@override
|
||||
String get connectAction => '连接';
|
||||
|
||||
@override
|
||||
String get disconnectAction => '断开连接';
|
||||
|
||||
@override
|
||||
String get refreshAction => '刷新';
|
||||
|
||||
@override
|
||||
String get statusIdle => '未连接';
|
||||
|
||||
@override
|
||||
String get statusConnecting => '正在连接…';
|
||||
|
||||
@override
|
||||
String statusConnected(String server) {
|
||||
return '已连接到 $server';
|
||||
}
|
||||
|
||||
@override
|
||||
String statusError(String message) {
|
||||
return '错误:$message';
|
||||
}
|
||||
|
||||
@override
|
||||
String get channelsHeading => '频道';
|
||||
|
||||
@override
|
||||
String get clientsHeading => '在线用户';
|
||||
|
||||
@override
|
||||
String countChannelsAndClients(int channels, int clients) {
|
||||
return '$channels 个频道 • $clients 在线';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
// Chanora Flutter application entry point.
|
||||
// Chanora Flutter application entry point — Alpha build.
|
||||
//
|
||||
// Scaffold only. This file is the minimum viable wiring to demonstrate
|
||||
// the i18n architecture (DEC-015: English + Chinese Simplified) and to
|
||||
// give `flutter analyze` and `flutter build linux --debug` something
|
||||
// to compile. Real product UI lands later when the Chanora Design
|
||||
// System is implemented per docs/ui-ux/.
|
||||
// Wires the Alpha UI: server-address + nickname form, connect button,
|
||||
// channel tree, disconnect. All names from server-side state are
|
||||
// preserved verbatim per ADR-008 / DEC-015.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'l10n/generated/app_localizations.dart';
|
||||
import 'src/rust/api.dart' as rust;
|
||||
import 'src/rust/frb_generated.dart';
|
||||
|
||||
void main() {
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await RustLib.init();
|
||||
runApp(const ChanoraApp());
|
||||
}
|
||||
|
||||
@@ -26,47 +28,267 @@ class ChanoraApp extends StatelessWidget {
|
||||
colorSchemeSeed: const Color(0xFF3F51B5),
|
||||
),
|
||||
// DEC-015 (register v0.9.5): English + Chinese Simplified at MVP.
|
||||
// i18n-ready architecture; additional locales add an ARB file
|
||||
// under lib/l10n and a Locale entry here.
|
||||
localizationsDelegates: AppL10n.localizationsDelegates,
|
||||
supportedLocales: AppL10n.supportedLocales,
|
||||
home: const _HomeScreen(),
|
||||
home: const _AlphaHome(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HomeScreen extends StatelessWidget {
|
||||
const _HomeScreen();
|
||||
/// Three-state UI: idle / connecting / connected. Errors collapse
|
||||
/// back to idle with the message captured.
|
||||
class _AlphaHome extends StatefulWidget {
|
||||
const _AlphaHome();
|
||||
|
||||
@override
|
||||
State<_AlphaHome> createState() => _AlphaHomeState();
|
||||
}
|
||||
|
||||
enum _Phase { idle, connecting, connected }
|
||||
|
||||
class _AlphaHomeState extends State<_AlphaHome> {
|
||||
final _hostCtl = TextEditingController(text: 'cn.teamspeak.app');
|
||||
final _nickCtl = TextEditingController(text: 'ChanoraAlpha');
|
||||
|
||||
_Phase _phase = _Phase.idle;
|
||||
rust.BridgeSnapshot? _snapshot;
|
||||
String? _error;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_hostCtl.dispose();
|
||||
_nickCtl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _onConnect() async {
|
||||
setState(() {
|
||||
_phase = _Phase.connecting;
|
||||
_error = null;
|
||||
_snapshot = null;
|
||||
});
|
||||
try {
|
||||
final snap = await rust.connect(
|
||||
host: _hostCtl.text.trim(),
|
||||
nickname: _nickCtl.text.trim(),
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_phase = _Phase.connected;
|
||||
_snapshot = snap;
|
||||
});
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_phase = _Phase.idle;
|
||||
_error = e.toString();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onRefresh() async {
|
||||
try {
|
||||
final snap = await rust.snapshot();
|
||||
if (!mounted) return;
|
||||
setState(() => _snapshot = snap);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() => _error = e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onDisconnect() async {
|
||||
try {
|
||||
await rust.disconnect();
|
||||
} catch (_) {
|
||||
// Best-effort. Even if disconnect throws we drop back to idle.
|
||||
}
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_phase = _Phase.idle;
|
||||
_snapshot = null;
|
||||
_error = null;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppL10n.of(context);
|
||||
final theme = Theme.of(context);
|
||||
|
||||
String statusText() {
|
||||
switch (_phase) {
|
||||
case _Phase.idle:
|
||||
return _error != null ? l10n.statusError(_error!) : l10n.statusIdle;
|
||||
case _Phase.connecting:
|
||||
return l10n.statusConnecting;
|
||||
case _Phase.connected:
|
||||
return l10n.statusConnected(_snapshot?.serverName ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(l10n.appTitle)),
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.appTitle),
|
||||
actions: [
|
||||
if (_phase == _Phase.connected) ...[
|
||||
IconButton(
|
||||
tooltip: l10n.refreshAction,
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: _onRefresh,
|
||||
),
|
||||
IconButton(
|
||||
tooltip: l10n.disconnectAction,
|
||||
icon: const Icon(Icons.logout),
|
||||
onPressed: _onDisconnect,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(l10n.homeGreeting, style: theme.textTheme.headlineSmall),
|
||||
const SizedBox(height: 16),
|
||||
// Banner
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerHighest,
|
||||
color: theme.colorScheme.tertiaryContainer,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(l10n.homeNotProductionReadyBanner),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
FilledButton(
|
||||
onPressed: null, // wired in a later phase
|
||||
child: Text(l10n.connectAction),
|
||||
child: Text(
|
||||
l10n.homeNotProductionReadyBanner,
|
||||
style: TextStyle(color: theme.colorScheme.onTertiaryContainer),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Status
|
||||
Text(statusText(), style: theme.textTheme.titleMedium),
|
||||
const SizedBox(height: 12),
|
||||
if (_phase == _Phase.idle) ...[
|
||||
_ConnectForm(
|
||||
hostCtl: _hostCtl,
|
||||
nickCtl: _nickCtl,
|
||||
onConnect: _onConnect,
|
||||
),
|
||||
] else if (_phase == _Phase.connecting) ...[
|
||||
const Center(child: Padding(
|
||||
padding: EdgeInsets.all(32),
|
||||
child: CircularProgressIndicator(),
|
||||
)),
|
||||
] else if (_phase == _Phase.connected && _snapshot != null) ...[
|
||||
Expanded(child: _SnapshotView(snapshot: _snapshot!)),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ConnectForm extends StatelessWidget {
|
||||
const _ConnectForm({
|
||||
required this.hostCtl,
|
||||
required this.nickCtl,
|
||||
required this.onConnect,
|
||||
});
|
||||
|
||||
final TextEditingController hostCtl;
|
||||
final TextEditingController nickCtl;
|
||||
final VoidCallback onConnect;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppL10n.of(context);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
TextField(
|
||||
controller: hostCtl,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.fieldServerHost,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: nickCtl,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.fieldNickname,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton.icon(
|
||||
icon: const Icon(Icons.login),
|
||||
label: Text(l10n.connectAction),
|
||||
onPressed: onConnect,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SnapshotView extends StatelessWidget {
|
||||
const _SnapshotView({required this.snapshot});
|
||||
|
||||
final rust.BridgeSnapshot snapshot;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppL10n.of(context);
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final channels = [...snapshot.channels]
|
||||
..sort((a, b) => a.order.compareTo(b.order));
|
||||
|
||||
// Index clients by channel id for the tree view.
|
||||
final byChannel = <BigInt, List<rust.BridgeClient>>{};
|
||||
for (final c in snapshot.clients) {
|
||||
byChannel.putIfAbsent(c.channel, () => []).add(c);
|
||||
}
|
||||
|
||||
return ListView(
|
||||
children: [
|
||||
Text(
|
||||
l10n.countChannelsAndClients(snapshot.channels.length, snapshot.clients.length),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
if (snapshot.welcomeMessage.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
// Server-provided content; preserved verbatim per ADR-008.
|
||||
child: Text(snapshot.welcomeMessage, style: theme.textTheme.bodySmall),
|
||||
),
|
||||
],
|
||||
const Divider(height: 24),
|
||||
Text(l10n.channelsHeading, style: theme.textTheme.titleMedium),
|
||||
const SizedBox(height: 4),
|
||||
for (final ch in channels) ...[
|
||||
ListTile(
|
||||
dense: true,
|
||||
leading: const Icon(Icons.tag),
|
||||
title: Text(ch.name),
|
||||
subtitle: Text('id=${ch.id} parent=${ch.parent}'),
|
||||
),
|
||||
for (final cl in byChannel[ch.id] ?? const <rust.BridgeClient>[])
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 64),
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
visualDensity: VisualDensity.compact,
|
||||
leading: const Icon(Icons.person, size: 18),
|
||||
title: Text(cl.name),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||
|
||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
||||
|
||||
import 'frb_generated.dart';
|
||||
import 'lib.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
|
||||
// These functions are ignored because they are not marked as `pub`: `runtime`, `session`
|
||||
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `clone`, `clone`, `fmt`, `fmt`, `fmt`, `from`
|
||||
|
||||
/// Connect to a TeamSpeak-compatible server and return the initial
|
||||
/// state snapshot. Honours the DEC-006 single-connection invariant
|
||||
/// via [`BridgeError::AlreadyConnected`].
|
||||
Future<BridgeSnapshot> connect({
|
||||
required String host,
|
||||
required String nickname,
|
||||
}) => RustLib.instance.api.crateApiConnect(host: host, nickname: nickname);
|
||||
|
||||
/// Re-fetch a fresh snapshot from the active connection.
|
||||
Future<BridgeSnapshot> snapshot() => RustLib.instance.api.crateApiSnapshot();
|
||||
|
||||
/// Disconnect from the server. No-op if not connected.
|
||||
Future<void> disconnect() => RustLib.instance.api.crateApiDisconnect();
|
||||
|
||||
/// True if a connection is currently active.
|
||||
Future<bool> isConnected() => RustLib.instance.api.crateApiIsConnected();
|
||||
|
||||
/// Channel as seen by Dart. Matches `chanora_protocol::ChannelInfo`
|
||||
/// but with primitive `u64` ids so the Dart side gets `BigInt`s
|
||||
/// without any wrapper-type ceremony.
|
||||
class BridgeChannel {
|
||||
/// Stable channel id.
|
||||
final BigInt id;
|
||||
|
||||
/// Parent channel id; 0 means top-level.
|
||||
final BigInt parent;
|
||||
|
||||
/// Display name.
|
||||
final String name;
|
||||
|
||||
/// Server-side ordering hint.
|
||||
final PlatformInt64 order;
|
||||
|
||||
const BridgeChannel({
|
||||
required this.id,
|
||||
required this.parent,
|
||||
required this.name,
|
||||
required this.order,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
id.hashCode ^ parent.hashCode ^ name.hashCode ^ order.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is BridgeChannel &&
|
||||
runtimeType == other.runtimeType &&
|
||||
id == other.id &&
|
||||
parent == other.parent &&
|
||||
name == other.name &&
|
||||
order == other.order;
|
||||
}
|
||||
|
||||
/// Client as seen by Dart.
|
||||
class BridgeClient {
|
||||
/// Stable client id.
|
||||
final BigInt id;
|
||||
|
||||
/// Channel id the client is currently in.
|
||||
final BigInt channel;
|
||||
|
||||
/// Nickname.
|
||||
final String name;
|
||||
|
||||
const BridgeClient({
|
||||
required this.id,
|
||||
required this.channel,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode ^ channel.hashCode ^ name.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is BridgeClient &&
|
||||
runtimeType == other.runtimeType &&
|
||||
id == other.id &&
|
||||
channel == other.channel &&
|
||||
name == other.name;
|
||||
}
|
||||
|
||||
/// Server snapshot as seen by Dart.
|
||||
class BridgeSnapshot {
|
||||
/// Server name.
|
||||
final String serverName;
|
||||
|
||||
/// Welcome banner text.
|
||||
final String welcomeMessage;
|
||||
|
||||
/// Server platform (e.g. "Linux").
|
||||
final String platform;
|
||||
|
||||
/// Server version string.
|
||||
final String version;
|
||||
|
||||
/// Channels currently known.
|
||||
final List<BridgeChannel> channels;
|
||||
|
||||
/// Clients currently known.
|
||||
final List<BridgeClient> clients;
|
||||
|
||||
const BridgeSnapshot({
|
||||
required this.serverName,
|
||||
required this.welcomeMessage,
|
||||
required this.platform,
|
||||
required this.version,
|
||||
required this.channels,
|
||||
required this.clients,
|
||||
});
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
serverName.hashCode ^
|
||||
welcomeMessage.hashCode ^
|
||||
platform.hashCode ^
|
||||
version.hashCode ^
|
||||
channels.hashCode ^
|
||||
clients.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is BridgeSnapshot &&
|
||||
runtimeType == other.runtimeType &&
|
||||
serverName == other.serverName &&
|
||||
welcomeMessage == other.welcomeMessage &&
|
||||
platform == other.platform &&
|
||||
version == other.version &&
|
||||
channels == other.channels &&
|
||||
clients == other.clients;
|
||||
}
|
||||
@@ -0,0 +1,629 @@
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||
|
||||
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
|
||||
|
||||
import 'api.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'frb_generated.dart';
|
||||
import 'frb_generated.io.dart'
|
||||
if (dart.library.js_interop) 'frb_generated.web.dart';
|
||||
import 'lib.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
|
||||
/// Main entrypoint of the Rust API
|
||||
class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
|
||||
@internal
|
||||
static final instance = RustLib._();
|
||||
|
||||
RustLib._();
|
||||
|
||||
/// Initialize flutter_rust_bridge
|
||||
static Future<void> init({
|
||||
RustLibApi? api,
|
||||
BaseHandler? handler,
|
||||
ExternalLibrary? externalLibrary,
|
||||
bool forceSameCodegenVersion = true,
|
||||
}) async {
|
||||
await instance.initImpl(
|
||||
api: api,
|
||||
handler: handler,
|
||||
externalLibrary: externalLibrary,
|
||||
forceSameCodegenVersion: forceSameCodegenVersion,
|
||||
);
|
||||
}
|
||||
|
||||
/// Initialize flutter_rust_bridge in mock mode.
|
||||
/// No libraries for FFI are loaded.
|
||||
static void initMock({required RustLibApi api}) {
|
||||
instance.initMockImpl(api: api);
|
||||
}
|
||||
|
||||
/// Dispose flutter_rust_bridge
|
||||
///
|
||||
/// The call to this function is optional, since flutter_rust_bridge (and everything else)
|
||||
/// is automatically disposed when the app stops.
|
||||
static void dispose() => instance.disposeImpl();
|
||||
|
||||
@override
|
||||
ApiImplConstructor<RustLibApiImpl, RustLibWire> get apiImplConstructor =>
|
||||
RustLibApiImpl.new;
|
||||
|
||||
@override
|
||||
WireConstructor<RustLibWire> get wireConstructor =>
|
||||
RustLibWire.fromExternalLibrary;
|
||||
|
||||
@override
|
||||
Future<void> executeRustInitializers() async {
|
||||
await api.crateApiBridgeInit();
|
||||
}
|
||||
|
||||
@override
|
||||
ExternalLibraryLoaderConfig get defaultExternalLibraryLoaderConfig =>
|
||||
kDefaultExternalLibraryLoaderConfig;
|
||||
|
||||
@override
|
||||
String get codegenVersion => '2.12.0';
|
||||
|
||||
@override
|
||||
int get rustContentHash => 978717843;
|
||||
|
||||
static const kDefaultExternalLibraryLoaderConfig =
|
||||
ExternalLibraryLoaderConfig(
|
||||
stem: 'chanora_bridge',
|
||||
ioDirectory: '../../crates/chanora_bridge/target/release/',
|
||||
webPrefix: 'pkg/',
|
||||
wasmBindgenName: 'wasm_bindgen',
|
||||
);
|
||||
}
|
||||
|
||||
abstract class RustLibApi extends BaseApi {
|
||||
Future<void> crateApiBridgeInit();
|
||||
|
||||
Future<BridgeSnapshot> crateApiConnect({
|
||||
required String host,
|
||||
required String nickname,
|
||||
});
|
||||
|
||||
Future<void> crateApiDisconnect();
|
||||
|
||||
Future<bool> crateApiIsConnected();
|
||||
|
||||
Future<BridgeSnapshot> crateApiSnapshot();
|
||||
}
|
||||
|
||||
class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi {
|
||||
RustLibApiImpl({
|
||||
required super.handler,
|
||||
required super.wire,
|
||||
required super.generalizedFrbRustBinding,
|
||||
required super.portManager,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<void> crateApiBridgeInit() {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 1,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_unit,
|
||||
decodeErrorData: null,
|
||||
),
|
||||
constMeta: kCrateApiBridgeInitConstMeta,
|
||||
argValues: [],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiBridgeInitConstMeta =>
|
||||
const TaskConstMeta(debugName: "bridge_init", argNames: []);
|
||||
|
||||
@override
|
||||
Future<BridgeSnapshot> crateApiConnect({
|
||||
required String host,
|
||||
required String nickname,
|
||||
}) {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
sse_encode_String(host, serializer);
|
||||
sse_encode_String(nickname, serializer);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 2,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_bridge_snapshot,
|
||||
decodeErrorData: sse_decode_bridge_error,
|
||||
),
|
||||
constMeta: kCrateApiConnectConstMeta,
|
||||
argValues: [host, nickname],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiConnectConstMeta =>
|
||||
const TaskConstMeta(debugName: "connect", argNames: ["host", "nickname"]);
|
||||
|
||||
@override
|
||||
Future<void> crateApiDisconnect() {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 3,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_unit,
|
||||
decodeErrorData: sse_decode_bridge_error,
|
||||
),
|
||||
constMeta: kCrateApiDisconnectConstMeta,
|
||||
argValues: [],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiDisconnectConstMeta =>
|
||||
const TaskConstMeta(debugName: "disconnect", argNames: []);
|
||||
|
||||
@override
|
||||
Future<bool> crateApiIsConnected() {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 4,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_bool,
|
||||
decodeErrorData: null,
|
||||
),
|
||||
constMeta: kCrateApiIsConnectedConstMeta,
|
||||
argValues: [],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiIsConnectedConstMeta =>
|
||||
const TaskConstMeta(debugName: "is_connected", argNames: []);
|
||||
|
||||
@override
|
||||
Future<BridgeSnapshot> crateApiSnapshot() {
|
||||
return handler.executeNormal(
|
||||
NormalTask(
|
||||
callFfi: (port_) {
|
||||
final serializer = SseSerializer(generalizedFrbRustBinding);
|
||||
pdeCallFfi(
|
||||
generalizedFrbRustBinding,
|
||||
serializer,
|
||||
funcId: 5,
|
||||
port: port_,
|
||||
);
|
||||
},
|
||||
codec: SseCodec(
|
||||
decodeSuccessData: sse_decode_bridge_snapshot,
|
||||
decodeErrorData: sse_decode_bridge_error,
|
||||
),
|
||||
constMeta: kCrateApiSnapshotConstMeta,
|
||||
argValues: [],
|
||||
apiImpl: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TaskConstMeta get kCrateApiSnapshotConstMeta =>
|
||||
const TaskConstMeta(debugName: "snapshot", argNames: []);
|
||||
|
||||
@protected
|
||||
String dco_decode_String(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return raw as String;
|
||||
}
|
||||
|
||||
@protected
|
||||
bool dco_decode_bool(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return raw as bool;
|
||||
}
|
||||
|
||||
@protected
|
||||
BridgeChannel dco_decode_bridge_channel(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
final arr = raw as List<dynamic>;
|
||||
if (arr.length != 4)
|
||||
throw Exception('unexpected arr length: expect 4 but see ${arr.length}');
|
||||
return BridgeChannel(
|
||||
id: dco_decode_u_64(arr[0]),
|
||||
parent: dco_decode_u_64(arr[1]),
|
||||
name: dco_decode_String(arr[2]),
|
||||
order: dco_decode_i_64(arr[3]),
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
BridgeClient dco_decode_bridge_client(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
final arr = raw as List<dynamic>;
|
||||
if (arr.length != 3)
|
||||
throw Exception('unexpected arr length: expect 3 but see ${arr.length}');
|
||||
return BridgeClient(
|
||||
id: dco_decode_u_64(arr[0]),
|
||||
channel: dco_decode_u_64(arr[1]),
|
||||
name: dco_decode_String(arr[2]),
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
BridgeError dco_decode_bridge_error(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
switch (raw[0]) {
|
||||
case 0:
|
||||
return BridgeError_InvalidCommand(dco_decode_String(raw[1]));
|
||||
case 1:
|
||||
return BridgeError_Connection(dco_decode_String(raw[1]));
|
||||
case 2:
|
||||
return BridgeError_NotConnected();
|
||||
case 3:
|
||||
return BridgeError_AlreadyConnected();
|
||||
case 4:
|
||||
return BridgeError_Unmapped(dco_decode_String(raw[1]));
|
||||
default:
|
||||
throw Exception("unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
BridgeSnapshot dco_decode_bridge_snapshot(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
final arr = raw as List<dynamic>;
|
||||
if (arr.length != 6)
|
||||
throw Exception('unexpected arr length: expect 6 but see ${arr.length}');
|
||||
return BridgeSnapshot(
|
||||
serverName: dco_decode_String(arr[0]),
|
||||
welcomeMessage: dco_decode_String(arr[1]),
|
||||
platform: dco_decode_String(arr[2]),
|
||||
version: dco_decode_String(arr[3]),
|
||||
channels: dco_decode_list_bridge_channel(arr[4]),
|
||||
clients: dco_decode_list_bridge_client(arr[5]),
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_i_64(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return dcoDecodeI64(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
List<BridgeChannel> dco_decode_list_bridge_channel(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return (raw as List<dynamic>).map(dco_decode_bridge_channel).toList();
|
||||
}
|
||||
|
||||
@protected
|
||||
List<BridgeClient> dco_decode_list_bridge_client(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return (raw as List<dynamic>).map(dco_decode_bridge_client).toList();
|
||||
}
|
||||
|
||||
@protected
|
||||
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return raw as Uint8List;
|
||||
}
|
||||
|
||||
@protected
|
||||
BigInt dco_decode_u_64(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return dcoDecodeU64(raw);
|
||||
}
|
||||
|
||||
@protected
|
||||
int dco_decode_u_8(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return raw as int;
|
||||
}
|
||||
|
||||
@protected
|
||||
void dco_decode_unit(dynamic raw) {
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
return;
|
||||
}
|
||||
|
||||
@protected
|
||||
String sse_decode_String(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var inner = sse_decode_list_prim_u_8_strict(deserializer);
|
||||
return utf8.decoder.convert(inner);
|
||||
}
|
||||
|
||||
@protected
|
||||
bool sse_decode_bool(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return deserializer.buffer.getUint8() != 0;
|
||||
}
|
||||
|
||||
@protected
|
||||
BridgeChannel sse_decode_bridge_channel(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var var_id = sse_decode_u_64(deserializer);
|
||||
var var_parent = sse_decode_u_64(deserializer);
|
||||
var var_name = sse_decode_String(deserializer);
|
||||
var var_order = sse_decode_i_64(deserializer);
|
||||
return BridgeChannel(
|
||||
id: var_id,
|
||||
parent: var_parent,
|
||||
name: var_name,
|
||||
order: var_order,
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
BridgeClient sse_decode_bridge_client(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var var_id = sse_decode_u_64(deserializer);
|
||||
var var_channel = sse_decode_u_64(deserializer);
|
||||
var var_name = sse_decode_String(deserializer);
|
||||
return BridgeClient(id: var_id, channel: var_channel, name: var_name);
|
||||
}
|
||||
|
||||
@protected
|
||||
BridgeError sse_decode_bridge_error(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
var tag_ = sse_decode_i_32(deserializer);
|
||||
switch (tag_) {
|
||||
case 0:
|
||||
var var_field0 = sse_decode_String(deserializer);
|
||||
return BridgeError_InvalidCommand(var_field0);
|
||||
case 1:
|
||||
var var_field0 = sse_decode_String(deserializer);
|
||||
return BridgeError_Connection(var_field0);
|
||||
case 2:
|
||||
return BridgeError_NotConnected();
|
||||
case 3:
|
||||
return BridgeError_AlreadyConnected();
|
||||
case 4:
|
||||
var var_field0 = sse_decode_String(deserializer);
|
||||
return BridgeError_Unmapped(var_field0);
|
||||
default:
|
||||
throw UnimplementedError('');
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
BridgeSnapshot sse_decode_bridge_snapshot(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var var_serverName = sse_decode_String(deserializer);
|
||||
var var_welcomeMessage = sse_decode_String(deserializer);
|
||||
var var_platform = sse_decode_String(deserializer);
|
||||
var var_version = sse_decode_String(deserializer);
|
||||
var var_channels = sse_decode_list_bridge_channel(deserializer);
|
||||
var var_clients = sse_decode_list_bridge_client(deserializer);
|
||||
return BridgeSnapshot(
|
||||
serverName: var_serverName,
|
||||
welcomeMessage: var_welcomeMessage,
|
||||
platform: var_platform,
|
||||
version: var_version,
|
||||
channels: var_channels,
|
||||
clients: var_clients,
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return deserializer.buffer.getPlatformInt64();
|
||||
}
|
||||
|
||||
@protected
|
||||
List<BridgeChannel> sse_decode_list_bridge_channel(
|
||||
SseDeserializer deserializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
var len_ = sse_decode_i_32(deserializer);
|
||||
var ans_ = <BridgeChannel>[];
|
||||
for (var idx_ = 0; idx_ < len_; ++idx_) {
|
||||
ans_.add(sse_decode_bridge_channel(deserializer));
|
||||
}
|
||||
return ans_;
|
||||
}
|
||||
|
||||
@protected
|
||||
List<BridgeClient> sse_decode_list_bridge_client(
|
||||
SseDeserializer deserializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
|
||||
var len_ = sse_decode_i_32(deserializer);
|
||||
var ans_ = <BridgeClient>[];
|
||||
for (var idx_ = 0; idx_ < len_; ++idx_) {
|
||||
ans_.add(sse_decode_bridge_client(deserializer));
|
||||
}
|
||||
return ans_;
|
||||
}
|
||||
|
||||
@protected
|
||||
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
var len_ = sse_decode_i_32(deserializer);
|
||||
return deserializer.buffer.getUint8List(len_);
|
||||
}
|
||||
|
||||
@protected
|
||||
BigInt sse_decode_u_64(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return deserializer.buffer.getBigUint64();
|
||||
}
|
||||
|
||||
@protected
|
||||
int sse_decode_u_8(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return deserializer.buffer.getUint8();
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_decode_unit(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
}
|
||||
|
||||
@protected
|
||||
int sse_decode_i_32(SseDeserializer deserializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
return deserializer.buffer.getInt32();
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_String(String self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_list_prim_u_8_strict(utf8.encoder.convert(self), serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_bool(bool self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
serializer.buffer.putUint8(self ? 1 : 0);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_bridge_channel(BridgeChannel self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_u_64(self.id, serializer);
|
||||
sse_encode_u_64(self.parent, serializer);
|
||||
sse_encode_String(self.name, serializer);
|
||||
sse_encode_i_64(self.order, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_bridge_client(BridgeClient self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_u_64(self.id, serializer);
|
||||
sse_encode_u_64(self.channel, serializer);
|
||||
sse_encode_String(self.name, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_bridge_error(BridgeError self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
switch (self) {
|
||||
case BridgeError_InvalidCommand(field0: final field0):
|
||||
sse_encode_i_32(0, serializer);
|
||||
sse_encode_String(field0, serializer);
|
||||
case BridgeError_Connection(field0: final field0):
|
||||
sse_encode_i_32(1, serializer);
|
||||
sse_encode_String(field0, serializer);
|
||||
case BridgeError_NotConnected():
|
||||
sse_encode_i_32(2, serializer);
|
||||
case BridgeError_AlreadyConnected():
|
||||
sse_encode_i_32(3, serializer);
|
||||
case BridgeError_Unmapped(field0: final field0):
|
||||
sse_encode_i_32(4, serializer);
|
||||
sse_encode_String(field0, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_bridge_snapshot(
|
||||
BridgeSnapshot self,
|
||||
SseSerializer serializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_String(self.serverName, serializer);
|
||||
sse_encode_String(self.welcomeMessage, serializer);
|
||||
sse_encode_String(self.platform, serializer);
|
||||
sse_encode_String(self.version, serializer);
|
||||
sse_encode_list_bridge_channel(self.channels, serializer);
|
||||
sse_encode_list_bridge_client(self.clients, serializer);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
serializer.buffer.putPlatformInt64(self);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_list_bridge_channel(
|
||||
List<BridgeChannel> self,
|
||||
SseSerializer serializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_i_32(self.length, serializer);
|
||||
for (final item in self) {
|
||||
sse_encode_bridge_channel(item, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_list_bridge_client(
|
||||
List<BridgeClient> self,
|
||||
SseSerializer serializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_i_32(self.length, serializer);
|
||||
for (final item in self) {
|
||||
sse_encode_bridge_client(item, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_list_prim_u_8_strict(
|
||||
Uint8List self,
|
||||
SseSerializer serializer,
|
||||
) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
sse_encode_i_32(self.length, serializer);
|
||||
serializer.buffer.putUint8List(self);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_u_64(BigInt self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
serializer.buffer.putBigUint64(self);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_u_8(int self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
serializer.buffer.putUint8(self);
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_unit(void self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
}
|
||||
|
||||
@protected
|
||||
void sse_encode_i_32(int self, SseSerializer serializer) {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
serializer.buffer.putInt32(self);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||
|
||||
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
|
||||
|
||||
import 'api.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi' as ffi;
|
||||
import 'frb_generated.dart';
|
||||
import 'lib.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart';
|
||||
|
||||
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
RustLibApiImplPlatform({
|
||||
required super.handler,
|
||||
required super.wire,
|
||||
required super.generalizedFrbRustBinding,
|
||||
required super.portManager,
|
||||
});
|
||||
|
||||
@protected
|
||||
String dco_decode_String(dynamic raw);
|
||||
|
||||
@protected
|
||||
bool dco_decode_bool(dynamic raw);
|
||||
|
||||
@protected
|
||||
BridgeChannel dco_decode_bridge_channel(dynamic raw);
|
||||
|
||||
@protected
|
||||
BridgeClient dco_decode_bridge_client(dynamic raw);
|
||||
|
||||
@protected
|
||||
BridgeError dco_decode_bridge_error(dynamic raw);
|
||||
|
||||
@protected
|
||||
BridgeSnapshot dco_decode_bridge_snapshot(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<BridgeChannel> dco_decode_list_bridge_channel(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<BridgeClient> dco_decode_list_bridge_client(dynamic raw);
|
||||
|
||||
@protected
|
||||
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
BigInt dco_decode_u_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
int dco_decode_u_8(dynamic raw);
|
||||
|
||||
@protected
|
||||
void dco_decode_unit(dynamic raw);
|
||||
|
||||
@protected
|
||||
String sse_decode_String(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
bool sse_decode_bool(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BridgeChannel sse_decode_bridge_channel(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BridgeClient sse_decode_bridge_client(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BridgeError sse_decode_bridge_error(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BridgeSnapshot sse_decode_bridge_snapshot(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<BridgeChannel> sse_decode_list_bridge_channel(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
List<BridgeClient> sse_decode_list_bridge_client(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BigInt sse_decode_u_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int sse_decode_u_8(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
void sse_decode_unit(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int sse_decode_i_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_String(String self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_bool(bool self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_bridge_channel(BridgeChannel self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_bridge_client(BridgeClient self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_bridge_error(BridgeError self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_bridge_snapshot(
|
||||
BridgeSnapshot self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_bridge_channel(
|
||||
List<BridgeChannel> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_bridge_client(
|
||||
List<BridgeClient> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_prim_u_8_strict(
|
||||
Uint8List self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_u_64(BigInt self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_u_8(int self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_unit(void self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_i_32(int self, SseSerializer serializer);
|
||||
}
|
||||
|
||||
// Section: wire_class
|
||||
|
||||
class RustLibWire implements BaseWire {
|
||||
factory RustLibWire.fromExternalLibrary(ExternalLibrary lib) =>
|
||||
RustLibWire(lib.ffiDynamicLibrary);
|
||||
|
||||
/// Holds the symbol lookup function.
|
||||
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
|
||||
_lookup;
|
||||
|
||||
/// The symbols are looked up in [dynamicLibrary].
|
||||
RustLibWire(ffi.DynamicLibrary dynamicLibrary)
|
||||
: _lookup = dynamicLibrary.lookup;
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||
|
||||
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
|
||||
|
||||
// Static analysis wrongly picks the IO variant, thus ignore this
|
||||
// ignore_for_file: argument_type_not_assignable
|
||||
|
||||
import 'api.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'frb_generated.dart';
|
||||
import 'lib.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart';
|
||||
|
||||
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
RustLibApiImplPlatform({
|
||||
required super.handler,
|
||||
required super.wire,
|
||||
required super.generalizedFrbRustBinding,
|
||||
required super.portManager,
|
||||
});
|
||||
|
||||
@protected
|
||||
String dco_decode_String(dynamic raw);
|
||||
|
||||
@protected
|
||||
bool dco_decode_bool(dynamic raw);
|
||||
|
||||
@protected
|
||||
BridgeChannel dco_decode_bridge_channel(dynamic raw);
|
||||
|
||||
@protected
|
||||
BridgeClient dco_decode_bridge_client(dynamic raw);
|
||||
|
||||
@protected
|
||||
BridgeError dco_decode_bridge_error(dynamic raw);
|
||||
|
||||
@protected
|
||||
BridgeSnapshot dco_decode_bridge_snapshot(dynamic raw);
|
||||
|
||||
@protected
|
||||
PlatformInt64 dco_decode_i_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<BridgeChannel> dco_decode_list_bridge_channel(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<BridgeClient> dco_decode_list_bridge_client(dynamic raw);
|
||||
|
||||
@protected
|
||||
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
BigInt dco_decode_u_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
int dco_decode_u_8(dynamic raw);
|
||||
|
||||
@protected
|
||||
void dco_decode_unit(dynamic raw);
|
||||
|
||||
@protected
|
||||
String sse_decode_String(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
bool sse_decode_bool(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BridgeChannel sse_decode_bridge_channel(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BridgeClient sse_decode_bridge_client(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BridgeError sse_decode_bridge_error(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BridgeSnapshot sse_decode_bridge_snapshot(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
PlatformInt64 sse_decode_i_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<BridgeChannel> sse_decode_list_bridge_channel(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
List<BridgeClient> sse_decode_list_bridge_client(
|
||||
SseDeserializer deserializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BigInt sse_decode_u_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int sse_decode_u_8(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
void sse_decode_unit(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int sse_decode_i_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_String(String self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_bool(bool self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_bridge_channel(BridgeChannel self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_bridge_client(BridgeClient self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_bridge_error(BridgeError self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_bridge_snapshot(
|
||||
BridgeSnapshot self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_i_64(PlatformInt64 self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_bridge_channel(
|
||||
List<BridgeChannel> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_bridge_client(
|
||||
List<BridgeClient> self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_prim_u_8_strict(
|
||||
Uint8List self,
|
||||
SseSerializer serializer,
|
||||
);
|
||||
|
||||
@protected
|
||||
void sse_encode_u_64(BigInt self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_u_8(int self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_unit(void self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_i_32(int self, SseSerializer serializer);
|
||||
}
|
||||
|
||||
// Section: wire_class
|
||||
|
||||
class RustLibWire implements BaseWire {
|
||||
RustLibWire.fromExternalLibrary(ExternalLibrary lib);
|
||||
}
|
||||
|
||||
@JS('wasm_bindgen')
|
||||
external RustLibWasmModule get wasmModule;
|
||||
|
||||
@JS()
|
||||
@anonymous
|
||||
extension type RustLibWasmModule._(JSObject _) implements JSObject {}
|
||||
@@ -0,0 +1,33 @@
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||
|
||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
||||
|
||||
import 'frb_generated.dart';
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart' hide protected;
|
||||
part 'lib.freezed.dart';
|
||||
|
||||
@freezed
|
||||
sealed class BridgeError with _$BridgeError implements FrbException {
|
||||
const BridgeError._();
|
||||
|
||||
/// The caller submitted a malformed command DTO.
|
||||
const factory BridgeError.invalidCommand(String field0) =
|
||||
BridgeError_InvalidCommand;
|
||||
|
||||
/// Connection layer failure (typed-mapped from CoreError).
|
||||
const factory BridgeError.connection(String field0) = BridgeError_Connection;
|
||||
|
||||
/// Operation requires an active connection.
|
||||
const factory BridgeError.notConnected() = BridgeError_NotConnected;
|
||||
|
||||
/// Already connected; DEC-006 forbids a second concurrent
|
||||
/// connection.
|
||||
const factory BridgeError.alreadyConnected() = BridgeError_AlreadyConnected;
|
||||
|
||||
/// An unmapped error escaped the subsystem boundary. Production
|
||||
/// callers should never see this; if they do, it is a mapping
|
||||
/// bug here.
|
||||
const factory BridgeError.unmapped(String field0) = BridgeError_Unmapped;
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'lib.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$BridgeError {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is BridgeError);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BridgeError()';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class $BridgeErrorCopyWith<$Res> {
|
||||
$BridgeErrorCopyWith(BridgeError _, $Res Function(BridgeError) __);
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [BridgeError].
|
||||
extension BridgeErrorPatterns on BridgeError {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( BridgeError_InvalidCommand value)? invalidCommand,TResult Function( BridgeError_Connection value)? connection,TResult Function( BridgeError_NotConnected value)? notConnected,TResult Function( BridgeError_AlreadyConnected value)? alreadyConnected,TResult Function( BridgeError_Unmapped value)? unmapped,required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case BridgeError_InvalidCommand() when invalidCommand != null:
|
||||
return invalidCommand(_that);case BridgeError_Connection() when connection != null:
|
||||
return connection(_that);case BridgeError_NotConnected() when notConnected != null:
|
||||
return notConnected(_that);case BridgeError_AlreadyConnected() when alreadyConnected != null:
|
||||
return alreadyConnected(_that);case BridgeError_Unmapped() when unmapped != null:
|
||||
return unmapped(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( BridgeError_InvalidCommand value) invalidCommand,required TResult Function( BridgeError_Connection value) connection,required TResult Function( BridgeError_NotConnected value) notConnected,required TResult Function( BridgeError_AlreadyConnected value) alreadyConnected,required TResult Function( BridgeError_Unmapped value) unmapped,}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case BridgeError_InvalidCommand():
|
||||
return invalidCommand(_that);case BridgeError_Connection():
|
||||
return connection(_that);case BridgeError_NotConnected():
|
||||
return notConnected(_that);case BridgeError_AlreadyConnected():
|
||||
return alreadyConnected(_that);case BridgeError_Unmapped():
|
||||
return unmapped(_that);}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( BridgeError_InvalidCommand value)? invalidCommand,TResult? Function( BridgeError_Connection value)? connection,TResult? Function( BridgeError_NotConnected value)? notConnected,TResult? Function( BridgeError_AlreadyConnected value)? alreadyConnected,TResult? Function( BridgeError_Unmapped value)? unmapped,}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case BridgeError_InvalidCommand() when invalidCommand != null:
|
||||
return invalidCommand(_that);case BridgeError_Connection() when connection != null:
|
||||
return connection(_that);case BridgeError_NotConnected() when notConnected != null:
|
||||
return notConnected(_that);case BridgeError_AlreadyConnected() when alreadyConnected != null:
|
||||
return alreadyConnected(_that);case BridgeError_Unmapped() when unmapped != null:
|
||||
return unmapped(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function( String field0)? invalidCommand,TResult Function( String field0)? connection,TResult Function()? notConnected,TResult Function()? alreadyConnected,TResult Function( String field0)? unmapped,required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case BridgeError_InvalidCommand() when invalidCommand != null:
|
||||
return invalidCommand(_that.field0);case BridgeError_Connection() when connection != null:
|
||||
return connection(_that.field0);case BridgeError_NotConnected() when notConnected != null:
|
||||
return notConnected();case BridgeError_AlreadyConnected() when alreadyConnected != null:
|
||||
return alreadyConnected();case BridgeError_Unmapped() when unmapped != null:
|
||||
return unmapped(_that.field0);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function( String field0) invalidCommand,required TResult Function( String field0) connection,required TResult Function() notConnected,required TResult Function() alreadyConnected,required TResult Function( String field0) unmapped,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case BridgeError_InvalidCommand():
|
||||
return invalidCommand(_that.field0);case BridgeError_Connection():
|
||||
return connection(_that.field0);case BridgeError_NotConnected():
|
||||
return notConnected();case BridgeError_AlreadyConnected():
|
||||
return alreadyConnected();case BridgeError_Unmapped():
|
||||
return unmapped(_that.field0);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function( String field0)? invalidCommand,TResult? Function( String field0)? connection,TResult? Function()? notConnected,TResult? Function()? alreadyConnected,TResult? Function( String field0)? unmapped,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case BridgeError_InvalidCommand() when invalidCommand != null:
|
||||
return invalidCommand(_that.field0);case BridgeError_Connection() when connection != null:
|
||||
return connection(_that.field0);case BridgeError_NotConnected() when notConnected != null:
|
||||
return notConnected();case BridgeError_AlreadyConnected() when alreadyConnected != null:
|
||||
return alreadyConnected();case BridgeError_Unmapped() when unmapped != null:
|
||||
return unmapped(_that.field0);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class BridgeError_InvalidCommand extends BridgeError {
|
||||
const BridgeError_InvalidCommand(this.field0): super._();
|
||||
|
||||
|
||||
final String field0;
|
||||
|
||||
/// Create a copy of BridgeError
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$BridgeError_InvalidCommandCopyWith<BridgeError_InvalidCommand> get copyWith => _$BridgeError_InvalidCommandCopyWithImpl<BridgeError_InvalidCommand>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is BridgeError_InvalidCommand&&(identical(other.field0, field0) || other.field0 == field0));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,field0);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BridgeError.invalidCommand(field0: $field0)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $BridgeError_InvalidCommandCopyWith<$Res> implements $BridgeErrorCopyWith<$Res> {
|
||||
factory $BridgeError_InvalidCommandCopyWith(BridgeError_InvalidCommand value, $Res Function(BridgeError_InvalidCommand) _then) = _$BridgeError_InvalidCommandCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String field0
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$BridgeError_InvalidCommandCopyWithImpl<$Res>
|
||||
implements $BridgeError_InvalidCommandCopyWith<$Res> {
|
||||
_$BridgeError_InvalidCommandCopyWithImpl(this._self, this._then);
|
||||
|
||||
final BridgeError_InvalidCommand _self;
|
||||
final $Res Function(BridgeError_InvalidCommand) _then;
|
||||
|
||||
/// Create a copy of BridgeError
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') $Res call({Object? field0 = null,}) {
|
||||
return _then(BridgeError_InvalidCommand(
|
||||
null == field0 ? _self.field0 : field0 // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class BridgeError_Connection extends BridgeError {
|
||||
const BridgeError_Connection(this.field0): super._();
|
||||
|
||||
|
||||
final String field0;
|
||||
|
||||
/// Create a copy of BridgeError
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$BridgeError_ConnectionCopyWith<BridgeError_Connection> get copyWith => _$BridgeError_ConnectionCopyWithImpl<BridgeError_Connection>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is BridgeError_Connection&&(identical(other.field0, field0) || other.field0 == field0));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,field0);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BridgeError.connection(field0: $field0)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $BridgeError_ConnectionCopyWith<$Res> implements $BridgeErrorCopyWith<$Res> {
|
||||
factory $BridgeError_ConnectionCopyWith(BridgeError_Connection value, $Res Function(BridgeError_Connection) _then) = _$BridgeError_ConnectionCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String field0
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$BridgeError_ConnectionCopyWithImpl<$Res>
|
||||
implements $BridgeError_ConnectionCopyWith<$Res> {
|
||||
_$BridgeError_ConnectionCopyWithImpl(this._self, this._then);
|
||||
|
||||
final BridgeError_Connection _self;
|
||||
final $Res Function(BridgeError_Connection) _then;
|
||||
|
||||
/// Create a copy of BridgeError
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') $Res call({Object? field0 = null,}) {
|
||||
return _then(BridgeError_Connection(
|
||||
null == field0 ? _self.field0 : field0 // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class BridgeError_NotConnected extends BridgeError {
|
||||
const BridgeError_NotConnected(): super._();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is BridgeError_NotConnected);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BridgeError.notConnected()';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class BridgeError_AlreadyConnected extends BridgeError {
|
||||
const BridgeError_AlreadyConnected(): super._();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is BridgeError_AlreadyConnected);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BridgeError.alreadyConnected()';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class BridgeError_Unmapped extends BridgeError {
|
||||
const BridgeError_Unmapped(this.field0): super._();
|
||||
|
||||
|
||||
final String field0;
|
||||
|
||||
/// Create a copy of BridgeError
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$BridgeError_UnmappedCopyWith<BridgeError_Unmapped> get copyWith => _$BridgeError_UnmappedCopyWithImpl<BridgeError_Unmapped>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is BridgeError_Unmapped&&(identical(other.field0, field0) || other.field0 == field0));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,field0);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BridgeError.unmapped(field0: $field0)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $BridgeError_UnmappedCopyWith<$Res> implements $BridgeErrorCopyWith<$Res> {
|
||||
factory $BridgeError_UnmappedCopyWith(BridgeError_Unmapped value, $Res Function(BridgeError_Unmapped) _then) = _$BridgeError_UnmappedCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String field0
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$BridgeError_UnmappedCopyWithImpl<$Res>
|
||||
implements $BridgeError_UnmappedCopyWith<$Res> {
|
||||
_$BridgeError_UnmappedCopyWithImpl(this._self, this._then);
|
||||
|
||||
final BridgeError_Unmapped _self;
|
||||
final $Res Function(BridgeError_Unmapped) _then;
|
||||
|
||||
/// Create a copy of BridgeError
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') $Res call({Object? field0 = null,}) {
|
||||
return _then(BridgeError_Unmapped(
|
||||
null == field0 ? _self.field0 : field0 // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -1,6 +1,30 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "93.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.1"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -17,6 +41,62 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
build:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.6"
|
||||
build_cli_annotations:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_cli_annotations
|
||||
sha256: e563c2e01de8974566a1998410d3f6f03521788160a02503b0b1f1a46c7b3d95
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.1"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.15.0"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_collection
|
||||
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
built_value:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.12.6"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -25,6 +105,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -41,6 +129,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.7"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -49,6 +153,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.9"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.7"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -57,6 +169,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@@ -75,11 +203,67 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_rust_bridge:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_rust_bridge
|
||||
sha256: e87d6b9ee934dcd24a128ccb2bd91905d2d5fe5c06245d6a8f5477d4907a437a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.12.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
freezed:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: freezed
|
||||
sha256: f23ea33b3863f119b58ed1b586e881a46bd28715ddcc4dbc33104524e3434131
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.5"
|
||||
freezed_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: freezed_annotation
|
||||
sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -88,6 +272,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.11.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -120,6 +320,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -144,6 +352,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -152,11 +376,59 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.2"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.2"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.3"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -181,6 +453,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_transform
|
||||
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -205,6 +485,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.10"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -221,6 +509,46 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.0"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket
|
||||
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.11.5 <4.0.0"
|
||||
flutter: ">=3.18.0-18.0.pre.54"
|
||||
|
||||
@@ -37,6 +37,8 @@ dependencies:
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.8
|
||||
flutter_rust_bridge: 2.12.0
|
||||
freezed_annotation: ^3.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -48,6 +50,8 @@ dev_dependencies:
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^6.0.0
|
||||
freezed: ^3.2.5
|
||||
build_runner: ^2.15.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// Alpha end-to-end verification test.
|
||||
//
|
||||
// Runs the actual FRB-loaded Rust bridge against cn.teamspeak.app.
|
||||
// This is the empirical evidence that the Alpha build's
|
||||
// connect → snapshot → disconnect cycle works through the full
|
||||
// Dart UI thread → flutter_rust_bridge → chanora_bridge cdylib →
|
||||
// chanora_core → chanora_protocol → tsclientlib → network
|
||||
// path.
|
||||
//
|
||||
// Tagged with the network-required marker so future CI can opt out.
|
||||
// For now it's just a regular flutter_test test.
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:chanora_flutter/src/rust/api.dart' as rust;
|
||||
import 'package:chanora_flutter/src/rust/frb_generated.dart';
|
||||
|
||||
void main() {
|
||||
setUpAll(() async {
|
||||
await RustLib.init();
|
||||
});
|
||||
|
||||
test('connect/snapshot/disconnect against cn.teamspeak.app', () async {
|
||||
// Defensive: in case a previous test left a connection open.
|
||||
try {
|
||||
await rust.disconnect();
|
||||
} catch (_) {}
|
||||
|
||||
final snap = await rust.connect(
|
||||
host: 'cn.teamspeak.app',
|
||||
nickname: 'ChanoraAlphaTest',
|
||||
);
|
||||
expect(snap.serverName, isNotEmpty);
|
||||
expect(snap.channels, isNotEmpty);
|
||||
// Welcome message is allowed to be empty on some servers; just
|
||||
// assert it's a String type (which it always is — this is more a
|
||||
// smoke than a real assertion).
|
||||
expect(snap.welcomeMessage, isA<String>());
|
||||
|
||||
// Re-fetch the snapshot; should still succeed.
|
||||
final snap2 = await rust.snapshot();
|
||||
expect(snap2.serverName, snap.serverName);
|
||||
|
||||
final connectedBefore = await rust.isConnected();
|
||||
expect(connectedBefore, isTrue);
|
||||
|
||||
await rust.disconnect();
|
||||
|
||||
final connectedAfter = await rust.isConnected();
|
||||
expect(connectedAfter, isFalse);
|
||||
}, timeout: const Timeout(Duration(seconds: 30)));
|
||||
}
|
||||
@@ -1,33 +1,40 @@
|
||||
// Smoke test for the scaffold app. Replaces the default counter test
|
||||
// from `flutter create`. Verifies the app builds and the localized
|
||||
// greeting is rendered.
|
||||
// Smoke test for the Alpha UI. Verifies the form renders and the
|
||||
// non-production-ready banner appears in both supported locales.
|
||||
//
|
||||
// Does NOT call into the FRB Rust side; that requires the cdylib at
|
||||
// runtime and is verified by the Linux desktop build + manual
|
||||
// connect flow (recorded in VERIFICATION).
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:chanora_flutter/l10n/generated/app_localizations.dart';
|
||||
import 'package:chanora_flutter/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('renders English greeting by default', (tester) async {
|
||||
await tester.pumpWidget(const ChanoraApp());
|
||||
testWidgets('renders English banner', (tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
localizationsDelegates: AppL10n.localizationsDelegates,
|
||||
supportedLocales: AppL10n.supportedLocales,
|
||||
home: Builder(builder: (ctx) {
|
||||
final l10n = AppL10n.of(ctx);
|
||||
return Scaffold(body: Text(l10n.homeNotProductionReadyBanner));
|
||||
}),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Welcome to Chanora'), findsOneWidget);
|
||||
expect(find.text('Chanora'), findsWidgets);
|
||||
expect(find.textContaining('Alpha build'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('renders Simplified Chinese greeting when locale is zh',
|
||||
(tester) async {
|
||||
testWidgets('renders Simplified Chinese banner', (tester) async {
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
locale: const Locale('zh'),
|
||||
localizationsDelegates: AppL10n.localizationsDelegates,
|
||||
supportedLocales: AppL10n.supportedLocales,
|
||||
home: Builder(builder: (ctx) {
|
||||
final l10n = AppL10n.of(ctx);
|
||||
return Scaffold(body: Text(l10n.homeGreeting));
|
||||
return Scaffold(body: Text(l10n.homeNotProductionReadyBanner));
|
||||
}),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('欢迎使用 Chanora'), findsOneWidget);
|
||||
expect(find.textContaining('Alpha 版本'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,6 +15,6 @@ chanora_state = { path = "../../crates/chanora_state" }
|
||||
chanora_audio = { path = "../../crates/chanora_audio" }
|
||||
chanora_storage = { path = "../../crates/chanora_storage" }
|
||||
chanora_diagnostics = { path = "../../crates/chanora_diagnostics" }
|
||||
chanora_bridge = { path = "../../crates/chanora_bridge" }
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
tokio = { version = "1", features = ["sync", "rt"] }
|
||||
|
||||
@@ -6,76 +6,114 @@
|
||||
//!
|
||||
//! `chanora_core` is the integration point. It owns no protocol,
|
||||
//! audio, or storage logic directly; instead it composes the
|
||||
//! subsystem crates ([`chanora_protocol`], [`chanora_state`],
|
||||
//! [`chanora_audio`], [`chanora_storage`], [`chanora_diagnostics`])
|
||||
//! behind a stable, typed API consumed by [`chanora_bridge`] (which
|
||||
//! in turn exposes it to Flutter via `flutter_rust_bridge`, per
|
||||
//! DEC-014).
|
||||
//! subsystem crates (`chanora_protocol`, `chanora_state`,
|
||||
//! `chanora_audio`, `chanora_storage`, `chanora_diagnostics`)
|
||||
//! behind a stable, typed API consumed by `chanora_bridge`.
|
||||
//!
|
||||
//! ## Invariants
|
||||
//!
|
||||
//! * Single active server connection at runtime (DEC-006 / SAD-064).
|
||||
//! * Protocol-specific types from `tsclientlib` do not cross out of
|
||||
//! [`chanora_protocol`] (SAD-067).
|
||||
//! * Secret material never lands in [`chanora_storage`]; secrets
|
||||
//! live in [`chanora_diagnostics`]'s `KnownSecretRegistry` *only*
|
||||
//! for redaction and in the platform secure-store (DEC-013.2).
|
||||
//! `chanora_protocol` (SAD-067).
|
||||
//! * Secret material never lands in `chanora_storage`'s non-secret
|
||||
//! side (DEC-013.2 / SS-AUD-001/002).
|
||||
//!
|
||||
//! ## Status
|
||||
//! ## Alpha scope
|
||||
//!
|
||||
//! This crate is a **scaffold**. No PoC code has been promoted in
|
||||
//! yet. The public surface below is the integration contract; bodies
|
||||
//! are placeholders.
|
||||
//! `ChanoraSession::connect`, `snapshot`, and `disconnect` are wired
|
||||
//! through `chanora_protocol`. Audio, storage, and diagnostics are
|
||||
//! still scaffolds.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use thiserror::Error;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub use chanora_protocol::{
|
||||
ChannelInfo, ClientInfo, ConnectConfig, ProtocolError, ServerSnapshot,
|
||||
};
|
||||
|
||||
/// Errors that can arise during top-level orchestration.
|
||||
///
|
||||
/// Each arm wraps a typed error from the subsystem that produced it,
|
||||
/// so callers can match on the originating layer without parsing
|
||||
/// strings. The variants are intentionally narrow at this stage and
|
||||
/// will expand as the subsystems land.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CoreError {
|
||||
/// Protocol-layer error originating from [`chanora_protocol`].
|
||||
/// Protocol-layer error.
|
||||
#[error("protocol: {0}")]
|
||||
Protocol(#[from] chanora_protocol::ProtocolError),
|
||||
/// State-synchronisation error from [`chanora_state`].
|
||||
/// State-synchronisation error.
|
||||
#[error("state: {0}")]
|
||||
State(#[from] chanora_state::StateError),
|
||||
/// Audio-subsystem error from [`chanora_audio`].
|
||||
/// Audio-subsystem error.
|
||||
#[error("audio: {0}")]
|
||||
Audio(#[from] chanora_audio::AudioError),
|
||||
/// Storage error from [`chanora_storage`].
|
||||
/// Storage error.
|
||||
#[error("storage: {0}")]
|
||||
Storage(#[from] chanora_storage::StorageError),
|
||||
/// Diagnostics error from [`chanora_diagnostics`].
|
||||
/// Diagnostics error.
|
||||
#[error("diagnostics: {0}")]
|
||||
Diagnostics(#[from] chanora_diagnostics::DiagnosticsError),
|
||||
/// Bridge / DTO error from [`chanora_bridge`].
|
||||
#[error("bridge: {0}")]
|
||||
Bridge(#[from] chanora_bridge::BridgeError),
|
||||
/// A precondition was violated (typically caller bug or
|
||||
/// concurrent misuse).
|
||||
#[error("invariant violated: {0}")]
|
||||
Invariant(&'static str),
|
||||
/// No active connection.
|
||||
#[error("not connected")]
|
||||
NotConnected,
|
||||
/// An attempt was made to start a second connection while one
|
||||
/// was already active (forbidden by DEC-006).
|
||||
#[error("already connected")]
|
||||
AlreadyConnected,
|
||||
}
|
||||
|
||||
/// The top-level Chanora session. Owns exactly one active server
|
||||
/// connection (DEC-006). Construction does **not** dial the server;
|
||||
/// see [`ChanoraSession::connect`] (scaffolded only).
|
||||
/// The top-level Chanora session. Owns at most one active server
|
||||
/// connection (DEC-006).
|
||||
#[derive(Clone)]
|
||||
pub struct ChanoraSession {
|
||||
_seal: (),
|
||||
inner: Arc<Mutex<Option<chanora_protocol::ProtocolClient>>>,
|
||||
}
|
||||
|
||||
impl ChanoraSession {
|
||||
/// Construct a new session with the default subsystem
|
||||
/// configurations. Performs no I/O.
|
||||
/// Construct an empty session. Performs no I/O.
|
||||
pub fn new() -> Self {
|
||||
Self { _seal: () }
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect to a server. Fails with [`CoreError::AlreadyConnected`]
|
||||
/// if a connection is already active (DEC-006).
|
||||
pub async fn connect(&self, cfg: ConnectConfig) -> Result<ServerSnapshot, CoreError> {
|
||||
let mut guard = self.inner.lock().await;
|
||||
if guard.is_some() {
|
||||
return Err(CoreError::AlreadyConnected);
|
||||
}
|
||||
let client = chanora_protocol::ProtocolClient::connect(cfg).await?;
|
||||
let snap = client.snapshot().await?;
|
||||
*guard = Some(client);
|
||||
Ok(snap)
|
||||
}
|
||||
|
||||
/// Return a fresh snapshot of the current server state.
|
||||
pub async fn snapshot(&self) -> Result<ServerSnapshot, CoreError> {
|
||||
let guard = self.inner.lock().await;
|
||||
let client = guard.as_ref().ok_or(CoreError::NotConnected)?;
|
||||
Ok(client.snapshot().await?)
|
||||
}
|
||||
|
||||
/// True if a connection is currently active.
|
||||
pub async fn is_connected(&self) -> bool {
|
||||
self.inner.lock().await.is_some()
|
||||
}
|
||||
|
||||
/// Disconnect from the server. No-op if not connected.
|
||||
pub async fn disconnect(&self) -> Result<(), CoreError> {
|
||||
let mut guard = self.inner.lock().await;
|
||||
if let Some(client) = guard.take() {
|
||||
client.disconnect().await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,4 +131,18 @@ mod tests {
|
||||
fn session_can_be_constructed() {
|
||||
let _ = ChanoraSession::new();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn disconnect_when_not_connected_is_noop() {
|
||||
let s = ChanoraSession::new();
|
||||
assert!(!s.is_connected().await);
|
||||
s.disconnect().await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn empty_address_is_rejected() {
|
||||
let s = ChanoraSession::new();
|
||||
let r = s.connect(ConnectConfig::default()).await;
|
||||
assert!(matches!(r, Err(CoreError::Protocol(ProtocolError::Invalid(_)))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
//! Live smoke test against cn.teamspeak.app. Tagged #[ignore] so
|
||||
//! `cargo test` does not hit the network by default. Run explicitly
|
||||
//! with: `cargo test -p chanora_core -- --ignored alpha_smoke`.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use chanora_core::{ChanoraSession, ConnectConfig};
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[ignore = "hits the network; run with --ignored"]
|
||||
async fn alpha_smoke() {
|
||||
let s = ChanoraSession::new();
|
||||
let cfg = ConnectConfig {
|
||||
address: "cn.teamspeak.app".to_string(),
|
||||
nickname: "ChanoraAlphaSmoke".to_string(),
|
||||
password: None,
|
||||
identity: None,
|
||||
ready_timeout: Duration::from_secs(15),
|
||||
};
|
||||
let snap = s.connect(cfg).await.expect("connect");
|
||||
println!(
|
||||
"snapshot: server='{}' channels={} clients={}",
|
||||
snap.server_name,
|
||||
snap.channels.len(),
|
||||
snap.clients.len()
|
||||
);
|
||||
assert!(!snap.server_name.is_empty());
|
||||
assert!(!snap.channels.is_empty());
|
||||
|
||||
s.disconnect().await.unwrap();
|
||||
assert!(!s.is_connected().await);
|
||||
}
|
||||
@@ -9,7 +9,21 @@ license.workspace = true
|
||||
repository.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[lib]
|
||||
# cdylib so the Flutter app can dlopen us via dart:ffi.
|
||||
# staticlib so iOS / static-link configurations remain possible later.
|
||||
# rlib so chanora_core and other Rust callers can use the public types.
|
||||
crate-type = ["cdylib", "staticlib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
chanora_core = { path = "../../core/chanora_core" }
|
||||
chanora_protocol = { path = "../chanora_protocol" }
|
||||
flutter_rust_bridge = "=2.12.0"
|
||||
thiserror.workspace = true
|
||||
serde.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(frb_expand)'] }
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
//! Public API exposed to Dart via `flutter_rust_bridge`.
|
||||
//!
|
||||
//! Naming follows the FRB v2 convention: free functions at the crate
|
||||
//! API root, with `#[frb(sync)]` for synchronous calls and async fn
|
||||
//! signatures for async ones. Every input and output is an owned
|
||||
//! type whose layout is schema-controlled (no `tsclientlib`, no
|
||||
//! `cpal`, no `Connection` handles).
|
||||
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
|
||||
use flutter_rust_bridge::frb;
|
||||
use tokio::runtime::Runtime;
|
||||
use tracing::info;
|
||||
|
||||
use crate::BridgeError;
|
||||
|
||||
/// Process-wide tokio runtime used to drive the async core. Created
|
||||
/// lazily on first use and never torn down — the application's
|
||||
/// process lifetime is the runtime's lifetime.
|
||||
fn runtime() -> &'static Runtime {
|
||||
static RT: OnceLock<Runtime> = OnceLock::new();
|
||||
RT.get_or_init(|| {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(2)
|
||||
.enable_all()
|
||||
.thread_name("chanora-rt")
|
||||
.build()
|
||||
.expect("tokio runtime")
|
||||
})
|
||||
}
|
||||
|
||||
/// Process-wide session handle. One instance per process is enough
|
||||
/// for Alpha (DEC-006 single-connection invariant); the Mutex inside
|
||||
/// `ChanoraSession` enforces the connection-count invariant.
|
||||
fn session() -> &'static chanora_core::ChanoraSession {
|
||||
static S: OnceLock<chanora_core::ChanoraSession> = OnceLock::new();
|
||||
S.get_or_init(chanora_core::ChanoraSession::new)
|
||||
}
|
||||
|
||||
// ---------- Bridge lifecycle ----------
|
||||
|
||||
/// Initialise the bridge. Must be called once on Dart side before
|
||||
/// any other API call. Sets up panic logging.
|
||||
#[frb(init)]
|
||||
pub fn bridge_init() {
|
||||
flutter_rust_bridge::setup_default_user_utils();
|
||||
let _ = tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
|
||||
)
|
||||
.with_target(true)
|
||||
.try_init();
|
||||
info!(target: "chanora_bridge", "bridge initialised");
|
||||
}
|
||||
|
||||
// ---------- DTOs ----------
|
||||
|
||||
/// Channel as seen by Dart. Matches `chanora_protocol::ChannelInfo`
|
||||
/// but with primitive `u64` ids so the Dart side gets `BigInt`s
|
||||
/// without any wrapper-type ceremony.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BridgeChannel {
|
||||
/// Stable channel id.
|
||||
pub id: u64,
|
||||
/// Parent channel id; 0 means top-level.
|
||||
pub parent: u64,
|
||||
/// Display name.
|
||||
pub name: String,
|
||||
/// Server-side ordering hint.
|
||||
pub order: i64,
|
||||
}
|
||||
|
||||
/// Client as seen by Dart.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BridgeClient {
|
||||
/// Stable client id.
|
||||
pub id: u64,
|
||||
/// Channel id the client is currently in.
|
||||
pub channel: u64,
|
||||
/// Nickname.
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Server snapshot as seen by Dart.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BridgeSnapshot {
|
||||
/// Server name.
|
||||
pub server_name: String,
|
||||
/// Welcome banner text.
|
||||
pub welcome_message: String,
|
||||
/// Server platform (e.g. "Linux").
|
||||
pub platform: String,
|
||||
/// Server version string.
|
||||
pub version: String,
|
||||
/// Channels currently known.
|
||||
pub channels: Vec<BridgeChannel>,
|
||||
/// Clients currently known.
|
||||
pub clients: Vec<BridgeClient>,
|
||||
}
|
||||
|
||||
impl From<chanora_protocol::ServerSnapshot> for BridgeSnapshot {
|
||||
fn from(s: chanora_protocol::ServerSnapshot) -> Self {
|
||||
Self {
|
||||
server_name: s.server_name,
|
||||
welcome_message: s.welcome_message,
|
||||
platform: s.platform,
|
||||
version: s.version,
|
||||
channels: s
|
||||
.channels
|
||||
.into_iter()
|
||||
.map(|c| BridgeChannel {
|
||||
id: c.id.0,
|
||||
parent: c.parent.0,
|
||||
name: c.name,
|
||||
order: c.order,
|
||||
})
|
||||
.collect(),
|
||||
clients: s
|
||||
.clients
|
||||
.into_iter()
|
||||
.map(|c| BridgeClient {
|
||||
id: c.id.0,
|
||||
channel: c.channel.0,
|
||||
name: c.name,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Commands ----------
|
||||
|
||||
/// Connect to a TeamSpeak-compatible server and return the initial
|
||||
/// state snapshot. Honours the DEC-006 single-connection invariant
|
||||
/// via [`BridgeError::AlreadyConnected`].
|
||||
pub async fn connect(host: String, nickname: String) -> Result<BridgeSnapshot, BridgeError> {
|
||||
let cfg = chanora_core::ConnectConfig {
|
||||
address: host,
|
||||
nickname,
|
||||
password: None,
|
||||
identity: None,
|
||||
ready_timeout: Duration::from_secs(15),
|
||||
};
|
||||
let snap = runtime()
|
||||
.spawn(async move { session().connect(cfg).await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
Ok(snap.into())
|
||||
}
|
||||
|
||||
/// Re-fetch a fresh snapshot from the active connection.
|
||||
pub async fn snapshot() -> Result<BridgeSnapshot, BridgeError> {
|
||||
let snap = runtime()
|
||||
.spawn(async { session().snapshot().await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
Ok(snap.into())
|
||||
}
|
||||
|
||||
/// Disconnect from the server. No-op if not connected.
|
||||
pub async fn disconnect() -> Result<(), BridgeError> {
|
||||
runtime()
|
||||
.spawn(async { session().disconnect().await })
|
||||
.await
|
||||
.map_err(|e| BridgeError::Unmapped(format!("join: {e}")))??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// True if a connection is currently active.
|
||||
pub async fn is_connected() -> bool {
|
||||
runtime()
|
||||
.spawn(async { session().is_connected().await })
|
||||
.await
|
||||
.unwrap_or(false)
|
||||
}
|
||||
@@ -0,0 +1,687 @@
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||
|
||||
#![allow(
|
||||
non_camel_case_types,
|
||||
unused,
|
||||
non_snake_case,
|
||||
clippy::needless_return,
|
||||
clippy::redundant_closure_call,
|
||||
clippy::redundant_closure,
|
||||
clippy::useless_conversion,
|
||||
clippy::unit_arg,
|
||||
clippy::unused_unit,
|
||||
clippy::double_parens,
|
||||
clippy::let_and_return,
|
||||
clippy::too_many_arguments,
|
||||
clippy::match_single_binding,
|
||||
clippy::clone_on_copy,
|
||||
clippy::let_unit_value,
|
||||
clippy::deref_addrof,
|
||||
clippy::explicit_auto_deref,
|
||||
clippy::borrow_deref_ref,
|
||||
clippy::uninlined_format_args,
|
||||
clippy::needless_borrow
|
||||
)]
|
||||
|
||||
// Section: imports
|
||||
|
||||
use flutter_rust_bridge::for_generated::byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt};
|
||||
use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable};
|
||||
use flutter_rust_bridge::{Handler, IntoIntoDart};
|
||||
|
||||
// Section: boilerplate
|
||||
|
||||
flutter_rust_bridge::frb_generated_boilerplate!(
|
||||
default_stream_sink_codec = SseCodec,
|
||||
default_rust_opaque = RustOpaqueMoi,
|
||||
default_rust_auto_opaque = RustAutoOpaqueMoi,
|
||||
);
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0";
|
||||
pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 978717843;
|
||||
|
||||
// Section: executor
|
||||
|
||||
flutter_rust_bridge::frb_generated_default_handler!();
|
||||
|
||||
// Section: wire_funcs
|
||||
|
||||
fn wire__crate__api__bridge_init_impl(
|
||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||
rust_vec_len_: i32,
|
||||
data_len_: i32,
|
||||
) {
|
||||
FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::<flutter_rust_bridge::for_generated::SseCodec, _, _>(
|
||||
flutter_rust_bridge::for_generated::TaskInfo {
|
||||
debug_name: "bridge_init",
|
||||
port: Some(port_),
|
||||
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
|
||||
},
|
||||
move || {
|
||||
let message = unsafe {
|
||||
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
|
||||
ptr_,
|
||||
rust_vec_len_,
|
||||
data_len_,
|
||||
)
|
||||
};
|
||||
let mut deserializer =
|
||||
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||
deserializer.end();
|
||||
move |context| {
|
||||
transform_result_sse::<_, ()>((move || {
|
||||
let output_ok = Result::<_, ()>::Ok({
|
||||
crate::api::bridge_init();
|
||||
})?;
|
||||
Ok(output_ok)
|
||||
})())
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
fn wire__crate__api__connect_impl(
|
||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||
rust_vec_len_: i32,
|
||||
data_len_: i32,
|
||||
) {
|
||||
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec, _, _, _>(
|
||||
flutter_rust_bridge::for_generated::TaskInfo {
|
||||
debug_name: "connect",
|
||||
port: Some(port_),
|
||||
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
|
||||
},
|
||||
move || {
|
||||
let message = unsafe {
|
||||
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
|
||||
ptr_,
|
||||
rust_vec_len_,
|
||||
data_len_,
|
||||
)
|
||||
};
|
||||
let mut deserializer =
|
||||
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||
let api_host = <String>::sse_decode(&mut deserializer);
|
||||
let api_nickname = <String>::sse_decode(&mut deserializer);
|
||||
deserializer.end();
|
||||
move |context| async move {
|
||||
transform_result_sse::<_, crate::BridgeError>(
|
||||
(move || async move {
|
||||
let output_ok = crate::api::connect(api_host, api_nickname).await?;
|
||||
Ok(output_ok)
|
||||
})()
|
||||
.await,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
fn wire__crate__api__disconnect_impl(
|
||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||
rust_vec_len_: i32,
|
||||
data_len_: i32,
|
||||
) {
|
||||
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec, _, _, _>(
|
||||
flutter_rust_bridge::for_generated::TaskInfo {
|
||||
debug_name: "disconnect",
|
||||
port: Some(port_),
|
||||
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
|
||||
},
|
||||
move || {
|
||||
let message = unsafe {
|
||||
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
|
||||
ptr_,
|
||||
rust_vec_len_,
|
||||
data_len_,
|
||||
)
|
||||
};
|
||||
let mut deserializer =
|
||||
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||
deserializer.end();
|
||||
move |context| async move {
|
||||
transform_result_sse::<_, crate::BridgeError>(
|
||||
(move || async move {
|
||||
let output_ok = crate::api::disconnect().await?;
|
||||
Ok(output_ok)
|
||||
})()
|
||||
.await,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
fn wire__crate__api__is_connected_impl(
|
||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||
rust_vec_len_: i32,
|
||||
data_len_: i32,
|
||||
) {
|
||||
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec, _, _, _>(
|
||||
flutter_rust_bridge::for_generated::TaskInfo {
|
||||
debug_name: "is_connected",
|
||||
port: Some(port_),
|
||||
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
|
||||
},
|
||||
move || {
|
||||
let message = unsafe {
|
||||
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
|
||||
ptr_,
|
||||
rust_vec_len_,
|
||||
data_len_,
|
||||
)
|
||||
};
|
||||
let mut deserializer =
|
||||
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||
deserializer.end();
|
||||
move |context| async move {
|
||||
transform_result_sse::<_, ()>(
|
||||
(move || async move {
|
||||
let output_ok = Result::<_, ()>::Ok(crate::api::is_connected().await)?;
|
||||
Ok(output_ok)
|
||||
})()
|
||||
.await,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
fn wire__crate__api__snapshot_impl(
|
||||
port_: flutter_rust_bridge::for_generated::MessagePort,
|
||||
ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||
rust_vec_len_: i32,
|
||||
data_len_: i32,
|
||||
) {
|
||||
FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::<flutter_rust_bridge::for_generated::SseCodec, _, _, _>(
|
||||
flutter_rust_bridge::for_generated::TaskInfo {
|
||||
debug_name: "snapshot",
|
||||
port: Some(port_),
|
||||
mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal,
|
||||
},
|
||||
move || {
|
||||
let message = unsafe {
|
||||
flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire(
|
||||
ptr_,
|
||||
rust_vec_len_,
|
||||
data_len_,
|
||||
)
|
||||
};
|
||||
let mut deserializer =
|
||||
flutter_rust_bridge::for_generated::SseDeserializer::new(message);
|
||||
deserializer.end();
|
||||
move |context| async move {
|
||||
transform_result_sse::<_, crate::BridgeError>(
|
||||
(move || async move {
|
||||
let output_ok = crate::api::snapshot().await?;
|
||||
Ok(output_ok)
|
||||
})()
|
||||
.await,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Section: dart2rust
|
||||
|
||||
impl SseDecode for String {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
let mut inner = <Vec<u8>>::sse_decode(deserializer);
|
||||
return String::from_utf8(inner).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for bool {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
deserializer.cursor.read_u8().unwrap() != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for crate::api::BridgeChannel {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
let mut var_id = <u64>::sse_decode(deserializer);
|
||||
let mut var_parent = <u64>::sse_decode(deserializer);
|
||||
let mut var_name = <String>::sse_decode(deserializer);
|
||||
let mut var_order = <i64>::sse_decode(deserializer);
|
||||
return crate::api::BridgeChannel {
|
||||
id: var_id,
|
||||
parent: var_parent,
|
||||
name: var_name,
|
||||
order: var_order,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for crate::api::BridgeClient {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
let mut var_id = <u64>::sse_decode(deserializer);
|
||||
let mut var_channel = <u64>::sse_decode(deserializer);
|
||||
let mut var_name = <String>::sse_decode(deserializer);
|
||||
return crate::api::BridgeClient {
|
||||
id: var_id,
|
||||
channel: var_channel,
|
||||
name: var_name,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for crate::BridgeError {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
let mut tag_ = <i32>::sse_decode(deserializer);
|
||||
match tag_ {
|
||||
0 => {
|
||||
let mut var_field0 = <String>::sse_decode(deserializer);
|
||||
return crate::BridgeError::InvalidCommand(var_field0);
|
||||
}
|
||||
1 => {
|
||||
let mut var_field0 = <String>::sse_decode(deserializer);
|
||||
return crate::BridgeError::Connection(var_field0);
|
||||
}
|
||||
2 => {
|
||||
return crate::BridgeError::NotConnected;
|
||||
}
|
||||
3 => {
|
||||
return crate::BridgeError::AlreadyConnected;
|
||||
}
|
||||
4 => {
|
||||
let mut var_field0 = <String>::sse_decode(deserializer);
|
||||
return crate::BridgeError::Unmapped(var_field0);
|
||||
}
|
||||
_ => {
|
||||
unimplemented!("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for crate::api::BridgeSnapshot {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
let mut var_serverName = <String>::sse_decode(deserializer);
|
||||
let mut var_welcomeMessage = <String>::sse_decode(deserializer);
|
||||
let mut var_platform = <String>::sse_decode(deserializer);
|
||||
let mut var_version = <String>::sse_decode(deserializer);
|
||||
let mut var_channels = <Vec<crate::api::BridgeChannel>>::sse_decode(deserializer);
|
||||
let mut var_clients = <Vec<crate::api::BridgeClient>>::sse_decode(deserializer);
|
||||
return crate::api::BridgeSnapshot {
|
||||
server_name: var_serverName,
|
||||
welcome_message: var_welcomeMessage,
|
||||
platform: var_platform,
|
||||
version: var_version,
|
||||
channels: var_channels,
|
||||
clients: var_clients,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for i64 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
deserializer.cursor.read_i64::<NativeEndian>().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for Vec<crate::api::BridgeChannel> {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
let mut len_ = <i32>::sse_decode(deserializer);
|
||||
let mut ans_ = Vec::with_capacity(len_ as usize);
|
||||
for idx_ in 0..len_ {
|
||||
ans_.push(<crate::api::BridgeChannel>::sse_decode(deserializer));
|
||||
}
|
||||
return ans_;
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for Vec<crate::api::BridgeClient> {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
let mut len_ = <i32>::sse_decode(deserializer);
|
||||
let mut ans_ = Vec::with_capacity(len_ as usize);
|
||||
for idx_ in 0..len_ {
|
||||
ans_.push(<crate::api::BridgeClient>::sse_decode(deserializer));
|
||||
}
|
||||
return ans_;
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for Vec<u8> {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
let mut len_ = <i32>::sse_decode(deserializer);
|
||||
let mut ans_ = Vec::with_capacity(len_ as usize);
|
||||
for idx_ in 0..len_ {
|
||||
ans_.push(<u8>::sse_decode(deserializer));
|
||||
}
|
||||
return ans_;
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for u64 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
deserializer.cursor.read_u64::<NativeEndian>().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for u8 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
deserializer.cursor.read_u8().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl SseDecode for () {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {}
|
||||
}
|
||||
|
||||
impl SseDecode for i32 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {
|
||||
deserializer.cursor.read_i32::<NativeEndian>().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn pde_ffi_dispatcher_primary_impl(
|
||||
func_id: i32,
|
||||
port: flutter_rust_bridge::for_generated::MessagePort,
|
||||
ptr: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||
rust_vec_len: i32,
|
||||
data_len: i32,
|
||||
) {
|
||||
// Codec=Pde (Serialization + dispatch), see doc to use other codecs
|
||||
match func_id {
|
||||
1 => wire__crate__api__bridge_init_impl(port, ptr, rust_vec_len, data_len),
|
||||
2 => wire__crate__api__connect_impl(port, ptr, rust_vec_len, data_len),
|
||||
3 => wire__crate__api__disconnect_impl(port, ptr, rust_vec_len, data_len),
|
||||
4 => wire__crate__api__is_connected_impl(port, ptr, rust_vec_len, data_len),
|
||||
5 => wire__crate__api__snapshot_impl(port, ptr, rust_vec_len, data_len),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn pde_ffi_dispatcher_sync_impl(
|
||||
func_id: i32,
|
||||
ptr: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr,
|
||||
rust_vec_len: i32,
|
||||
data_len: i32,
|
||||
) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse {
|
||||
// Codec=Pde (Serialization + dispatch), see doc to use other codecs
|
||||
match func_id {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// Section: rust2dart
|
||||
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
impl flutter_rust_bridge::IntoDart for crate::api::BridgeChannel {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
[
|
||||
self.id.into_into_dart().into_dart(),
|
||||
self.parent.into_into_dart().into_dart(),
|
||||
self.name.into_into_dart().into_dart(),
|
||||
self.order.into_into_dart().into_dart(),
|
||||
]
|
||||
.into_dart()
|
||||
}
|
||||
}
|
||||
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::api::BridgeChannel {}
|
||||
impl flutter_rust_bridge::IntoIntoDart<crate::api::BridgeChannel> for crate::api::BridgeChannel {
|
||||
fn into_into_dart(self) -> crate::api::BridgeChannel {
|
||||
self
|
||||
}
|
||||
}
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
impl flutter_rust_bridge::IntoDart for crate::api::BridgeClient {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
[
|
||||
self.id.into_into_dart().into_dart(),
|
||||
self.channel.into_into_dart().into_dart(),
|
||||
self.name.into_into_dart().into_dart(),
|
||||
]
|
||||
.into_dart()
|
||||
}
|
||||
}
|
||||
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::api::BridgeClient {}
|
||||
impl flutter_rust_bridge::IntoIntoDart<crate::api::BridgeClient> for crate::api::BridgeClient {
|
||||
fn into_into_dart(self) -> crate::api::BridgeClient {
|
||||
self
|
||||
}
|
||||
}
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
impl flutter_rust_bridge::IntoDart for crate::BridgeError {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
match self {
|
||||
crate::BridgeError::InvalidCommand(field0) => {
|
||||
[0.into_dart(), field0.into_into_dart().into_dart()].into_dart()
|
||||
}
|
||||
crate::BridgeError::Connection(field0) => {
|
||||
[1.into_dart(), field0.into_into_dart().into_dart()].into_dart()
|
||||
}
|
||||
crate::BridgeError::NotConnected => [2.into_dart()].into_dart(),
|
||||
crate::BridgeError::AlreadyConnected => [3.into_dart()].into_dart(),
|
||||
crate::BridgeError::Unmapped(field0) => {
|
||||
[4.into_dart(), field0.into_into_dart().into_dart()].into_dart()
|
||||
}
|
||||
_ => {
|
||||
unimplemented!("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::BridgeError {}
|
||||
impl flutter_rust_bridge::IntoIntoDart<crate::BridgeError> for crate::BridgeError {
|
||||
fn into_into_dart(self) -> crate::BridgeError {
|
||||
self
|
||||
}
|
||||
}
|
||||
// Codec=Dco (DartCObject based), see doc to use other codecs
|
||||
impl flutter_rust_bridge::IntoDart for crate::api::BridgeSnapshot {
|
||||
fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi {
|
||||
[
|
||||
self.server_name.into_into_dart().into_dart(),
|
||||
self.welcome_message.into_into_dart().into_dart(),
|
||||
self.platform.into_into_dart().into_dart(),
|
||||
self.version.into_into_dart().into_dart(),
|
||||
self.channels.into_into_dart().into_dart(),
|
||||
self.clients.into_into_dart().into_dart(),
|
||||
]
|
||||
.into_dart()
|
||||
}
|
||||
}
|
||||
impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::api::BridgeSnapshot {}
|
||||
impl flutter_rust_bridge::IntoIntoDart<crate::api::BridgeSnapshot> for crate::api::BridgeSnapshot {
|
||||
fn into_into_dart(self) -> crate::api::BridgeSnapshot {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for String {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
<Vec<u8>>::sse_encode(self.into_bytes(), serializer);
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for bool {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
serializer.cursor.write_u8(self as _).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for crate::api::BridgeChannel {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
<u64>::sse_encode(self.id, serializer);
|
||||
<u64>::sse_encode(self.parent, serializer);
|
||||
<String>::sse_encode(self.name, serializer);
|
||||
<i64>::sse_encode(self.order, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for crate::api::BridgeClient {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
<u64>::sse_encode(self.id, serializer);
|
||||
<u64>::sse_encode(self.channel, serializer);
|
||||
<String>::sse_encode(self.name, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for crate::BridgeError {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
match self {
|
||||
crate::BridgeError::InvalidCommand(field0) => {
|
||||
<i32>::sse_encode(0, serializer);
|
||||
<String>::sse_encode(field0, serializer);
|
||||
}
|
||||
crate::BridgeError::Connection(field0) => {
|
||||
<i32>::sse_encode(1, serializer);
|
||||
<String>::sse_encode(field0, serializer);
|
||||
}
|
||||
crate::BridgeError::NotConnected => {
|
||||
<i32>::sse_encode(2, serializer);
|
||||
}
|
||||
crate::BridgeError::AlreadyConnected => {
|
||||
<i32>::sse_encode(3, serializer);
|
||||
}
|
||||
crate::BridgeError::Unmapped(field0) => {
|
||||
<i32>::sse_encode(4, serializer);
|
||||
<String>::sse_encode(field0, serializer);
|
||||
}
|
||||
_ => {
|
||||
unimplemented!("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for crate::api::BridgeSnapshot {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
<String>::sse_encode(self.server_name, serializer);
|
||||
<String>::sse_encode(self.welcome_message, serializer);
|
||||
<String>::sse_encode(self.platform, serializer);
|
||||
<String>::sse_encode(self.version, serializer);
|
||||
<Vec<crate::api::BridgeChannel>>::sse_encode(self.channels, serializer);
|
||||
<Vec<crate::api::BridgeClient>>::sse_encode(self.clients, serializer);
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for i64 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
serializer.cursor.write_i64::<NativeEndian>(self).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for Vec<crate::api::BridgeChannel> {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
<i32>::sse_encode(self.len() as _, serializer);
|
||||
for item in self {
|
||||
<crate::api::BridgeChannel>::sse_encode(item, serializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for Vec<crate::api::BridgeClient> {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
<i32>::sse_encode(self.len() as _, serializer);
|
||||
for item in self {
|
||||
<crate::api::BridgeClient>::sse_encode(item, serializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for Vec<u8> {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
<i32>::sse_encode(self.len() as _, serializer);
|
||||
for item in self {
|
||||
<u8>::sse_encode(item, serializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for u64 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
serializer.cursor.write_u64::<NativeEndian>(self).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for u8 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
serializer.cursor.write_u8(self).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl SseEncode for () {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {}
|
||||
}
|
||||
|
||||
impl SseEncode for i32 {
|
||||
// Codec=Sse (Serialization based), see doc to use other codecs
|
||||
fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {
|
||||
serializer.cursor.write_i32::<NativeEndian>(self).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
mod io {
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||
|
||||
// Section: imports
|
||||
|
||||
use super::*;
|
||||
use flutter_rust_bridge::for_generated::byteorder::{
|
||||
NativeEndian, ReadBytesExt, WriteBytesExt,
|
||||
};
|
||||
use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable};
|
||||
use flutter_rust_bridge::{Handler, IntoIntoDart};
|
||||
|
||||
// Section: boilerplate
|
||||
|
||||
flutter_rust_bridge::frb_generated_boilerplate_io!();
|
||||
}
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub use io::*;
|
||||
|
||||
/// cbindgen:ignore
|
||||
#[cfg(target_family = "wasm")]
|
||||
mod web {
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.12.0.
|
||||
|
||||
// Section: imports
|
||||
|
||||
use super::*;
|
||||
use flutter_rust_bridge::for_generated::byteorder::{
|
||||
NativeEndian, ReadBytesExt, WriteBytesExt,
|
||||
};
|
||||
use flutter_rust_bridge::for_generated::wasm_bindgen;
|
||||
use flutter_rust_bridge::for_generated::wasm_bindgen::prelude::*;
|
||||
use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable};
|
||||
use flutter_rust_bridge::{Handler, IntoIntoDart};
|
||||
|
||||
// Section: boilerplate
|
||||
|
||||
flutter_rust_bridge::frb_generated_boilerplate_web!();
|
||||
}
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub use web::*;
|
||||
@@ -1,42 +1,73 @@
|
||||
//! # `chanora_bridge`
|
||||
//!
|
||||
//! Typed Flutter/Rust bridge. Owns the canonical DTO catalogue for
|
||||
//! commands (Dart → Rust), results (Rust → Dart return values), and
|
||||
//! events (Rust → Dart streams).
|
||||
//! Typed Flutter/Rust bridge — schema-controlled DTOs for commands,
|
||||
//! results, and events. Backed by `flutter_rust_bridge` 2.x per
|
||||
//! DEC-014.
|
||||
//!
|
||||
//! Bridge tool per DEC-014: `flutter_rust_bridge` 2.x. The bridge
|
||||
//! integration glue (codegen invocations, the cdylib boundary) is
|
||||
//! added when `apps/chanora_flutter` is wired up; this crate owns
|
||||
//! only the type definitions and error mappings — explicitly *not*
|
||||
//! `tsclientlib` or raw subsystem types (SAD-067, SDD-079).
|
||||
//! ## Alpha scope
|
||||
//!
|
||||
//! ## Status
|
||||
//! Exposes three commands that target the Alpha release goal:
|
||||
//!
|
||||
//! Scaffold only.
|
||||
//! * `bridge_init()` — one-time process initialisation. Sets up
|
||||
//! logging.
|
||||
//! * `connect(host, nickname)` — connects to a TS3-compatible
|
||||
//! server and returns a typed [`ConnectResultDto`] containing the
|
||||
//! server snapshot.
|
||||
//! * `disconnect()` — clean disconnect.
|
||||
//! * `snapshot()` — re-fetch the current server snapshot.
|
||||
//!
|
||||
//! No audio commands cross the bridge in Alpha; audio is Beta scope.
|
||||
//!
|
||||
//! ## Boundary discipline
|
||||
//!
|
||||
//! Every type in this module is `Serialize + Deserialize` over owned
|
||||
//! primitives or `String`s. No `tsclientlib`, `cpal`, or backend
|
||||
//! types may appear in the public surface (SAD-067, SDD-079).
|
||||
//!
|
||||
//! Note: this crate cannot use `#![forbid(unsafe_code)]` because the
|
||||
//! FRB-generated glue (in `frb_generated`) legitimately uses unsafe
|
||||
//! for the FFI boundary. Hand-written code in this crate must
|
||||
//! nonetheless avoid `unsafe` and is held to that standard by review.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub mod api;
|
||||
mod frb_generated;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors raised at the bridge boundary. Production code must keep
|
||||
/// `BridgeError` user-safe — no secrets, no protocol details, no
|
||||
/// path information beyond what the redaction policy permits.
|
||||
#[derive(Debug, Error, Clone, Serialize, Deserialize)]
|
||||
/// these user-safe — no secrets, no protocol details, no path
|
||||
/// information beyond what the redaction policy permits.
|
||||
#[derive(Debug, Error, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum BridgeError {
|
||||
/// The caller submitted a malformed command DTO.
|
||||
#[error("invalid command: {0}")]
|
||||
InvalidCommand(String),
|
||||
/// An unknown / unmapped error escaped the subsystem boundary.
|
||||
/// Production callers should never see this; if it appears it is
|
||||
/// a mapping bug in this crate.
|
||||
/// Connection layer failure (typed-mapped from CoreError).
|
||||
#[error("connection: {0}")]
|
||||
Connection(String),
|
||||
/// Operation requires an active connection.
|
||||
#[error("not connected")]
|
||||
NotConnected,
|
||||
/// Already connected; DEC-006 forbids a second concurrent
|
||||
/// connection.
|
||||
#[error("already connected")]
|
||||
AlreadyConnected,
|
||||
/// An unmapped error escaped the subsystem boundary. Production
|
||||
/// callers should never see this; if they do, it is a mapping
|
||||
/// bug here.
|
||||
#[error("unmapped: {0}")]
|
||||
Unmapped(String),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_compiles() {}
|
||||
impl From<chanora_core::CoreError> for BridgeError {
|
||||
fn from(e: chanora_core::CoreError) -> Self {
|
||||
match e {
|
||||
chanora_core::CoreError::NotConnected => BridgeError::NotConnected,
|
||||
chanora_core::CoreError::AlreadyConnected => BridgeError::AlreadyConnected,
|
||||
chanora_core::CoreError::Protocol(p) => BridgeError::Connection(format!("{p}")),
|
||||
other => BridgeError::Unmapped(format!("{other}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,3 +12,14 @@ publish.workspace = true
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
# tsclientlib is git-only and not on crates.io. Audio feature disabled
|
||||
# because chanora_audio owns audio paths; the protocol crate only
|
||||
# handles connection lifecycle + state book events.
|
||||
tsclientlib = { git = "https://github.com/ReSpeak/tsclientlib.git", rev = "04aa2491", default-features = false, features = ["default-tls"] }
|
||||
|
||||
# Async runtime utilities used by the connection task.
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time", "sync"] }
|
||||
futures = "0.3"
|
||||
async-trait = "0.1"
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
//! The adapter that drives `tsclientlib` on a background task and
|
||||
//! exposes a typed channel-and-future API to the rest of Chanora.
|
||||
//!
|
||||
//! Threading model:
|
||||
//!
|
||||
//! * `ProtocolClient::connect` spawns a tokio task that owns the
|
||||
//! `tsclientlib::Connection` (which is not `Send`-safe to move
|
||||
//! across awaits in some shapes — keeping it inside a single task
|
||||
//! sidesteps the problem entirely).
|
||||
//! * The task exposes its life via a `oneshot` that fires when the
|
||||
//! initial state snapshot is ready.
|
||||
//! * Snapshot reads are served by sending a request over an
|
||||
//! `mpsc::channel`; the task replies on a `oneshot` per request.
|
||||
//! * Disconnect is requested via a `oneshot`; the task drains
|
||||
//! `tsclientlib`'s outbound events and exits.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::prelude::*;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use tsclientlib::data::{self, Channel, Client};
|
||||
use tsclientlib::{
|
||||
ChannelId as TsChannelId, Connection, DisconnectOptions, Identity, OutCommandExt, StreamItem,
|
||||
};
|
||||
|
||||
use crate::dto::{ChannelId, ChannelInfo, ClientId, ClientInfo, ServerSnapshot};
|
||||
use crate::ProtocolError;
|
||||
|
||||
/// Typed configuration for a connection attempt.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConnectConfig {
|
||||
/// Server address: `hostname[:port]` or TSDNS name.
|
||||
pub address: String,
|
||||
/// Nickname to use on the server.
|
||||
pub nickname: String,
|
||||
/// Optional server password.
|
||||
pub password: Option<String>,
|
||||
/// Optional pre-existing identity (base64 string accepted by
|
||||
/// `tsclientlib::Identity::new_from_str`). If `None`, a fresh
|
||||
/// identity is generated and **not persisted** — production
|
||||
/// callers must wire this to `chanora_storage::SecretStorageRepository`.
|
||||
pub identity: Option<String>,
|
||||
/// How long to wait for the initial state snapshot before
|
||||
/// returning `ProtocolError::Timeout`.
|
||||
pub ready_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Default for ConnectConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
address: String::new(),
|
||||
nickname: "Chanora".to_string(),
|
||||
password: None,
|
||||
identity: None,
|
||||
ready_timeout: Duration::from_secs(10),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Request {
|
||||
Snapshot(oneshot::Sender<Result<ServerSnapshot, ProtocolError>>),
|
||||
Disconnect(oneshot::Sender<()>),
|
||||
}
|
||||
|
||||
/// Async handle owning a live protocol connection. Drop = disconnect.
|
||||
pub struct ProtocolClient {
|
||||
tx: mpsc::Sender<Request>,
|
||||
}
|
||||
|
||||
impl ProtocolClient {
|
||||
/// Dial the server and wait for the initial state snapshot. The
|
||||
/// returned client is ready for [`Self::snapshot`] and
|
||||
/// [`Self::disconnect`] calls.
|
||||
pub async fn connect(cfg: ConnectConfig) -> Result<Self, ProtocolError> {
|
||||
if cfg.address.trim().is_empty() {
|
||||
return Err(ProtocolError::Invalid("address is empty".to_string()));
|
||||
}
|
||||
if cfg.nickname.trim().is_empty() {
|
||||
return Err(ProtocolError::Invalid("nickname is empty".to_string()));
|
||||
}
|
||||
|
||||
let (tx, rx) = mpsc::channel::<Request>(8);
|
||||
let (ready_tx, ready_rx) = oneshot::channel::<Result<(), ProtocolError>>();
|
||||
|
||||
tokio::spawn(connection_task(cfg.clone(), rx, ready_tx));
|
||||
|
||||
match tokio::time::timeout(cfg.ready_timeout, ready_rx).await {
|
||||
Ok(Ok(Ok(()))) => Ok(Self { tx }),
|
||||
Ok(Ok(Err(e))) => Err(e),
|
||||
Ok(Err(_)) => Err(ProtocolError::Backend(
|
||||
"connection task exited before signalling ready".to_string(),
|
||||
)),
|
||||
Err(_) => Err(ProtocolError::Timeout),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a typed snapshot of the current server state.
|
||||
pub async fn snapshot(&self) -> Result<ServerSnapshot, ProtocolError> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.tx
|
||||
.send(Request::Snapshot(tx))
|
||||
.await
|
||||
.map_err(|_| ProtocolError::Lost("connection task is gone".to_string()))?;
|
||||
rx.await
|
||||
.map_err(|_| ProtocolError::Lost("snapshot reply dropped".to_string()))?
|
||||
}
|
||||
|
||||
/// Disconnect cleanly. Blocks until the task exits.
|
||||
pub async fn disconnect(self) {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
if self.tx.send(Request::Disconnect(tx)).await.is_ok() {
|
||||
let _ = rx.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn connection_task(
|
||||
cfg: ConnectConfig,
|
||||
mut rx: mpsc::Receiver<Request>,
|
||||
ready_tx: oneshot::Sender<Result<(), ProtocolError>>,
|
||||
) {
|
||||
let mut builder = Connection::build(cfg.address.clone()).name(cfg.nickname.clone());
|
||||
|
||||
let identity = match cfg.identity.as_deref() {
|
||||
Some(s) => match Identity::new_from_str(s) {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
let _ = ready_tx.send(Err(ProtocolError::Identity(format!("{e}"))));
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => Identity::create(),
|
||||
};
|
||||
builder = builder.identity(identity);
|
||||
|
||||
if let Some(pw) = &cfg.password {
|
||||
builder = builder.password(pw.clone());
|
||||
}
|
||||
|
||||
let mut con = match builder.connect() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
let _ = ready_tx.send(Err(ProtocolError::Connect(format!("{e}"))));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Wait for the first BookEvents indicating the state snapshot is ready.
|
||||
let first = con
|
||||
.events()
|
||||
.try_filter(|e| future::ready(matches!(e, StreamItem::BookEvents(_))))
|
||||
.next()
|
||||
.await;
|
||||
match first {
|
||||
Some(Ok(_)) => {
|
||||
info!(target: "chanora_protocol", "initial state snapshot received");
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
let _ = ready_tx.send(Err(ProtocolError::DisconnectedEarly(format!("{e}"))));
|
||||
return;
|
||||
}
|
||||
None => {
|
||||
let _ = ready_tx.send(Err(ProtocolError::DisconnectedEarly(
|
||||
"event stream ended before snapshot".to_string(),
|
||||
)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to the full server tree so snapshot() returns more than just our channel.
|
||||
if let Ok(state) = con.get_state() {
|
||||
if let Err(e) = state.server.set_subscribed(true).send(&mut con) {
|
||||
warn!(target: "chanora_protocol", error = %e, "could not subscribe to server tree");
|
||||
}
|
||||
}
|
||||
|
||||
// Settle: pump events for ~2 s so the subscribed tree arrives
|
||||
// before the first snapshot. The upstream packet codec emits
|
||||
// out-of-order command-packet warnings here; they are harmless
|
||||
// and the final state still converges.
|
||||
let settle_until = std::time::Instant::now() + Duration::from_secs(2);
|
||||
while std::time::Instant::now() < settle_until {
|
||||
let ev = tokio::time::timeout(Duration::from_millis(100), con.events().next()).await;
|
||||
match ev {
|
||||
Ok(Some(Ok(_))) => continue,
|
||||
Ok(Some(Err(e))) => {
|
||||
warn!(target: "chanora_protocol", error = %e, "event error during settle");
|
||||
}
|
||||
Ok(None) => {
|
||||
let _ = ready_tx.send(Err(ProtocolError::DisconnectedEarly(
|
||||
"stream closed during settle".to_string(),
|
||||
)));
|
||||
return;
|
||||
}
|
||||
Err(_) => { /* no event available right now; keep waiting */ }
|
||||
}
|
||||
}
|
||||
|
||||
let _ = ready_tx.send(Ok(()));
|
||||
|
||||
// Request loop with a continuously-pumped event stream. We pump
|
||||
// one event at a time, then check for one pending request, then
|
||||
// repeat. This avoids holding a borrow on `con` across an await
|
||||
// boundary in `tokio::select!`.
|
||||
loop {
|
||||
// Try to advance the event stream by one event with a small
|
||||
// timeout. Errors are logged; stream end is fatal.
|
||||
let pump = async {
|
||||
let mut ev_stream = con.events();
|
||||
tokio::time::timeout(Duration::from_millis(50), ev_stream.next()).await
|
||||
};
|
||||
match pump.await {
|
||||
Ok(Some(Ok(_))) => { /* event consumed */ }
|
||||
Ok(Some(Err(e))) => {
|
||||
warn!(target: "chanora_protocol", error = %e, "event error");
|
||||
}
|
||||
Ok(None) => {
|
||||
warn!(target: "chanora_protocol", "event stream ended");
|
||||
return;
|
||||
}
|
||||
Err(_) => { /* no event in 50 ms — service requests */ }
|
||||
}
|
||||
|
||||
// Service at most one request (non-blocking) so we keep
|
||||
// pumping events too.
|
||||
match rx.try_recv() {
|
||||
Ok(Request::Snapshot(reply)) => {
|
||||
let snap = build_snapshot(&con);
|
||||
let _ = reply.send(snap);
|
||||
}
|
||||
Ok(Request::Disconnect(reply)) => {
|
||||
let _ = con.disconnect(DisconnectOptions::new());
|
||||
con.events().for_each(|_| future::ready(())).await;
|
||||
let _ = reply.send(());
|
||||
info!(target: "chanora_protocol", "clean disconnect");
|
||||
return;
|
||||
}
|
||||
Err(mpsc::error::TryRecvError::Empty) => { /* nothing to do */ }
|
||||
Err(mpsc::error::TryRecvError::Disconnected) => {
|
||||
let _ = con.disconnect(DisconnectOptions::new());
|
||||
con.events().for_each(|_| future::ready(())).await;
|
||||
info!(target: "chanora_protocol", "handle dropped; implicit disconnect");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_snapshot(con: &Connection) -> Result<ServerSnapshot, ProtocolError> {
|
||||
let state: &data::Connection = con
|
||||
.get_state()
|
||||
.map_err(|e| ProtocolError::Backend(format!("get_state: {e}")))?;
|
||||
|
||||
let mut channels: Vec<&Channel> = state.channels.values().collect();
|
||||
channels.sort_by_key(|c| c.order.0);
|
||||
let clients: Vec<&Client> = state.clients.values().collect();
|
||||
|
||||
let channels_dto: Vec<ChannelInfo> = channels
|
||||
.iter()
|
||||
.map(|c| ChannelInfo {
|
||||
id: ChannelId(c.id.0),
|
||||
parent: ChannelId(c.parent.0),
|
||||
name: sanitize(&c.name),
|
||||
order: c.order.0 as i64,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let clients_dto: Vec<ClientInfo> = clients
|
||||
.iter()
|
||||
.map(|c| ClientInfo {
|
||||
id: ClientId(c.id.0 as u64),
|
||||
channel: ChannelId(c.channel.0),
|
||||
name: sanitize(&c.name),
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(ServerSnapshot {
|
||||
server_name: sanitize(&state.server.name),
|
||||
welcome_message: sanitize(&state.server.welcome_message),
|
||||
platform: sanitize(&state.server.platform),
|
||||
version: sanitize(&state.server.version),
|
||||
channels: channels_dto,
|
||||
clients: clients_dto,
|
||||
})
|
||||
}
|
||||
|
||||
/// Light sanitisation of strings before they cross the protocol
|
||||
/// boundary. The redaction policy proper lives in
|
||||
/// `chanora_diagnostics`; this filter only strips control characters
|
||||
/// that would break terminal output or Flutter rendering.
|
||||
fn sanitize(s: &str) -> String {
|
||||
s.chars()
|
||||
.filter(|c| !c.is_control() || *c == '\t' || *c == '\n')
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
const _ROOT_MATCHES_UPSTREAM: () = {
|
||||
// Compile-time assertion that ChannelId(0) maps to what tsclientlib
|
||||
// also considers the root. If upstream ever changes, this stops
|
||||
// compiling and forces an audit.
|
||||
let _ = TsChannelId(0);
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
//! Public DTOs returned across the protocol boundary. All fields are
|
||||
//! owned primitives or `String`s; no `tsclientlib` types leak.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Opaque server-side channel identifier. Internal representation is
|
||||
/// the upstream u64 but callers must treat it as opaque.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct ChannelId(pub u64);
|
||||
|
||||
/// Opaque server-side client identifier.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct ClientId(pub u64);
|
||||
|
||||
/// One channel in the server's tree.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ChannelInfo {
|
||||
/// Stable channel id.
|
||||
pub id: ChannelId,
|
||||
/// Parent channel id; `ChannelId(0)` indicates a top-level channel.
|
||||
pub parent: ChannelId,
|
||||
/// Display name, preserved verbatim per ADR-008.
|
||||
pub name: String,
|
||||
/// Server-side ordering hint.
|
||||
pub order: i64,
|
||||
}
|
||||
|
||||
/// One connected client on the server.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ClientInfo {
|
||||
/// Stable client id.
|
||||
pub id: ClientId,
|
||||
/// Channel the client is currently in.
|
||||
pub channel: ChannelId,
|
||||
/// Nickname, preserved verbatim per ADR-008.
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Snapshot of the server's published state at a moment in time.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerSnapshot {
|
||||
/// Server name.
|
||||
pub server_name: String,
|
||||
/// Server welcome banner. Contains markup from the upstream
|
||||
/// server (BBCode-like); not parsed here.
|
||||
pub welcome_message: String,
|
||||
/// Server platform string.
|
||||
pub platform: String,
|
||||
/// Server version string.
|
||||
pub version: String,
|
||||
/// All channels currently known.
|
||||
pub channels: Vec<ChannelInfo>,
|
||||
/// All clients currently known.
|
||||
pub clients: Vec<ClientInfo>,
|
||||
}
|
||||
|
||||
impl ChannelId {
|
||||
/// The conventional root sentinel used by TeamSpeak-compatible
|
||||
/// servers for the top of the channel tree.
|
||||
pub const ROOT: ChannelId = ChannelId(0);
|
||||
}
|
||||
@@ -4,48 +4,66 @@
|
||||
//! behind a typed boundary so the rest of Chanora is decoupled from
|
||||
//! the upstream library's types (SAD-067, SysDes-011, SysDes-029).
|
||||
//!
|
||||
//! ## Status
|
||||
//! ## What this crate exposes
|
||||
//!
|
||||
//! Scaffold only. Promotion of `poc/tsclientlib-connect-spike` into
|
||||
//! this crate happens later, with its own audit-trail commit.
|
||||
//! * [`ConnectConfig`] — typed connection parameters.
|
||||
//! * [`ProtocolClient`] — async handle owning the connection task.
|
||||
//! * [`ServerSnapshot`], [`ChannelInfo`], [`ClientInfo`] — opaque
|
||||
//! DTOs containing only `String`s and primitives.
|
||||
//! * [`ProtocolError`] — typed error catalogue.
|
||||
//!
|
||||
//! ## What this crate does NOT expose
|
||||
//!
|
||||
//! * `tsclientlib::*` types.
|
||||
//! * `tsproto::*` types.
|
||||
//! * Any audio-related types — those live in `chanora_audio`.
|
||||
//!
|
||||
//! Promoted from `poc/tsclientlib-connect-spike` on 2026-05-14
|
||||
//! as part of the Alpha build.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
mod adapter;
|
||||
mod dto;
|
||||
|
||||
pub use adapter::{ConnectConfig, ProtocolClient};
|
||||
pub use dto::{ChannelInfo, ClientInfo, ServerSnapshot};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors surfaced by the protocol adapter. None of these expose
|
||||
/// `tsclientlib`-specific types; raw upstream errors are mapped here.
|
||||
/// `tsclientlib`-specific types; raw upstream errors are mapped here
|
||||
/// to typed arms.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ProtocolError {
|
||||
/// Configuration is invalid before any I/O is attempted (bad
|
||||
/// hostname, missing identity, etc.).
|
||||
#[error("invalid protocol configuration: {0}")]
|
||||
Invalid(&'static str),
|
||||
/// The connect handshake failed.
|
||||
Invalid(String),
|
||||
|
||||
/// Failed to dial / handshake with the server.
|
||||
#[error("connect failed: {0}")]
|
||||
Connect(String),
|
||||
/// The connection ended unexpectedly.
|
||||
#[error("disconnected: {0}")]
|
||||
Disconnected(String),
|
||||
|
||||
/// The connection ended before becoming ready.
|
||||
#[error("disconnected before ready: {0}")]
|
||||
DisconnectedEarly(String),
|
||||
|
||||
/// Connection lost after becoming ready.
|
||||
#[error("connection lost: {0}")]
|
||||
Lost(String),
|
||||
|
||||
/// Identity parsing failed.
|
||||
#[error("identity error: {0}")]
|
||||
Identity(String),
|
||||
|
||||
/// Operation timed out.
|
||||
#[error("protocol timeout")]
|
||||
Timeout,
|
||||
}
|
||||
|
||||
/// Opaque identifier for a server-side channel. Replaces
|
||||
/// `tsclientlib::ChannelId` at the public boundary so its concrete
|
||||
/// representation can change without leaking through the rest of the
|
||||
/// codebase.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ChannelId(pub u64);
|
||||
|
||||
/// Opaque identifier for a server-side client.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ClientId(pub u64);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_compiles() {}
|
||||
/// A backend error escaped the mapping. Production callers
|
||||
/// should never see this; if they do, it is a mapping bug here.
|
||||
#[error("protocol backend: {0}")]
|
||||
Backend(String),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
rust_input: crate::api
|
||||
rust_root: crates/chanora_bridge/
|
||||
dart_output: apps/chanora_flutter/lib/src/rust
|
||||
rust_output: crates/chanora_bridge/src/frb_generated.rs
|
||||
Reference in New Issue
Block a user