dingtalk_stream/transport/
token.rs1use crate::credential::Credential;
4use crate::error::{Error, Result};
5use crate::transport::http::HttpClient;
6use std::time::{SystemTime, UNIX_EPOCH};
7use tokio::sync::RwLock;
8
9struct TokenCache {
11 access_token: String,
12 expire_time: i64,
13}
14
15pub struct TokenManager {
17 credential: Credential,
18 http_client: HttpClient,
19 cache: RwLock<Option<TokenCache>>,
20}
21
22impl TokenManager {
23 pub fn new(credential: Credential, http_client: HttpClient) -> Self {
25 Self {
26 credential,
27 http_client,
28 cache: RwLock::new(None),
29 }
30 }
31
32 fn now() -> i64 {
34 SystemTime::now()
35 .duration_since(UNIX_EPOCH)
36 .map(|d| d.as_secs() as i64)
37 .unwrap_or(0)
38 }
39
40 pub async fn get_access_token(&self) -> Result<String> {
42 {
44 let cache = self.cache.read().await;
45 if let Some(ref c) = *cache {
46 if Self::now() < c.expire_time {
47 return Ok(c.access_token.clone());
48 }
49 }
50 }
51
52 let url = format!(
54 "{}/v1.0/oauth2/accessToken",
55 self.http_client.openapi_endpoint()
56 );
57 let body = serde_json::json!({
58 "appKey": self.credential.client_id,
59 "appSecret": self.credential.client_secret,
60 });
61
62 let result: serde_json::Value = self.http_client.post_json(&url, &body, None).await?;
63
64 let access_token = result
65 .get("accessToken")
66 .and_then(|v| v.as_str())
67 .ok_or_else(|| Error::Auth("accessToken not found in response".to_owned()))?
68 .to_owned();
69
70 let expire_in = result
71 .get("expireIn")
72 .and_then(|v| v.as_i64())
73 .unwrap_or(7200);
74
75 let expire_time = Self::now() + expire_in - 300;
77
78 let mut cache = self.cache.write().await;
79 *cache = Some(TokenCache {
80 access_token: access_token.clone(),
81 expire_time,
82 });
83
84 Ok(access_token)
85 }
86
87 pub async fn reset(&self) {
89 let mut cache = self.cache.write().await;
90 *cache = None;
91 }
92}