diff --git a/Package.swift b/Package.swift index 0bb854f..4d319f3 100644 --- a/Package.swift +++ b/Package.swift @@ -11,7 +11,7 @@ let package = Package( .library(name: "APNS", targets: ["APNS"]), ], dependencies: [ - .package(url: "https://github.com/swift-server-community/APNSwift.git", from: "5.0.0-alpha.4"), + .package(url: "https://github.com/swift-server-community/APNSwift.git", from: "5.0.0-alpha.5"), .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), ], targets: [ diff --git a/Sources/APNS/APNSContainers.swift b/Sources/APNS/APNSContainers.swift index dcbdcae..cbdddba 100644 --- a/Sources/APNS/APNSContainers.swift +++ b/Sources/APNS/APNSContainers.swift @@ -1,5 +1,8 @@ import Vapor import APNSwift +import NIO + +public typealias APNSGenericClient = APNSClient public class APNSContainers { public struct ID: Hashable, Codable { @@ -10,10 +13,10 @@ public class APNSContainers { } public final class Container { - public let configuration: APNSConfiguration - public let client: APNSClient + public let configuration: APNSClientConfiguration + public let client: APNSGenericClient - init(configuration: APNSConfiguration, client: APNSClient) { + internal init(configuration: APNSClientConfiguration, client: APNSGenericClient) { self.configuration = configuration self.client = client } @@ -27,12 +30,29 @@ public class APNSContainers { self.containers = [:] self.lock = .init() } + + public func syncShutdown() { + self.lock.lock() + defer { self.lock.unlock() } + do { + try containers.forEach { key, container in + try container.client.syncShutdown() + } + } catch { + fatalError("Could not shutdown APNS Containers") + } + } } extension APNSContainers { public func use( - _ config: APNSConfiguration, + _ config: APNSClientConfiguration, + eventLoopGroupProvider: NIOEventLoopGroupProvider, + responseDecoder: JSONDecoder, + requestEncoder: JSONEncoder, + byteBufferAllocator: ByteBufferAllocator = .init(), + backgroundActivityLogger: Logger, as id: ID, isDefault: Bool? = nil ) { @@ -41,7 +61,14 @@ extension APNSContainers { self.containers[id] = Container( configuration: config, - client: APNSClient(configuration: config) + client: APNSGenericClient( + configuration: config, + eventLoopGroupProvider: eventLoopGroupProvider, + responseDecoder: responseDecoder, + requestEncoder: requestEncoder, + byteBufferAllocator: byteBufferAllocator, + backgroundActivityLogger: backgroundActivityLogger + ) ) if isDefault == true || (self.defaultID == nil && isDefault != false) { @@ -69,19 +96,3 @@ extension APNSContainers { } } -extension APNSContainers { - - public func shutdown() { - self.lock.lock() - let group = DispatchGroup() - defer { self.lock.unlock() } - for container in self.containers.values { - group.enter() - Task { - try await container.client.shutdown() - group.leave() - } - } - group.wait() - } -} diff --git a/Sources/APNS/Application+APNS.swift b/Sources/APNS/Application+APNS.swift index 5083beb..cf4e769 100644 --- a/Sources/APNS/Application+APNS.swift +++ b/Sources/APNS/Application+APNS.swift @@ -24,20 +24,20 @@ extension Application { defer { lock.unlock() } let new = APNSContainers() self.application.storage.set(ContainersKey.self, to: new) { - $0.shutdown() + $0.syncShutdown() } return new } } - public var client: APNSClient { + public var client: APNSGenericClient { guard let container = containers.container() else { fatalError("No default APNS container configured.") } return container.client } - public func client(_ id: APNSContainers.ID = .default) -> APNSClient { + public func client(_ id: APNSContainers.ID = .default) -> APNSGenericClient { guard let container = containers.container(for: id) else { fatalError("No APNS container for \(id).") } diff --git a/Tests/APNSTests/APNSTests.swift b/Tests/APNSTests/APNSTests.swift index 121084e..bfc52f3 100644 --- a/Tests/APNSTests/APNSTests.swift +++ b/Tests/APNSTests/APNSTests.swift @@ -2,6 +2,7 @@ import APNS import XCTVapor class APNSTests: XCTestCase { + struct Payload: Codable {} let appleECP8PrivateKey = """ -----BEGIN PRIVATE KEY----- MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg2sD+kukkA8GZUpmm @@ -14,25 +15,38 @@ class APNSTests: XCTestCase { func testApplication() throws { let app = Application(.testing) defer { app.shutdown() } - - let authenticationConfig: APNSConfiguration.Authentication = .init( - privateKey: try .loadFrom(string: appleECP8PrivateKey), - teamIdentifier: "ABBM6U9RM5", - keyIdentifier: "9UC9ZLQ8YW" + let apnsConfig = APNSClientConfiguration( + authenticationMethod: .jwt( + privateKey: try .init(pemRepresentation: appleECP8PrivateKey), + keyIdentifier: "9UC9ZLQ8YW", + teamIdentifier: "ABBM6U9RM5" + ), + environment: .sandbox ) - let apnsConfig: APNSConfiguration = .init( - authenticationConfig: authenticationConfig, - topic: "MY_TOPIC", - environment: .sandbox, - eventLoopGroupProvider: .shared(app.eventLoopGroup), - logger: app.logger + + app.apns.containers.use( + apnsConfig, + eventLoopGroupProvider: .createNew, + responseDecoder: JSONDecoder(), + requestEncoder: JSONEncoder(), + backgroundActivityLogger: app.logger, + as: .default ) - app.apns.containers.use(apnsConfig, as: .default) app.get("test-push") { req -> HTTPStatus in - try await req.apns.client.send( - .init(title: "Hello", subtitle: "This is a test from vapor/apns"), - to: "98AAD4A2398DDC58595F02FA307DF9A15C18B6111D1B806949549085A8E6A55D" + try await req.apns.client.sendAlertNotification( + .init( + alert: .init( + title: .raw("Hello"), + subtitle: .raw("This is a test from vapor/apns") + ), + expiration: .immediately, + priority: .immediately, + topic: "MY_TOPC", + payload: Payload() + ), + deviceToken: "98AAD4A2398DDC58595F02FA307DF9A15C18B6111D1B806949549085A8E6A55D", + deadline: .distantFuture ) return .ok } @@ -44,22 +58,26 @@ class APNSTests: XCTestCase { func testContainers() throws { let app = Application(.testing) defer { app.shutdown() } - - let authenticationConfig: APNSConfiguration.Authentication = .init( - privateKey: try .loadFrom(string: appleECP8PrivateKey), - teamIdentifier: "ABBM6U9RM5", - keyIdentifier: "9UC9ZLQ8YW" + app.logger.logLevel = .trace + let authConfig: APNSClientConfiguration.AuthenticationMethod = .jwt( + privateKey: try .init(pemRepresentation: appleECP8PrivateKey), + keyIdentifier: "9UC9ZLQ8YW", + teamIdentifier: "ABBM6U9RM5" ) - let apnsConfig: APNSConfiguration = .init( - authenticationConfig: authenticationConfig, - topic: "MY_TOPIC", - environment: .sandbox, - eventLoopGroupProvider: .shared(app.eventLoopGroup), - logger: app.logger + let apnsConfig = APNSClientConfiguration( + authenticationMethod: authConfig, + environment: .sandbox ) - app.apns.containers.use(apnsConfig, as: .default, isDefault: true) + app.apns.containers.use( + apnsConfig, + eventLoopGroupProvider: .createNew, + responseDecoder: JSONDecoder(), + requestEncoder: JSONEncoder(), + backgroundActivityLogger: app.logger, + as: .default + ) let defaultContainer = app.apns.containers.container() XCTAssertNotNil(defaultContainer) @@ -77,21 +95,24 @@ class APNSTests: XCTestCase { XCTAssertEqual(res.status, .ok) } - let customConfig: APNSConfiguration = .init( - authenticationConfig: authenticationConfig, - topic: "MY_TOPIC_CUSTOM", - environment: .production, - eventLoopGroupProvider: .shared(app.eventLoopGroup), - logger: app.logger + let customConfig: APNSClientConfiguration = .init( + authenticationMethod: authConfig, + environment: .custom(url: "http://apple.com") ) - app.apns.containers.use(customConfig, as: .custom, isDefault: true) + app.apns.containers.use( + customConfig, + eventLoopGroupProvider: .createNew, + responseDecoder: JSONDecoder(), + requestEncoder: JSONEncoder(), + backgroundActivityLogger: app.logger, + as: .custom + ) let containerPostCustom = app.apns.containers.container() XCTAssertNotNil(containerPostCustom) app.get("test-push2") { req -> HTTPStatus in XCTAssert(req.apns.client === containerPostCustom?.client) - return .ok } try app.test(.GET, "test-push2") { res in @@ -103,37 +124,47 @@ class APNSTests: XCTestCase { let app = Application(.testing) defer { app.shutdown() } - let authenticationConfig: APNSConfiguration.Authentication = .init( - privateKey: try .loadFrom(string: appleECP8PrivateKey), - teamIdentifier: "ABBM6U9RM5", - keyIdentifier: "9UC9ZLQ8YW" + let authConfig: APNSClientConfiguration.AuthenticationMethod = .jwt( + privateKey: try .init(pemRepresentation: appleECP8PrivateKey), + keyIdentifier: "9UC9ZLQ8YW", + teamIdentifier: "ABBM6U9RM5" ) - let apnsConfig: APNSConfiguration = .init( - authenticationConfig: authenticationConfig, - topic: "MY_TOPIC", - environment: .sandbox, - eventLoopGroupProvider: .shared(app.eventLoopGroup), - logger: app.logger + let apnsConfig = APNSClientConfiguration( + authenticationMethod: authConfig, + environment: .sandbox ) - app.apns.containers.use(apnsConfig, as: .default, isDefault: true) + app.apns.containers.use( + apnsConfig, + eventLoopGroupProvider: .createNew, + responseDecoder: JSONDecoder(), + requestEncoder: JSONEncoder(), + backgroundActivityLogger: app.logger, + as: .default, + isDefault: true + ) - let customConfig: APNSConfiguration = .init( - authenticationConfig: authenticationConfig, - topic: "MY_TOPIC_CUSTOM", - environment: .production, - eventLoopGroupProvider: .shared(app.eventLoopGroup), - logger: app.logger + let customConfig: APNSClientConfiguration = .init( + authenticationMethod: authConfig, + environment: .custom(url: "http://apple.com") ) - app.apns.containers.use(customConfig, as: .custom, isDefault: true) + app.apns.containers.use( + customConfig, + eventLoopGroupProvider: .createNew, + responseDecoder: JSONDecoder(), + requestEncoder: JSONEncoder(), + backgroundActivityLogger: app.logger, + as: .custom, + isDefault: true + ) let containerPostCustom = app.apns.containers.container() XCTAssertNotNil(containerPostCustom) app.get("test-push2") { req -> HTTPStatus in XCTAssert(req.apns.client === containerPostCustom?.client) - + return .ok } try app.test(.GET, "test-push2") { res in @@ -145,31 +176,40 @@ class APNSTests: XCTestCase { let app = Application(.testing) defer { app.shutdown() } - let authenticationConfig: APNSConfiguration.Authentication = .init( - privateKey: try .loadFrom(string: appleECP8PrivateKey), - teamIdentifier: "ABBM6U9RM5", - keyIdentifier: "9UC9ZLQ8YW" + let authConfig: APNSClientConfiguration.AuthenticationMethod = .jwt( + privateKey: try .init(pemRepresentation: appleECP8PrivateKey), + keyIdentifier: "9UC9ZLQ8YW", + teamIdentifier: "ABBM6U9RM5" ) - let apnsConfig: APNSConfiguration = .init( - authenticationConfig: authenticationConfig, - topic: "MY_TOPIC", - environment: .sandbox, - eventLoopGroupProvider: .shared(app.eventLoopGroup), - logger: app.logger + let apnsConfig = APNSClientConfiguration( + authenticationMethod: authConfig, + environment: .sandbox ) - app.apns.containers.use(apnsConfig, as: .default, isDefault: true) + app.apns.containers.use( + apnsConfig, + eventLoopGroupProvider: .createNew, + responseDecoder: JSONDecoder(), + requestEncoder: JSONEncoder(), + backgroundActivityLogger: app.logger, + as: .default, + isDefault: true + ) - let customConfig: APNSConfiguration = .init( - authenticationConfig: authenticationConfig, - topic: "MY_TOPIC_CUSTOM", - environment: .production, - eventLoopGroupProvider: .shared(app.eventLoopGroup), - logger: app.logger + let customConfig: APNSClientConfiguration = .init( + authenticationMethod: authConfig, + environment: .custom(url: "http://apple.com") ) - app.apns.containers.use(customConfig, as: .custom) + app.apns.containers.use( + customConfig, + eventLoopGroupProvider: .createNew, + responseDecoder: JSONDecoder(), + requestEncoder: JSONEncoder(), + backgroundActivityLogger: app.logger, + as: .custom + ) let containerPostCustom = app.apns.containers.container() let containerNonDefaultCustom = app.apns.containers.container(for: .custom)