Skip to main content

sockudo_http/
push.rs

1use crate::{Result, Sockudo, SockudoError};
2use reqwest::Response;
3use serde::{Deserialize, Serialize};
4use sonic_rs::{JsonValueTrait, Value, json};
5use std::collections::{BTreeMap, HashMap};
6
7#[derive(Clone, Debug, Default, Serialize, Deserialize)]
8pub struct PushCursorParams {
9    pub limit: Option<u32>,
10    pub cursor: Option<String>,
11}
12
13impl PushCursorParams {
14    pub fn to_map(&self) -> BTreeMap<String, String> {
15        let mut map = BTreeMap::new();
16        if let Some(limit) = self.limit {
17            map.insert("limit".to_string(), limit.to_string());
18        }
19        if let Some(cursor) = &self.cursor {
20            map.insert("cursor".to_string(), cursor.clone());
21        }
22        map
23    }
24}
25
26#[derive(Clone, Debug, Default, Serialize, Deserialize)]
27pub struct PushSubscriptionParams {
28    pub limit: Option<u32>,
29    pub cursor: Option<String>,
30    pub channel: Option<String>,
31    pub device_id: Option<String>,
32}
33
34impl PushSubscriptionParams {
35    pub fn to_map(&self) -> BTreeMap<String, String> {
36        let mut map = PushCursorParams {
37            limit: self.limit,
38            cursor: self.cursor.clone(),
39        }
40        .to_map();
41        if let Some(channel) = &self.channel {
42            map.insert("channel".to_string(), channel.clone());
43        }
44        if let Some(device_id) = &self.device_id {
45            map.insert("deviceId".to_string(), device_id.clone());
46        }
47        map
48    }
49}
50
51fn push_headers(capability: &str, device_identity_token: Option<&str>) -> HashMap<String, String> {
52    let mut headers = HashMap::from([(
53        "X-Sockudo-Push-Capability".to_string(),
54        capability.to_string(),
55    )]);
56    if let Some(token) = device_identity_token {
57        headers.insert(
58            "X-Sockudo-Device-Identity-Token".to_string(),
59            token.to_string(),
60        );
61    }
62    headers
63}
64
65fn push_path(path: &str) -> String {
66    format!("/push{path}")
67}
68
69impl Sockudo {
70    pub async fn activate_device(&self, device: &Value) -> Result<Response> {
71        self.post_with_headers(
72            &push_path("/deviceRegistrations"),
73            device,
74            &push_headers("push-admin", None),
75        )
76        .await
77    }
78
79    pub async fn create_device_activation(&self, device: &Value) -> Result<Response> {
80        self.activate_device(device).await
81    }
82
83    pub async fn update_device_registration(
84        &self,
85        device: &Value,
86        device_identity_token: &str,
87    ) -> Result<Response> {
88        self.post_with_headers(
89            &push_path("/deviceRegistrations"),
90            device,
91            &push_headers("push-subscribe", Some(device_identity_token)),
92        )
93        .await
94    }
95
96    pub async fn list_device_registrations(
97        &self,
98        params: Option<&PushCursorParams>,
99    ) -> Result<Response> {
100        let params = params.map(PushCursorParams::to_map);
101        self.get_with_headers(
102            &push_path("/deviceRegistrations"),
103            params.as_ref(),
104            &push_headers("push-admin", None),
105        )
106        .await
107    }
108
109    pub async fn get_device_registration(
110        &self,
111        device_id: &str,
112        device_identity_token: Option<&str>,
113    ) -> Result<Response> {
114        let capability = if device_identity_token.is_some() {
115            "push-subscribe"
116        } else {
117            "push-admin"
118        };
119        self.get_with_headers(
120            &push_path(&format!("/deviceRegistrations/{device_id}")),
121            None,
122            &push_headers(capability, device_identity_token),
123        )
124        .await
125    }
126
127    pub async fn delete_device_registration(
128        &self,
129        device_id: &str,
130        device_identity_token: Option<&str>,
131    ) -> Result<Response> {
132        let capability = if device_identity_token.is_some() {
133            "push-subscribe"
134        } else {
135            "push-admin"
136        };
137        self.delete_with_headers(
138            &push_path(&format!("/deviceRegistrations/{device_id}")),
139            None,
140            &push_headers(capability, device_identity_token),
141        )
142        .await
143    }
144
145    pub async fn remove_device_registrations_by_client(&self, client_id: &str) -> Result<Response> {
146        let params = BTreeMap::from([("clientId".to_string(), client_id.to_string())]);
147        self.delete_with_headers(
148            &push_path("/deviceRegistrations"),
149            Some(&params),
150            &push_headers("push-admin", None),
151        )
152        .await
153    }
154
155    pub async fn upsert_channel_push_subscription(
156        &self,
157        subscription: &Value,
158        device_identity_token: Option<&str>,
159    ) -> Result<Response> {
160        let capability = if device_identity_token.is_some() {
161            "push-subscribe"
162        } else {
163            "push-admin"
164        };
165        self.post_with_headers(
166            &push_path("/channelSubscriptions"),
167            subscription,
168            &push_headers(capability, device_identity_token),
169        )
170        .await
171    }
172
173    pub async fn list_channel_push_subscriptions(
174        &self,
175        params: Option<&PushSubscriptionParams>,
176        device_identity_token: Option<&str>,
177    ) -> Result<Response> {
178        let capability = if device_identity_token.is_some() {
179            "push-subscribe"
180        } else {
181            "push-admin"
182        };
183        let params = params.map(PushSubscriptionParams::to_map);
184        self.get_with_headers(
185            &push_path("/channelSubscriptions"),
186            params.as_ref(),
187            &push_headers(capability, device_identity_token),
188        )
189        .await
190    }
191
192    pub async fn delete_channel_push_subscriptions(
193        &self,
194        params: &PushSubscriptionParams,
195        device_identity_token: Option<&str>,
196    ) -> Result<Response> {
197        let capability = if device_identity_token.is_some() {
198            "push-subscribe"
199        } else {
200            "push-admin"
201        };
202        let params = params.to_map();
203        self.delete_with_headers(
204            &push_path("/channelSubscriptions"),
205            Some(&params),
206            &push_headers(capability, device_identity_token),
207        )
208        .await
209    }
210
211    pub async fn list_channel_push_subscription_channels(
212        &self,
213        params: Option<&PushCursorParams>,
214    ) -> Result<Response> {
215        let params = params.map(PushCursorParams::to_map);
216        self.get_with_headers(
217            &push_path("/channelSubscriptions/channels"),
218            params.as_ref(),
219            &push_headers("push-admin", None),
220        )
221        .await
222    }
223
224    pub async fn list_push_credentials(
225        &self,
226        params: Option<&PushCursorParams>,
227    ) -> Result<Response> {
228        let params = params.map(PushCursorParams::to_map);
229        self.get_with_headers(
230            &push_path("/credentials"),
231            params.as_ref(),
232            &push_headers("push-admin", None),
233        )
234        .await
235    }
236
237    pub async fn put_push_credential(
238        &self,
239        provider: &str,
240        credential: &Value,
241    ) -> Result<Response> {
242        self.post_with_headers(
243            &push_path(&format!("/credentials/{provider}")),
244            credential,
245            &push_headers("push-admin", None),
246        )
247        .await
248    }
249
250    pub async fn publish_push(&self, request: &Value) -> Result<Response> {
251        let mut request = request.clone();
252        request["sync"] = json!(false);
253        self.post_with_headers(
254            &push_path("/publish"),
255            &request,
256            &push_headers("push-admin", None),
257        )
258        .await
259    }
260
261    pub async fn publish_push_direct(&self, request: &Value) -> Result<Response> {
262        self.publish_push(request).await
263    }
264
265    pub async fn publish_push_batch(&self, requests: &[Value]) -> Result<Response> {
266        let requests: Vec<Value> = requests
267            .iter()
268            .map(|request| {
269                let mut item = request.clone();
270                item["sync"] = json!(false);
271                item
272            })
273            .collect();
274        let body = sonic_rs::to_value(&requests).map_err(SockudoError::Json)?;
275        self.post_with_headers(
276            &push_path("/batch/publish"),
277            &body,
278            &push_headers("push-admin", None),
279        )
280        .await
281    }
282
283    pub async fn schedule_push(&self, request: &Value) -> Result<Response> {
284        if request.get("notBeforeMs").is_none() {
285            return Err(SockudoError::Validation {
286                message: "scheduled push requires notBeforeMs".to_string(),
287            });
288        }
289        self.publish_push(request).await
290    }
291
292    pub async fn get_publish_status(&self, publish_id: &str) -> Result<Response> {
293        self.get_with_headers(
294            &push_path(&format!("/publish/{publish_id}/status")),
295            None,
296            &push_headers("push-admin", None),
297        )
298        .await
299    }
300
301    pub async fn cancel_scheduled_push(&self, publish_id: &str) -> Result<Response> {
302        self.delete_with_headers(
303            &push_path(&format!("/scheduled/{publish_id}")),
304            None,
305            &push_headers("push-admin", None),
306        )
307        .await
308    }
309
310    pub async fn post_push_delivery_status(&self, event: &Value) -> Result<Response> {
311        self.post_with_headers(
312            &push_path("/deliveryStatus"),
313            event,
314            &push_headers("push-admin", None),
315        )
316        .await
317    }
318}