Skip to content

Commit

Permalink
WIP: APNS push
Browse files Browse the repository at this point in the history
  • Loading branch information
threema-danilo committed Mar 29, 2018
1 parent c31c836 commit 9e37a4d
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 32 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ that looks like this:

[apns]
keyfile = "your-keyfile.p8"
key_id = "AB123456XY"
team_id = "Your Team"

Then simply run

Expand Down
4 changes: 2 additions & 2 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ quick_error! {
pub enum PushError {
SendError(err: HyperError) {
from()
display("GCM message could not be sent: {}", err)
display("Push message could not be sent: {}", err)
cause(err)
}
ProcessingError(msg: String) {
display("GCM message could not be processed: {}", msg)
display("Push message could not be processed: {}", msg)
}
Other(msg: String) {
display("Other: {}", msg)
Expand Down
16 changes: 12 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ fn main() {
process::exit(2);
});

// Determine APNS API keyfile
// Determine APNS config
let config_apns = config.section(Some("apns".to_owned())).unwrap_or_else(|| {
error!("Invalid config file: No [apns] section in {}", configfile);
process::exit(2);
Expand All @@ -90,20 +90,28 @@ fn main() {
error!("Invalid config file: No 'keyfile' key in [apns] section in {}", configfile);
process::exit(2);
});
let apns_team_id = config_apns.get("team_id").unwrap_or_else(|| {
error!("Invalid config file: No 'team_id' key in [apns] section in {}", configfile);
process::exit(2);
});
let apns_key_id = config_apns.get("key_id").unwrap_or_else(|| {
error!("Invalid config file: No 'key_id' key in [apns] section in {}", configfile);
process::exit(2);
});

// Open APNS keyfile
let mut apns_keyfile = File::open(apns_keyfile_path).unwrap_or_else(|e| {
error!("Invalid 'keyfile' path: Could not open '{}': {}", apns_keyfile_path, e);
process::exit(3);
});
let mut apns_api_key_string = String::new();
apns_keyfile.read_to_string(&mut apns_api_key_string).unwrap_or_else(|e| {
let mut apns_api_key_string = Vec::<u8>::new();
apns_keyfile.read_to_end(&mut apns_api_key_string).unwrap_or_else(|e| {
error!("Invalid 'keyfile' path: Could not read '{}': {}", apns_keyfile_path, e);
process::exit(3);
});

info!("Starting Push Relay Server {} on {}", VERSION, &addr);
server::serve(gcm_api_key, apns_api_key_string, addr).unwrap_or_else(|e| {
server::serve(gcm_api_key, apns_api_key_string, apns_team_id, apns_key_id, addr).unwrap_or_else(|e| {
error!("Could not start relay server: {}", e);
process::exit(3);
});
Expand Down
50 changes: 40 additions & 10 deletions src/push/apns.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use apns2::client::Endpoint;
use futures::future;
use apns2::client::{Client, Endpoint};
use apns2::request::notification::{
NotificationOptions, Priority, NotificationBuilder, SilentNotificationBuilder,
};
use futures::Future;
use tokio_core::reactor::Handle;

use ::errors::PushError;
Expand All @@ -8,15 +11,42 @@ use ::utils::BoxedFuture;


/// Send an APNS push notification.
///
/// TODO: Once the next release is out, remove Option around version.
#[allow(dead_code)]
pub fn send_push(
_handle: Handle,
_endpoint: Endpoint,
_push_token: &ApnsToken,
pub fn send_push<S>(
handle: Handle,
endpoint: Endpoint,
api_key: Vec<u8>,
team_id: S,
key_id: S,
push_token: &ApnsToken,
_version: u16,
_session: &str,
) -> BoxedFuture<(), PushError> {
boxed!(future::ok(()))
) -> BoxedFuture<(), PushError> where S: Into<String> {

// Connecting to APNS
let client = Client::token(
&*api_key,
key_id,
team_id,
&handle,
endpoint,
).unwrap();

let options = NotificationOptions {
apns_id: None,
apns_expiration: Some(30),
apns_priority: Priority::High,
apns_topic: Some("wc".to_string()),
apns_collapse_id: None,
};

// Notification payload
let payload = SilentNotificationBuilder::new().build(&*push_token.0, options);

boxed!(
client
.send(payload)
.map(|response| debug!("Success details: {:?}", response))
.map_err(|error| PushError::ProcessingError(format!("Push was unsuccessful: {}", error)))
)
}
9 changes: 5 additions & 4 deletions src/push/gcm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@ fn get_timestamp() -> i64 {
}

/// Send a GCM push notification.
///
/// TODO: Once the next release is out, remove Option around version.
pub fn send_push(
handle: Handle,
api_key: String,
Expand All @@ -78,7 +76,7 @@ pub fn send_push(
session: &str,
priority: Priority,
ttl: u32,
) -> BoxedFuture<MessageResponse, PushError> {
) -> BoxedFuture<(), PushError> {
let data = ThreemaPayload { wcs: session, wct: get_timestamp(), wcv: version };

let payload = Payload {
Expand Down Expand Up @@ -151,7 +149,10 @@ pub fn send_push(
))?;

match data.success {
1 => Ok(data),
1 => {
debug!("Success details: {:?}", data);
Ok(())
},
_ => Err(PushError::ProcessingError("Success count in response is not 1".into())),
}
}))
Expand Down
9 changes: 9 additions & 0 deletions src/push/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ pub enum PushToken {
Apns(ApnsToken),
}

impl PushToken {
pub fn abbrev(&self) -> &'static str {
match *self {
PushToken::Gcm(_) => "GCM",
PushToken::Apns(_) => "APNS",
}
}
}

/// Payload sent to end device inside the push notification.
#[derive(Debug, Serialize)]
struct ThreemaPayload<'a> {
Expand Down
42 changes: 30 additions & 12 deletions src/server.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::net::SocketAddr;

use apns2::client::Endpoint;
use futures::Stream;
use futures::future::{self, Future};
use hyper::{Error as HyperError, Method, StatusCode};
Expand All @@ -9,16 +10,18 @@ use tokio_core::reactor::{Core, Handle};
use url::form_urlencoded;

use ::push::{PushToken, GcmToken, ApnsToken};
use ::push::gcm;
use ::push::{gcm, apns};
use ::utils::BoxedFuture;


/// Start the server and run infinitely.
pub fn serve<S, T>(
pub fn serve<S, T, U> (
gcm_api_key: S,
apns_api_key: T,
apns_api_key: Vec<u8>,
apns_team_id: T,
apns_key_id: U,
listen_on: SocketAddr,
) -> Result<(), HyperError> where S: ToString, T: ToString {
) -> Result<(), HyperError> where S: ToString, T: ToString, U: ToString {
// TODO: CSRF

// Create reactor loop
Expand All @@ -31,7 +34,9 @@ pub fn serve<S, T>(
let serve = Http::new().serve_addr_handle(&listen_on, &handle1, || {
Ok(PushHandler {
gcm_api_key: gcm_api_key.to_string(),
apns_api_key: apns_api_key.to_string(),
apns_api_key: apns_api_key.clone(),
apns_team_id: apns_team_id.to_string(),
apns_key_id: apns_key_id.to_string(),
handle: handle2.clone(),
})
})?;
Expand All @@ -48,7 +53,9 @@ pub fn serve<S, T>(

pub struct PushHandler {
gcm_api_key: String,
apns_api_key: String,
apns_api_key: Vec<u8>,
apns_team_id: String,
apns_key_id: String,
handle: Handle,
}

Expand Down Expand Up @@ -104,7 +111,10 @@ impl Service for PushHandler {
};

// Parse request body
let api_key_clone = self.gcm_api_key.clone();
let gcm_api_key_clone = self.gcm_api_key.clone();
let apns_api_key_clone = self.apns_api_key.clone();
let apns_team_id_clone = self.apns_team_id.clone();
let apns_key_id_clone = self.apns_key_id.clone();
let handle_clone = self.handle.clone();
Box::new(
body
Expand Down Expand Up @@ -163,24 +173,32 @@ impl Service for PushHandler {
};

// Send push notification
info!("Sending push message to GCM for session {} [v{}]", session_public_key, version);
info!("Sending push message to {} for session {} [v{}]", push_token.abbrev(), session_public_key, version);
let push_future = match push_token {
PushToken::Gcm(ref token) => gcm::send_push(
handle_clone,
api_key_clone,
gcm_api_key_clone,
token,
version,
&session_public_key,
gcm::Priority::High,
90,
),
PushToken::Apns(ref _token) => unimplemented!(),
PushToken::Apns(ref token) => apns::send_push(
handle_clone,
Endpoint::Production,
apns_api_key_clone,
apns_team_id_clone,
apns_key_id_clone,
token,
version,
&session_public_key,
),
};

boxed!(push_future
.map(|resp| {
.map(|_| {
debug!("Success!");
debug!("Details: {:?}", resp);
Response::new()
.with_status(StatusCode::NoContent)
.with_header(ContentLength(0))
Expand Down

0 comments on commit 9e37a4d

Please sign in to comment.