tauri-plugin-devtools-app 2.1.3

Connect with the Devtools for Tauri application
use std::fmt::Display;

use tokio::{
    io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
    sync::mpsc::Receiver,
};

use interprocess::local_socket::tokio::prelude::*;

use super::LOCAL_DEV;

pub enum DevtoolsMessage {
    Authenticated(bool),
    ServerPort(u16),
    AuthError(serde_json::Value),
}

pub enum Message {
    Login,
}

impl Display for Message {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Login => write!(f, "login"),
        }
    }
}

pub async fn start<F: FnMut(DevtoolsMessage) + Send + 'static>(
    mut outgoing_message_rx: Receiver<Message>,
    mut on_message: F,
) -> Result<(), std::io::Error> {
    #[cfg(windows)]
    let name = {
        use interprocess::local_socket::ToNsName;
        "cn-devtools-premium.sock".to_ns_name::<interprocess::local_socket::GenericNamespaced>()?
    };
    #[cfg(not(windows))]
    let name = {
        use interprocess::local_socket::ToFsName;
        if let Ok(name) = std::env::temp_dir()
            .join("cn-devtools-premium.socket")
            .to_fs_name::<interprocess::local_socket::GenericFilePath>()
        {
            name
        } else {
            "/tmp/cn-devtools-premium.sock"
                .to_fs_name::<interprocess::local_socket::GenericFilePath>()?
        }
    };

    let max_attempts = 10;
    let mut i = 0;
    let conn = loop {
        match LocalSocketStream::connect(name.clone()).await {
            Ok(c) => break c,
            // retry if the devtools app did not start listening on the socket yet
            Err(e)
                if e.kind() == std::io::ErrorKind::ConnectionRefused
                    || e.kind() == std::io::ErrorKind::NotFound =>
            {
                if LOCAL_DEV {
                    println!("retrying...");
                }
                if i == max_attempts {
                    return Err(e);
                }
                std::thread::sleep(std::time::Duration::from_millis(300));
                i += 1;
            }
            Err(e) => return Err(e),
        }
    };

    let (receiver, mut sender) = conn.split();
    let mut reader = BufReader::new(receiver);

    std::thread::spawn(move || {
        tauri::async_runtime::spawn(async move {
            let mut buf = String::with_capacity(128);
            loop {
                tokio::select! {
                    res = reader.read_line(&mut buf) => {
                        match res {
                            Ok(0) => break,
                            Ok(_) => {
                                buf.pop();
                                if LOCAL_DEV {
                                    println!("devtools message: {buf}");
                                }
                                if let Some(authenticated) = buf
                                    .strip_prefix("authenticated:")
                                    .and_then(|b| b.parse().ok())
                                {
                                    on_message(DevtoolsMessage::Authenticated(authenticated));
                                } else if let Some(port) =
                                    buf.strip_prefix("port:").and_then(|b| b.parse().ok())
                                {
                                    on_message(DevtoolsMessage::ServerPort(port));
                                } else if let Some(payload) = buf
                                    .strip_prefix("auth-error:")
                                    .and_then(|b| serde_json::from_str::<serde_json::Value>(b).ok())
                                {
                                    on_message(DevtoolsMessage::AuthError(payload));
                                }

                                buf.clear();
                            }
                            Err(e) => {
                                if LOCAL_DEV {
                                    eprintln!("failed to read IPC line: {e}");
                                }
                                break;
                            }
                        }
                    }
                    Some(message) = outgoing_message_rx.recv() => {
                        let write_message = format!("{message}\n");
                        if LOCAL_DEV {
                            println!("sending to devtools app: {write_message:?}");
                        }
                        if let Err(e) = sender.write_all(write_message.as_bytes()).await {
                            if LOCAL_DEV {
                                eprintln!("failed to send message: {e}");
                            }
                        }
                    }
                }
            }
        });
    });

    Ok(())
}