Merge pull request #5 from TheNeikos/claude/fix-ios-websocket-xDpcb
Make TLS certificate optional and add dual HTTP/HTTPS support
This commit is contained in:
commit
0df86e79ff
2 changed files with 92 additions and 17 deletions
|
|
@ -50,11 +50,26 @@ struct Args {
|
||||||
#[arg(short, long, conflicts_with = "verbose")]
|
#[arg(short, long, conflicts_with = "verbose")]
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
|
|
||||||
|
/// TLS certificate file (PEM format)
|
||||||
|
///
|
||||||
|
/// When provided along with --key, the server will use HTTPS/WSS.
|
||||||
|
/// A plain HTTP listener is also started on --http-port for clients
|
||||||
|
/// that cannot validate the certificate (e.g. iOS with self-signed certs).
|
||||||
|
/// If omitted, the server runs over plain HTTP only.
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
certificate: PathBuf,
|
certificate: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// TLS private key file (PEM format)
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
key: PathBuf,
|
key: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Port for the plain HTTP listener (used alongside HTTPS)
|
||||||
|
///
|
||||||
|
/// When TLS is enabled, a second HTTP-only listener is started on this
|
||||||
|
/// port so that iOS devices (which reject self-signed WSS certs) can
|
||||||
|
/// connect via ws:// instead.
|
||||||
|
#[arg(long, default_value_t = 8081, value_name = "PORT")]
|
||||||
|
http_port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Messages sent between peers via the signaling server
|
/// Messages sent between peers via the signaling server
|
||||||
|
|
@ -199,6 +214,8 @@ async fn main() {
|
||||||
.parse()
|
.parse()
|
||||||
.expect("Invalid host:port combination");
|
.expect("Invalid host:port combination");
|
||||||
|
|
||||||
|
let use_tls = args.certificate.is_some() && args.key.is_some();
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
println!(" ╭─────────────────────────────────────────╮");
|
println!(" ╭─────────────────────────────────────────╮");
|
||||||
println!(" │ \x1b[1;33mLAN Share\x1b[0m │");
|
println!(" │ \x1b[1;33mLAN Share\x1b[0m │");
|
||||||
|
|
@ -206,12 +223,27 @@ async fn main() {
|
||||||
println!(" ╰─────────────────────────────────────────╯");
|
println!(" ╰─────────────────────────────────────────╯");
|
||||||
println!();
|
println!();
|
||||||
println!(" \x1b[1mServer running at:\x1b[0m");
|
println!(" \x1b[1mServer running at:\x1b[0m");
|
||||||
|
|
||||||
|
if use_tls {
|
||||||
|
println!(" → https://{}", addr);
|
||||||
|
let http_addr: SocketAddr = format!("{}:{}", args.host, args.http_port)
|
||||||
|
.parse()
|
||||||
|
.expect("Invalid host:http_port combination");
|
||||||
|
println!(" → http://{} \x1b[90m(plain HTTP for iOS)\x1b[0m", http_addr);
|
||||||
|
if args.host == "0.0.0.0" {
|
||||||
|
if let Ok(local_ip) = local_ip_address::local_ip() {
|
||||||
|
println!(" → https://{}:{}", local_ip, args.port);
|
||||||
|
println!(" → http://{}:{} \x1b[90m(plain HTTP for iOS)\x1b[0m", local_ip, args.http_port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
println!(" → http://{}", addr);
|
println!(" → http://{}", addr);
|
||||||
if args.host == "0.0.0.0" {
|
if args.host == "0.0.0.0" {
|
||||||
if let Ok(local_ip) = local_ip_address::local_ip() {
|
if let Ok(local_ip) = local_ip_address::local_ip() {
|
||||||
println!(" → http://{}:{}", local_ip, args.port);
|
println!(" → http://{}:{}", local_ip, args.port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
println!();
|
println!();
|
||||||
println!(" \x1b[90mOpen this URL in browsers on your local network.\x1b[0m");
|
println!(" \x1b[90mOpen this URL in browsers on your local network.\x1b[0m");
|
||||||
println!(" \x1b[90mPress Ctrl+C to stop the server.\x1b[0m");
|
println!(" \x1b[90mPress Ctrl+C to stop the server.\x1b[0m");
|
||||||
|
|
@ -219,13 +251,36 @@ async fn main() {
|
||||||
|
|
||||||
info!("Server starting on {}", addr);
|
info!("Server starting on {}", addr);
|
||||||
|
|
||||||
|
if use_tls {
|
||||||
|
let cert = args.certificate.unwrap();
|
||||||
|
let key = args.key.unwrap();
|
||||||
|
let config = RustlsConfig::from_pem_file(cert, key).await.unwrap();
|
||||||
|
|
||||||
let config = RustlsConfig::from_pem_file(args.certificate, args.key).await.unwrap();
|
// Start a plain HTTP listener alongside HTTPS so that iOS devices
|
||||||
|
// (which silently reject self-signed WSS certificates) can connect
|
||||||
|
// via ws:// instead.
|
||||||
|
let http_addr: SocketAddr = format!("{}:{}", args.host, args.http_port)
|
||||||
|
.parse()
|
||||||
|
.expect("Invalid host:http_port combination");
|
||||||
|
let http_app = app.clone();
|
||||||
|
info!("Plain HTTP listener on {}", http_addr);
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let listener = tokio::net::TcpListener::bind(http_addr).await.unwrap();
|
||||||
|
axum::serve(listener, http_app.into_make_service_with_connect_info::<SocketAddr>())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
axum_server::bind_rustls(addr, config)
|
axum_server::bind_rustls(addr, config)
|
||||||
.serve(app.into_make_service_with_connect_info::<SocketAddr>())
|
.serve(app.into_make_service_with_connect_info::<SocketAddr>())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||||
|
axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn serve_index() -> impl IntoResponse {
|
async fn serve_index() -> impl IntoResponse {
|
||||||
|
|
|
||||||
|
|
@ -504,7 +504,25 @@
|
||||||
connectionStatus.textContent = 'Establishing WebSocket connection...';
|
connectionStatus.textContent = 'Establishing WebSocket connection...';
|
||||||
state.ws = new WebSocket(wsUrl);
|
state.ws = new WebSocket(wsUrl);
|
||||||
|
|
||||||
|
// iOS Safari silently rejects wss:// connections to servers with
|
||||||
|
// self-signed certificates — onopen never fires and the UI hangs.
|
||||||
|
// Use a timeout to detect this and show a helpful message.
|
||||||
|
const connectTimeout = setTimeout(() => {
|
||||||
|
if (state.ws && state.ws.readyState !== WebSocket.OPEN) {
|
||||||
|
console.warn('WebSocket connection timed out');
|
||||||
|
state.ws.close();
|
||||||
|
if (window.location.protocol === 'https:') {
|
||||||
|
const httpPort = parseInt(window.location.port || '443', 10) + 1;
|
||||||
|
const httpUrl = `http://${window.location.hostname}:${httpPort}`;
|
||||||
|
connectionStatus.innerHTML =
|
||||||
|
'Connection failed — iOS may not support self-signed WSS certificates.<br>' +
|
||||||
|
'Try the plain HTTP URL instead: <a href="' + httpUrl + '" style="color:#60a5fa;text-decoration:underline">' + httpUrl + '</a>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
state.ws.onopen = () => {
|
state.ws.onopen = () => {
|
||||||
|
clearTimeout(connectTimeout);
|
||||||
console.log('WebSocket connected');
|
console.log('WebSocket connected');
|
||||||
state.reconnectAttempts = 0;
|
state.reconnectAttempts = 0;
|
||||||
connectionStatus.textContent = 'Connected, waiting for welcome...';
|
connectionStatus.textContent = 'Connected, waiting for welcome...';
|
||||||
|
|
@ -516,6 +534,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
state.ws.onclose = () => {
|
state.ws.onclose = () => {
|
||||||
|
clearTimeout(connectTimeout);
|
||||||
console.log('WebSocket disconnected');
|
console.log('WebSocket disconnected');
|
||||||
updateConnectionStatus(false);
|
updateConnectionStatus(false);
|
||||||
|
|
||||||
|
|
@ -529,6 +548,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
state.ws.onerror = (err) => {
|
state.ws.onerror = (err) => {
|
||||||
|
clearTimeout(connectTimeout);
|
||||||
console.error('WebSocket error:', err);
|
console.error('WebSocket error:', err);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue