After the discussion thread, we are proposing this as a final revision of this proposal and enter the proposal review phase which will run until the 31st of May 2019.
We have integrated most of the feedback from the discussion thread so even if you have read the previous version, you will find some changes that you hopefully agree with. To highlight a few of the major changes:
- use SSL PointerTricks to fix our usages on OpenSSL 1.1 versus OpenSSL 1.0
- tests have been added
- more configuration options for APNS provided options
- documentation
- Naming convention changes
- Switch to ByteBuffer
- Switch to in memory signing key
The feedback model will be very similar to the one known from Swift Evolution. The community is asked to provide feedback in the way outlined below and after the review period finishes, the SSWG will -- based on the community feedback -- decide whether to promote the proposal to the Sandbox maturity level or not.
What goes into a review of a proposal?
The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the evolution of the server-side Swift ecosystem.
When reviewing a proposal, here are some questions to consider:
- What is your evaluation of the proposal?
- Is the problem being addressed significant enough?
- Does this proposal fit well with the feel and direction of Swift on Server?
- If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
- How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
Thank you for contributing to the Swift Server Work Group!
What happens if the proposal gets accepted?
If this proposal gets accepted, a version (likely 0.2.0
) will be tagged. The development (in form of pull requests) will continue as a regular open-source project.
NIO-based Apple Push Notification Service
- Proposal: SSWG-0006
- Authors: Kyle Browning
- Sponsor: Vapor
- Review Manager: TBD
- Status: Implemented
- Implementation: kylebrowning/swift-nio-apns
- Forum Threads: Pitch, Discussion
Package Description
Apple push notification service implementation built on Swift NIO.
Package name | nio-apns |
Module Name | NIOAPNS |
Status | Active review (10th...31st May, 2019) |
Proposed Maturity Level | Sandbox |
License | Apache 2 |
Dependencies | SwiftNIO 2.x, SwiftNIOSSL 2.x, SwiftNIOHTTP2 1.x |
Introduction
NIOAPNS
is a module thats gives server side swift applications the ability to use the Apple push notification service.
Motivation
APNS is used to push billions of pushes a day, (7 billion per day in 2012). Many of us using Swift on the backend are using it to power our iOS applications. Having a community supported APNS implementation would go a long way to making it the fastest, free-ist, and simplest solution that exists.
All of the currently maintained libraries either have framework specific dependencies, are not built with NIO, or do not provide enough extensibility while providing "out of the box" capabilities.
Proposed Solution
NIOAPNS
provides the essential types for interacting with APNS Server (both production and sandbox).
What it does do
- Provides an API for handling connection to Apples HTTP2 APNS server.
- Provides proper error messages that APNS might respond with.
- Uses custom/non dependency implementations of JSON Web Token specific to APNS (using rfc7519.
- Imports OpenSSL for SHA256 and ES256.
- Provides an interface for signing your Push Notifications.
- Signs your token request.
- Sends push notifications to a specific device.
- Adheres to guidelines Apple Provides.
What it won't do.
- Store/register device tokens.
- Build an HTTP2 generic client.
- Google Cloud Message.
- Refresh your token no more than once every 20 minutes and no less than once every 60 minutes. (up to connection handler)
Future considerations and dependencies
- BoringSSL
- swift-log
- swift-metrics
- swift-jwt?
- swift-http2-client?
APNSConfiguration
APNSConfiguration
is a structure that provides the system with common configuration.
public struct APNSConfiguration {
public var keyIdentifier: String
public var teamIdentifier: String
public var signer: APNSSigner
public var topic: String
public var environment: Environment
public var tlsConfiguration: TLSConfiguration
public var url: URL {
switch environment {
case .production:
return URL(string: "https://api.push.apple.com")!
case .sandbox:
return URL(string: "https://api.development.push.apple.com")!
}
}
Example APNSConfiguration
let signer = ...
let apnsConfig = try APNSConfiguration(keyIdentifier: "9UC9ZLQ8YW",
teamIdentifier: "ABBM6U9RM5",
signer: signer),
topic: "com.grasscove.Fern",
environment: .sandbox)
Signer
APNSSigner
provides a structure to sign the payloads with. This should be loaded into memory at the configuration level. It requires the data to be in a ByteBuffer format. We've provided a convenience initializer for users to do this from filePath. This should only be done once, and not on an EventLoop.
let signer = try! APNSSigner(filePath: "/Users/kylebrowning/Downloads/AuthKey_9UC9ZLQ8YW.p8")
APNSConnection
APNSConnection
is a class with methods thats provides a wrapper to NIO's ClientBootstrap. The swift-nio-http2
dependency is utilized here. It also provides a function to send a notification to a specific device token string.
Example APNSConnection
let apnsConfig = ...
let apns = try APNSConnection.connect(configuration: apnsConfig, on: group.next()).wait()
Alert
Alert
is the actual meta data of the push notification alert someone wishes to send. More details on the specifics of each property are provided here. They follow a 1-1 naming scheme listed in Apple's documentation
Example Alert
let alert = Alert(title: "Hey There", subtitle: "Full moon sighting", body: "There was a full moon last night did you see it")
APSPayload
APSPayload
is the meta data of the push notification. Things like the alert, badge count. More details on the specifics of each property are provided here. They follow a 1-1 naming scheme listed in Apple's documentation
Example APSPayload
let alert = ...
let aps = APSPayload(alert: alert, badge: 1, sound: .normal("cow.wav"))
Putting it all together
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let url = URL(fileURLWithPath: "/Users/kylebrowning/Downloads/AuthKey_9UC9ZLQ8YW.p8")
let data: Data
do {
data = try Data(contentsOf: url)
} catch {
throw APNSError.SigningError.certificateFileDoesNotExist
}
var byteBuffer = ByteBufferAllocator().buffer(capacity: data.count)
byteBuffer.writeBytes(data)
let signer = try! APNSSigner.init(buffer: byteBuffer)
let apnsConfig = APNSConfiguration(keyIdentifier: "9UC9ZLQ8YW",
teamIdentifier: "ABBM6U9RM5",
signer: signer,
topic: "com.grasscove.Fern",
environment: .sandbox)
let apns = try APNSConnection.connect(configuration: apnsConfig, on: group.next()).wait()
let alert = Alert(title: "Hey There", subtitle: "Full moon sighting", body: "There was a full moon last night did you see it")
let aps = APSPayload(alert: alert, badge: 1, sound: .normal("cow.wav"))
let notification = BasicNotification(aps: aps)
let res = try apns.send(notification, to: "de1d666223de85db0186f654852cc960551125ee841ca044fdf5ef6a4756a77e").wait()
try apns.close().wait()
try group.syncShutdownGracefully()
Custom Notification Data
Apple provides engineers with the ability to add custom payload data to each notification. In order to facilitate this we have the APNSNotification
.
Example
struct AcmeNotification: APNSNotification {
let acme2: [String]
let aps: APSPayload
init(acme2: [String], aps: APSPayload) {
self.acme2 = acme2
self.aps = aps
}
}
let apns: APNSConnection: = ...
let aps: APSPayload = ...
let notification = AcmeNotification(acme2: ["bang", "whiz"], aps: aps)
let res = try apns.send(notification, to: "de1d666223de85db0186f654852cc960551125ee841ca044fdf5ef6a4756a77e").wait()
Maturity
We are requesting a Sandbox maturity level
This package meets the following criteria according to the SSWG Incubation Process:
- Ecosystem (SwiftNIO)
- Code Style is up to date
- Errors implemented
- Tests being used
- documentation
- Apache 2 license
- Swift Code of Conduct
- Contributing Guide
- CircleCI builds
Alternatives Considered
N/A