1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
//! Helpers for working with websockets and ports.

use std::net::ToSocketAddrs;
use std::path::PathBuf;
use std::sync::Arc;

use holochain_conductor_api::{
    config::conductor::ConductorConfig, AdminInterfaceConfig, InterfaceDriver,
};
use holochain_websocket::{self as ws, WebsocketConfig, WebsocketSender};

use crate::config::read_config;
use crate::config::write_config;

/// Update the first admin interface to use this port.
pub fn force_admin_port(path: PathBuf, port: u16) -> anyhow::Result<()> {
    let mut config = read_config(path.clone())?.expect("Failed to find config to force admin port");
    set_admin_port(&mut config, port);
    write_config(path, &config);
    Ok(())
}

/// List the admin ports for each sandbox.
pub async fn get_admin_ports(paths: Vec<PathBuf>) -> anyhow::Result<Vec<u16>> {
    let live_ports = crate::save::find_ports(std::env::current_dir()?, &paths[..])?;
    let mut ports = Vec::new();
    for (p, port) in paths.into_iter().zip(live_ports) {
        if let Some(port) = port {
            ports.push(port);
            continue;
        }
        if let Some(config) = read_config(p)? {
            if let Some(ai) = config.admin_interfaces {
                if let Some(AdminInterfaceConfig {
                    driver: InterfaceDriver::Websocket { port },
                }) = ai.first()
                {
                    ports.push(*port)
                }
            }
        }
    }
    Ok(ports)
}

/// Creates a [`WebsocketSender`] along with a task which simply consumes and discards
/// all messages on the receiving side
pub(crate) async fn get_admin_api(
    port: u16,
) -> std::io::Result<(WebsocketSender, tokio::task::JoinHandle<()>)> {
    tracing::debug!(port);
    websocket_client_by_port(port).await
}

async fn websocket_client_by_port(
    port: u16,
) -> std::io::Result<(WebsocketSender, tokio::task::JoinHandle<()>)> {
    let (send, mut recv) = ws::connect(
        Arc::new(WebsocketConfig::default()),
        format!("localhost:{port}")
            .to_socket_addrs()?
            .next()
            .ok_or_else(|| std::io::Error::other("Could not resolve localhost"))?,
    )
    .await?;
    let task = tokio::task::spawn(async move {
        while recv
            .recv::<holochain_conductor_api::AdminResponse>()
            .await
            .is_ok()
        {}
    });
    Ok((send, task))
}

pub(crate) fn random_admin_port(config: &mut ConductorConfig) {
    match config.admin_interfaces.as_mut().and_then(|i| i.first_mut()) {
        Some(AdminInterfaceConfig {
            driver: InterfaceDriver::Websocket { port },
        }) => {
            if *port != 0 {
                *port = 0;
            }
        }
        None => {
            let port = 0;
            config.admin_interfaces = Some(vec![AdminInterfaceConfig {
                driver: InterfaceDriver::Websocket { port },
            }]);
        }
    }
}

pub(crate) fn set_admin_port(config: &mut ConductorConfig, port: u16) {
    let p = port;
    let port = AdminInterfaceConfig {
        driver: InterfaceDriver::Websocket { port },
    };
    match config
        .admin_interfaces
        .as_mut()
        .and_then(|ai| ai.get_mut(0))
    {
        Some(admin_interface) => {
            *admin_interface = port;
        }
        None => config.admin_interfaces = Some(vec![port]),
    }
    msg!("Admin port set to: {}", p);
}