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}