creator_simctl/
push.rs

1//! Supporting types for the `simctl push` subcommand.
2
3use serde::Serialize;
4use std::process::Stdio;
5
6use super::{Device, Result, Validate};
7
8/// Represents a push notification that can be sent to a device.
9#[derive(Clone, Debug, Default, Serialize)]
10pub struct Push {
11    /// Contains the payload of this push notification.
12    pub aps: PushPayload,
13}
14
15/// Alert that is presented to the user.
16#[derive(Clone, Debug, Default, Serialize)]
17pub struct PushAlert {
18    /// Title that is shown to the user.
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub title: Option<String>,
21
22    /// Subtitle that is shown to the user.
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub subtitle: Option<String>,
25
26    /// Body that is shown to the user.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub body: Option<String>,
29
30    /// Path to a launch image contained in the app bundle that will be shown to
31    /// the user when the user opens the notification and has to wait for the
32    /// application to launch.
33    #[serde(rename = "launch-image", skip_serializing_if = "Option::is_none")]
34    pub launch_image: Option<String>,
35
36    /// Key of a localized string that will be used as a title in lieu of
37    /// [`PushAlert::title`].
38    #[serde(rename = "title-loc-key", skip_serializing_if = "Option::is_none")]
39    pub title_loc_key: Option<String>,
40
41    /// Arguments that are passed to the localized title that will be shown to
42    /// the user. The number of arguments should equal the number of `%@`
43    /// formatters in the localized string.
44    #[serde(rename = "title-loc-args", skip_serializing_if = "Option::is_none")]
45    pub title_loc_args: Option<Vec<String>>,
46
47    /// Key of a localized string that will be used as a subtitle in lieu of
48    /// [`PushAlert::subtitle`].
49    #[serde(rename = "subtitle-loc-key", skip_serializing_if = "Option::is_none")]
50    pub subtitle_loc_key: Option<String>,
51
52    /// Arguments that are passed to the localized subtitle that will be shown
53    /// to the user. The number of arguments should equal the number of `%@`
54    /// formatters in the localized string.
55    #[serde(rename = "subtitle-loc-args", skip_serializing_if = "Option::is_none")]
56    pub subtitle_loc_args: Option<Vec<String>>,
57
58    /// Key of a localized string that will be used as body in lieu of
59    /// [`PushAlert::body`].
60    #[serde(rename = "loc-key", skip_serializing_if = "Option::is_none")]
61    pub loc_key: Option<String>,
62
63    /// Arguments that are passed to the localized body that will be shown to
64    /// the user. The number of arguments should equal the number of `%@`
65    /// formatters in the localized string.
66    #[serde(rename = "loc-args", skip_serializing_if = "Option::is_none")]
67    pub loc_args: Option<Vec<String>>,
68}
69
70/// Sound that is played through the device's speakers when a push notification
71/// arrives.
72#[derive(Clone, Debug, Default, Serialize)]
73pub struct PushSound {
74    /// Enables "critical" push sound.
75    pub critical: usize,
76
77    /// Name of the sound file in the app's bundle that will be played.
78    pub name: String,
79
80    /// Volume that will be used to play the sound.
81    pub volume: f32,
82}
83
84/// Payload of a push notification that is sent to a device.
85#[derive(Clone, Debug, Default, Serialize)]
86pub struct PushPayload {
87    /// Optional alert that will be presented to the user.
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub alert: Option<PushAlert>,
90
91    /// Optional number that will update the badge on the springboard. Set this
92    /// to `Some(0)` to remove an existing badge.
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub badge: Option<usize>,
95
96    /// Optional sound that will be played when the notification arrives.
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub sound: Option<PushSound>,
99
100    /// Optional thread id that is used by the OS to group multiple messages
101    /// that are related to the same "thread" (e.g. conversation or topic).
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub thread_id: Option<String>,
104
105    /// Category that matches with one of the categories registered in the app.
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub category: Option<String>,
108
109    /// Flag that indicates if content is available (should be either 0 or 1).
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub content_available: Option<usize>,
112
113    /// Flag that indicates if this payload should be run through the push
114    /// notification extension of this app to update its content.
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub mutable_content: Option<usize>,
117
118    /// Content ID that is passed to the app when this notification is opened.
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub target_content_id: Option<String>,
121}
122
123impl Device {
124    /// Sends the given push message to this device for an app with the given
125    /// bundle ID.
126    pub fn push(&self, bundle_id: &str, push: &Push) -> Result<()> {
127        let mut process = self
128            .simctl()
129            .command("push")
130            .arg(&self.udid)
131            .arg(bundle_id)
132            .arg("-")
133            .stdin(Stdio::piped())
134            // .stdout(Stdio::inherit())
135            .spawn()?;
136
137        if let Some(stdin) = process.stdin.as_mut() {
138            serde_json::to_writer(stdin, push)?;
139        }
140
141        process.wait_with_output()?.validate()
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use serial_test::serial;
148
149    use super::*;
150    use crate::mock;
151
152    #[test]
153    #[serial]
154    fn test_push() -> Result<()> {
155        mock::device()?.boot()?;
156        mock::device()?.push(
157            "com.apple.mobilecal",
158            &Push {
159                aps: PushPayload {
160                    alert: Some(PushAlert {
161                        body: Some("Hello World!".to_owned()),
162                        ..Default::default()
163                    }),
164                    ..Default::default()
165                },
166            },
167        )?;
168        mock::device()?.shutdown()?;
169
170        Ok(())
171    }
172}