iterm2_client/
transport.rs1use crate::auth::Credentials;
8use crate::error::{Error, Result};
9use futures_util::stream::{SplitSink, SplitStream};
10use futures_util::StreamExt;
11use tokio::io::{AsyncRead, AsyncWrite};
12use tokio_tungstenite::tungstenite::client::IntoClientRequest;
13use tokio_tungstenite::tungstenite::http::header::HeaderValue;
14use tokio_tungstenite::tungstenite::protocol::WebSocketConfig;
15use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
16
17pub type WsStream<S> = WebSocketStream<S>;
18pub type WsSink<S> = SplitSink<WsStream<S>, tokio_tungstenite::tungstenite::Message>;
19pub type WsSource<S> = SplitStream<WsStream<S>>;
20
21const SUBPROTOCOL: &str = "api.iterm2.com";
22const TCP_URL: &str = "ws://localhost:1912";
23fn unix_socket_path() -> std::path::PathBuf {
24 let home = std::env::var("HOME").unwrap_or_default();
25 std::path::PathBuf::from(home)
26 .join("Library/Application Support/iTerm2/private/socket")
27}
28
29pub async fn connect_tcp(
31 credentials: &Credentials,
32 app_name: &str,
33) -> Result<(WsSink<MaybeTlsStream<tokio::net::TcpStream>>, WsSource<MaybeTlsStream<tokio::net::TcpStream>>)> {
34 let mut request = TCP_URL.into_client_request()?;
35 apply_headers(request.headers_mut(), credentials, app_name)?;
36 let config = ws_config();
37 let (ws_stream, _response) =
38 tokio_tungstenite::connect_async_with_config(request, Some(config), false).await?;
39 Ok(ws_stream.split())
40}
41
42pub async fn connect_unix(
44 credentials: &Credentials,
45 app_name: &str,
46) -> Result<(WsSink<tokio::net::UnixStream>, WsSource<tokio::net::UnixStream>)> {
47 let path = unix_socket_path();
48 let stream = tokio::net::UnixStream::connect(&path).await?;
49 connect_with_stream(stream, credentials, app_name).await
50}
51
52pub async fn connect_with_stream<S>(
56 stream: S,
57 credentials: &Credentials,
58 app_name: &str,
59) -> Result<(WsSink<S>, WsSource<S>)>
60where
61 S: AsyncRead + AsyncWrite + Unpin,
62{
63 let mut request = TCP_URL.into_client_request()?;
64 apply_headers(request.headers_mut(), credentials, app_name)?;
65 let config = ws_config();
66 let (ws_stream, _response) =
67 tokio_tungstenite::client_async_with_config(request, stream, Some(config)).await?;
68 Ok(ws_stream.split())
69}
70
71pub async fn connect(
77 credentials: &Credentials,
78 app_name: &str,
79) -> Result<(WsSink<tokio::net::UnixStream>, WsSource<tokio::net::UnixStream>)> {
80 connect_unix(credentials, app_name).await
81}
82
83fn make_header_value(value: &str, field_name: &str) -> Result<HeaderValue> {
84 HeaderValue::from_str(value).map_err(|_| {
85 Error::Auth(format!(
86 "Invalid characters in {field_name} (must be visible ASCII)"
87 ))
88 })
89}
90
91fn apply_headers(
92 headers: &mut tokio_tungstenite::tungstenite::http::HeaderMap,
93 credentials: &Credentials,
94 app_name: &str,
95) -> Result<()> {
96 headers.insert(
97 "Sec-WebSocket-Protocol",
98 HeaderValue::from_static(SUBPROTOCOL),
99 );
100 headers.insert(
101 "Origin",
102 HeaderValue::from_static("ws://localhost"),
103 );
104 headers.insert(
105 "x-iterm2-library-version",
106 HeaderValue::from_static(concat!("rust ", "0.2.1")),
107 );
108 headers.insert(
109 "x-iterm2-cookie",
110 make_header_value(&credentials.cookie, "cookie")?,
111 );
112 headers.insert(
113 "x-iterm2-key",
114 make_header_value(&credentials.key, "key")?,
115 );
116 headers.insert(
117 "x-iterm2-advisory-name",
118 make_header_value(app_name, "app_name")?,
119 );
120 Ok(())
121}
122
123fn ws_config() -> WebSocketConfig {
124 let mut config = WebSocketConfig::default();
125 config.max_frame_size = Some(4 * 1024 * 1024); config.max_message_size = Some(8 * 1024 * 1024); config
128}