Apple Push Notifications (APNs) Server-Side library.

Overview

Perfect-Notifications 简体中文

Get Involed with Perfect!

Swift 5.2 Platforms OS X | Linux License Apache

APNs remote Notifications for Perfect. This package adds push notification support to your server. Send notifications to iOS/macOS devices.

Building

This is a Swift Package manager based project. Add this repository as a dependency in your Package.swift file.

.package(url:"https://github.com/PerfectlySoft/Perfect-Notifications.git", from: "5.0.0")

Overview

This system runs on the server side. Typically at app launch, an Apple device will register with Apple's system for remote notifications. Doing so will return to the device an ID which can be used by external systems to address the device and send notifications through APNs.

When the device obtains its ID it will need to transmit it to your server. Your server will store this id and use it when sending notifications to one or more devices through APNs.

Obtain APNs Auth Key

To connect your server to Apple's push notification system you will first need to obtain an "APNs Auth Key". This key is used on your server to configure its APNs access. You can generate this key through your Apple developer account portal. Log in to your developer account and choose "Certificates, IDs & Profiles" from the menu. Then, under "Keys", choose "All".

If you haven't already created and downloaded the auth key, click "+" to create a new one. Enter a name for the key and make sure you select Apple Push Notifications service (APNs). This one key can be used for both development or production and can be used for any of your iOS/macOS apps.

Click "Continue", then "Confirm", then you will be given a chance to download the private key. You must download this key now and save the file. Also copy the "Key ID" shown in the same view. This will be a 10 character string.

Finally you will need to locate your developer team id. Click "Account" near the window's top. Select "Membership" in the menu. You will then be shown much of your personal information, including "Team ID". This is another 10 character string. Copy this value.

Server Configuration

To send notifications from your server your must have three pieces of information:

  1. The private key file which was downloaded
  2. The 10 character key id
  3. Your 10 character team id
  4. An iOS/macOS app id

These four pieces of information are used to perform push notifications. This information must reside on your server. You can store this information in any manner provided it can be used by the server. For simplicity, the rest of this example assumes that the private key file is in the server's working directory and that the two keys and the app id are embedded in the Swift code.

In your server Swift code, you must import PerfectNotifications. Then, before you start any HTTP servers or send any notifications you must add a "configuration" for the notifications you will be sending. This very simply ties your APNs keys to a name which you can then use later when pushing notifications.

import PerfectNotifications

// your app id. we use this as the configuration name, but they do not have to match
let notificationsAppId = "my.app.id"

let apnsKeyIdentifier = "AB90CD56XY"
let apnsTeamIdentifier = "YX65DC09BA"
let apnsPrivateKeyFilePath = "./APNsAuthKey_AB90CD56XY.p8"

NotificationPusher.addConfigurationAPNS(
	name: notificationsTestId, 
	production: false, // should be false when running pre-release app in debugger
	keyId: apnsKeyIdentifier, 
	teamId: apnsTeamIdentifier, 
	privateKeyPath: apnsPrivateKeyFilePath)

After the configuration has been added, notifications can be sent at any point. To do so, create a NotificationPusher with your app id, or "topic", then trigger a notification to one or more devices by calling its pushAPNS function:

let deviceIds: [String] = [...]
let n = NotificationPusher(apnsTopic: notificationsTestId)
n.pushAPNS(
	configurationName: notificationsTestId, 
	deviceTokens: deviceIds, 
	notificationItems: [.alertBody("Hello!"), .sound("default")]) {
		responses in
		print("\(responses)")
		...
}

The topic is required when creating a NotificationPusher. Additional optional parameters can be provided to customize the notification's expiration, priority and collapse-id. Consult Apple's APNS documentation for the semantics of these options.

Public API

The full public version 3.0 API for notification pusher follows:

public class NotificationPusher {
	
	/// Add an APNS configuration which can be later used to push notifications.
	public static func addConfigurationAPNS(
		name: String, 
		production: Bool, 
		keyId: String, 
		teamId: String, 
		privateKeyPath: String)

	/// Initialize given an apns-topic string.
	public init(
		apnsTopic: String,
		expiration: APNSExpiration = .immediate,
		priority: APNSPriority = .immediate,
		collapseId: String? = nil)
		
	/// Push one message to one device.
	/// Provide the previously set configuration name, device token.
	/// Provide a list of APNSNotificationItems.
	/// Provide a callback with which to receive the response.
	public func pushAPNS(
		configurationName: String, 
		deviceToken: String, 
		notificationItems: [APNSNotificationItem], 
		callback: @escaping (NotificationResponse) -> ())
	
	/// Push one message to multiple devices.
	/// Provide the previously set configuration name, and zero or more device tokens. The same message will be sent to each device.
	/// Provide a list of APNSNotificationItems.
	/// Provide a callback with which to receive the responses.
	public func pushAPNS(
		configurationName: String, deviceTokens: [String],
		notificationItems: [APNSNotificationItem],
		callback: @escaping ([NotificationResponse]) -> ())
}

The remaining structures, including APNSNotificationItem follow:

/// Items to configure an individual notification push.
public enum APNSNotificationItem {
    /// alert body child property
	case alertBody(String)
    /// alert title child property
	case alertTitle(String)
    /// alert title-loc-key
	case alertTitleLoc(String, [String]?)
    /// alert action-loc-key
	case alertActionLoc(String)
    /// alert loc-key
	case alertLoc(String, [String]?)
    /// alert launch-image
	case alertLaunchImage(String)
    /// aps badge key
	case badge(Int)
    /// aps sound key
	case sound(String)
    /// aps content-available key
	case contentAvailable
	/// aps category key
	case category(String)
	/// aps thread-id key
	case threadId(String)
    /// custom payload data
	case customPayload(String, Any)
    /// apn mutable-content key
	case mutableContent
}

public enum APNSPriority: Int {
	case immediate = 10
	case background = 5
}

/// Time in the future when the notification, if has not be able to be delivered, will expire.
public enum APNSExpiration {
	/// Discard the notification if it can't be immediately delivered.
	case immediate
	/// now + seconds
	case relative(Int)
	/// absolute UTC time since epoch
	case absolute(Int)
}

/// The response object given after a push attempt.
public struct NotificationResponse: CustomStringConvertible {
	/// The response code for the request.
	public let status: HTTPResponseStatus
	/// The response body data bytes.
	public let body: [UInt8]
	/// The body data bytes interpreted as JSON and decoded into a Dictionary.
	public var jsonObjectBody: [String:Any]
	/// The body data bytes converted to String.
	public var stringBody: String
	public var description: String
}

Additional Notes

APNs requests are made from your server to Apple's servers "api.development.push.apple.com" or "api.push.apple.com" on port 443. One request will be used when sending one notification to one or more devices. Each connection will remain open and will be reused when sending subsequent notifications. If a connection "goes away" or there are no idle connections that can be used then a new connection will be opened. This is in accordance with Apple's recommended usage of APNs and should provide the best throughput when dealing with many concurrent notification requests.

Consult Perfect-NotificationsExample for a client/server combination which can be easily configured with your own information to quickly get APNS notifications for your apps.

Further Information

For more information on the Perfect project, please visit perfect.org.

Comments
  • Added mutable-content to IOSNotificationItem to support Notification …

    Added mutable-content to IOSNotificationItem to support Notification …

    Added mutable-content to IOSNotificationItem to support Notification Service Extensions. The mutable-content flag must be set in the aps payload in order to support notification service extensions from remote notifications.

    opened by adamthecashew 2
  • Update 'Obtain APNs Auth Key' section in README.md

    Update 'Obtain APNs Auth Key' section in README.md

    Update 'Obtain APNs Auth Key' section in README.md to accurately reflect Apple Developer Portal current organization. APNs Auth Keys are now in a 'Keys' section rather than in 'Certificates'.

    opened by jeffreyfultonca 0
  • Adopt APNs changes with iOS 13

    Adopt APNs changes with iOS 13

    Adds the apns-push-type option to NotificationPusher, as required for watchOS 6 and recommended for all other platforms.

    The initialization of NotificationPusher receives an additional, optional argument of type APNSPushType, which is an enum specifying the type of the notification.

    Specifying the type adds the apns-push-type key with the appropriate value to the header of the notification request.

    The APNSPushType enum is commented with additional information about the different types, as detailed in Sending Notification Requests to APNs.

    Adding the option resolves issues with push notifications being dropped when no alert is included, specifically when sending content-available push updates.

    opened by christophhagen 0
  • Why it reading AuthKey from build path. Could you please explain? How we can configure build path on production

    Why it reading AuthKey from build path. Could you please explain? How we can configure build path on production

    Fatal error: The private key file "/AuthKey_A9HP4L5K6P.p8" does not exist. Current working directory: /Users/chandrakant/Library/Developer/Xcode/DerivedData/ashi-api-ackosslvopviteghopwhwturjxql/Build/Products/Debug/

    opened by awasthi027 0
  • Adopt iOS 13 APNS changes

    Adopt iOS 13 APNS changes

    Hello,

    Will there be a release which adopts the changes that Apple introduced in APNS for iOS 13+ and watch OS 6+?? I talk about the apns-push-type and apns-priority requirements.

    Thanx!

    opened by nick3389 1
  • Parameter production is unused

    Parameter production is unused

    public static func addConfigurationAPNS(name: String, production: Bool, certificatePath: String) { addConfigurationIOS(name: name) { net in guard File(certificatePath).exists else { fatalError("File not found (certificatePath)") } guard net.useCertificateFile(cert: certificatePath) && net.usePrivateKeyFile(cert: certificatePath) && net.checkPrivateKey() else { let code = Int32(net.errorCode()) print("Error validating private key file: (net.errorStr(forCode: code))") return } } }

    The parameter is never used within the function

    opened by enolik97 0
  • Lots of 500s

    Lots of 500s

    I'm using this library to send notifications in batches. I'm creating a single NotificationPusher instance and then using it to send many batches of 100 devices each (please let me know if this is not how the library is meant to be used). Note that I'm doing batching because I don't want my server to be swamped by the app's reaction to the notification, not because of APNS.

    Some batches work fine, returning 100 responses that are either "ok" or "inactive device".

    Most batches reply with a single 500 error response, with no body. Judging by the code this happens when the response count doesn't match the token count inside of the pusher: https://github.com/PerfectlySoft/Perfect-Notifications/blob/master/Sources/PerfectNotifications/NotificationPusher.swift#L531. Sounds like this is being handled like an internal consistency "this should never happen" error, but it happens a lot :)

    What can I do about that? Because this single 500 replaces all responses, I'm not getting the "inactive device" responses. If I keep sending notifications to those tokens, I risk angering APNS. Please help :)

    opened by mipstian 2
  • Refresh APNS Connection

    Refresh APNS Connection

    Hello,

    APNS service has a known. bug that, if you send a request in a session which stays idle for a while, APNS service returns an internal server error such that : "500 Internal Server Error: Unable to write frame"

    Other push services overcome this problem by refreshing their APNS connections when they encounter this kind of a problem. (for example: https://github.com/relayrides/pushy/pull/529)

    Is there a way to refresh connection in Perfect Notifications?

    opened by bisikli 1
Owner
PerfectlySoft Inc.
Server-side Swift
PerfectlySoft Inc.
💧 A server-side Swift web framework.

Vapor is a web framework for Swift. It provides a beautifully expressive and easy to use foundation for your next website, API, or cloud project. Take

Vapor 22.4k Jan 2, 2023
Meet Corvus, the first strongly declarative server-side framework.

Corvus Corvus is the first truly declarative server-side framework for Swift. It provides a declarative, composable syntax which makes it easy to get

null 42 Jun 29, 2022
📡 RealHTTP is a lightweight yet powerful client-side HTTP library.

RealHTTP RealHTTP is a lightweight yet powerful client-side HTTP library. Our goal is make an easy to use and effortless http client for Swift. Featur

Immobiliare Labs 233 Jan 7, 2023
An iOS library to route API paths to objects on client side with request, mapping, routing and auth layers

WANetworkRouting Developed and Maintained by ipodishima Founder & CTO at Wasappli Inc. Sponsored by Wisembly A routing library to fetch objects from a

null 10 Nov 20, 2022
Client side for goyotashi

B_2121_client サーバサイドはこちら Required Dependency Cocoa Pods — https://guides.cocoapods.org/using/getting-started.html Getting Started ⚠️ M1 Mac の場合、「Finde

JPHACKS 2 Oct 31, 2021
Lightweight library for web server applications in Swift on macOS and Linux powered by coroutines.

Why Zewo? • Support • Community • Contributing Zewo Zewo is a lightweight library for web applications in Swift. What sets Zewo apart? Zewo is not a w

Zewo 1.9k Dec 22, 2022
Super lightweight async HTTP server library in pure Swift runs in iOS / MacOS / Linux

Embassy Super lightweight async HTTP server in pure Swift. Please read: Embedded web server for iOS UI testing. See also: Our lightweight web framewor

Envoy 540 Dec 15, 2022
A Swift web framework and HTTP server.

A Swift Web Framework and HTTP Server Summary Kitura is a web framework and web server that is created for web services written in Swift. For more inf

Kitura 7.6k Jan 6, 2023
Tiny http server engine written in Swift programming language.

What is Swifter? Tiny http server engine written in Swift programming language. Branches * stable - lands on CocoaPods and others. Supports the latest

null 3.6k Dec 31, 2022
Swift Express is a simple, yet unopinionated web application server written in Swift

Documentation <h5 align="right"><a href="http://demo.swiftexpress.io/">Live ?? server running Demo <img src="https://cdn0.iconfinder.com/data/icons/

Crossroad Labs 850 Dec 2, 2022
🔌 Non-blocking TCP socket layer, with event-driven server and client.

Original authors Honza Dvorsky - http://honzadvorsky.com, @czechboy0 Matthias Kreileder - @matthiaskr1 At the request of the original authors, we ask

Vapor Community 574 Dec 7, 2022
Access Xcode Server API with native Swift objects.

Xcode Server SDK Use Xcode Server's API with native Swift objects. First brought to you in Buildasaur, now in an independent project. This is an unoff

Buildasaurs 401 Dec 29, 2022
Swift HTTP server using the pre-fork worker model

Curassow Curassow is a Swift Nest HTTP Server. It uses the pre-fork worker model and it's similar to Python's Gunicorn and Ruby's Unicorn. It exposes

Kyle Fuller Archive 397 Oct 30, 2022
Swift backend / server framework (Pure Swift, Supports Linux)

NetworkObjects NetworkObjects is a #PureSwift backend. This framework compiles for OS X, iOS and Linux and serves as the foundation for building power

Alsey Coleman Miller 258 Oct 6, 2022
A simple GCD based HTTP client and server, written in 'pure' Swift

SwiftyHTTP Note: I'm probably not going to update this any further - If you need a Swift networking toolset for the server side, consider: Macro.swift

Always Right Institute 116 Aug 6, 2022
WebSocket implementation for use by Client and Server

WebSocket ⚠️ This module contains no networking. To create a WebSocket Server, see WebSocketServer. To create a WebSocket Client, see WebSocketClient.

Zewo Graveyard 63 Jan 29, 2022
High Performance (nearly)100% Swift Web server supporting dynamic content.

Dynamo - Dynamic Swift Web Server Starting this project the intention was to code the simplest possible Web Server entirely in Swift. Unfortunately I

John Holdsworth 68 Jul 25, 2022
libuv base Swift web HTTP server framework

Notice Trevi now open a Trevi Community. Yoseob/Trevi project split up into respective Trevi, lime, middlewares and sys packages at our community. If

leeyoseob 46 Jan 29, 2022
A very basic proof-of-concept Swift HTTP server that does not require Foundation

Swift Server Introduction This is very rough and basic HTTP server written in Swift without using Foundation. This is partially based on the Swifter r

Cezary Wojcik 55 Apr 27, 2022