#![deny(warnings)]
extern crate curl;
#[macro_use]
extern crate failure;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate uuid;
mod types;
pub use self::types::*;
mod error;
use self::error::*;
use std::path::{Path, PathBuf};
use std::cell::RefCell;
use uuid::Uuid;
use failure::Error;
use curl::easy::{Easy2, Handler, HttpVersion, List, WriteError};
struct Collector(Vec<u8>);
impl Handler for Collector {
fn write(&mut self, data: &[u8]) -> Result<usize, WriteError> {
self.0.extend_from_slice(data);
Ok(data.len())
}
}
#[derive(Clone, Debug)]
pub struct ProviderCertificate {
pub p12_path: PathBuf,
pub passphrase: Option<String>,
}
#[derive(Clone, Debug)]
pub enum Auth {
ProviderCertificate(ProviderCertificate),
}
impl Auth {
fn as_cert(&self) -> &ProviderCertificate {
match self {
&Auth::ProviderCertificate(ref c) => c,
}
}
}
pub struct ApnsSync {
production: bool,
verbose: bool,
delivery_disabled: bool,
auth: Auth,
easy: RefCell<Easy2<Collector>>,
}
impl ApnsSync {
pub fn new(auth: Auth) -> Result<Self, Error> {
let mut easy = Easy2::new(Collector(Vec::new()));
easy.http_version(HttpVersion::V2)?;
{
let cert = auth.as_cert();
easy.ssl_cert(&cert.p12_path)?;
if let Some(ref pw) = cert.passphrase.as_ref() {
easy.key_password(&pw)?;
}
}
let apns = ApnsSync {
production: true,
verbose: false,
delivery_disabled: false,
auth,
easy: RefCell::new(easy),
};
Ok(apns)
}
pub fn with_certificate<P: AsRef<Path>>(
path: P,
passphrase: Option<String>,
) -> Result<ApnsSync, Error> {
Self::new(Auth::ProviderCertificate(ProviderCertificate {
p12_path: path.as_ref().to_path_buf(),
passphrase,
}))
}
pub fn set_verbose(&mut self, verbose: bool) {
self.verbose = verbose;
}
pub fn set_production(&mut self, production: bool) {
self.production = production;
}
pub fn disable_delivery_for_testing(&mut self) {
self.delivery_disabled = true;
}
fn build_url(&self, device_token: &str) -> String {
let root = if self.production {
APN_URL_PRODUCTION
} else {
APN_URL_DEV
};
format!("{}/3/device/{}", root, device_token)
}
pub fn send(&self, notification: Notification) -> Result<Uuid, SendError> {
let n = notification;
let id = n.id.unwrap_or(Uuid::new_v4());
if self.delivery_disabled {
return Ok(id);
}
let url = self.build_url(&n.device_token);
let mut headers = List::new();
headers.append(&format!("apns-id:{}", id.to_string(),))?;
headers.append(&format!(
"apns-expiration:{}",
n.expiration
.map(|x| x.to_string())
.unwrap_or("".to_string())
))?;
headers.append(&format!(
"apns-priority:{}",
n.priority
.map(|x| x.to_int().to_string())
.unwrap_or("".to_string())
))?;
headers.append(&format!("apns-topic:{}", n.topic))?;
headers.append(&format!(
"apns-collapse-id:{}",
n.collapse_id
.map(|x| x.as_str().to_string())
.unwrap_or("".to_string())
))?;
let request = ApnsRequest { aps: n.payload };
let raw_request = ::serde_json::to_vec(&request)?;
let mut easy = self.easy.borrow_mut();
match &self.auth {
_ => {}
}
easy.verbose(self.verbose)?;
easy.http_headers(headers)?;
easy.post(true)?;
easy.post_fields_copy(&raw_request)?;
easy.url(&url)?;
easy.perform()?;
let status = easy.response_code()?;
if status != 200 {
let response_data = easy.get_ref();
let reason = ErrorResponse::parse_payload(&response_data.0);
Err(ApiError { status, reason }.into())
} else {
Ok(id)
}
}
}
#[cfg(test)]
mod test {
use std::env::var;
use super::*;
#[test]
fn test_cert() {
let cert_path = var("APNS_CERT_PATH").unwrap();
let cert_pw = Some(var("APNS_CERT_PW").unwrap());
let topic = var("APNS_TOPIC").unwrap();
let token = var("APNS_DEVICE_TOKEN").unwrap();
let mut apns = ApnsSync::with_certificate(cert_path, cert_pw).unwrap();
apns.set_verbose(true);
let n = NotificationBuilder::new(topic, token)
.title("title")
.build();
apns.send(n).unwrap();
}
}