-
Notifications
You must be signed in to change notification settings - Fork 1
Architecture
Launcher is a Tauri v2 desktop application with a Rust backend and a Nuxt 4 / Vue 3 frontend.
launcher/
├── app/ # Frontend (Nuxt 4)
│ ├── app.vue # Root: renders the main UI, or the console
│ │ # when running in a console-* webview window
│ ├── assets/css/main.css # Tailwind CSS v4 design tokens
│ ├── components/ # Vue components
│ │ ├── BriefServerInfo.vue # Server row in the list
│ │ ├── ConnectionInput.vue # Reusable form input
│ │ ├── ConsoleLog.vue # Native console window UI (stdout/stderr)
│ │ ├── InsertableDropdown.vue # Dropdown with inline creation
│ │ └── TrustCertModal.vue # Certificate trust / cert-change dialog
│ ├── composables/ # Vue composables
│ │ ├── useConfirmRejectModal.ts # Imperative modal mounting
│ │ ├── useConsole.ts # Subscribes a console window to its stream
│ │ ├── useConsoleTheme.ts # Per-console light/dark toggle
│ │ ├── useTheme.ts # Main-window theme
│ │ └── useZoom.ts # Keyboard zoom (Cmd/Ctrl +/-/0)
│ ├── enums.ts # Shared frontend enums
│ ├── pages/ # File-based routing
│ │ ├── index.vue # Main server list
│ │ └── connections/[id].vue # Connection editor
│ └── types.d.ts # TypeScript interfaces
├── src-tauri/ # Backend (Rust)
│ ├── src/
│ │ ├── main.rs # Tauri commands and app setup
│ │ ├── connection.rs # ConnectionStore, persistence, cert pins
│ │ ├── webstart.rs # JNLP parsing, JAR downloading, Java process
│ │ ├── tls.rs # Per-connection TLS cert pinning (TOFU)
│ │ └── console.rs # Native console: streams stdout/stderr
│ ├── capabilities/
│ │ ├── default.json # Main-window permissions
│ │ └── console.json # Console-window permissions (console-*)
│ ├── Cargo.toml # Rust dependencies
│ └── tauri.conf.json # Tauri app configuration
└── nuxt.config.ts # Nuxt configuration
Commands are the IPC bridge between frontend and backend. They use rename_all = "snake_case", so the JavaScript side must use snake_case parameter names.
| Command | Returns | Description |
|---|---|---|
launch |
Result<String, String> |
Checks Java, verifies the server cert against the connection's pin, downloads JARs, launches the administrator. Ok value is a JSON object with a code (see below) |
set_pin |
Result<(), String> |
Stores the operator-approved leaf-cert fingerprint as the connection's pin |
save |
Result<String, String> |
Saves a connection entry |
delete |
Result<String, String> |
Deletes a connection |
import |
Result<String, String> |
Imports connections from a JSON file (with duplicate detection) |
load_connections |
String |
Returns all connections as JSON array |
load_single_connection |
Result<Value, String> |
Returns one connection by ID |
get_default_connectionentry |
Result<Value, String> |
Returns a default connection template |
get_all_groups |
Result<Value, String> |
Returns all group names |
get_all_engine_types |
Result<Value, String> |
Returns all engine-type names |
console_subscribe |
Result<(), String> |
A console window subscribes to its output: replays the backlog, then streams live lines over a Channel
|
console_save |
Result<(), String> |
Writes the console contents to a chosen file path |
get_launcher_info |
String |
Returns app version info |
The launch Ok string carries a result code so the frontend knows what to do next:
| Code | Meaning |
|---|---|
0 |
Administrator launched |
2 |
First connect to this server — cert details returned for the trust prompt |
3 |
Server certificate changed — cert details returned for a confirm-or-abort warning |
-1 |
Error — msg describes it (e.g. Java not found, server unreachable) |
- Frontend calls
launchwith a connection ID and a progressChannel - Backend runs a fail-fast
java -versioncheck (using the connection's Java Home, elsejavaon PATH). If Java cannot run it returnscode -1with a "Java (with JavaFX) not found" message — before any network work - Captures the server's leaf TLS certificate via a handshake to
/webstart.jnlpand computes its SHA-256 - Compares that fingerprint to the connection's stored pin (
pinnedCertSha256):- No pin yet → returns
code 2with cert details so the operator can trust it (TOFU) - Fingerprint differs from the pin → returns
code 3with cert details (the cert-changed warning) - Fingerprint matches → proceeds. On a trust prompt the frontend calls
set_pinand retrieslaunch
- No pin yet → returns
- Downloads the JNLP and JARs over a pinned rustls client that rejects any leaf whose SHA-256 does not match the pin, sending progress over the Channel. The vendor/version shared cache is reused, skipping downloads whose SHA-256 already matches
- Sanitizes JVM arguments (blocks dangerous flags like
-javaagent:) and builds the classpath (Mirth/engine JARs first) - Launches the JavaFX administrator, passing the username and password as trailing launch arguments to the main class
- If "Show console" is enabled, captures the process's stdout and stderr and opens a native console window — but only after the process spawns successfully, so a failed launch never pops an empty console
-
ConnectionStoremanages all connections in aMutex<HashMap<String, Arc<ConnectionEntry>>> - Persisted to
~/.launcher/launcher-data.json(written to a temp file, fsynced, then atomically renamed so a crash can't truncate it). Each entry carries its ownpinnedCertSha256 - JAR cache stored in
~/.launcher/cache/(vendor/version shared cache) - Diagnostic logs in
~/.launcher/logs/ - There is no separate trust-store file — the pin lives inline on the connection
- The trust boundary is per-connection TLS certificate pinning (trust-on-first-use), implemented in
tls.rs. Integration engine servers commonly present self-signed certs, so chain validation is the wrong model - On first connect, the launcher captures the server's leaf-cert SHA-256 and prompts the operator to trust that fingerprint. The approved value is pinned to the connection (
pinnedCertSha256) - On every later connect, the pinned rustls client accepts only a leaf whose SHA-256 equals the pin; a changed cert is rejected and surfaced as the cert-changed warning (
code 3) - The verifier still validates the TLS handshake signature (proof the server holds the private key) via the ring crypto provider — pinning the public cert bytes alone would let anyone replay them
- Hostname/SAN is intentionally not checked; the pin replaces it, which is what self-signed engine certs (which rarely carry a matching SAN) require
-
native-tls(vendored) is retained only for the connectivity status probe, which needs reqwest'sacceptInvalidHostnames; the launch download path uses the pinned rustls client, notdanger_accept_invalid_certs - JNLP version attributes are sanitized to prevent path traversal
- JVM arguments from JNLP are filtered to block code execution flags (
-javaagent:,-agentpath:, etc.)
When "Show console" is enabled, output is streamed to a dedicated Tauri webview window (label console-<id>) instead of a bundled second Java process. console.rs reads the administrator's stdout and stderr on background threads and pushes each line into a per-window buffer. A window that attaches late replays a capped backlog (MAX_BACKLOG_LINES) and then receives live lines over a Channel, with the buffer lock held across the handover so no line is lost or duplicated. The window closes automatically on a clean exit (status 0) and stays open on a non-zero exit so the error stays readable. Relaunching into an open console bumps a generation counter and shows a "relaunched" separator, so a superseded process's later exit can't flip a live console to "exited".
The main window's capabilities are declared in src-tauri/capabilities/default.json:
| Permission | Purpose |
|---|---|
core:default |
Core IPC and event system |
dialog:default |
Native file picker and confirm dialogs |
core:webview:allow-set-webview-zoom |
Keyboard zoom support |
http:default |
HTTP requests (scoped to http/https URLs, for server connectivity checks) |
shell:allow-open |
Open URLs in default browser (restricted to the wiki URL only) |
Console windows have their own capability in src-tauri/capabilities/console.json (matched by the console-* window glob): core:default, dialog:default, clipboard-manager:allow-write-text, and core:webview:allow-set-webview-zoom.
Tailwind CSS v4 with @theme design tokens in app/assets/css/main.css:
| Token | Value | Usage |
|---|---|---|
surface-0 |
#24272e |
Page background |
surface-1 |
#2c2f37 |
Cards, inputs |
surface-2 |
#353840 |
Hover, selected states |
surface-3 |
#3e414a |
Highest elevation |
accent |
#4f7df7 |
Primary actions |
text-primary |
#e2e5eb |
Main text |
text-secondary |
#9ca3b0 |
Supporting text |
Icons use Phosphor Icons via the ph: prefix with the @nuxt/icon module.
-
Channel API:
launchusestauri::ipc::Channelto stream progress from Rust to the frontend, andconsole_subscribeuses one to stream console output to a console window -
Result-code loop:
index.vueinvokeslaunchin a bounded loop so a first-use trust (code 2) or cert-change (code 3) can show a modal, callset_pin, and retry, while a cert that keeps changing can't re-prompt forever -
Imperative modals:
useConfirmRejectModalmountsTrustCertModalprogrammatically for both the first-use trust prompt and the cert-change confirmation, returning a Promise -
Console windows: each console is a separate Tauri webview window;
app.vuedetects aconsole-*window label and rendersConsoleLog.vue, which owns its own light/dark theme and auto-scroll independently of the main window -
Module-level state:
useZoomuses a module-scopedreffor app-wide singleton zoom state -
CSS tooltips: Custom
[data-tooltip]attribute with::afterpseudo-element (nativetitletooltips are unreliable in Tauri's WKWebView). Usedata-tooltip-belowfor elements near the top of the window - localStorage persistence: Sort mode, collapsed groups, zoom level, and console theme all persist to localStorage
-
Rust: Prefer
?operator and.map_err()over.unwrap(). Return errors to frontend viaResult<T, String>. Use.expect("descriptive message")only for mutex locks (poisoning is unrecoverable). -
Frontend: Wrap
invokecalls in try/catch and display errors inline via the error bar at the bottom of the main screen.