feat(useragent): added connection info setup screen

This commit is contained in:
hdbg
2026-03-15 14:51:39 +01:00
parent 16d5b9a233
commit ec0e8a980c
25 changed files with 800 additions and 2441 deletions

128
AGENTS.md Normal file
View File

@@ -0,0 +1,128 @@
# AGENTS.md
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
## Project Overview
Arbiter is a **permissioned signing service** for cryptocurrency wallets. It consists of:
- **`server/`** — Rust gRPC daemon that holds encrypted keys and enforces policies
- **`useragent/`** — Flutter desktop app (macOS/Windows) with a Rust backend via Rinf
- **`protobufs/`** — Protocol Buffer definitions shared between server and client
The vault never exposes key material; it only produces signatures when requests satisfy configured policies.
## Toolchain Setup
Tools are managed via [mise](https://mise.jdx.dev/). Install all required tools:
```sh
mise install
```
Key versions: Rust 1.93.0 (with clippy), Flutter 3.38.9-stable, protoc 29.6, diesel_cli 2.3.6 (sqlite).
## Server (Rust workspace at `server/`)
### Crates
| Crate | Purpose |
|---|---|
| `arbiter-proto` | Generated gRPC stubs + protobuf types; compiled from `protobufs/*.proto` via `tonic-prost-build` |
| `arbiter-server` | Main daemon — actors, DB, EVM policy engine, gRPC service implementation |
| `arbiter-useragent` | Rust client library for the user agent side of the gRPC protocol |
| `arbiter-client` | Rust client library for SDK clients |
### Common Commands
```sh
cd server
# Build
cargo build
# Run the server daemon
cargo run -p arbiter-server
# Run all tests (preferred over cargo test)
cargo nextest run
# Run a single test
cargo nextest run <test_name>
# Lint
cargo clippy
# Security audit
cargo audit
# Check unused dependencies
cargo shear
# Run snapshot tests and update snapshots
cargo insta review
```
### Architecture
The server is actor-based using the **kameo** crate. All long-lived state lives in `GlobalActors`:
- **`Bootstrapper`** — Manages the one-time bootstrap token written to `~/.arbiter/bootstrap_token` on first run.
- **`KeyHolder`** — Holds the encrypted root key and manages the Sealed/Unsealed vault state machine. On unseal, decrypts the root key into a `memsafe` hardened memory cell.
- **`MessageRouter`** — Coordinates streaming messages between user agents and SDK clients.
- **`EvmActor`** — Handles EVM transaction policy enforcement and signing.
Per-connection actors live under `actors/user_agent/` and `actors/client/`, each with `auth` (challenge-response authentication) and `session` (post-auth operations) sub-modules.
**Database:** SQLite via `diesel-async` + `bb8` connection pool. Schema managed by embedded Diesel migrations in `crates/arbiter-server/migrations/`. DB file lives at `~/.arbiter/arbiter.sqlite`. Tests use a temp-file DB via `db::create_test_pool()`.
**Cryptography:**
- Authentication: ed25519 (challenge-response, nonce-tracked per peer)
- Encryption at rest: XChaCha20-Poly1305 (versioned via `scheme` field for transparent migration on unseal)
- Password KDF: Argon2
- Unseal transport: X25519 ephemeral key exchange
- TLS: self-signed certificate (aws-lc-rs backend), fingerprint distributed via `ArbiterUrl`
**Protocol:** gRPC with Protocol Buffers. The `ArbiterUrl` type encodes host, port, CA cert, and bootstrap token into a single shareable string (printed to console on first run).
### Proto Regeneration
When `.proto` files in `protobufs/` change, rebuild to regenerate:
```sh
cd server && cargo build -p arbiter-proto
```
### Database Migrations
```sh
# Create a new migration
diesel migration generate <name> --migration-dir crates/arbiter-server/migrations
# Run migrations manually (server also runs them on startup)
diesel migration run --migration-dir crates/arbiter-server/migrations
```
## User Agent (Flutter + Rinf at `useragent/`)
The Flutter app uses [Rinf](https://rinf.cunarist.org) to call Rust code. The Rust logic lives in `useragent/native/hub/` as a separate crate that uses `arbiter-useragent` for the gRPC client.
Communication between Dart and Rust uses typed **signals** defined in `useragent/native/hub/src/signals/`. After modifying signal structs, regenerate Dart bindings:
```sh
cd useragent && rinf gen
```
### Common Commands
```sh
cd useragent
# Run the app (macOS or Windows)
flutter run
# Regenerate Rust↔Dart signal bindings
rinf gen
# Analyze Dart code
flutter analyze
```
The Rinf Rust entry point is `useragent/native/hub/src/lib.rs`. It spawns actors defined in `useragent/native/hub/src/actors/` which handle Dart↔server communication via signals.

View File

@@ -1,904 +0,0 @@
{
"configVersion": 2,
"packages": [
{
"name": "_fe_analyzer_shared",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/_fe_analyzer_shared-91.0.0",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{
"name": "analyzer",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/analyzer-8.4.1",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{
"name": "analyzer_buffer",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/analyzer_buffer-0.1.11",
"packageUri": "lib/",
"languageVersion": "3.6"
},
{
"name": "ansicolor",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/ansicolor-2.0.3",
"packageUri": "lib/",
"languageVersion": "3.0"
},
{
"name": "args",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/args-2.7.0",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "async",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/async-2.13.0",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "auto_route",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/auto_route-11.1.0",
"packageUri": "lib/",
"languageVersion": "3.0"
},
{
"name": "auto_route_generator",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/auto_route_generator-10.4.0",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "biometric_signature",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/biometric_signature-10.2.0",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{
"name": "bloc",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/bloc-9.2.0",
"packageUri": "lib/",
"languageVersion": "2.14"
},
{
"name": "boolean_selector",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/boolean_selector-2.1.2",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "build",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/build-4.0.4",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "build_config",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/build_config-1.3.0",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "build_daemon",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/build_daemon-4.1.1",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "build_runner",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/build_runner-2.12.2",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "built_collection",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/built_collection-5.1.1",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "built_value",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/built_value-8.12.4",
"packageUri": "lib/",
"languageVersion": "3.0"
},
{
"name": "characters",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/characters-1.4.0",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "checked_yaml",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/checked_yaml-2.0.4",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "cli_config",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cli_config-0.2.0",
"packageUri": "lib/",
"languageVersion": "3.0"
},
{
"name": "clock",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/clock-1.1.2",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "code_assets",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/code_assets-1.0.0",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{
"name": "code_builder",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/code_builder-4.11.1",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "collection",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/collection-1.19.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "convert",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/convert-3.1.2",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "coverage",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/coverage-1.15.0",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "cross_file",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cross_file-0.3.5+2",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "crypto",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/crypto-3.0.7",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "cryptography",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cryptography-2.9.0",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "cryptography_flutter",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cryptography_flutter-2.3.4",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "cupertino_icons",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/cupertino_icons-1.0.8",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "dart_style",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/dart_style-3.1.3",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{
"name": "fake_async",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/fake_async-1.3.3",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "ffi",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/ffi-2.2.0",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "file",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/file-7.0.1",
"packageUri": "lib/",
"languageVersion": "3.0"
},
{
"name": "fixnum",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/fixnum-1.1.1",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "flutter",
"rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/packages/flutter",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "flutter_adaptive_scaffold",
"rootUri": "file:///Users/kaska/.pub-cache/git/flutter_adaptive_scaffold-b2e3615901a7ab837cb7fc35efbfcf8b55f27638/",
"packageUri": "lib/",
"languageVersion": "3.10"
},
{
"name": "flutter_bloc",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_bloc-9.1.1",
"packageUri": "lib/",
"languageVersion": "2.14"
},
{
"name": "flutter_hooks",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_hooks-0.21.3+1",
"packageUri": "lib/",
"languageVersion": "2.17"
},
{
"name": "flutter_lints",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_lints-6.0.0",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "flutter_riverpod",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_riverpod-3.1.0",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "flutter_secure_storage",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage-10.0.0",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "flutter_secure_storage_darwin",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage_darwin-0.2.0",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "flutter_secure_storage_linux",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-3.0.0",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "flutter_secure_storage_platform_interface",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage_platform_interface-2.0.1",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "flutter_secure_storage_web",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-2.1.0",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "flutter_secure_storage_windows",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-4.1.0",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "flutter_spinkit",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/flutter_spinkit-5.2.2",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "flutter_test",
"rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/packages/flutter_test",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "flutter_web_plugins",
"rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/packages/flutter_web_plugins",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "freezed_annotation",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/freezed_annotation-3.1.0",
"packageUri": "lib/",
"languageVersion": "3.0"
},
{
"name": "frontend_server_client",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/frontend_server_client-4.0.0",
"packageUri": "lib/",
"languageVersion": "3.0"
},
{
"name": "glob",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/glob-2.1.3",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "google_identity_services_web",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/google_identity_services_web-0.3.3+1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "googleapis_auth",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/googleapis_auth-2.0.0",
"packageUri": "lib/",
"languageVersion": "3.6"
},
{
"name": "graphs",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/graphs-2.3.2",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "group_button",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/group_button-5.3.4",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "grpc",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/grpc-5.1.0",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "hooks",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/hooks-1.0.2",
"packageUri": "lib/",
"languageVersion": "3.10"
},
{
"name": "hooks_riverpod",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/hooks_riverpod-3.1.0",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "hotreloader",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/hotreloader-4.3.0",
"packageUri": "lib/",
"languageVersion": "3.0"
},
{
"name": "http",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/http-1.6.0",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "http2",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/http2-2.3.1",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "http_multi_server",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/http_multi_server-3.2.2",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "http_parser",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/http_parser-4.1.2",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "io",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/io-1.0.5",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "js",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/js-0.7.2",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "json_annotation",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/json_annotation-4.11.0",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{
"name": "leak_tracker",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/leak_tracker-11.0.2",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "leak_tracker_flutter_testing",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.10",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "leak_tracker_testing",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/leak_tracker_testing-3.0.2",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "lean_builder",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/lean_builder-0.1.6",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "lints",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/lints-6.1.0",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "logging",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/logging-1.3.0",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "matcher",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/matcher-0.12.17",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "material_color_utilities",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/material_color_utilities-0.11.1",
"packageUri": "lib/",
"languageVersion": "2.17"
},
{
"name": "meta",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/meta-1.17.0",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "mime",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/mime-2.0.0",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "mockito",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/mockito-5.6.3",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "mtcore",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/git.markettakers.org%2547api%2547packages%2547MarketTakers%2547pub%2547/mtcore-1.0.6",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{
"name": "native_toolchain_c",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/native_toolchain_c-0.17.5",
"packageUri": "lib/",
"languageVersion": "3.10"
},
{
"name": "nested",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/nested-1.0.0",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "node_preamble",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/node_preamble-2.0.2",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "objective_c",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/objective_c-9.3.0",
"packageUri": "lib/",
"languageVersion": "3.10"
},
{
"name": "package_config",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/package_config-2.2.0",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "path",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path-1.9.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "path_provider",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider-2.1.5",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "path_provider_android",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider_android-2.2.22",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{
"name": "path_provider_foundation",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider_foundation-2.6.0",
"packageUri": "lib/",
"languageVersion": "3.10"
},
{
"name": "path_provider_linux",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1",
"packageUri": "lib/",
"languageVersion": "2.19"
},
{
"name": "path_provider_platform_interface",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider_platform_interface-2.1.2",
"packageUri": "lib/",
"languageVersion": "3.0"
},
{
"name": "path_provider_windows",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "percent_indicator",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/percent_indicator-4.2.5",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "platform",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/platform-3.1.6",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "plugin_platform_interface",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/plugin_platform_interface-2.1.8",
"packageUri": "lib/",
"languageVersion": "3.0"
},
{
"name": "pool",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/pool-1.5.2",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "protobuf",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/protobuf-6.0.0",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "provider",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/provider-6.1.5+1",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "pub_semver",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/pub_semver-2.2.0",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "pubspec_parse",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/pubspec_parse-1.5.0",
"packageUri": "lib/",
"languageVersion": "3.6"
},
{
"name": "rive",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/rive-0.14.4",
"packageUri": "lib/",
"languageVersion": "3.6"
},
{
"name": "rive_native",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/rive_native-0.1.4",
"packageUri": "lib/",
"languageVersion": "3.6"
},
{
"name": "riverpod",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/riverpod-3.1.0",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "riverpod_analyzer_utils",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/riverpod_analyzer_utils-1.0.0-dev.8",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "riverpod_annotation",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/riverpod_annotation-4.0.0",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "riverpod_generator",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/riverpod_generator-4.0.0+1",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "share_plus",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/share_plus-12.0.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "share_plus_platform_interface",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/share_plus_platform_interface-6.1.0",
"packageUri": "lib/",
"languageVersion": "2.18"
},
{
"name": "shelf",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/shelf-1.4.2",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "shelf_packages_handler",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/shelf_packages_handler-3.0.2",
"packageUri": "lib/",
"languageVersion": "2.17"
},
{
"name": "shelf_static",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/shelf_static-1.1.3",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "shelf_web_socket",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/shelf_web_socket-3.0.0",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "sizer",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/sizer-3.1.3",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "sky_engine",
"rootUri": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable/bin/cache/pkg/sky_engine",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "source_gen",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/source_gen-4.2.1",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{
"name": "source_map_stack_trace",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/source_map_stack_trace-2.1.2",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "source_maps",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/source_maps-0.10.13",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "source_span",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/source_span-1.10.2",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "stack_trace",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/stack_trace-1.12.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "state_notifier",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/state_notifier-1.0.0",
"packageUri": "lib/",
"languageVersion": "2.12"
},
{
"name": "stream_channel",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/stream_channel-2.1.4",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "stream_transform",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/stream_transform-2.1.1",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "string_scanner",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/string_scanner-1.4.1",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "talker",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/talker-5.1.15",
"packageUri": "lib/",
"languageVersion": "2.17"
},
{
"name": "talker_flutter",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/talker_flutter-5.1.15",
"packageUri": "lib/",
"languageVersion": "3.6"
},
{
"name": "talker_logger",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/talker_logger-5.1.15",
"packageUri": "lib/",
"languageVersion": "2.15"
},
{
"name": "term_glyph",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/term_glyph-1.2.2",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "test",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/test-1.26.3",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "test_api",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/test_api-0.7.7",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "test_core",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/test_core-0.6.12",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "typed_data",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/typed_data-1.4.0",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "url_launcher_linux",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/url_launcher_linux-3.2.2",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "url_launcher_platform_interface",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/url_launcher_platform_interface-2.3.2",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "url_launcher_web",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/url_launcher_web-2.4.2",
"packageUri": "lib/",
"languageVersion": "3.10"
},
{
"name": "url_launcher_windows",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/url_launcher_windows-3.1.5",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "uuid",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/uuid-4.5.3",
"packageUri": "lib/",
"languageVersion": "3.0"
},
{
"name": "vector_math",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/vector_math-2.2.0",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "vm_service",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/vm_service-15.0.2",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "watcher",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/watcher-1.1.4",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "web",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/web-1.1.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "web_socket",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/web_socket-1.0.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "web_socket_channel",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/web_socket_channel-3.0.3",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "webkit_inspection_protocol",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/webkit_inspection_protocol-1.2.1",
"packageUri": "lib/",
"languageVersion": "3.0"
},
{
"name": "win32",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/win32-5.15.0",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "xdg_directories",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/xdg_directories-1.1.0",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "xxh3",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/xxh3-1.2.0",
"packageUri": "lib/",
"languageVersion": "2.16"
},
{
"name": "yaml",
"rootUri": "file:///Users/kaska/.pub-cache/hosted/pub.dev/yaml-3.1.3",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "arbiter",
"rootUri": "../",
"packageUri": "lib/",
"languageVersion": "3.10"
}
],
"generator": "pub",
"generatorVersion": "3.10.8",
"flutterRoot": "file:///Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable",
"flutterVersion": "3.38.9",
"pubCache": "file:///Users/kaska/.pub-cache"
}

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
3.38.9

View File

@@ -0,0 +1,56 @@
import 'dart:convert';
class ArbiterUrl {
const ArbiterUrl({
required this.host,
required this.port,
required this.caCert,
this.bootstrapToken,
});
final String host;
final int port;
final List<int> caCert;
final String? bootstrapToken;
static const _scheme = 'arbiter';
static const _certQueryKey = 'cert';
static const _bootstrapTokenQueryKey = 'bootstrap_token';
static ArbiterUrl parse(String value) {
final uri = Uri.tryParse(value);
if (uri == null || uri.scheme != _scheme) {
throw const FormatException("Invalid URL scheme, expected 'arbiter://'");
}
if (uri.host.isEmpty) {
throw const FormatException('Missing host in URL');
}
if (!uri.hasPort) {
throw const FormatException('Missing port in URL');
}
final cert = uri.queryParameters[_certQueryKey];
if (cert == null || cert.isEmpty) {
throw const FormatException("Missing 'cert' query parameter in URL");
}
final decodedCert = _decodeCert(cert);
return ArbiterUrl(
host: uri.host,
port: uri.port,
caCert: decodedCert,
bootstrapToken: uri.queryParameters[_bootstrapTokenQueryKey],
);
}
static List<int> _decodeCert(String cert) {
try {
return base64Url.decode(base64Url.normalize(cert));
} on FormatException catch (error) {
throw FormatException("Invalid base64 in 'cert' query parameter: ${error.message}");
}
}
}

View File

@@ -0,0 +1,3 @@
class Connection {}

View File

@@ -1,6 +1,3 @@
import 'package:flutter/services.dart';
enum KeyAlgorithm {
rsa, ecdsa, ed25519
}
@@ -16,4 +13,4 @@ abstract class KeyHandle {
abstract class KeyManager {
Future<KeyHandle?> get();
Future<KeyHandle> create();
}
}

View File

@@ -0,0 +1,67 @@
import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class StoredServerInfo {
const StoredServerInfo({
required this.address,
required this.port,
required this.caCertFingerprint,
});
final String address;
final int port;
final String caCertFingerprint;
Map<String, dynamic> toJson() => {
'address': address,
'port': port,
'caCertFingerprint': caCertFingerprint,
};
factory StoredServerInfo.fromJson(Map<String, dynamic> json) {
return StoredServerInfo(
address: json['address'] as String,
port: json['port'] as int,
caCertFingerprint: json['caCertFingerprint'] as String,
);
}
}
abstract class ServerInfoStorage {
Future<StoredServerInfo?> load();
Future<void> save(StoredServerInfo serverInfo);
Future<void> clear();
}
class SecureServerInfoStorage implements ServerInfoStorage {
static const _storageKey = 'server_info';
const SecureServerInfoStorage();
static const _storage = FlutterSecureStorage();
@override
Future<StoredServerInfo?> load() async {
final rawValue = await _storage.read(key: _storageKey);
if (rawValue == null) {
return null;
}
final decoded = jsonDecode(rawValue) as Map<String, dynamic>;
return StoredServerInfo.fromJson(decoded);
}
@override
Future<void> save(StoredServerInfo serverInfo) {
return _storage.write(
key: _storageKey,
value: jsonEncode(serverInfo.toJson()),
);
}
@override
Future<void> clear() {
return _storage.delete(key: _storageKey);
}
}

View File

@@ -1,14 +1,35 @@
import 'package:arbiter/router.dart';
import 'package:flutter/material.dart' hide Router;
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:sizer/sizer.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(
ProviderScope(
child: MaterialApp.router(
routerConfig: Router().config(),
),
),
);
runApp(const ProviderScope(child: App()));
}
class App extends StatefulWidget {
const App({super.key});
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
late final Router _router;
@override
void initState() {
super.initState();
_router = Router();
}
@override
Widget build(BuildContext context) {
return Sizer(
builder: (context, orientation, deviceType) {
return MaterialApp.router(routerConfig: _router.config());
},
);
}
}

View File

@@ -0,0 +1,51 @@
import 'package:arbiter/features/server_info_storage.dart';
import 'package:cryptography/cryptography.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'server_info.g.dart';
@riverpod
ServerInfoStorage serverInfoStorage(Ref ref) {
return const SecureServerInfoStorage();
}
@Riverpod(keepAlive: true)
class ServerInfo extends _$ServerInfo {
@override
Future<StoredServerInfo?> build() {
final storage = ref.watch(serverInfoStorageProvider);
return storage.load();
}
Future<void> save({
required String address,
required int port,
required List<int> caCert,
}) async {
final storage = ref.read(serverInfoStorageProvider);
final fingerprint = await _fingerprint(caCert);
final serverInfo = StoredServerInfo(
address: address,
port: port,
caCertFingerprint: fingerprint,
);
state = await AsyncValue.guard(() async {
await storage.save(serverInfo);
return serverInfo;
});
}
Future<void> clear() async {
final storage = ref.read(serverInfoStorageProvider);
state = await AsyncValue.guard(() async {
await storage.clear();
return null;
});
}
Future<String> _fingerprint(List<int> caCert) async {
final digest = await Sha256().hash(caCert);
return digest.bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
}
}

View File

@@ -0,0 +1,102 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'server_info.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(serverInfoStorage)
final serverInfoStorageProvider = ServerInfoStorageProvider._();
final class ServerInfoStorageProvider
extends
$FunctionalProvider<
ServerInfoStorage,
ServerInfoStorage,
ServerInfoStorage
>
with $Provider<ServerInfoStorage> {
ServerInfoStorageProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'serverInfoStorageProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$serverInfoStorageHash();
@$internal
@override
$ProviderElement<ServerInfoStorage> $createElement(
$ProviderPointer pointer,
) => $ProviderElement(pointer);
@override
ServerInfoStorage create(Ref ref) {
return serverInfoStorage(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(ServerInfoStorage value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<ServerInfoStorage>(value),
);
}
}
String _$serverInfoStorageHash() => r'fc06865e7314b1a2493c5de1a9347923a3d21c5c';
@ProviderFor(ServerInfo)
final serverInfoProvider = ServerInfoProvider._();
final class ServerInfoProvider
extends $AsyncNotifierProvider<ServerInfo, StoredServerInfo?> {
ServerInfoProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'serverInfoProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$serverInfoHash();
@$internal
@override
ServerInfo create() => ServerInfo();
}
String _$serverInfoHash() => r'6e94f52de03259695a2166b766004eec60ff45fa';
abstract class _$ServerInfo extends $AsyncNotifier<StoredServerInfo?> {
FutureOr<StoredServerInfo?> build();
@$mustCallSuper
@override
void runBuild() {
final ref =
this.ref as $Ref<AsyncValue<StoredServerInfo?>, StoredServerInfo?>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AsyncValue<StoredServerInfo?>, StoredServerInfo?>,
AsyncValue<StoredServerInfo?>,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}

View File

@@ -1,8 +1,4 @@
import 'package:arbiter/screens/dashboard/about.dart';
import 'package:arbiter/screens/dashboard/calc.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
import 'router.gr.dart';
@@ -11,6 +7,7 @@ class Router extends RootStackRouter {
@override
List<AutoRoute> get routes => [
AutoRoute(page: Bootstrap.page, path: '/bootstrap', initial: true),
AutoRoute(page: ServerInfoSetupRoute.page, path: '/server-info'),
AutoRoute(
page: DashboardRouter.page,

View File

@@ -13,33 +13,34 @@ import 'package:arbiter/screens/bootstrap.dart' as _i2;
import 'package:arbiter/screens/dashboard.dart' as _i4;
import 'package:arbiter/screens/dashboard/about.dart' as _i1;
import 'package:arbiter/screens/dashboard/calc.dart' as _i3;
import 'package:auto_route/auto_route.dart' as _i5;
import 'package:arbiter/screens/server_info_setup.dart' as _i5;
import 'package:auto_route/auto_route.dart' as _i6;
/// generated route for
/// [_i1.AboutScreen]
class AboutRoute extends _i5.PageRouteInfo<void> {
const AboutRoute({List<_i5.PageRouteInfo>? children})
class AboutRoute extends _i6.PageRouteInfo<void> {
const AboutRoute({List<_i6.PageRouteInfo>? children})
: super(AboutRoute.name, initialChildren: children);
static const String name = 'AboutRoute';
static _i5.PageInfo page = _i5.PageInfo(
static _i6.PageInfo page = _i6.PageInfo(
name,
builder: (data) {
return _i1.AboutScreen();
return const _i1.AboutScreen();
},
);
}
/// generated route for
/// [_i2.Bootstrap]
class Bootstrap extends _i5.PageRouteInfo<void> {
const Bootstrap({List<_i5.PageRouteInfo>? children})
class Bootstrap extends _i6.PageRouteInfo<void> {
const Bootstrap({List<_i6.PageRouteInfo>? children})
: super(Bootstrap.name, initialChildren: children);
static const String name = 'Bootstrap';
static _i5.PageInfo page = _i5.PageInfo(
static _i6.PageInfo page = _i6.PageInfo(
name,
builder: (data) {
return const _i2.Bootstrap();
@@ -49,13 +50,13 @@ class Bootstrap extends _i5.PageRouteInfo<void> {
/// generated route for
/// [_i3.CalcScreen]
class CalcRoute extends _i5.PageRouteInfo<void> {
const CalcRoute({List<_i5.PageRouteInfo>? children})
class CalcRoute extends _i6.PageRouteInfo<void> {
const CalcRoute({List<_i6.PageRouteInfo>? children})
: super(CalcRoute.name, initialChildren: children);
static const String name = 'CalcRoute';
static _i5.PageInfo page = _i5.PageInfo(
static _i6.PageInfo page = _i6.PageInfo(
name,
builder: (data) {
return const _i3.CalcScreen();
@@ -65,16 +66,32 @@ class CalcRoute extends _i5.PageRouteInfo<void> {
/// generated route for
/// [_i4.DashboardRouter]
class DashboardRouter extends _i5.PageRouteInfo<void> {
const DashboardRouter({List<_i5.PageRouteInfo>? children})
class DashboardRouter extends _i6.PageRouteInfo<void> {
const DashboardRouter({List<_i6.PageRouteInfo>? children})
: super(DashboardRouter.name, initialChildren: children);
static const String name = 'DashboardRouter';
static _i5.PageInfo page = _i5.PageInfo(
static _i6.PageInfo page = _i6.PageInfo(
name,
builder: (data) {
return const _i4.DashboardRouter();
},
);
}
/// generated route for
/// [_i5.ServerInfoSetupScreen]
class ServerInfoSetupRoute extends _i6.PageRouteInfo<void> {
const ServerInfoSetupRoute({List<_i6.PageRouteInfo>? children})
: super(ServerInfoSetupRoute.name, initialChildren: children);
static const String name = 'ServerInfoSetupRoute';
static _i6.PageInfo page = _i6.PageInfo(
name,
builder: (data) {
return const _i5.ServerInfoSetupScreen();
},
);
}

View File

@@ -14,13 +14,13 @@ class Bootstrap extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final container = ProviderScope.containerOf( context);
final container = ProviderScope.containerOf(context);
final completer = useMemoized(() {
final completer = Completer<void>();
completer.future.then((_) async {
if (context.mounted) {
final router = AutoRouter.of(context);
router.replace(const DashboardRouter());
router.replace(const ServerInfoSetupRoute());
}
});

View File

@@ -2,9 +2,10 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:mtcore/markettakers.dart' as mt;
@RoutePage()
class AboutScreen extends StatelessWidget {
const AboutScreen({super.key});
@override
Widget build(BuildContext context) {
return mt.AboutScreen(decription: "Arbiter is bla bla bla");

View File

@@ -0,0 +1,284 @@
import 'package:arbiter/features/arbiter_url.dart';
import 'package:arbiter/features/server_info_storage.dart';
import 'package:arbiter/providers/server_info.dart';
import 'package:arbiter/router.gr.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:sizer/sizer.dart';
@RoutePage()
class ServerInfoSetupScreen extends HookConsumerWidget {
const ServerInfoSetupScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final storedServerInfo = ref.watch(serverInfoProvider);
final resolvedServerInfo = storedServerInfo.asData?.value;
final controller = useTextEditingController();
final errorText = useState<String?>(null);
final isSaving = useState(false);
useEffect(() {
final serverInfo = resolvedServerInfo;
if (serverInfo != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (context.mounted) {
context.router.replace(const DashboardRouter());
}
});
}
return null;
}, [context, resolvedServerInfo]);
Future<void> saveRemoteServerInfo() async {
errorText.value = null;
isSaving.value = true;
try {
final arbiterUrl = ArbiterUrl.parse(controller.text.trim());
await ref
.read(serverInfoProvider.notifier)
.save(
address: arbiterUrl.host,
port: arbiterUrl.port,
caCert: arbiterUrl.caCert,
);
if (context.mounted) {
context.router.replace(const DashboardRouter());
}
} on FormatException catch (error) {
errorText.value = error.message;
} catch (_) {
errorText.value = 'Failed to store connection settings.';
} finally {
isSaving.value = false;
}
}
if (storedServerInfo.isLoading) {
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}
if (storedServerInfo.hasError) {
return Scaffold(
appBar: AppBar(title: const Text('Server Info Setup')),
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 6.w),
child: Text(
'Failed to load stored server info.',
style: Theme.of(context).textTheme.bodyLarge,
textAlign: TextAlign.center,
),
),
),
);
}
final serverInfo = resolvedServerInfo;
if (serverInfo != null) {
return _RedirectingView(serverInfo: serverInfo);
}
return Scaffold(
appBar: AppBar(title: const Text('Server Info Setup')),
body: LayoutBuilder(
builder: (context, constraints) {
final useRowLayout = constraints.maxWidth > constraints.maxHeight;
final gap = 2.h;
final horizontalPadding = 6.w;
final verticalPadding = 3.h;
final options = [
const _OptionCard(
title: 'Local',
subtitle: 'Will start and connect to a local service in a future update.',
enabled: false,
child: SizedBox.shrink(),
),
_OptionCard(
title: 'Remote',
subtitle: 'Paste an Arbiter URL to store the server address, port, and CA fingerprint.',
child: _RemoteConnectionForm(
controller: controller,
errorText: errorText.value,
isSaving: isSaving.value,
onSave: saveRemoteServerInfo,
),
),
];
return ListView(
padding: EdgeInsets.symmetric(
horizontal: horizontalPadding,
vertical: verticalPadding,
),
children: [
_SetupHeader(gap: gap),
SizedBox(height: gap),
useRowLayout
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: options[0]),
SizedBox(width: 3.w),
Expanded(child: options[1]),
],
)
: Column(
children: [
options[0],
SizedBox(height: gap),
options[1],
],
),
],
);
},
),
);
}
}
class _SetupHeader extends StatelessWidget {
const _SetupHeader({required this.gap});
final double gap;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Choose how this user agent should reach Arbiter.',
style: Theme.of(context).textTheme.headlineSmall,
),
SizedBox(height: gap * 0.5),
Text(
'Remote accepts the shareable Arbiter URL emitted by the server.',
style: Theme.of(context).textTheme.bodyMedium,
),
],
);
}
}
class _RedirectingView extends StatelessWidget {
const _RedirectingView({required this.serverInfo});
final StoredServerInfo serverInfo;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator(),
SizedBox(height: 2.h),
Text('Using saved server ${serverInfo.address}:${serverInfo.port}'),
],
),
),
);
}
}
class _RemoteConnectionForm extends StatelessWidget {
const _RemoteConnectionForm({
required this.controller,
required this.errorText,
required this.isSaving,
required this.onSave,
});
final TextEditingController controller;
final String? errorText;
final bool isSaving;
final VoidCallback onSave;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: controller,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Arbiter URL',
hintText: 'arbiter://host:port?cert=...',
),
minLines: 2,
maxLines: 4,
),
if (errorText != null) ...[
SizedBox(height: 1.5.h),
Text(
errorText!,
style: TextStyle(color: Theme.of(context).colorScheme.error),
),
],
SizedBox(height: 2.h),
Align(
alignment: Alignment.centerLeft,
child: FilledButton(
onPressed: isSaving ? null : onSave,
child: Text(isSaving ? 'Saving...' : 'Save connection'),
),
),
],
);
}
}
class _OptionCard extends StatelessWidget {
const _OptionCard({
required this.title,
required this.subtitle,
required this.child,
this.enabled = true,
});
final String title;
final String subtitle;
final Widget child;
final bool enabled;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Card(
child: Padding(
padding: EdgeInsets.all(2.h),
child: Opacity(
opacity: enabled ? 1 : 0.55,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(title, style: theme.textTheme.titleLarge),
if (!enabled) ...[
SizedBox(width: 2.w),
const Chip(label: Text('Coming soon')),
],
],
),
SizedBox(height: 1.h),
Text(subtitle, style: theme.textTheme.bodyMedium),
if (enabled) ...[
SizedBox(height: 2.h),
child,
],
],
),
),
),
);
}
}

View File

@@ -1,13 +0,0 @@
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=/Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable
FLUTTER_APPLICATION_PATH=/Users/kaska/Documents/Projects/Major/arbiter/useragent
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_TARGET=/Users/kaska/Documents/Projects/Major/arbiter/useragent/lib/main.dart
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_NAME=1.0.0
FLUTTER_BUILD_NUMBER=1
DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuMzguOQ==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049NjczMjNkZTI4NQ==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NTg3YzE4Zjg3Mw==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMC44
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=true
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=/Users/kaska/Documents/Projects/Major/arbiter/useragent/.dart_tool/package_config.json

View File

@@ -1,14 +0,0 @@
#!/bin/sh
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/kaska/.local/share/mise/installs/flutter/3.38.9-stable"
export "FLUTTER_APPLICATION_PATH=/Users/kaska/Documents/Projects/Major/arbiter/useragent"
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
export "FLUTTER_TARGET=/Users/kaska/Documents/Projects/Major/arbiter/useragent/lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_DEFINES=RkxVVFRFUl9WRVJTSU9OPTMuMzguOQ==,RkxVVFRFUl9DSEFOTkVMPXN0YWJsZQ==,RkxVVFRFUl9HSVRfVVJMPWh0dHBzOi8vZ2l0aHViLmNvbS9mbHV0dGVyL2ZsdXR0ZXIuZ2l0,RkxVVFRFUl9GUkFNRVdPUktfUkVWSVNJT049NjczMjNkZTI4NQ==,RkxVVFRFUl9FTkdJTkVfUkVWSVNJT049NTg3YzE4Zjg3Mw==,RkxVVFRFUl9EQVJUX1ZFUlNJT049My4xMC44"
export "DART_OBFUSCATION=false"
export "TRACK_WIDGET_CREATION=true"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=/Users/kaska/Documents/Projects/Major/arbiter/useragent/.dart_tool/package_config.json"

View File

@@ -1,4 +1,4 @@
platform :osx, '10.15'
platform :osx, '26.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@@ -42,6 +42,6 @@ SPEC CHECKSUMS:
rive_native: 1c53d33e44c2b54424810effea4590671dd220c7
share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
PODFILE CHECKSUM: 224cb1c0d6f5312abfc2477bcb5c7f1fca2574fb
COCOAPODS: 1.16.2

View File

@@ -195,7 +195,6 @@
5385F9987FF8E7FD3BA4E87E /* Pods-RunnerTests.release.xcconfig */,
ADF8B0EB51CA38AE67931C44 /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
@@ -478,6 +477,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.useragent.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -493,6 +493,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.useragent.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -508,6 +509,7 @@
BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.useragent.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -560,6 +562,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
@@ -572,14 +575,22 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = 8L884L537J;
ENABLE_APP_SANDBOX = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 26.0;
PRODUCT_BUNDLE_IDENTIFIER = org.markettakers.arbiter;
PROVISIONING_PROFILE_SPECIFIER = "";
RUNTIME_EXCEPTION_ALLOW_JIT = YES;
SWIFT_VERSION = 5.0;
};
name = Profile;
@@ -588,6 +599,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
MACOSX_DEPLOYMENT_TARGET = 11.0;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Profile;
@@ -643,6 +655,7 @@
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
@@ -692,6 +705,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
@@ -704,14 +718,22 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = 8L884L537J;
ENABLE_APP_SANDBOX = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 26.0;
PRODUCT_BUNDLE_IDENTIFIER = org.markettakers.arbiter;
PROVISIONING_PROFILE_SPECIFIER = "";
RUNTIME_EXCEPTION_ALLOW_JIT = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
@@ -724,14 +746,22 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = 8L884L537J;
ENABLE_APP_SANDBOX = YES;
ENABLE_INCOMING_NETWORK_CONNECTIONS = YES;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 26.0;
PRODUCT_BUNDLE_IDENTIFIER = org.markettakers.arbiter;
PROVISIONING_PROFILE_SPECIFIER = "";
RUNTIME_EXCEPTION_ALLOW_JIT = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
@@ -740,6 +770,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Manual;
MACOSX_DEPLOYMENT_TARGET = 11.0;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
@@ -748,6 +779,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
MACOSX_DEPLOYMENT_TARGET = 11.0;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;

View File

@@ -2,11 +2,7 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>keychain-access-groups</key>
<array/>
</dict>
</plist>

View File

@@ -4,5 +4,7 @@
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>keychain-access-groups</key>
<array/>
</dict>
</plist>

View File

@@ -282,7 +282,7 @@ packages:
source: hosted
version: "7.0.1"
fixnum:
dependency: transitive
dependency: "direct main"
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
@@ -770,7 +770,7 @@ packages:
source: hosted
version: "1.5.2"
protobuf:
dependency: transitive
dependency: "direct main"
description:
name: protobuf
sha256: "75ec242d22e950bdcc79ee38dd520ce4ee0bc491d7fadc4ea47694604d22bf06"

View File

@@ -26,8 +26,10 @@ dependencies:
cryptography_flutter: ^2.3.4
riverpod_annotation: ^4.0.0
grpc: ^5.1.0
fixnum: ^1.1.1
flutter_hooks: ^0.21.3+1
auto_route: ^11.1.0
protobuf: ^6.0.0
dev_dependencies:
flutter_test:
@@ -39,4 +41,4 @@ dev_dependencies:
auto_route_generator: ^10.4.0
flutter:
uses-material-design: true
uses-material-design: true