feat: allow autoreload websocket connection to work outside of localhost (#1548)

* Updated client reloading to use window.location.protocol/host to determine websocket connection. Added optional config reload_external_port to provide further control of the client websocket connection. These changes allow reloading while accessing the served site from outside of localhost.
This commit is contained in:
Matt Cuneo 2023-08-26 05:54:22 +10:00 committed by GitHub
parent abeca70625
commit a789100e22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 49 additions and 5 deletions

View File

@ -7,14 +7,18 @@ extern crate tracing;
#[tracing::instrument(level = "trace", fields(error), skip_all)]
fn autoreload(nonce_str: &str, options: &LeptosOptions) -> String {
let site_ip = &options.site_addr.ip().to_string();
let reload_port = options.reload_port;
let reload_port = match options.reload_external_port {
Some(val) => val,
None => options.reload_port,
};
match std::env::var("LEPTOS_WATCH").is_ok() {
true => format!(
r#"
<script crossorigin=""{nonce_str}>(function () {{
{}
let ws = new WebSocket('ws://{site_ip}:{reload_port}/live_reload');
let protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
let host = window.location.hostname;
let ws = new WebSocket(protocol + host + ':{reload_port}/live_reload');
ws.onmessage = (ev) => {{
let msg = JSON.parse(ev.data);
if (msg.all) window.location.reload();

View File

@ -56,6 +56,11 @@ pub struct LeptosOptions {
#[builder(default = default_reload_port())]
#[serde(default = "default_reload_port")]
pub reload_port: u32,
/// The port the Websocket watcher listens on when on the client, e.g., when behind a reverse proxy.
/// Defaults to match reload_port
#[builder(default)]
#[serde(default)]
pub reload_external_port: Option<u32>,
}
impl LeptosOptions {
@ -84,6 +89,12 @@ impl LeptosOptions {
.parse()?,
reload_port: env_w_default("LEPTOS_RELOAD_PORT", "3001")?
.parse()?,
reload_external_port: match env_wo_default(
"LEPTOS_RELOAD_EXTERNAL_PORT",
)? {
Some(val) => Some(val.parse()?),
None => None,
},
})
}
}
@ -107,7 +118,13 @@ fn default_site_addr() -> SocketAddr {
fn default_reload_port() -> u32 {
3001
}
fn env_wo_default(key: &str) -> Result<Option<String>, LeptosConfigError> {
match std::env::var(key) {
Ok(val) => Ok(Some(val)),
Err(VarError::NotPresent) => Ok(None),
Err(e) => Err(LeptosConfigError::EnvVarError(format!("{key}: {e}"))),
}
}
fn env_w_default(
key: &str,
default: &str,

View File

@ -1,4 +1,4 @@
use crate::{env_w_default, from_str, Env, LeptosOptions};
use crate::{env_w_default, env_wo_default, from_str, Env, LeptosOptions};
use std::{net::SocketAddr, str::FromStr};
#[test]
@ -29,6 +29,17 @@ fn env_w_default_test() {
);
}
#[test]
fn env_wo_default_test() {
std::env::set_var("LEPTOS_CONFIG_ENV_TEST", "custom");
assert_eq!(
env_wo_default("LEPTOS_CONFIG_ENV_TEST").unwrap(),
Some(String::from("custom"))
);
std::env::remove_var("LEPTOS_CONFIG_ENV_TEST");
assert_eq!(env_wo_default("LEPTOS_CONFIG_ENV_TEST").unwrap(), None);
}
#[test]
fn try_from_env_test() {
// Test config values from environment variables
@ -37,6 +48,7 @@ fn try_from_env_test() {
std::env::set_var("LEPTOS_SITE_PKG_DIR", "my_pkg");
std::env::set_var("LEPTOS_SITE_ADDR", "0.0.0.0:80");
std::env::set_var("LEPTOS_RELOAD_PORT", "8080");
std::env::set_var("LEPTOS_RELOAD_EXTERNAL_PORT", "8080");
let config = LeptosOptions::try_from_env().unwrap();
assert_eq!(config.output_name, "app_test");
@ -48,4 +60,5 @@ fn try_from_env_test() {
SocketAddr::from_str("0.0.0.0:80").unwrap()
);
assert_eq!(config.reload_port, 8080);
assert_eq!(config.reload_external_port, Some(8080));
}

View File

@ -17,6 +17,7 @@ site-root = "my_target/site"
site-pkg-dir = "my_pkg"
site-addr = "0.0.0.0:80"
reload-port = "8080"
reload-external-port = "8080"
env = "PROD"
"#;
@ -27,6 +28,7 @@ _site-root = "my_target/site"
_site-pkg-dir = "my_pkg"
_site-addr = "0.0.0.0:80"
_reload-port = "8080"
_reload-external-port = "8080"
_env = "PROD"
"#;
@ -54,6 +56,7 @@ async fn get_configuration_from_file_ok() {
SocketAddr::from_str("0.0.0.0:80").unwrap()
);
assert_eq!(config.reload_port, 8080);
assert_eq!(config.reload_external_port, Some(8080));
}
#[tokio::test]
@ -101,6 +104,7 @@ async fn get_config_from_file_ok() {
SocketAddr::from_str("0.0.0.0:80").unwrap()
);
assert_eq!(config.reload_port, 8080);
assert_eq!(config.reload_external_port, Some(8080));
}
#[tokio::test]
@ -136,6 +140,7 @@ fn get_config_from_str_content() {
SocketAddr::from_str("0.0.0.0:80").unwrap()
);
assert_eq!(config.reload_port, 8080);
assert_eq!(config.reload_external_port, Some(8080));
}
#[tokio::test]
@ -146,6 +151,7 @@ async fn get_config_from_env() {
std::env::set_var("LEPTOS_SITE_PKG_DIR", "my_pkg");
std::env::set_var("LEPTOS_SITE_ADDR", "0.0.0.0:80");
std::env::set_var("LEPTOS_RELOAD_PORT", "8080");
std::env::set_var("LEPTOS_RELOAD_EXTERNAL_PORT", "8080");
let config = get_configuration(None).await.unwrap().leptos_options;
assert_eq!(config.output_name, "app-test");
@ -157,12 +163,14 @@ async fn get_config_from_env() {
SocketAddr::from_str("0.0.0.0:80").unwrap()
);
assert_eq!(config.reload_port, 8080);
assert_eq!(config.reload_external_port, Some(8080));
// Test default config values
std::env::remove_var("LEPTOS_SITE_ROOT");
std::env::remove_var("LEPTOS_SITE_PKG_DIR");
std::env::remove_var("LEPTOS_SITE_ADDR");
std::env::remove_var("LEPTOS_RELOAD_PORT");
std::env::set_var("LEPTOS_RELOAD_EXTERNAL_PORT", "443");
let config = get_configuration(None).await.unwrap().leptos_options;
assert_eq!(config.site_root, "target/site");
@ -172,6 +180,7 @@ async fn get_config_from_env() {
SocketAddr::from_str("127.0.0.1:3000").unwrap()
);
assert_eq!(config.reload_port, 3001);
assert_eq!(config.reload_external_port, Some(443));
}
#[test]
@ -186,4 +195,5 @@ fn leptos_options_builder_default() {
SocketAddr::from_str("127.0.0.1:3000").unwrap()
);
assert_eq!(conf.reload_port, 3001);
assert_eq!(conf.reload_external_port, None);
}