An implementation of Apple Push Notification Service on Finagle.
It is currently not available in Maven Central, so you'll have to compile it and deploy it locally:
# git clone https://github.com/Hopper/finagle-apns
# cd finagle-apns
# git checkout v6.18-3
# sbt +publish-local
Then add it as a dependency to your project
libraryDependencies += "com.hopper" %% "finagle-apns" % "6.18-3"
import com.hopper.finagle._
import com.hopper.finagle.apns._
// Load the KeyStore for Apple's Sandbox environment
val keystore: java.security.KeyStore = ???
val password: Array[Char] = ???
// Create your client
val apns = Apns.newRichClient(ApnsEnvironment.Sandbox(keystore, password))
// Obtain a device token to send a notification to
val deviceToken: Array[Byte] = ???
// Create a notification
val notification = Notification(deviceToken, Payload(alert = Some(SimpleAlert("Hi!"))))
// Send the notification
apns(notification)
.onSuccess { _ =>
println("Notification was sent to Apple... Maybe.")
println("We can only guarantee that we wrote the notification to your network interface.")
}
When APNS rejects one of your notifications, it sends a message asynchronously. This is represented as an Offer[Rejection]
. Here's how to use it:
val rejections: Offer[Rejection] = apns.rejectedNotifications
rejections.foreach { case Rejection(reason, rejected, resent) =>
println("The following notification was rejected (%s):" format reason)
println(rejected)
println("The rejection forced the client to resend the following notifications")
println(resent.mkString(","))
}
As few notes:
- When APNS rejects one of your notifications, it closes the connection and will not deliver any subsequent notifications sent on that connection. It is up to your process to resend any subsequent notifications. Currently, this client will handle that for you (I may reconsider that decision, so beware).
- In order to provide the rejected notification and to be able to re-send subsequent notifications, the client needs to hold on to the last few notifications you send in memory. This buffer's size is configurable.
- The consequence is that the client may not be able to provide you with the rejected notification and it may also not be able to resend all notifications that might be necessary. You'll need to adjust the buffer size with the rate at which you're sending notifications.
APNS requires that you periodically connect to the feedback service to fetch device tokens that have unregistered from your notifications.
This is exposed by the fetchFeedback
function on the client:
// Some method to asynchronously unregister a token from your database
def unregisterToken(token: Array[Byte]): Future[Unit] = ???
val feedback: Future[Spool[Feedback]] = apns.fetchFeedback()
feedback.flatMap { spool =>
spool.mapFuture { feedback =>
unregisterToken(feedback.token)
}
}