Skip to main content

webex_message_handler/
types.rs

1//! Data types for webex-message-handler.
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::future::Future;
6use std::pin::Pin;
7use std::sync::Arc;
8
9/// Networking mode for the handler.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
11pub enum NetworkMode {
12    /// Use built-in HTTP and WebSocket libraries (default).
13    #[default]
14    Native,
15    /// Use provided fetch and WebSocket factory functions.
16    Injected,
17}
18
19/// HTTP request for injected fetch function.
20#[derive(Debug, Clone)]
21pub struct FetchRequest {
22    pub url: String,
23    pub method: String,
24    pub headers: HashMap<String, String>,
25    pub body: Option<String>,
26}
27
28/// HTTP response from injected fetch function.
29pub struct FetchResponse {
30    pub status: u16,
31    pub ok: bool,
32    pub body: Vec<u8>,
33}
34
35/// Custom fetch function for injected mode.
36pub type FetchFn = Arc<
37    dyn Fn(FetchRequest) -> Pin<Box<dyn Future<Output = Result<FetchResponse, Box<dyn std::error::Error + Send + Sync>>> + Send>>
38        + Send
39        + Sync,
40>;
41
42/// Boxed future result alias to reduce type complexity.
43type BoxFutureResult<'a, T> = Pin<Box<dyn Future<Output = Result<T, Box<dyn std::error::Error + Send + Sync>>> + Send + 'a>>;
44
45/// WebSocket interface for injected mode.
46pub trait InjectedWebSocket: Send + Sync {
47    fn send(&self, data: String) -> BoxFutureResult<'_, ()>;
48    fn receive(&self) -> BoxFutureResult<'_, String>;
49    fn close(&self) -> BoxFutureResult<'_, ()>;
50}
51
52/// WebSocket factory for injected mode.
53pub type WebSocketFactory = Arc<
54    dyn Fn(String) -> Pin<Box<dyn Future<Output = Result<Box<dyn InjectedWebSocket>, Box<dyn std::error::Error + Send + Sync>>> + Send>>
55        + Send
56        + Sync,
57>;
58
59/// Configuration for WebexMessageHandler.
60#[derive(Clone)]
61pub struct Config {
62    /// Webex bot or user access token (required).
63    pub token: String,
64
65    /// Networking mode: Native or Injected (default: Native).
66    pub mode: NetworkMode,
67
68    /// Optional HTTP client for proxy support (native mode only).
69    /// If None, a default client will be created.
70    pub client: Option<reqwest::Client>,
71
72    /// Custom fetch function for all HTTP requests (injected mode).
73    pub fetch: Option<FetchFn>,
74
75    /// Custom WebSocket factory (injected mode).
76    pub web_socket_factory: Option<WebSocketFactory>,
77
78    /// Mercury ping interval in seconds (default: 15).
79    pub ping_interval: f64,
80
81    /// Pong response timeout in seconds (default: 14).
82    pub pong_timeout: f64,
83
84    /// Max reconnect backoff in seconds (default: 32).
85    pub reconnect_backoff_max: f64,
86
87    /// Max consecutive reconnection attempts (default: 10).
88    pub max_reconnect_attempts: u32,
89
90    /// Automatically filter out messages sent by this bot to prevent loops (default: true).
91    pub ignore_self_messages: bool,
92}
93
94impl Default for Config {
95    fn default() -> Self {
96        Self {
97            token: String::new(),
98            mode: NetworkMode::Native,
99            client: None,
100            fetch: None,
101            web_socket_factory: None,
102            ping_interval: 15.0,
103            pong_timeout: 14.0,
104            reconnect_backoff_max: 32.0,
105            max_reconnect_attempts: 10,
106            ignore_self_messages: true,
107        }
108    }
109}
110
111/// Result of WDM device registration.
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct DeviceRegistration {
114    /// Mercury WebSocket URL.
115    #[serde(rename = "webSocketUrl")]
116    pub web_socket_url: String,
117
118    /// Device URL (used as clientId for KMS).
119    #[serde(rename = "url")]
120    pub device_url: String,
121
122    /// Bot's user ID.
123    #[serde(rename = "userId")]
124    pub user_id: String,
125
126    /// Service catalog from WDM.
127    #[serde(default)]
128    pub services: HashMap<String, String>,
129
130    /// Encryption service URL extracted from services.
131    #[serde(skip)]
132    pub encryption_service_url: String,
133}
134
135/// Actor in a Mercury activity.
136#[derive(Debug, Clone, Default, Serialize, Deserialize)]
137pub struct MercuryActor {
138    #[serde(default)]
139    pub id: String,
140
141    #[serde(rename = "objectType", default)]
142    pub object_type: String,
143
144    #[serde(rename = "emailAddress", default)]
145    pub email_address: Option<String>,
146}
147
148/// Object in a Mercury activity.
149#[derive(Debug, Clone, Default, Serialize, Deserialize)]
150pub struct MercuryObject {
151    #[serde(default)]
152    pub id: String,
153
154    #[serde(rename = "objectType", default)]
155    pub object_type: String,
156
157    #[serde(rename = "displayName", default)]
158    pub display_name: Option<String>,
159
160    #[serde(default)]
161    pub content: Option<String>,
162
163    #[serde(rename = "encryptionKeyUrl", default)]
164    pub encryption_key_url: Option<String>,
165}
166
167/// Target in a Mercury activity.
168#[derive(Debug, Clone, Default, Serialize, Deserialize)]
169pub struct MercuryTarget {
170    #[serde(default)]
171    pub id: String,
172
173    #[serde(rename = "objectType", default)]
174    pub object_type: String,
175
176    #[serde(rename = "encryptionKeyUrl", default)]
177    pub encryption_key_url: Option<String>,
178
179    #[serde(default)]
180    pub tags: Vec<String>,
181}
182
183/// A Mercury conversation activity.
184#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct MercuryActivity {
186    #[serde(default)]
187    pub id: String,
188
189    #[serde(default)]
190    pub verb: String,
191
192    #[serde(default)]
193    pub actor: MercuryActor,
194
195    #[serde(default)]
196    pub object: MercuryObject,
197
198    #[serde(default)]
199    pub target: MercuryTarget,
200
201    #[serde(default)]
202    pub published: String,
203
204    #[serde(rename = "encryptionKeyUrl", default)]
205    pub encryption_key_url: Option<String>,
206}
207
208/// A decrypted Webex message.
209#[derive(Debug, Clone)]
210pub struct DecryptedMessage {
211    /// Unique message ID.
212    pub id: String,
213
214    /// Conversation/space ID.
215    pub room_id: String,
216
217    /// Sender's user ID.
218    pub person_id: String,
219
220    /// Sender's email address.
221    pub person_email: String,
222
223    /// Decrypted plain text.
224    pub text: String,
225
226    /// Decrypted HTML content (rich text messages).
227    pub html: Option<String>,
228
229    /// ISO 8601 timestamp.
230    pub created: String,
231
232    /// "direct", "group", or None.
233    pub room_type: Option<String>,
234
235    /// Full decrypted activity for advanced use.
236    pub raw: MercuryActivity,
237}
238
239/// A deleted Webex message notification.
240#[derive(Debug, Clone)]
241pub struct DeletedMessage {
242    pub message_id: String,
243    pub room_id: String,
244    pub person_id: String,
245}
246
247/// A membership activity from Mercury.
248#[derive(Debug, Clone)]
249pub struct MembershipActivity {
250    /// Activity ID.
251    pub id: String,
252
253    /// ID of the person who performed the action.
254    pub actor_id: String,
255
256    /// ID of the member affected.
257    pub person_id: String,
258
259    /// Conversation/space ID.
260    pub room_id: String,
261
262    /// Membership action: "add", "leave", "assignModerator", or "unassignModerator".
263    pub action: String,
264
265    /// ISO 8601 timestamp.
266    pub created: String,
267
268    /// "direct", "group", or None.
269    pub room_type: Option<String>,
270
271    /// Full raw activity for advanced use.
272    pub raw: MercuryActivity,
273}
274
275/// Overall connection state.
276#[derive(Debug, Clone, Copy, PartialEq, Eq)]
277pub enum ConnectionStatus {
278    Connected,
279    Connecting,
280    Reconnecting,
281    Disconnected,
282}
283
284impl std::fmt::Display for ConnectionStatus {
285    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286        match self {
287            Self::Connected => write!(f, "connected"),
288            Self::Connecting => write!(f, "connecting"),
289            Self::Reconnecting => write!(f, "reconnecting"),
290            Self::Disconnected => write!(f, "disconnected"),
291        }
292    }
293}
294
295/// Structured health check of all connection subsystems.
296#[derive(Debug, Clone)]
297pub struct HandlerStatus {
298    /// Overall connection state.
299    pub status: ConnectionStatus,
300
301    /// Whether the Mercury WebSocket is currently open.
302    pub web_socket_open: bool,
303
304    /// Whether the KMS encryption context has been established.
305    pub kms_initialized: bool,
306
307    /// Whether the device is registered with WDM.
308    pub device_registered: bool,
309
310    /// Current auto-reconnect attempt number (0 if not reconnecting).
311    pub reconnect_attempt: u32,
312}