rahti_native/lib.rs
1//! Running a Rahti application inside a native package.
2//!
3//! A native Rahti package is the application it already was. The Rust backend
4//! is compiled for the target platform and runs inside the installed program;
5//! the generated Axum router answers on a loopback socket; and the operating
6//! system's WebView loads the same server-rendered HTML, the same PulsePoint
7//! bundle, the same RPCs and the same WebSockets it would over the network.
8//!
9//! **This is not a compiler from HTML to native widgets.** Nothing here
10//! translates markup. What the user sees is a WebView, and calling its
11//! contents native controls would be untrue.
12//!
13//! ## What this crate is, and is not
14//!
15//! It is the platform-neutral half: where a packaged application's files live,
16//! how its server binds, how its session key survives a restart, and which
17//! native commands its JavaScript may call. It has **no Tauri dependency**, so
18//! it compiles and tests in an ordinary `cargo test --workspace` run.
19//!
20//! The Tauri half is generated into the application's own `native/` directory
21//! by `cargo rahti native init`. That shell owns the identifier, the icons, the
22//! permissions and the window — application decisions, in application files.
23//!
24//! It is also not the application's startup. Connecting a database, applying
25//! migrations and installing an auth policy are decisions a project makes in
26//! its own `src/lib.rs`; this crate starts a server, it does not start *your*
27//! server.
28//!
29//! ## The shape of a launch
30//!
31//! ```no_run
32//! # use rahti_native::{AppPaths, EmbeddedServer, NativeError};
33//! # async fn example(router: axum::Router) -> Result<(), NativeError> {
34//! // 1. Where this installation keeps its files.
35//! let paths = AppPaths::resolve("com.example.myapp")?;
36//! paths.prepare()?;
37//!
38//! // 2. Write the embedded assets into internal storage, and put the paths
39//! // in the environment the application is about to read.
40//! let assets = [rahti_native::EmbeddedAsset { path: "js/main.js", bytes: b"" }];
41//! rahti_native::stage_embedded_assets(&assets, &paths.public(), "1.0.0")?;
42//! paths.apply_environment(&paths.public());
43//!
44//! // 3. Bind first, so the port is real before anything is told to go there.
45//! let server = EmbeddedServer::bind().await?;
46//! let url = server.base_url();
47//!
48//! // 4. Serve, then create the window. Never the other way round.
49//! let running = server.serve(router);
50//! running.wait_until_ready().await?;
51//! // open_webview(&url);
52//!
53//! # let _ = (url, running);
54//! # Ok(())
55//! # }
56//! ```
57//!
58//! Step 3 before step 4 is the whole of the startup race: [`EmbeddedServer`]
59//! has no constructor that produces a URL it is not already listening on.
60
61// Framework surface: exported for native shells to use, so "nothing in this
62// crate calls it yet" is not a defect.
63#![allow(dead_code)]
64
65mod bridge;
66mod capabilities;
67mod config;
68mod error;
69mod gate;
70mod headers;
71mod paths;
72mod platform;
73mod secret;
74mod server;
75
76const DEV_ENV: &str = "RAHTI_DEV";
77
78#[cfg(test)]
79#[path = "tests/mod.rs"]
80mod tests;
81
82pub use bridge::{BRIDGE_PATH, bridge_route, bridge_script};
83pub use capabilities::{Capability, NativeCommand, commands, is_allowed, is_external_url};
84pub use config::{
85 AndroidConfig, AuthConfig, BundleConfig, DatabaseConfig, DatabaseMode, MIN_ANDROID_SDK,
86 NativeConfig, SCHEMA_VERSION, SecurityConfig, TARGETS, WindowConfig, check_identifier,
87 check_product_name, check_version, default_csp, superseded_csp,
88};
89pub use error::NativeError;
90pub use gate::{LAUNCH_PARAM, LaunchToken, gate, launch_token};
91pub use headers::{csp, install_csp, secure, security_headers};
92pub use paths::{ASSET_STAMP, AppPaths, EmbeddedAsset, stage_embedded_assets, stage_public_assets};
93pub use platform::Platform;
94pub use secret::{SECRET_FILE, install_session_secret, session_secret};
95pub use server::{EmbeddedServer, RunningServer};
96
97/// Turn off every development affordance, for a package that is being shipped.
98///
99/// Release builds already default to dev mode off, so this is belt and braces
100/// — but the belt matters here in a way it does not on a server. `RAHTI_DEV`
101/// is read from the process environment, an installed application inherits the
102/// environment of whoever launched it, and a user with `RAHTI_DEV=1` exported
103/// for their own project would otherwise start a shipped application with its
104/// diagnostics endpoint, its reload stream and its `.rahti/dev.log` writer
105/// live — inside a WebView with access to native commands.
106///
107/// So a release package states the value rather than inheriting it. A debug
108/// build is left alone: that is `cargo rahti native dev`, where the
109/// diagnostics are the point.
110pub fn harden_release() {
111 if cfg!(debug_assertions) {
112 return;
113 }
114 // SAFETY: called from the native host before any task is spawned and
115 // before the router is built, which is the same single-threaded moment
116 // `main` sets anything else.
117 unsafe {
118 std::env::set_var(DEV_ENV, "0");
119 }
120}