Skip to content

Instance Method

use(_:eventLoopGroupProvider:responseDecoder:requestEncoder:byteBufferAllocator:as:isDefault:)

Configure APNs for a given container ID.
func use(_ config: APNSClientConfiguration, eventLoopGroupProvider: NIOEventLoopGroupProvider, responseDecoder: JSONDecoder, requestEncoder: JSONEncoder, byteBufferAllocator: ByteBufferAllocator = .init(), as id: APNSContainers.ID, isDefault: Bool? = nil)

Parameters

config

The APNs configuration.

eventLoopGroupProvider

Specify how the NIOCore/EventLoopGroup will be created. Example: .shared(app.eventLoopGroup)

responseDecoder

A decoder to use when decoding responses from the APNs server. Example: JSONDecoder()

requestEncoder

An encoder to use when encoding notifications. Example: JSONEncoder()

byteBufferAllocator

The allocator to use.

id

The container ID to access the configuration under.

isDefault

A flag to specify the configuration as the default when client is called. The first configuration that doesn’t specify false is automatically configured as the default.

Discussion

You must configure at lease one client in order to send notifications to devices. If you plan on supporting both development builds (ie. run from Xcode) and release builds (ie. TestFlight/App Store), you must configure at least two configurations:

/// The .p8 file as a string.
let apnsKey = Environment.get("APNS_KEY_P8")
/// The identifier of the key in the developer portal.
let keyIdentifier = Environment.get("APNS_KEY_ID")
/// The team identifier of the app in the developer portal.
let teamIdentifier = Environment.get("APNS_TEAM_ID")

let productionConfig = APNSClientConfiguration(
    authenticationMethod: .jwt(
        privateKey: try .loadFrom(string: apnsKey),
        keyIdentifier: keyIdentifier,
        teamIdentifier: teamIdentifier
    ),
    environment: .production
)

app.apns.containers.use(
    productionConfig,
    eventLoopGroupProvider: .shared(app.eventLoopGroup),
    responseDecoder: JSONDecoder(),
    requestEncoder: JSONEncoder(),
    as: .production
)

var developmentConfig = productionConfig
developmentConfig.environment = .sandbox

app.apns.containers.use(
    developmentConfig,
    eventLoopGroupProvider: .shared(app.eventLoopGroup),
    responseDecoder: JSONDecoder(),
    requestEncoder: JSONEncoder(),
    as: .development
)

As shown above, the same key can be used for both the development and production environments.

Important

Make sure not to store your APNs key within your code or repo directly, and opt to store it via a secure store specific to your deployment, such as in a .env supplied at deploy time.

You can determine which environment is being used in your app by checking its entitlements, and including the information along with the device token when sending it to your server:

enum APNSDeviceTokenEnvironment: String {
    case production
    case development
}

/// Get the APNs environment from the embedded 
/// provisioning profile, or nil if it can't
/// be determined.
///
/// Note that both TestFlight and the App Store
/// don't have provisioning profiles, and always
/// run in the production environment.
var pushEnvironment: APNSDeviceTokenEnvironment? {
    #if canImport(AppKit)
    let provisioningProfileURL = Bundle.main.bundleURL
        .appending(path: "Contents", directoryHint: .isDirectory)
        .appending(path: "embedded.provisionprofile", directoryHint: .notDirectory)
    guard let data = try? Data(contentsOf: provisioningProfileURL)
    else { return nil }
    #else
    guard
        let provisioningProfileURL = Bundle.main
            .url(forResource: "embedded", withExtension: "mobileprovision"),
        let data = try? Data(contentsOf: provisioningProfileURL)
    else {
        #if targetEnvironment(simulator)
        return .development
        #else
        return nil
        #endif
    }
    #endif

    let string = String(decoding: data, as: UTF8.self)

    guard
        let start = string.firstRange(of: "<plist"),
        let end = string.firstRange(of: "</plist>")
    else { return nil }

    let propertylist = string[start.lowerBound..<end.upperBound]

    guard
        let provisioningProfile = try? PropertyListSerialization
            .propertyList(from: Data(propertylist.utf8), format: nil) as? [String : Any],
        let entitlements = provisioningProfile["Entitlements"] as? [String: Any],
        let environment = (
            entitlements["aps-environment"]
            ?? entitlements["com.apple.developer.aps-environment"]
        ) as? String
    else { return nil }

    return APNSDeviceTokenEnvironment(rawValue: environment)
}

Note that the simulator doesn’t have a provisioning profile, and will always register under the development environment.