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