diff --git a/Sources/VaporAPNS/APNSContainerID.swift b/Sources/VaporAPNS/APNSContainerID.swift index adf6eda..600a6d5 100644 --- a/Sources/VaporAPNS/APNSContainerID.swift +++ b/Sources/VaporAPNS/APNSContainerID.swift @@ -1,6 +1,26 @@ extension APNSContainers.ID { + /// A default container ID available for use. + /// + /// If you are configuring both a production and development container, ``production`` and ``development`` are also available. + /// + /// - Note: You must configure this ID before using it by calling ``VaporAPNS/APNSContainers/use(_:eventLoopGroupProvider:responseDecoder:requestEncoder:byteBufferAllocator:as:isDefault:)``. + /// - Important: The actual default ID to use in ``Vapor/Application/APNS/client`` when none is provided is the first configuration that doesn't specify a value of `false` for `isDefault:`. public static var `default`: APNSContainers.ID { return .init(string: "default") } + + /// An ID that can be used for the production APNs environment. + /// + /// - Note: You must configure this ID before using it by calling ``APNSContainers/use(_:eventLoopGroupProvider:responseDecoder:requestEncoder:byteBufferAllocator:as:isDefault:)`` + public static var production: APNSContainers.ID { + return .init(string: "production") + } + + /// An ID that can be used for the development (aka sandbox) APNs environment. + /// + /// - Note: You must configure this ID before using it by calling ``APNSContainers/use(_:eventLoopGroupProvider:responseDecoder:requestEncoder:byteBufferAllocator:as:isDefault:)`` + public static var development: APNSContainers.ID { + return .init(string: "development") + } } diff --git a/Sources/VaporAPNS/APNSContainers.swift b/Sources/VaporAPNS/APNSContainers.swift index 7ea4686..704aa91 100644 --- a/Sources/VaporAPNS/APNSContainers.swift +++ b/Sources/VaporAPNS/APNSContainers.swift @@ -50,7 +50,118 @@ public final class APNSContainers: Sendable { } extension APNSContainers { - + /// Configure APNs for a given container ID. + /// + /// 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: + /// + /// ```swift + /// /// 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: + /// ```swift + /// 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: "") + /// else { return nil } + /// + /// let propertylist = string[start.lowerBound..