use std::pin::Pin;
use futures::Stream;
use tracing::warn;
use crate::{HeartbeatError, Idevice, IdeviceError, IdeviceService, obf};
#[derive(Debug)]
pub struct NotificationProxyClient {
pub idevice: Idevice,
}
impl IdeviceService for NotificationProxyClient {
fn service_name() -> std::borrow::Cow<'static, str> {
obf!("com.apple.mobile.notification_proxy")
}
async fn from_stream(idevice: Idevice) -> Result<Self, crate::IdeviceError> {
Ok(Self::new(idevice))
}
}
impl NotificationProxyClient {
pub fn new(idevice: Idevice) -> Self {
Self { idevice }
}
pub async fn post_notification(
&mut self,
notification_name: impl Into<String>,
) -> Result<(), IdeviceError> {
let request = crate::plist!({
"Command": "PostNotification",
"Name": notification_name.into()
});
self.idevice.send_plist(request).await
}
pub async fn observe_notification(
&mut self,
notification_name: impl Into<String>,
) -> Result<(), IdeviceError> {
let request = crate::plist!({
"Command": "ObserveNotification",
"Name": notification_name.into()
});
self.idevice.send_plist(request).await
}
pub async fn observe_notifications(
&mut self,
notification_names: &[&str],
) -> Result<(), IdeviceError> {
for name in notification_names {
self.observe_notification(*name).await?;
}
Ok(())
}
pub async fn receive_notification(&mut self) -> Result<String, IdeviceError> {
let response = self.idevice.read_plist().await?;
match response.get("Command").and_then(|c| c.as_string()) {
Some("RelayNotification") => match response.get("Name").and_then(|n| n.as_string()) {
Some(name) => Ok(name.to_string()),
None => Err(IdeviceError::UnexpectedResponse(
"missing Name in RelayNotification".into(),
)),
},
Some("ProxyDeath") => {
warn!("NotificationProxy died!");
Err(IdeviceError::NotificationProxyDeath)
}
_ => Err(IdeviceError::UnexpectedResponse(
"unexpected Command in notification response".into(),
)),
}
}
pub async fn receive_notification_with_timeout(
&mut self,
interval: u64,
) -> Result<String, IdeviceError> {
tokio::select! {
result = self.receive_notification() => result,
_ = crate::time::sleep(std::time::Duration::from_secs(interval)) => {
Err(HeartbeatError::Timeout.into())
}
}
}
pub fn into_stream(
mut self,
) -> Pin<Box<dyn Stream<Item = Result<String, IdeviceError>> + Send>> {
Box::pin(async_stream::try_stream! {
loop {
let response = self.idevice.read_plist().await?;
match response.get("Command").and_then(|c| c.as_string()) {
Some("RelayNotification") => {
match response.get("Name").and_then(|n| n.as_string()) {
Some(name) => yield name.to_string(),
None => Err(IdeviceError::UnexpectedResponse("missing Name in RelayNotification stream".into()))?,
}
}
Some("ProxyDeath") => {
warn!("NotificationProxy died!");
Err(IdeviceError::NotificationProxyDeath)?;
}
_ => Err(IdeviceError::UnexpectedResponse("unexpected Command in notification stream".into()))?,
}
}
})
}
pub async fn shutdown(&mut self) -> Result<(), IdeviceError> {
let request = crate::plist!({
"Command": "Shutdown"
});
self.idevice.send_plist(request).await?;
let _ = self.idevice.read_plist().await;
Ok(())
}
}
#[cfg(feature = "rsd")]
impl crate::RsdService for NotificationProxyClient {
fn rsd_service_name() -> std::borrow::Cow<'static, str> {
crate::obf!("com.apple.mobile.notification_proxy.shim.remote")
}
async fn from_stream(stream: Box<dyn crate::ReadWrite>) -> Result<Self, crate::IdeviceError> {
let mut idevice = crate::Idevice::new(stream, "");
idevice.rsd_checkin().await?;
Ok(Self::new(idevice))
}
}