diff --git a/Cargo.lock b/Cargo.lock index 2407ec6140..881acc3f45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -486,9 +486,9 @@ dependencies = [ [[package]] name = "async-tungstenite" -version = "0.14.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8645e929ec7964448a901db9da30cd2ae8c7fecf4d6176af427837531dbbb63b" +checksum = "5682ea0913e5c20780fe5785abacb85a411e7437bf52a1bedb93ddb3972cb8dd" dependencies = [ "async-tls", "futures-io", @@ -2452,15 +2452,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" -[[package]] -name = "input_buffer" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" -dependencies = [ - "bytes 1.0.1", -] - [[package]] name = "instant" version = "0.1.9" @@ -5186,16 +5177,15 @@ checksum = "85e00391c1f3d171490a3f8bd79999b0002ae38d3da0d6a3a306c754b053d71b" [[package]] name = "tungstenite" -version = "0.13.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8dada8c1a3aeca77d6b51a4f1314e0f4b8e438b7b1b71e3ddaca8080e4093" +checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" dependencies = [ "base64 0.13.0", "byteorder", "bytes 1.0.1", "http", "httparse", - "input_buffer", "log", "rand 0.8.3", "sha-1 0.9.6", @@ -5698,7 +5688,6 @@ dependencies = [ "anyhow", "async-recursion", "async-trait", - "async-tungstenite", "chat_panel", "client", "clock", diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index bec27f84c5..ac7bbcff87 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -16,7 +16,7 @@ rpc = { path = "../rpc" } sum_tree = { path = "../sum_tree" } anyhow = "1.0.38" async-recursion = "0.3" -async-tungstenite = { version = "0.14", features = ["async-tls"] } +async-tungstenite = { version = "0.16", features = ["async-tls"] } futures = "0.3" image = "0.23" lazy_static = "1.4.0" diff --git a/crates/client/src/channel.rs b/crates/client/src/channel.rs index 90b5bbe425..d9555da7dd 100644 --- a/crates/client/src/channel.rs +++ b/crates/client/src/channel.rs @@ -599,8 +599,8 @@ mod tests { #[gpui::test] async fn test_channel_messages(mut cx: TestAppContext) { let user_id = 5; - let mut client = Client::new(); let http_client = FakeHttpClient::new(|_| async move { Ok(Response::new(404)) }); + let mut client = Client::new(http_client.clone()); let server = FakeServer::for_client(user_id, &mut client, &cx).await; let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 000f47507c..a8356bcea0 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -12,6 +12,7 @@ use async_tungstenite::tungstenite::{ http::{Request, StatusCode}, }; use gpui::{action, AsyncAppContext, Entity, ModelContext, MutableAppContext, Task}; +use http::HttpClient; use lazy_static::lazy_static; use parking_lot::RwLock; use postage::{prelude::Stream, watch}; @@ -26,7 +27,7 @@ use std::{ sync::{Arc, Weak}, time::{Duration, Instant}, }; -use surf::Url; +use surf::{http::Method, Url}; use thiserror::Error; use util::{ResultExt, TryFutureExt}; @@ -35,10 +36,8 @@ pub use rpc::*; pub use user::*; lazy_static! { - static ref COLLAB_URL: String = - std::env::var("ZED_COLLAB_URL").unwrap_or("https://collab.zed.dev:443".to_string()); - static ref SITE_URL: String = - std::env::var("ZED_SITE_URL").unwrap_or("https://zed.dev".to_string()); + static ref ZED_SERVER_URL: String = + std::env::var("ZED_SERVER_URL").unwrap_or("https://zed.dev".to_string()); static ref IMPERSONATE_LOGIN: Option = std::env::var("ZED_IMPERSONATE") .ok() .and_then(|s| if s.is_empty() { None } else { Some(s) }); @@ -56,6 +55,7 @@ pub fn init(rpc: Arc, cx: &mut MutableAppContext) { pub struct Client { peer: Arc, + http: Arc, state: RwLock, authenticate: Option Task>>>, @@ -131,7 +131,7 @@ struct ClientState { heartbeat_interval: Duration, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Credentials { pub user_id: u64, pub access_token: String, @@ -171,9 +171,10 @@ impl Drop for Subscription { } impl Client { - pub fn new() -> Arc { + pub fn new(http: Arc) -> Arc { Arc::new(Self { peer: Peer::new(), + http, state: Default::default(), authenticate: None, establish_connection: None, @@ -405,7 +406,6 @@ impl Client { match self.establish_connection(&credentials, cx).await { Ok(conn) => { - log::info!("connected to rpc address {}", *COLLAB_URL); self.state.write().credentials = Some(credentials.clone()); if !used_keychain && IMPERSONATE_LOGIN.is_none() { write_credentials_to_keychain(&credentials, cx).log_err(); @@ -416,7 +416,7 @@ impl Client { Err(EstablishConnectionError::Unauthorized) => { self.state.write().credentials.take(); if used_keychain { - cx.platform().delete_credentials(&COLLAB_URL).log_err(); + cx.platform().delete_credentials(&ZED_SERVER_URL).log_err(); self.set_status(Status::SignedOut, cx); self.authenticate_and_connect(cx).await } else { @@ -523,20 +523,57 @@ impl Client { format!("{} {}", credentials.user_id, credentials.access_token), ) .header("X-Zed-Protocol-Version", rpc::PROTOCOL_VERSION); + + let http = self.http.clone(); cx.background().spawn(async move { - if let Some(host) = COLLAB_URL.strip_prefix("https://") { - let stream = smol::net::TcpStream::connect(host).await?; - let request = request.uri(format!("wss://{}/rpc", host)).body(())?; - let (stream, _) = - async_tungstenite::async_tls::client_async_tls(request, stream).await?; - Ok(Connection::new(stream)) - } else if let Some(host) = COLLAB_URL.strip_prefix("http://") { - let stream = smol::net::TcpStream::connect(host).await?; - let request = request.uri(format!("ws://{}/rpc", host)).body(())?; - let (stream, _) = async_tungstenite::client_async(request, stream).await?; - Ok(Connection::new(stream)) - } else { - Err(anyhow!("invalid server url: {}", *COLLAB_URL))? + let mut rpc_url = format!("{}/rpc", *ZED_SERVER_URL); + let rpc_request = surf::Request::new( + Method::Get, + surf::Url::parse(&rpc_url).context("invalid ZED_SERVER_URL")?, + ); + let rpc_response = http.send(rpc_request).await?; + + if rpc_response.status().is_redirection() { + rpc_url = rpc_response + .header("Location") + .ok_or_else(|| anyhow!("missing location header in /rpc response"))? + .as_str() + .to_string(); + } + // Until we switch the zed.dev domain to point to the new Next.js app, there + // will be no redirect required, and the app will connect directly to + // wss://zed.dev/rpc. + else if rpc_response.status() != surf::StatusCode::UpgradeRequired { + Err(anyhow!( + "unexpected /rpc response status {}", + rpc_response.status() + ))? + } + + let mut rpc_url = surf::Url::parse(&rpc_url).context("invalid rpc url")?; + let rpc_host = rpc_url + .host_str() + .zip(rpc_url.port_or_known_default()) + .ok_or_else(|| anyhow!("missing host in rpc url"))?; + let stream = smol::net::TcpStream::connect(rpc_host).await?; + + log::info!("connected to rpc endpoint {}", rpc_url); + + match rpc_url.scheme() { + "https" => { + rpc_url.set_scheme("wss").unwrap(); + let request = request.uri(rpc_url.as_str()).body(())?; + let (stream, _) = + async_tungstenite::async_tls::client_async_tls(request, stream).await?; + Ok(Connection::new(stream)) + } + "http" => { + rpc_url.set_scheme("ws").unwrap(); + let request = request.uri(rpc_url.as_str()).body(())?; + let (stream, _) = async_tungstenite::client_async(request, stream).await?; + Ok(Connection::new(stream)) + } + _ => Err(anyhow!("invalid rpc url: {}", rpc_url))?, } }) } @@ -564,7 +601,7 @@ impl Client { // that the user is signing in from a Zed app running on the same device. let mut url = format!( "{}/native_app_signin?native_app_port={}&native_app_public_key={}", - *SITE_URL, port, public_key_string + *ZED_SERVER_URL, port, public_key_string ); if let Some(impersonate_login) = IMPERSONATE_LOGIN.as_ref() { @@ -595,7 +632,8 @@ impl Client { } } - let post_auth_url = format!("{}/native_app_signin_succeeded", *SITE_URL); + let post_auth_url = + format!("{}/native_app_signin_succeeded", *ZED_SERVER_URL); req.respond( tiny_http::Response::empty(302).with_header( tiny_http::Header::from_bytes( @@ -668,7 +706,7 @@ fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option { let (user_id, access_token) = cx .platform() - .read_credentials(&COLLAB_URL) + .read_credentials(&ZED_SERVER_URL) .log_err() .flatten()?; Some(Credentials { @@ -679,7 +717,7 @@ fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option { fn write_credentials_to_keychain(credentials: &Credentials, cx: &AsyncAppContext) -> Result<()> { cx.platform().write_credentials( - &COLLAB_URL, + &ZED_SERVER_URL, &credentials.user_id.to_string(), credentials.access_token.as_bytes(), ) @@ -705,7 +743,7 @@ pub fn decode_worktree_url(url: &str) -> Option<(u64, String)> { #[cfg(test)] mod tests { use super::*; - use crate::test::FakeServer; + use crate::test::{FakeHttpClient, FakeServer}; use gpui::TestAppContext; #[gpui::test(iterations = 10)] @@ -713,7 +751,7 @@ mod tests { cx.foreground().forbid_parking(); let user_id = 5; - let mut client = Client::new(); + let mut client = Client::new(FakeHttpClient::with_404_response()); let server = FakeServer::for_client(user_id, &mut client, &cx).await; cx.foreground().advance_clock(Duration::from_secs(10)); @@ -733,7 +771,7 @@ mod tests { cx.foreground().forbid_parking(); let user_id = 5; - let mut client = Client::new(); + let mut client = Client::new(FakeHttpClient::with_404_response()); let server = FakeServer::for_client(user_id, &mut client, &cx).await; let mut status = client.status(); assert!(matches!( diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index a626091c03..87d9c521ab 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -582,7 +582,7 @@ mod tests { async fn test_diagnostics(mut cx: TestAppContext) { let settings = cx.update(WorkspaceParams::test).settings; let http_client = FakeHttpClient::new(|_| async move { Ok(ServerResponse::new(404)) }); - let client = Client::new(); + let client = Client::new(http_client.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); let fs = Arc::new(FakeFs::new()); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 2a524216db..182e205cc2 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -898,7 +898,7 @@ impl Collaborator { #[cfg(test)] mod tests { use super::*; - use client::{http::ServerResponse, test::FakeHttpClient}; + use client::test::FakeHttpClient; use fs::RealFs; use gpui::TestAppContext; use language::LanguageRegistry; @@ -1004,8 +1004,8 @@ mod tests { fn build_project(cx: &mut TestAppContext) -> ModelHandle { let languages = Arc::new(LanguageRegistry::new()); let fs = Arc::new(RealFs); - let client = client::Client::new(); - let http_client = FakeHttpClient::new(|_| async move { Ok(ServerResponse::new(404)) }); + let http_client = FakeHttpClient::with_404_response(); + let client = client::Client::new(http_client.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); cx.update(|cx| Project::local(client, user_store, languages, fs, cx)) } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index f99837fee0..86bdeae706 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3054,8 +3054,8 @@ mod tests { ) .await; - let client = Client::new(); let http_client = FakeHttpClient::with_404_response(); + let client = Client::new(http_client.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); let tree = Worktree::open_local( @@ -3092,8 +3092,8 @@ mod tests { "file1": "the old contents", })); - let client = Client::new(); let http_client = FakeHttpClient::with_404_response(); + let client = Client::new(http_client.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); let tree = Worktree::open_local( @@ -3127,8 +3127,8 @@ mod tests { })); let file_path = dir.path().join("file1"); - let client = Client::new(); let http_client = FakeHttpClient::with_404_response(); + let client = Client::new(http_client.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); let tree = Worktree::open_local( @@ -3176,7 +3176,8 @@ mod tests { })); let user_id = 5; - let mut client = Client::new(); + let http_client = FakeHttpClient::with_404_response(); + let mut client = Client::new(http_client.clone()); let server = FakeServer::for_client(user_id, &mut client, &cx).await; let user_store = server.build_user_store(client.clone(), &mut cx).await; let tree = Worktree::open_local( @@ -3221,7 +3222,7 @@ mod tests { 1, 1, initial_snapshot.to_proto(), - Client::new(), + Client::new(http_client.clone()), user_store, Default::default(), &mut cx.to_async(), @@ -3327,8 +3328,8 @@ mod tests { } })); - let client = Client::new(); let http_client = FakeHttpClient::with_404_response(); + let client = Client::new(http_client.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); let tree = Worktree::open_local( @@ -3369,7 +3370,8 @@ mod tests { #[gpui::test] async fn test_buffer_deduping(mut cx: gpui::TestAppContext) { let user_id = 100; - let mut client = Client::new(); + let http_client = FakeHttpClient::with_404_response(); + let mut client = Client::new(http_client); let server = FakeServer::for_client(user_id, &mut client, &cx).await; let user_store = server.build_user_store(client.clone(), &mut cx).await; @@ -3433,8 +3435,8 @@ mod tests { "file2": "def", "file3": "ghi", })); - let client = Client::new(); let http_client = FakeHttpClient::with_404_response(); + let client = Client::new(http_client.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); let tree = Worktree::open_local( @@ -3570,8 +3572,8 @@ mod tests { let initial_contents = "aaa\nbbbbb\nc\n"; let dir = temp_tree(json!({ "the-file": initial_contents })); - let client = Client::new(); let http_client = FakeHttpClient::with_404_response(); + let client = Client::new(http_client.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); let tree = Worktree::open_local( @@ -3687,8 +3689,8 @@ mod tests { "b.rs": "const y: i32 = 1", })); - let client = Client::new(); let http_client = FakeHttpClient::with_404_response(); + let client = Client::new(http_client.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); let tree = Worktree::open_local( @@ -3755,8 +3757,8 @@ mod tests { #[gpui::test] async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { let fs = Arc::new(FakeFs::new()); - let client = Client::new(); let http_client = FakeHttpClient::with_404_response(); + let client = Client::new(http_client.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); fs.insert_tree( diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index 55f8a50d7f..f16d7f39c2 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -13,7 +13,7 @@ test-support = [] [dependencies] anyhow = "1.0" async-lock = "2.4" -async-tungstenite = "0.14" +async-tungstenite = "0.16" base64 = "0.13" futures = "0.3" log = "0.4" diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index d11b9c4e82..5a24560e01 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -19,7 +19,7 @@ rpc = { path = "../rpc" } anyhow = "1.0.40" async-std = { version = "1.8.0", features = ["attributes"] } async-trait = "0.1.50" -async-tungstenite = "0.14" +async-tungstenite = "0.16" base64 = "0.13" clap = "=3.0.0-beta.2" comrak = "0.10" diff --git a/crates/server/src/auth.rs b/crates/server/src/auth.rs index 9b2c355226..1fbd137d12 100644 --- a/crates/server/src/auth.rs +++ b/crates/server/src/auth.rs @@ -112,6 +112,9 @@ pub fn add_routes(app: &mut Server>) { app.at("/sign_in").get(get_sign_in); app.at("/sign_out").post(post_sign_out); app.at("/auth_callback").get(get_auth_callback); + app.at("/native_app_signin").get(get_sign_in); + app.at("/native_app_signin_succeeded") + .get(get_app_signin_success); } #[derive(Debug, Deserialize)] @@ -166,6 +169,10 @@ async fn get_sign_in(mut request: Request) -> tide::Result { Ok(tide::Redirect::new(auth_url).into()) } +async fn get_app_signin_success(_: Request) -> tide::Result { + Ok(tide::Redirect::new("/").into()) +} + async fn get_auth_callback(mut request: Request) -> tide::Result { #[derive(Debug, Deserialize)] struct Query { diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 331126729b..e507d3d7b8 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -2440,9 +2440,10 @@ mod tests { } async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient { + let http = FakeHttpClient::with_404_response(); let user_id = self.app_state.db.create_user(name, false).await.unwrap(); let client_name = name.to_string(); - let mut client = Client::new(); + let mut client = Client::new(http.clone()); let server = self.server.clone(); let connection_killers = self.connection_killers.clone(); let forbid_connections = self.forbid_connections.clone(); @@ -2489,7 +2490,6 @@ mod tests { }) }); - let http = FakeHttpClient::new(|_| async move { Ok(surf::http::Response::new(404)) }); client .authenticate_and_connect(&cx.to_async()) .await diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 311b68e096..ffbfee7d6d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -368,10 +368,10 @@ impl WorkspaceParams { pub fn test(cx: &mut MutableAppContext) -> Self { let fs = Arc::new(project::FakeFs::new()); let languages = Arc::new(LanguageRegistry::new()); - let client = Client::new(); let http_client = client::test::FakeHttpClient::new(|_| async move { Ok(client::http::ServerResponse::new(404)) }); + let client = Client::new(http_client.clone()); let theme = gpui::fonts::with_font_cache(cx.font_cache().clone(), || theme::Theme::default()); let settings = Settings::new("Courier", cx.font_cache(), Arc::new(theme)).unwrap(); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 2b9d7150f2..b520eee0b6 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -55,7 +55,6 @@ workspace = { path = "../workspace" } anyhow = "1.0.38" async-recursion = "0.3" async-trait = "0.1" -async-tungstenite = { version = "0.14", features = ["async-tls"] } crossbeam-channel = "0.5.0" ctor = "0.1.20" dirs = "3.0" diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index c676523554..09b53603df 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -48,8 +48,8 @@ fn main() { languages.set_theme(&settings.borrow().theme.editor.syntax); app.run(move |cx| { - let client = client::Client::new(); let http = http::client(); + let client = client::Client::new(http.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); let mut entry_openers = Vec::new(); diff --git a/crates/zed/src/test.rs b/crates/zed/src/test.rs index fa540ae72a..c56e24dd1c 100644 --- a/crates/zed/src/test.rs +++ b/crates/zed/src/test.rs @@ -1,5 +1,5 @@ use crate::{assets::Assets, build_window_options, build_workspace, AppState}; -use client::{http::ServerResponse, test::FakeHttpClient, ChannelList, Client, UserStore}; +use client::{test::FakeHttpClient, ChannelList, Client, UserStore}; use gpui::{AssetSource, MutableAppContext}; use language::LanguageRegistry; use parking_lot::Mutex; @@ -20,8 +20,8 @@ pub fn test_app_state(cx: &mut MutableAppContext) -> Arc { editor::init(cx, &mut entry_openers); let (settings_tx, settings) = watch::channel_with(build_settings(cx)); let themes = ThemeRegistry::new(Assets, cx.font_cache().clone()); - let client = Client::new(); - let http = FakeHttpClient::new(|_| async move { Ok(ServerResponse::new(404)) }); + let http = FakeHttpClient::with_404_response(); + let client = Client::new(http.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx)); let mut languages = LanguageRegistry::new(); languages.add(Arc::new(language::Language::new( diff --git a/script/zed_with_local_servers b/script/zed_with_local_servers index 18ccdf3141..0796b7808e 100755 --- a/script/zed_with_local_servers +++ b/script/zed_with_local_servers @@ -1 +1 @@ -ZED_SITE_URL=http://localhost:3000 ZED_COLLAB_URL=http://localhost:8080 cargo run $@ +ZED_SERVER_URL=http://localhost:3000 cargo run $@