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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use crate::channel::Channel;
use crate::error::{Result, UrbitAPIError};
use json::JsonValue;
use reqwest::blocking::{Client, Response};
use reqwest::header::{HeaderValue, COOKIE};

// The struct which holds the details for connecting to a given Urbit ship
#[derive(Debug, Clone)]
pub struct ShipInterface {
    /// The URL of the ship given as `http://ip:port` such as
    /// `http://0.0.0.0:8080`.
    pub url: String,
    /// The session auth string header value
    pub session_auth: HeaderValue,
    /// The ship name
    pub ship_name: String,
    /// The Reqwest `Client` to be reused for making requests
    req_client: Client,
}

impl ShipInterface {
    /// Logs into the given ship and creates a new `ShipInterface`.
    /// `ship_url` should be `http://ip:port` of the given ship. Example:
    /// `http://0.0.0.0:8080`. `ship_code` is the code acquire from your ship
    /// by typing `+code` in dojo.
    pub fn new(ship_url: &str, ship_code: &str) -> Result<ShipInterface> {
        let client = Client::new();
        let login_url = format!("{}/~/login", ship_url);
        let resp = client
            .post(&login_url)
            .body("password=".to_string() + &ship_code)
            .send()?;

        // Check for status code
        if resp.status().as_u16() != 204 {
            return Err(UrbitAPIError::FailedToLogin);
        }

        // Acquire the session auth header value
        let session_auth = resp
            .headers()
            .get("set-cookie")
            .ok_or(UrbitAPIError::FailedToLogin)?;

        // Convert sessions auth to a string
        let auth_string = session_auth
            .to_str()
            .map_err(|_| UrbitAPIError::FailedToLogin)?;

        // Trim the auth string to acquire the ship name
        let ship_name = &auth_string[9..auth_string.find('=').unwrap()];

        Ok(ShipInterface {
            url: ship_url.to_string(),
            session_auth: session_auth.clone(),
            ship_name: ship_name.to_string(),
            req_client: client,
        })
    }

    /// Create a `Channel` using this `ShipInterface`
    pub fn create_channel(&self) -> Result<Channel> {
        Channel::new(self.clone())
    }

    // Send a put request using the `ShipInterface`
    pub fn send_put_request(&self, url: &str, body: &JsonValue) -> Result<Response> {
        let json = body.dump();
        let resp = self
            .req_client
            .put(url)
            .header(COOKIE, self.session_auth.clone())
            .header("Content-Type", "application/json")
            .body(json);

        Ok(resp.send()?)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::subscription::Subscription;
    #[test]
    // Verify that we can login to a local `~zod` dev ship.
    fn can_login() {
        let ship_interface =
            ShipInterface::new("http://0.0.0.0:8080", "lidlut-tabwed-pillex-ridrup").unwrap();
    }

    #[test]
    // Verify that we can create a channel
    fn can_create_channel() {
        let ship_interface =
            ShipInterface::new("http://0.0.0.0:8080", "lidlut-tabwed-pillex-ridrup").unwrap();
        let channel = ship_interface.create_channel().unwrap();
        channel.delete_channel();
    }

    #[test]
    // Verify that we can create a channel
    fn can_subscribe() {
        let ship_interface =
            ShipInterface::new("http://0.0.0.0:8080", "lidlut-tabwed-pillex-ridrup").unwrap();
        let mut channel = ship_interface.create_channel().unwrap();
        channel
            .create_new_subscription("chat-view", "/primary")
            .unwrap();

        channel.find_subscription("chat-view", "/primary");
        channel.unsubscribe("chat-view", "/primary");
        channel.delete_channel();
    }

    #[test]
    // Verify that we can make a poke
    fn can_poke() {
        let ship_interface =
            ShipInterface::new("http://0.0.0.0:8080", "lidlut-tabwed-pillex-ridrup").unwrap();
        let mut channel = ship_interface.create_channel().unwrap();
        let poke_res = channel
            .poke("hood", "helm-hi", "A poke has been made")
            .unwrap();
        assert!(poke_res.status().as_u16() == 204);
        channel.delete_channel();
    }
}