Fix iOS WebSocket hang with self-signed TLS certificates
iOS Safari silently rejects wss:// connections to servers using self-signed certificates — the trust exception accepted for the page does not extend to WebSocket handshakes. This causes the UI to hang on "Establishing WebSocket connection..." indefinitely. - Make --certificate and --key optional; server runs plain HTTP when omitted - When TLS is enabled, also start a plain HTTP listener on --http-port (default 8081) so iOS clients can connect via ws:// instead of wss:// - Add a 5-second connection timeout on the frontend that detects the hang and shows a clickable link to the HTTP fallback URL https://claude.ai/code/session_01VJ4CsBALnYcVhFJpY5cD5k
This commit is contained in:
parent
bd3a5e2af0
commit
c617a371cf
2 changed files with 92 additions and 17 deletions
|
|
@ -50,11 +50,26 @@ struct Args {
|
|||
#[arg(short, long, conflicts_with = "verbose")]
|
||||
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)]
|
||||
certificate: PathBuf,
|
||||
certificate: Option<PathBuf>,
|
||||
|
||||
/// TLS private key file (PEM format)
|
||||
#[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
|
||||
|
|
@ -199,6 +214,8 @@ async fn main() {
|
|||
.parse()
|
||||
.expect("Invalid host:port combination");
|
||||
|
||||
let use_tls = args.certificate.is_some() && args.key.is_some();
|
||||
|
||||
println!();
|
||||
println!(" ╭─────────────────────────────────────────╮");
|
||||
println!(" │ \x1b[1;33mLAN Share\x1b[0m │");
|
||||
|
|
@ -206,10 +223,25 @@ async fn main() {
|
|||
println!(" ╰─────────────────────────────────────────╯");
|
||||
println!();
|
||||
println!(" \x1b[1mServer running at:\x1b[0m");
|
||||
println!(" → http://{}", addr);
|
||||
if args.host == "0.0.0.0" {
|
||||
if let Ok(local_ip) = local_ip_address::local_ip() {
|
||||
println!(" → http://{}:{}", local_ip, args.port);
|
||||
|
||||
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);
|
||||
if args.host == "0.0.0.0" {
|
||||
if let Ok(local_ip) = local_ip_address::local_ip() {
|
||||
println!(" → http://{}:{}", local_ip, args.port);
|
||||
}
|
||||
}
|
||||
}
|
||||
println!();
|
||||
|
|
@ -219,13 +251,36 @@ async fn main() {
|
|||
|
||||
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)
|
||||
.serve(app.into_make_service_with_connect_info::<SocketAddr>())
|
||||
.await
|
||||
.unwrap();
|
||||
axum_server::bind_rustls(addr, config)
|
||||
.serve(app.into_make_service_with_connect_info::<SocketAddr>())
|
||||
.await
|
||||
.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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue