feat: device hot-reload, tools-relative adb, auto port bind (#3586)
* wip: bind to 0.0.0.0 instead of 127.0.0.1 * dont hang in background - ignore suspend commands when tty is disabled * use system adb * pass configs through to android mobile launch * fix: line sizes + clear pushes frame * dont resume window state on mobile * fix screen sizing for ipad * attempt and fail to implement codesigning * Clippy and note about safety in android bindings
This commit is contained in:
1 parent
e199e86dad
commit
8334f7fbaa
22 files changed
+620
-212
No files matched your search
@@ -31,10 +31,9 @@ use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
fs, io,
|
||||
net::{IpAddr, SocketAddr, TcpListener},
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
sync::RwLock,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use tower_http::{
|
||||
cors::Any,
|
||||
@@ -49,7 +48,8 @@ use tower_http::{
|
||||
/// which carries all the message types. This would make it easier for us to add more message types
|
||||
/// and better tooling on the pages that we serve.
|
||||
pub(crate) struct WebServer {
|
||||
devserver_ip: IpAddr,
|
||||
devserver_exposed_ip: IpAddr,
|
||||
devserver_bind_ip: IpAddr,
|
||||
devserver_port: u16,
|
||||
proxied_port: Option<u16>,
|
||||
hot_reload_sockets: Vec<WebSocket>,
|
||||
@@ -71,18 +71,43 @@ impl WebServer {
|
||||
let (hot_reload_sockets_tx, hot_reload_sockets_rx) = futures_channel::mpsc::unbounded();
|
||||
let (build_status_sockets_tx, build_status_sockets_rx) = futures_channel::mpsc::unbounded();
|
||||
|
||||
let devserver_ip = args.address.addr;
|
||||
let devserver_port = args.address.port;
|
||||
let devserver_address = SocketAddr::new(devserver_ip, devserver_port);
|
||||
const SELF_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
|
||||
|
||||
// Use 0.0.0.0 as the default address if none is specified - this will let us expose the
|
||||
// devserver to the network (for other devices like phones/embedded)
|
||||
let devserver_bind_ip = args.address.addr.unwrap_or(SELF_IP);
|
||||
|
||||
// If the user specified a port, use that, otherwise use any available port, preferring 8080
|
||||
let devserver_port = args
|
||||
.address
|
||||
.port
|
||||
.unwrap_or_else(|| get_available_port(devserver_bind_ip, Some(8080)).unwrap_or(8080));
|
||||
|
||||
// All servers will end up behind us (the devserver) but on a different port
|
||||
// This is so we can serve a loading screen as well as devtools without anything particularly fancy
|
||||
let proxied_port = args
|
||||
.should_proxy_build()
|
||||
.then(|| get_available_port(devserver_ip))
|
||||
.then(|| get_available_port(devserver_bind_ip, None))
|
||||
.flatten();
|
||||
|
||||
let proxied_address = proxied_port.map(|port| SocketAddr::new(devserver_ip, port));
|
||||
// Create the listener that we'll pass into the devserver, but save its IP here so
|
||||
// we can display it to the user in the tui
|
||||
let devserver_bind_address = SocketAddr::new(devserver_bind_ip, devserver_port);
|
||||
let listener = std::net::TcpListener::bind(devserver_bind_address).with_context(|| {
|
||||
anyhow::anyhow!(
|
||||
"Failed to bind server to: {devserver_bind_address}, is there another devserver running?\nTo run multiple devservers, use the --port flag to specify a different port"
|
||||
)
|
||||
})?;
|
||||
|
||||
// If the IP is 0.0.0.0, we need to get the actual IP of the machine
|
||||
// This will let ios/android/network clients connect to the devserver
|
||||
let devserver_exposed_ip = if devserver_bind_ip == SELF_IP {
|
||||
local_ip_address::local_ip().unwrap_or(devserver_bind_ip)
|
||||
} else {
|
||||
devserver_bind_ip
|
||||
};
|
||||
|
||||
let proxied_address = proxied_port.map(|port| SocketAddr::new(devserver_exposed_ip, port));
|
||||
|
||||
// Set up the router with some shared state that we'll update later to reflect the current state of the build
|
||||
let build_status = SharedStatus::new_with_starting_build();
|
||||
@@ -95,14 +120,6 @@ impl WebServer {
|
||||
build_status.clone(),
|
||||
)?;
|
||||
|
||||
// Create the listener that we'll pass into the devserver, but save its IP here so
|
||||
// we can display it to the user in the tui
|
||||
let listener = std::net::TcpListener::bind(devserver_address).with_context(|| {
|
||||
anyhow::anyhow!(
|
||||
"Failed to bind server to: {devserver_address}, is there another devserver running?\nTo run multiple devservers, use the --port flag to specify a different port"
|
||||
)
|
||||
})?;
|
||||
|
||||
// And finally, start the server mainloop
|
||||
tokio::spawn(devserver_mainloop(
|
||||
krate.config.web.https.clone(),
|
||||
@@ -113,7 +130,8 @@ impl WebServer {
|
||||
Ok(Self {
|
||||
build_status,
|
||||
proxied_port,
|
||||
devserver_ip,
|
||||
devserver_bind_ip,
|
||||
devserver_exposed_ip,
|
||||
devserver_port,
|
||||
hot_reload_sockets: Default::default(),
|
||||
build_status_sockets: Default::default(),
|
||||
@@ -315,13 +333,13 @@ impl WebServer {
|
||||
|
||||
/// Get the address the devserver should run on
|
||||
pub fn devserver_address(&self) -> SocketAddr {
|
||||
SocketAddr::new(self.devserver_ip, self.devserver_port)
|
||||
SocketAddr::new(self.devserver_exposed_ip, self.devserver_port)
|
||||
}
|
||||
|
||||
// Get the address the server should run on if we're serving the user's server
|
||||
pub fn proxied_server_address(&self) -> Option<SocketAddr> {
|
||||
self.proxied_port
|
||||
.map(|port| SocketAddr::new(self.devserver_ip, port))
|
||||
.map(|port| SocketAddr::new(self.devserver_exposed_ip, port))
|
||||
}
|
||||
|
||||
pub fn server_address(&self) -> Option<SocketAddr> {
|
||||
@@ -330,6 +348,19 @@ impl WebServer {
|
||||
_ => self.proxied_server_address(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the address the server is running - showing 127.0.0.1 if the devserver is bound to 0.0.0.0
|
||||
/// This is designed this way to not confuse users who expect the devserver to be bound to localhost
|
||||
/// ... which it is, but they don't know that 0.0.0.0 also serves localhost.
|
||||
pub fn displayed_address(&self) -> Option<SocketAddr> {
|
||||
let mut address = self.server_address()?;
|
||||
|
||||
if self.devserver_bind_ip == IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)) {
|
||||
address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), address.port());
|
||||
}
|
||||
|
||||
Some(address)
|
||||
}
|
||||
}
|
||||
|
||||
async fn devserver_mainloop(
|
||||
@@ -382,6 +413,7 @@ fn build_devserver_router(
|
||||
if args.should_proxy_build() {
|
||||
// For fullstack, liveview, and server, forward all requests to the inner server
|
||||
let address = fullstack_address.unwrap();
|
||||
tracing::debug!("Proxying requests to fullstack server at {address}");
|
||||
router = router.nest_service("/",super::proxy::proxy_to(
|
||||
format!("http://{address}").parse().unwrap(),
|
||||
true,
|
||||
@@ -616,7 +648,15 @@ async fn get_rustls(web_config: &WebHttpsConfig) -> Result<(String, String)> {
|
||||
///
|
||||
/// Todo: we might want to do this on every new build in case the OS tries to bind things to this port
|
||||
/// and we don't already have something bound to it. There's no great way of "reserving" a port.
|
||||
fn get_available_port(address: IpAddr) -> Option<u16> {
|
||||
fn get_available_port(address: IpAddr, prefer: Option<u16>) -> Option<u16> {
|
||||
// First, try to bind to the preferred port
|
||||
if let Some(port) = prefer {
|
||||
if let Ok(_listener) = TcpListener::bind((address, port)) {
|
||||
return Some(port);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, try to bind to any port and return the first one we can
|
||||
TcpListener::bind((address, 0))
|
||||
.map(|listener| listener.local_addr().unwrap().port())
|
||||
.ok()
|
||||
|
||||
Reference in new issue
Block a user