Skip to content

Commit f80e790

Browse files
authored
Added Rest and Updated Native App API (#4)
* Added Rest and Updated Native App API * cargo fmt * remove protocols * cargo fmt * cargo clippy * cargo fmt
1 parent e8f400c commit f80e790

File tree

7 files changed

+931
-170
lines changed

7 files changed

+931
-170
lines changed

.vscode/settings.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"cSpell.words": [
3+
"chrono",
4+
"reqwest"
5+
]
6+
}

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "homeassistant"
3-
description = "This is a library for connecting to the Home Assistent API"
3+
description = "This is a library for connecting to the Home Assistant API"
44
version = "0.2.0"
55
authors = ["Bradley Nelson <[email protected]>"]
66
edition = "2018"
@@ -15,3 +15,4 @@ license = "Apache-2.0"
1515
futures = "0.3"
1616
reqwest = { version = "0.10", features = ["json"] }
1717
serde = { version = "1.0", features = ["derive"] }
18+
chrono = "0.4"

src/errors.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ pub enum Error {
77
Config(String),
88
Refresh(),
99
NoAuth(),
10+
PoisonError(
11+
std::sync::PoisonError<
12+
std::sync::RwLockReadGuard<'static, std::option::Option<crate::Token>>,
13+
>,
14+
),
1015
}
1116

1217
impl From<reqwest::Error> for Error {
@@ -15,14 +20,31 @@ impl From<reqwest::Error> for Error {
1520
}
1621
}
1722

23+
impl
24+
From<
25+
std::sync::PoisonError<
26+
std::sync::RwLockReadGuard<'static, std::option::Option<crate::Token>>,
27+
>,
28+
> for Error
29+
{
30+
fn from(
31+
error: std::sync::PoisonError<
32+
std::sync::RwLockReadGuard<'static, std::option::Option<crate::Token>>,
33+
>,
34+
) -> Self {
35+
Error::PoisonError(error)
36+
}
37+
}
38+
1839
impl fmt::Display for Error {
1940
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2041
match self {
2142
Error::Request(inner) => write!(f, "{}", inner),
2243
Error::Config(inner) => write!(f, "{}", inner),
2344
Error::HaApi(inner) => write!(f, "{}", inner),
45+
Error::PoisonError(inner) => write!(f, "{}", inner),
2446
Error::Refresh() => write!(f, "Tried to refresh a long lived access token"),
25-
Error::NoAuth() => write!(f, "There are no Authentication Credentals"),
47+
Error::NoAuth() => write!(f, "There are no Authentication Credentials"),
2648
}
2749
}
2850
}

src/lib.rs

+63-168
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,49 @@
11
use crate::types::*;
2+
use std::convert::TryFrom;
3+
use std::sync::{Arc, RwLock, Weak};
24
use std::time;
35

46
pub mod errors;
7+
pub mod native_app;
8+
pub mod rest;
59
pub mod types;
610

711
#[derive(Debug)]
812
pub struct HomeAssistantAPI {
913
instance_url: String,
10-
token: Option<Token>,
11-
client: reqwest::Client,
12-
webhook_id: Option<String>,
13-
cloudhook_url: Option<String>,
14-
remote_ui_url: Option<String>,
14+
token: Token,
1515
client_id: String,
16+
self_reference: Weak<RwLock<Self>>,
1617
}
1718

1819
#[derive(Debug, Clone)]
1920
pub enum Token {
2021
Oauth(OAuthToken),
2122
LongLived(LongLivedToken),
23+
None,
24+
}
25+
26+
impl Token {
27+
pub fn as_string(&self) -> Result<String, errors::Error> {
28+
match self {
29+
Token::Oauth(token) => Ok(token.token.clone()),
30+
Token::LongLived(token) => Ok(token.token.clone()),
31+
Token::None => Err(errors::Error::NoAuth()),
32+
}
33+
}
34+
35+
pub fn need_refresh(&self) -> bool {
36+
match self {
37+
Token::Oauth(token) => {
38+
match time::SystemTime::now().duration_since(token.token_expiration) {
39+
Ok(sec_left) => sec_left > time::Duration::from_secs(10),
40+
Err(_) => false,
41+
}
42+
}
43+
Token::LongLived(_) => false,
44+
Token::None => false,
45+
}
46+
}
2247
}
2348

2449
#[derive(Debug, Clone)]
@@ -34,20 +59,18 @@ pub struct LongLivedToken {
3459
}
3560

3661
impl HomeAssistantAPI {
37-
pub fn new(
38-
instance_url: String,
39-
client_id: String,
40-
maybe_long_lived_token: Option<String>,
41-
) -> Self {
42-
Self {
62+
pub fn new(instance_url: String, client_id: String) -> Arc<RwLock<Self>> {
63+
let token = Token::None;
64+
let ret = Arc::new(RwLock::new(Self {
4365
instance_url,
44-
token: maybe_long_lived_token.map(|token| Token::LongLived(LongLivedToken { token })),
45-
client: reqwest::Client::new(),
46-
webhook_id: None,
47-
cloudhook_url: None,
48-
remote_ui_url: None,
66+
token,
4967
client_id,
50-
}
68+
self_reference: Weak::new(),
69+
}));
70+
71+
ret.write().unwrap().self_reference = Arc::downgrade(&ret);
72+
73+
ret
5174
}
5275

5376
pub fn set_oauth_token(
@@ -62,63 +85,18 @@ impl HomeAssistantAPI {
6285
+ time::Duration::from_secs(expires_in as u64),
6386
refresh_token,
6487
};
65-
let oauth_token = Token::Oauth(oauth);
66-
self.token = Some(oauth_token);
88+
self.token = Token::Oauth(oauth);
6789
}
6890

6991
pub fn set_long_lived_token(&mut self, token: String) {
70-
let long_lived = Token::LongLived(LongLivedToken { token });
71-
self.token = Some(long_lived)
72-
}
73-
74-
pub fn set_webhook_info(
75-
&mut self,
76-
webhook_id: String,
77-
cloudhook_url: Option<String>,
78-
remote_ui_url: Option<String>,
79-
) {
80-
self.webhook_id = Some(webhook_id);
81-
self.cloudhook_url = cloudhook_url;
82-
self.remote_ui_url = remote_ui_url;
83-
}
84-
85-
pub fn need_refresh(&self) -> bool {
86-
let token_result = self.token.as_ref().ok_or_else(|| {
87-
errors::Error::Config("Refreshing token - expected a token to exist".to_string())
88-
});
89-
90-
match token_result {
91-
Ok(token) => match token {
92-
Token::Oauth(token) => {
93-
match time::SystemTime::now().duration_since(token.token_expiration) {
94-
Ok(sec_left) => sec_left > time::Duration::from_secs(10),
95-
Err(_) => false,
96-
}
97-
}
98-
Token::LongLived(_) => false,
99-
},
100-
Err(_) => false,
101-
}
92+
self.token = Token::LongLived(LongLivedToken { token });
10293
}
10394

104-
pub async fn refresh_token(&mut self) -> Result<(), errors::Error> {
105-
let token = self.token.clone(); // This is dump but I have to do it apparently
106-
let refresh_token: String;
107-
match token {
108-
Some(token) => {
109-
refresh_token = match token {
110-
Token::Oauth(oauth) => oauth.refresh_token,
111-
Token::LongLived(_) => {
112-
return Err(errors::Error::Refresh());
113-
}
114-
};
115-
}
116-
None => return Err(errors::Error::NoAuth()),
117-
}
95+
pub async fn refresh_oauth_token(&mut self) -> Result<(), errors::Error> {
96+
let refresh_token = String::from("test");
11897

119-
let response = self
120-
.client
121-
.post(format!("http://{}/auth/token", self.instance_url).as_str())
98+
let response = reqwest::Client::new()
99+
.post(format!("{}/auth/token", self.instance_url).as_str())
122100
.query(&[
123101
("grant_type", "refresh_token"),
124102
("client_id", &self.client_id),
@@ -146,9 +124,8 @@ impl HomeAssistantAPI {
146124
code,
147125
client_id,
148126
};
149-
let resp = self
150-
.client
151-
.post(format!("http://{}/auth/token", self.instance_url).as_str())
127+
let resp = reqwest::Client::new()
128+
.post(format!("{}/auth/token", self.instance_url).as_str())
152129
.form(&request)
153130
.send()
154131
.await?;
@@ -173,109 +150,27 @@ impl HomeAssistantAPI {
173150
}
174151
}
175152

176-
pub async fn api_states(&self) -> Result<Vec<HaEntityState>, errors::Error> {
177-
let endpoint = format!("http://{}/api/states", self.instance_url);
178-
let token = self.get_token()?;
179-
let resp = self
180-
.client
181-
.get(endpoint.as_str())
182-
.header("Authorization", format!("Bearer {}", token))
183-
.send()
184-
.await?;
185-
186-
let api_states = resp.json::<Vec<HaEntityState>>().await?;
187-
Ok(api_states)
188-
}
189-
190-
pub async fn register_machine(
191-
&mut self,
192-
request: &RegisterDeviceRequest,
193-
) -> Result<RegisterDeviceResponse, errors::Error> {
194-
if self.need_refresh() {
195-
self.refresh_token().await?;
196-
}
197-
let endpoint = format!("http://{}/api/mobile_app/registrations", self.instance_url);
198-
let token = self.get_token()?;
199-
let resp = self
200-
.client
201-
.post(endpoint.as_str())
202-
.header("Authorization", format!("Bearer {}", token))
203-
.json(&request)
204-
.send()
205-
.await?;
206-
207-
let r: RegisterDeviceResponse = resp.json().await?;
208-
self.set_webhook_info(
209-
r.webhook_id.clone(),
210-
r.cloud_hook_url.clone(),
211-
r.remote_ui_url.clone(),
212-
);
213-
Ok(r)
214-
}
215-
216-
pub async fn register_sensor(
217-
&mut self,
218-
request: &SensorRegistrationRequest,
219-
) -> Result<RegisterSensorResponse, errors::Error> {
220-
if self.need_refresh() {
221-
self.refresh_token().await?;
153+
pub async fn get_rest_client(&self) -> rest::Rest {
154+
match rest::Rest::try_from(self.self_reference.clone()) {
155+
Ok(rest) => rest,
156+
Err(_) => unreachable!(),
222157
}
223-
let webhook_id = self
224-
.webhook_id
225-
.as_ref()
226-
.ok_or_else(|| errors::Error::Config("expected webhook_id to exist".to_string()))?;
227-
let token = self.get_token()?;
228-
let endpoint = format!("http://{}/api/webhook/{}", self.instance_url, webhook_id);
229-
230-
let response = self
231-
.client
232-
.post(endpoint.as_str())
233-
.header("Authorization", format!("Bearer {}", token))
234-
.json(&request)
235-
.send()
236-
.await?;
237-
238-
let resp_json: RegisterSensorResponse = response.json().await?;
239-
Ok(resp_json)
240158
}
241159

242-
pub async fn update_sensor(
243-
&mut self,
244-
sensor_data: SensorUpdateData,
245-
) -> Result<(), errors::Error> {
246-
if self.need_refresh() {
247-
self.refresh_token().await?;
160+
pub async fn get_native_client_from_config(
161+
&self,
162+
config: native_app::NativeAppConfig,
163+
) -> native_app::NativeApp {
164+
match native_app::NativeApp::from_config(config, self.self_reference.clone()) {
165+
Ok(native_app) => native_app,
166+
Err(_) => unreachable!(),
248167
}
249-
let webhook_id = self
250-
.webhook_id
251-
.as_ref()
252-
.ok_or_else(|| errors::Error::Config("missing webhook id".to_string()))?;
253-
254-
let endpoint = format!("{}/api/webhook/{}", self.instance_url, webhook_id);
255-
let token = self.get_token()?;
256-
257-
let request = types::SensorUpdateRequest {
258-
data: sensor_data,
259-
r#type: String::from("update_sensor_states"),
260-
};
261-
262-
self.client
263-
.post(endpoint.as_str())
264-
.header("Authorization", format!("Bearer {}", token))
265-
.json(&request)
266-
.send()
267-
.await?;
268-
269-
Ok(())
270168
}
271169

272-
fn get_token(&self) -> Result<String, errors::Error> {
273-
let token = self.token.as_ref().ok_or_else(|| {
274-
errors::Error::Config("Get Token - expected a token to exist".to_string())
275-
})?;
276-
match token {
277-
Token::Oauth(token) => Ok(token.token.clone()),
278-
Token::LongLived(token) => Ok(token.token.clone()),
170+
pub async fn get_native_client(&self) -> native_app::NativeApp {
171+
match native_app::NativeApp::new(self.self_reference.clone()) {
172+
Ok(native_app) => native_app,
173+
Err(_) => unreachable!(),
279174
}
280175
}
281176
}

0 commit comments

Comments
 (0)