Skip to main content

webex_message_handler/
device_manager.rs

1//! WDM device registration, refresh, and unregistration.
2
3use crate::errors::{Result, WebexError};
4use crate::types::{DeviceRegistration, FetchFn, FetchRequest};
5use crate::url_validation::validate_webex_url;
6use serde_json::json;
7use std::collections::HashMap;
8use tracing::{debug, error, info};
9
10const WDM_API_BASE: &str = "https://wdm-a.wbx2.com/wdm/api/v1/devices";
11
12fn device_body() -> serde_json::Value {
13    json!({
14        "deviceName": "webex-message-handler",
15        "deviceType": "DESKTOP",
16        "localizedModel": "rust",
17        "model": "rust",
18        "name": "webex-message-handler",
19        "systemName": "webex-message-handler",
20        "systemVersion": "1.0.0"
21    })
22}
23
24/// Manages WDM device registration lifecycle.
25pub struct DeviceManager {
26    device_url: Option<String>,
27    http_do: FetchFn,
28}
29
30impl DeviceManager {
31    pub fn new(http_do: FetchFn) -> Self {
32        Self {
33            device_url: None,
34            http_do,
35        }
36    }
37
38    /// Register a new device with WDM.
39    pub async fn register(&mut self, token: &str) -> Result<DeviceRegistration> {
40        debug!("Registering device with WDM");
41
42        let mut headers = HashMap::new();
43        headers.insert("Authorization".to_string(), format!("Bearer {}", token));
44        headers.insert("Content-Type".to_string(), "application/json".to_string());
45
46        let body = serde_json::to_string(&device_body())
47            .map_err(|e| WebexError::device_registration(format!("Failed to serialize body: {e}"), None))?;
48
49        let response = (self.http_do)(FetchRequest {
50            url: WDM_API_BASE.to_string(),
51            method: "POST".to_string(),
52            headers,
53            body: Some(body),
54        })
55        .await
56        .map_err(|e| WebexError::device_registration(format!("Failed to register device: {e}"), None))?;
57
58        let status = response.status;
59
60        if status == 401 {
61            error!("Device registration failed: Unauthorized");
62            return Err(WebexError::auth("Unauthorized to register device"));
63        }
64
65        if !response.ok {
66            error!("Device registration failed with status {status}");
67            return Err(WebexError::device_registration("Failed to register device", Some(status)));
68        }
69
70        let mut reg: DeviceRegistration = serde_json::from_slice(&response.body)
71            .map_err(|e| WebexError::device_registration(format!("Failed to parse response: {e}"), None))?;
72
73        reg.encryption_service_url = reg.services.get("encryptionServiceUrl").cloned().unwrap_or_default();
74
75        // Validate URLs from the response
76        if !reg.web_socket_url.is_empty() {
77            validate_webex_url(&reg.web_socket_url, "wss")
78                .map_err(|e| WebexError::device_registration(format!("Invalid web_socket_url: {e}"), None))?;
79        }
80
81        if !reg.encryption_service_url.is_empty() {
82            validate_webex_url(&reg.encryption_service_url, "https")
83                .map_err(|e| WebexError::device_registration(format!("Invalid encryption_service_url: {e}"), None))?;
84        }
85
86        self.device_url = Some(reg.device_url.clone());
87
88        info!("Device registered successfully");
89        Ok(reg)
90    }
91
92    /// Refresh an existing device registration.
93    pub async fn refresh(&self, token: &str) -> Result<DeviceRegistration> {
94        let device_url = self.device_url.as_deref().ok_or_else(|| {
95            WebexError::device_registration("Device not registered. Call register() first.", None)
96        })?;
97
98        debug!("Refreshing device registration");
99
100        let mut headers = HashMap::new();
101        headers.insert("Authorization".to_string(), format!("Bearer {}", token));
102        headers.insert("Content-Type".to_string(), "application/json".to_string());
103
104        let body = serde_json::to_string(&device_body())
105            .map_err(|e| WebexError::device_registration(format!("Failed to serialize body: {e}"), None))?;
106
107        let response = (self.http_do)(FetchRequest {
108            url: device_url.to_string(),
109            method: "PUT".to_string(),
110            headers,
111            body: Some(body),
112        })
113        .await
114        .map_err(|e| WebexError::device_registration(format!("Failed to refresh device: {e}"), None))?;
115
116        let status = response.status;
117
118        if status == 401 {
119            error!("Device refresh failed: Unauthorized");
120            return Err(WebexError::auth("Unauthorized to refresh device"));
121        }
122
123        if !response.ok {
124            error!("Device refresh failed with status {status}");
125            return Err(WebexError::device_registration("Failed to refresh device", Some(status)));
126        }
127
128        let mut reg: DeviceRegistration = serde_json::from_slice(&response.body)
129            .map_err(|e| WebexError::device_registration(format!("Failed to parse response: {e}"), None))?;
130
131        reg.encryption_service_url = reg.services.get("encryptionServiceUrl").cloned().unwrap_or_default();
132
133        // Validate URLs from the response
134        if !reg.web_socket_url.is_empty() {
135            validate_webex_url(&reg.web_socket_url, "wss")
136                .map_err(|e| WebexError::device_registration(format!("Invalid web_socket_url: {e}"), None))?;
137        }
138
139        if !reg.encryption_service_url.is_empty() {
140            validate_webex_url(&reg.encryption_service_url, "https")
141                .map_err(|e| WebexError::device_registration(format!("Invalid encryption_service_url: {e}"), None))?;
142        }
143
144        info!("Device refreshed successfully");
145        Ok(reg)
146    }
147
148    /// Unregister the device from WDM.
149    pub async fn unregister(&mut self, token: &str) -> Result<()> {
150        let device_url = self.device_url.as_deref().ok_or_else(|| {
151            WebexError::device_registration("Device not registered. Call register() first.", None)
152        })?;
153
154        debug!("Unregistering device");
155
156        let mut headers = HashMap::new();
157        headers.insert("Authorization".to_string(), format!("Bearer {}", token));
158        headers.insert("Content-Type".to_string(), "application/json".to_string());
159
160        let response = (self.http_do)(FetchRequest {
161            url: device_url.to_string(),
162            method: "DELETE".to_string(),
163            headers,
164            body: None,
165        })
166        .await
167        .map_err(|e| WebexError::device_registration(format!("Failed to unregister device: {e}"), None))?;
168
169        let status = response.status;
170
171        if status == 401 {
172            error!("Device unregistration failed: Unauthorized");
173            return Err(WebexError::auth("Unauthorized to unregister device"));
174        }
175
176        if !response.ok && status != 404 {
177            error!("Device unregistration failed with status {status}");
178            return Err(WebexError::device_registration("Failed to unregister device", Some(status)));
179        }
180
181        self.device_url = None;
182        info!("Device unregistered successfully");
183        Ok(())
184    }
185}