1use std::process::{Command, Stdio};
8
9use dvm_api::{CollectCliLogin, CreatedApiKey, Me, NewCliLogin, StartedCliLogin};
10use reqwest::Method;
11
12use crate::client::{Client, api_url_of};
13use crate::error::Result;
14
15#[derive(Clone, Debug)]
18pub struct LoggedIn {
19 pub api_url: String,
20 pub login: String,
22 pub key_name: String,
23 pub key: String,
24}
25
26pub async fn login(
31 api_url: Option<&str>,
32 open_browser: bool,
33 mut say: impl FnMut(&str),
34) -> Result<LoggedIn> {
35 let url = api_url_of(api_url);
36 let terminal = Client::with_key(url.clone(), None);
37 let machine = NewCliLogin {
38 machine: gethostname::gethostname().to_string_lossy().into_owned(),
39 };
40 let started: StartedCliLogin = terminal
41 .json(Method::POST, "/cli-logins", &[], Some(&machine))
42 .await?;
43 say(&format!(
44 "Approve this login in your browser: {}",
45 started.approve_url
46 ));
47 say(&format!("The page shows the code {}.", started.code));
48 if open_browser {
49 open_in_browser(&started.approve_url);
50 }
51 let collect = CollectCliLogin {
52 collect_token: started.collect_token,
53 };
54 let created = loop {
55 let answer: Option<CreatedApiKey> = terminal
56 .json(Method::POST, "/cli-logins/key", &[], Some(&collect))
57 .await?;
58 if let Some(created) = answer {
59 break created;
60 }
61 };
62 let me: Me = Client::new(url.clone(), created.secret.clone())
63 .json(Method::GET, "/me", &[], None::<&()>)
64 .await?;
65 Ok(LoggedIn {
66 api_url: url,
67 login: me.login,
68 key_name: created.key.name,
69 key: created.secret,
70 })
71}
72
73fn open_in_browser(url: &str) {
76 let opener = if cfg!(target_os = "macos") {
77 Command::new("open").arg(url).spawn()
78 } else if cfg!(windows) {
79 Command::new("cmd").args(["/c", "start", "", url]).spawn()
80 } else {
81 Command::new("xdg-open")
82 .arg(url)
83 .stdin(Stdio::null())
84 .stdout(Stdio::null())
85 .stderr(Stdio::null())
86 .spawn()
87 };
88 drop(opener);
89}